mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2024-12-29 10:26:36 +01:00
402 lines
12 KiB
C
402 lines
12 KiB
C
/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */
|
|
/*
|
|
* Copyright 2020-2022 by Eric House (xwords@eehouse.org). All rights
|
|
* reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "knownplyr.h"
|
|
#include "strutils.h"
|
|
#include "comms.h"
|
|
#include "dbgutil.h"
|
|
|
|
#ifdef XWFEATURE_KNOWNPLAYERS
|
|
|
|
typedef struct _KnownPlayer {
|
|
struct _KnownPlayer* next;
|
|
XP_U32 newestMod;
|
|
XP_UCHAR* name;
|
|
CommsAddrRec addr;
|
|
} KnownPlayer;
|
|
|
|
typedef struct _KPState {
|
|
KnownPlayer* players;
|
|
XP_U16 nPlayers;
|
|
XP_Bool dirty;
|
|
XP_Bool inUse;
|
|
} KPState;
|
|
|
|
static void addPlayer( XW_DUtilCtxt* dutil, KPState* state,
|
|
const XP_UCHAR* name, const CommsAddrRec* addr,
|
|
XP_U32 newestMod );
|
|
static void getPlayersImpl( const KPState* state, const XP_UCHAR** players,
|
|
XP_U16* nFound );
|
|
|
|
static void
|
|
loadFromStream( XW_DUtilCtxt* dutil, KPState* state, XWStreamCtxt* stream )
|
|
{
|
|
while ( 0 < stream_getSize( stream ) ) {
|
|
XP_U32 newestMod = stream_getU32( stream );
|
|
XP_UCHAR buf[64];
|
|
stringFromStreamHere( stream, buf, VSIZE(buf) );
|
|
|
|
CommsAddrRec addr = {0};
|
|
addrFromStream( &addr, stream );
|
|
|
|
addPlayer( dutil, state, buf, &addr, newestMod );
|
|
}
|
|
}
|
|
|
|
static KPState*
|
|
loadState( XW_DUtilCtxt* dutil, XWEnv xwe )
|
|
{
|
|
// LOG_FUNC();
|
|
pthread_mutex_lock( &dutil->kpMutex );
|
|
|
|
KPState* state = (KPState*)dutil->kpCtxt;
|
|
if ( NULL == state ) {
|
|
dutil->kpCtxt = state = XP_CALLOC( dutil->mpool, sizeof(*state) );
|
|
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(dutil->mpool)
|
|
dutil_getVTManager(dutil) );
|
|
const XP_UCHAR* keys[] = { KNOWN_PLAYERS_KEY, NULL };
|
|
dutil_loadStream( dutil, xwe, keys, stream );
|
|
if ( 0 < stream_getSize( stream ) ) {
|
|
XP_U8 vers = stream_getU8( stream );
|
|
stream_setVersion( stream, vers );
|
|
loadFromStream( dutil, state, stream );
|
|
}
|
|
|
|
stream_destroy( stream );
|
|
}
|
|
XP_ASSERT( !state->inUse );
|
|
state->inUse = XP_TRUE;
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
saveState( XW_DUtilCtxt* dutil, XWEnv xwe, KPState* state )
|
|
{
|
|
if ( state->dirty ) {
|
|
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(dutil->mpool)
|
|
dutil_getVTManager(dutil) );
|
|
stream_setVersion( stream, CUR_STREAM_VERS );
|
|
stream_putU8( stream, CUR_STREAM_VERS );
|
|
for ( KnownPlayer* kp = state->players; !!kp; kp = kp->next ) {
|
|
stream_putU32( stream, kp->newestMod );
|
|
stringToStream( stream, kp->name );
|
|
addrToStream( stream, &kp->addr );
|
|
}
|
|
const XP_UCHAR* keys[] = { KNOWN_PLAYERS_KEY, NULL };
|
|
dutil_storeStream( dutil, xwe, keys, stream );
|
|
stream_destroy( stream );
|
|
state->dirty = XP_FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
releaseState( XW_DUtilCtxt* dutil, XWEnv xwe, KPState* state )
|
|
{
|
|
XP_ASSERT( state->inUse );
|
|
saveState( dutil, xwe, state );
|
|
state->inUse = XP_FALSE;
|
|
|
|
pthread_mutex_unlock( &dutil->kpMutex );
|
|
}
|
|
|
|
static const XP_UCHAR*
|
|
figureNameFor( XP_U16 posn, const CurGameInfo* gi )
|
|
{
|
|
const XP_UCHAR* result = NULL;
|
|
for ( int ii = 0, nthRemote = 0;
|
|
NULL == result && ii < gi->nPlayers;
|
|
++ii ) {
|
|
const LocalPlayer* lp = &gi->players[ii];
|
|
if ( lp->isLocal ) {
|
|
continue;
|
|
} else if ( nthRemote++ == posn ) {
|
|
result = lp->name;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
makeUniqueName( const KPState* state, const XP_UCHAR* name,
|
|
XP_UCHAR newName[], XP_U16 len )
|
|
{
|
|
XP_U16 nPlayers = state->nPlayers;
|
|
const XP_UCHAR* names[nPlayers];
|
|
getPlayersImpl( state, names, &nPlayers );
|
|
XP_ASSERT( nPlayers == state->nPlayers );
|
|
for ( int ii = 2; ; ++ii ) {
|
|
XP_SNPRINTF( newName, len, "%s %d", name, ii );
|
|
XP_Bool found = XP_FALSE;
|
|
for ( int jj = 0; !found && jj < nPlayers; ++jj ) {
|
|
found = 0 == XP_STRCMP( names[jj], newName );
|
|
}
|
|
if ( !found ) {
|
|
break;
|
|
}
|
|
}
|
|
XP_LOGFF( "created new name: %s", newName );
|
|
}
|
|
|
|
/* Adding players is the hard part. There will be a lot with the same name and
|
|
* representing the same device. That's easy: skip adding a new entry, but if
|
|
* there's a change or addition, make it. For changes, e.g. a different
|
|
* BlueTooth device name, the newer one wins.
|
|
*
|
|
* If two different names have the same mqtt devID, they're the same
|
|
* device!!. Change the name to be that of the newer of the two, making sure
|
|
* it's not a duplicate.
|
|
*
|
|
* For early testing, however, just make a new name.
|
|
*/
|
|
static void
|
|
addPlayer( XW_DUtilCtxt* XP_UNUSED_DBG(dutil), KPState* state, const XP_UCHAR* name,
|
|
const CommsAddrRec* addr, XP_U32 newestMod )
|
|
{
|
|
XP_LOGFF( "(name=%s)", name );
|
|
KnownPlayer* withSameDevID = NULL;
|
|
KnownPlayer* withSameName = NULL;
|
|
|
|
for ( KnownPlayer* kp = state->players;
|
|
!!kp && (!withSameDevID || !withSameName);
|
|
kp = kp->next ) {
|
|
if ( 0 == XP_STRCMP( kp->name, name ) ) {
|
|
withSameName = kp;
|
|
}
|
|
if ( addr->u.mqtt.devID == kp->addr.u.mqtt.devID ) {
|
|
withSameDevID = kp;
|
|
}
|
|
}
|
|
|
|
XP_UCHAR tmpName[64];
|
|
if ( !!withSameDevID ) { /* only one allowed */
|
|
XP_Bool isNewer = newestMod > withSameDevID->newestMod;
|
|
XP_Bool changed = augmentAddr( &withSameDevID->addr, addr, isNewer );
|
|
if ( isNewer ) {
|
|
withSameDevID->newestMod = newestMod;
|
|
changed = XP_TRUE;
|
|
}
|
|
state->dirty = changed || state->dirty;
|
|
} else {
|
|
if ( !!withSameName ) {
|
|
/* Same name but different devID? Create a unique name */
|
|
makeUniqueName( state, name, tmpName, VSIZE(tmpName) );
|
|
name = tmpName;
|
|
}
|
|
/* XP_LOGFF( "adding new player %s!", name ); */
|
|
KnownPlayer* newPlayer = XP_CALLOC( dutil->mpool, sizeof(*newPlayer) );
|
|
newPlayer->name = copyString( dutil->mpool, name );
|
|
newPlayer->addr = *addr;
|
|
newPlayer->next = state->players;
|
|
state->players = newPlayer;
|
|
state->dirty = XP_TRUE;
|
|
++state->nPlayers;
|
|
}
|
|
XP_LOGFF( "nPlayers now: %d", state->nPlayers );
|
|
}
|
|
|
|
XP_Bool
|
|
kplr_addAddrs( XW_DUtilCtxt* dutil, XWEnv xwe, const CurGameInfo* gi,
|
|
CommsAddrRec addrs[], XP_U16 nAddrs, XP_U32 modTime )
|
|
{
|
|
XP_LOGFF( "(nAddrs=%d)", nAddrs );
|
|
XP_Bool canUse = XP_TRUE;
|
|
for ( int ii = 0; ii < nAddrs && canUse; ++ii ) {
|
|
canUse = addr_hasType( &addrs[ii], COMMS_CONN_MQTT );
|
|
if ( !canUse ) {
|
|
XP_LOGFF( "addr %d has no mqqt id", ii );
|
|
}
|
|
}
|
|
|
|
if ( canUse ) {
|
|
KPState* state = loadState( dutil, xwe );
|
|
for ( int ii = 0; ii < nAddrs && canUse; ++ii ) {
|
|
const XP_UCHAR* name = figureNameFor( ii, gi );
|
|
if ( !!name ) {
|
|
addPlayer( dutil, state, name, &addrs[ii], modTime );
|
|
} else {
|
|
XP_LOGFF( "unable to find %dth name", ii );
|
|
}
|
|
}
|
|
releaseState( dutil, xwe, state );
|
|
}
|
|
|
|
LOG_RETURNF( "%s", boolToStr(canUse) );
|
|
return canUse;
|
|
}
|
|
|
|
XP_Bool
|
|
kplr_havePlayers( XW_DUtilCtxt* dutil, XWEnv xwe )
|
|
{
|
|
KPState* state = loadState( dutil, xwe );
|
|
XP_Bool result = 0 < state->nPlayers;
|
|
releaseState( dutil, xwe, state );
|
|
LOG_RETURNF( "%s", boolToStr(result) );
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
getPlayersImpl( const KPState* state, const XP_UCHAR** players, XP_U16* nFound )
|
|
{
|
|
if ( state->nPlayers <= *nFound && !!players ) {
|
|
XP_U16 ii = 0;
|
|
for ( KnownPlayer* kp = state->players; !!kp; kp = kp->next ) {
|
|
players[ii++] = kp->name;
|
|
}
|
|
}
|
|
*nFound = state->nPlayers;
|
|
}
|
|
|
|
void
|
|
kplr_getNames( XW_DUtilCtxt* dutil, XWEnv xwe,
|
|
const XP_UCHAR** players, XP_U16* nFound )
|
|
{
|
|
KPState* state = loadState( dutil, xwe );
|
|
getPlayersImpl( state, players, nFound );
|
|
releaseState( dutil, xwe, state );
|
|
}
|
|
|
|
static KnownPlayer*
|
|
findByName( KPState* state, const XP_UCHAR* name )
|
|
{
|
|
KnownPlayer* result = NULL;
|
|
for ( KnownPlayer* kp = state->players; !!kp && !result; kp = kp->next ) {
|
|
if ( 0 == XP_STRCMP( kp->name, name ) ) {
|
|
result = kp;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
XP_Bool
|
|
kplr_getAddr( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_UCHAR* name,
|
|
CommsAddrRec* addr, XP_U32* lastMod )
|
|
{
|
|
KPState* state = loadState( dutil, xwe );
|
|
XP_Bool found = XP_FALSE;
|
|
KnownPlayer* kp = findByName( state, name );
|
|
found = NULL != kp;
|
|
if ( found ) {
|
|
*addr = kp->addr;
|
|
if ( !!lastMod ) {
|
|
*lastMod = kp->newestMod;
|
|
}
|
|
}
|
|
releaseState( dutil, xwe, state );
|
|
LOG_RETURNF( "%s", boolToStr(found) );
|
|
return found;
|
|
}
|
|
|
|
const XP_UCHAR*
|
|
kplr_nameForMqttDev( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_UCHAR* mqttDevID )
|
|
{
|
|
const XP_UCHAR* name = NULL;
|
|
MQTTDevID devID;
|
|
if ( strToMQTTCDevID( mqttDevID, &devID ) ) {
|
|
KPState* state = loadState( dutil, xwe );
|
|
for ( KnownPlayer* kp = state->players; !!kp && !name; kp = kp->next ) {
|
|
const CommsAddrRec* addr = &kp->addr;
|
|
if ( addr_hasType( addr, COMMS_CONN_MQTT ) ) {
|
|
if ( 0 == XP_MEMCMP( &addr->u.mqtt.devID, &devID, sizeof(devID) ) ) {
|
|
name = kp->name;
|
|
}
|
|
}
|
|
}
|
|
releaseState( dutil, xwe, state );
|
|
}
|
|
LOG_RETURNF( "%s", name );
|
|
return name;
|
|
}
|
|
|
|
static void
|
|
freeKP( XW_DUtilCtxt* XP_UNUSED_DBG(dutil), KnownPlayer* kp )
|
|
{
|
|
XP_FREEP( dutil->mpool, &kp->name );
|
|
XP_FREE( dutil->mpool, kp );
|
|
}
|
|
|
|
KP_Rslt
|
|
kplr_renamePlayer( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_UCHAR* oldName,
|
|
const XP_UCHAR* newName )
|
|
{
|
|
KP_Rslt result;
|
|
KPState* state = loadState( dutil, xwe );
|
|
|
|
KnownPlayer* kp = findByName( state, oldName );
|
|
if ( !kp ) {
|
|
result = KP_NAME_NOT_FOUND;
|
|
} else if ( NULL != findByName( state, newName ) ) {
|
|
result = KP_NAME_IN_USE;
|
|
} else {
|
|
XP_FREEP( dutil->mpool, &kp->name );
|
|
kp->name = copyString( dutil->mpool, newName );
|
|
state->dirty = XP_TRUE;
|
|
result = KP_OK;
|
|
}
|
|
|
|
releaseState( dutil, xwe, state );
|
|
return result;
|
|
}
|
|
|
|
KP_Rslt
|
|
kplr_deletePlayer( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_UCHAR* name )
|
|
{
|
|
KP_Rslt result = KP_NAME_NOT_FOUND;
|
|
KnownPlayer* doomed = NULL;
|
|
KPState* state = loadState( dutil, xwe );
|
|
|
|
KnownPlayer* prev = NULL;
|
|
for ( KnownPlayer* kp = state->players; !!kp && !doomed; kp = kp->next ) {
|
|
if ( 0 == XP_STRCMP( kp->name, name ) ) {
|
|
doomed = kp;
|
|
if ( NULL == prev ) { /* first time through? */
|
|
state->players = kp->next;
|
|
} else {
|
|
prev->next = kp->next;
|
|
}
|
|
--state->nPlayers;
|
|
state->dirty = XP_TRUE;
|
|
result = KP_OK;
|
|
}
|
|
prev = kp;
|
|
}
|
|
releaseState( dutil, xwe, state );
|
|
|
|
XP_ASSERT( !!doomed );
|
|
if ( !!doomed ) {
|
|
freeKP( dutil, doomed );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
kplr_cleanup( XW_DUtilCtxt* dutil )
|
|
{
|
|
KPState** state = (KPState**)&dutil->kpCtxt;
|
|
if ( !!*state ) {
|
|
XP_ASSERT( !(*state)->inUse );
|
|
KnownPlayer* next = NULL;
|
|
for ( KnownPlayer* kp = (*state)->players; !!kp; kp = next ) {
|
|
next = kp->next;
|
|
freeKP( dutil, kp );
|
|
}
|
|
XP_FREEP( dutil->mpool, state );
|
|
}
|
|
}
|
|
#endif
|