From 6b0fe35c8f288792dad90cce0b95d951cb816362 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 16 Oct 2017 22:12:35 -0700 Subject: [PATCH 1/7] use readFully() rather than read() readFully() blocks; read() can return with less than you expect. --- .../org/eehouse/android/xw4/BTService.java | 32 ++++++++----------- .../org/eehouse/android/xw4/BiDiSockWrap.java | 6 ++-- .../org/eehouse/android/xw4/NetUtils.java | 2 +- .../eehouse/android/xw4/RefreshNamesTask.java | 2 +- .../org/eehouse/android/xw4/RelayService.java | 12 +++---- .../org/eehouse/android/xw4/SMSService.java | 4 +-- 6 files changed, 24 insertions(+), 34 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java index 70df610ca..1fb0a9c3f 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BTService.java @@ -550,7 +550,7 @@ public class BTService extends XWService { } else { short len = is.readShort(); byte[] nliData = new byte[len]; - is.read( nliData ); + is.readFully( nliData ); nli = XwJNI.nliFromStream( nliData ); } @@ -573,26 +573,20 @@ public class BTService extends XWService { int gameID = dis.readInt(); switch ( cmd ) { case MESG_SEND: - short len = dis.readShort(); - byte[] buffer = new byte[len]; - int nRead = dis.read( buffer, 0, len ); - if ( nRead == len ) { - BluetoothDevice host = socket.getRemoteDevice(); - addAddr( host ); + byte[] buffer = new byte[dis.readShort()]; + dis.readFully( buffer ); + BluetoothDevice host = socket.getRemoteDevice(); + addAddr( host ); - CommsAddrRec addr = new CommsAddrRec( host.getName(), - host.getAddress() ); - ReceiveResult rslt - = BTService.this.receiveMessage( BTService.this, - gameID, m_btMsgSink, - buffer, addr ); + CommsAddrRec addr = new CommsAddrRec( host.getName(), + host.getAddress() ); + ReceiveResult rslt + = BTService.this.receiveMessage( BTService.this, + gameID, m_btMsgSink, + buffer, addr ); - result = rslt == ReceiveResult.GAME_GONE ? - BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT; - } else { - Log.e( TAG, "receiveMessage(): read only %d of %d bytes", - nRead, len ); - } + result = rslt == ReceiveResult.GAME_GONE ? + BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT; break; case MESG_GAMEGONE: postEvent( MultiEvent.MESSAGE_NOGAME, gameID ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BiDiSockWrap.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BiDiSockWrap.java index 9b97432d5..e2744c9f5 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BiDiSockWrap.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BiDiSockWrap.java @@ -188,10 +188,8 @@ public class BiDiSockWrap { DataInputStream inStream = new DataInputStream( mSocket.getInputStream() ); while ( mRunThreads ) { - short len = inStream.readShort(); - Log.d( TAG, "got len: %d", len ); - byte[] packet = new byte[len]; - inStream.read( packet ); + byte[] packet = new byte[inStream.readShort()]; + inStream.readFully( packet ); mIface.gotPacket( BiDiSockWrap.this, packet ); } } catch( IOException ioe ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetUtils.java index 62ff478da..78c43b68e 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NetUtils.java @@ -184,7 +184,7 @@ public class NetUtils { short len = dis.readShort(); if ( len > 0 ) { byte[] packet = new byte[len]; - dis.read( packet ); + dis.readFully( packet ); msgs[ii][jj] = packet; } } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RefreshNamesTask.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RefreshNamesTask.java index f94f04725..9844be6d8 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RefreshNamesTask.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RefreshNamesTask.java @@ -94,7 +94,7 @@ public class RefreshNamesTask extends AsyncTask { // Can't figure out how to read a null-terminated string // from DataInputStream so parse it myself. byte[] bytes = new byte[len]; - dis.read( bytes ); + dis.readFully( bytes ); int index = -1; for ( int ii = 0; ii < nRooms; ++ii ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java index 740aa945d..89c24ebb4 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java @@ -736,7 +736,7 @@ public class RelayService extends XWService case XWPDEV_MSG: int token = dis.readInt(); byte[] msg = new byte[dis.available()]; - dis.read( msg ); + dis.readFully( msg ); postData( this, token, msg ); // game-related packets only count @@ -756,9 +756,8 @@ public class RelayService extends XWService resetBackoff = true; intent = getIntentTo( this, MsgCmds.GOT_INVITE ); int srcDevID = dis.readInt(); - short len = dis.readShort(); - byte[] nliData = new byte[len]; - dis.read( nliData ); + byte[] nliData = new byte[dis.readShort()]; + dis.readFully( nliData ); NetLaunchInfo nli = XwJNI.nliFromStream( nliData ); intent.putExtra( INVITE_FROM, srcDevID ); String asStr = nli.toString(); @@ -995,9 +994,8 @@ public class RelayService extends XWService private String getVLIString( DataInputStream dis ) throws java.io.IOException { - int len = vli2un( dis ); - byte[] tmp = new byte[len]; - dis.read( tmp ); + byte[] tmp = new byte[vli2un( dis )]; + dis.readFully( tmp ); String result = new String( tmp ); return result; } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java index 38c1bef61..c5a226eec 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java @@ -522,7 +522,7 @@ public class SMSService extends XWService { case DATA: int gameID = dis.readInt(); byte[] rest = new byte[dis.available()]; - dis.read( rest ); + dis.readFully( rest ); if ( feedMessage( gameID, rest, new CommsAddrRec( phone ) ) ) { SMSResendReceiver.resetTimer( this ); } @@ -618,7 +618,7 @@ public class SMSService extends XWService { } else { SMS_CMD cmd = SMS_CMD.values()[dis.readByte()]; byte[] rest = new byte[dis.available()]; - dis.read( rest ); + dis.readFully( rest ); receive( cmd, rest, senderPhone ); success = true; } From da3f6db9e44a8f46f29efdc441c4a92b47f015ef Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Nov 2017 08:10:48 -0800 Subject: [PATCH 2/7] fix failure to notify relay of half-game deletion Wasn't detecting as a relay game something registered on relay but not yet joined by another device, a common case! --- .../main/java/org/eehouse/android/xw4/GameUtils.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java index dba4c57de..92c476792 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java @@ -1196,7 +1196,7 @@ public class GameUtils { for ( CommsConnType typ : conTypes ) { switch ( typ ) { case COMMS_CONN_RELAY: - tellRelayDied( context, summary, informNow ); + // see below break; case COMMS_CONN_BT: BTService.gameDied( context, addr.bt_btAddr, gameID ); @@ -1210,6 +1210,14 @@ public class GameUtils { } } } + + // comms doesn't have a relay address for us until the game's + // in play (all devices registered, at least.) To enable + // deleting on relay half-games that we created but nobody + // joined, special-case this one. + if ( summary.inRelayGame() ) { + tellRelayDied( context, summary, informNow ); + } gamePtr.release(); } From 39deeeb089099447bb72ddad301371b26dd372cf Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Nov 2017 15:43:07 -0800 Subject: [PATCH 3/7] fix tap down lower not opening game Making the right_side elem match its parent height prevents the lower-right region of game list items from falling through and triggering a toggle-selection event. --- xwords4/android/app/src/main/res/layout/game_list_item.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/res/layout/game_list_item.xml b/xwords4/android/app/src/main/res/layout/game_list_item.xml index 2591b7afe..49e924837 100644 --- a/xwords4/android/app/src/main/res/layout/game_list_item.xml +++ b/xwords4/android/app/src/main/res/layout/game_list_item.xml @@ -64,7 +64,7 @@ From f072c68bf9a4f556b982064710e7b97069f103fb Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 12 Nov 2017 20:25:29 -0800 Subject: [PATCH 4/7] bring in changes from relay_via_http branch It's time to make them live so client development can use a live relay, and all the old tests pass, so why not. --- xwords4/relay/Makefile | 4 +- xwords4/relay/cref.cpp | 34 +++---- xwords4/relay/cref.h | 3 +- xwords4/relay/crefmgr.cpp | 63 ++++++++++++- xwords4/relay/crefmgr.h | 6 +- xwords4/relay/dbmgr.cpp | 135 ++++++++++++++++++---------- xwords4/relay/dbmgr.h | 15 ++-- xwords4/relay/scripts/showinplay.sh | 7 +- xwords4/relay/xwrelay.cpp | 84 ++++++++++------- 9 files changed, 231 insertions(+), 120 deletions(-) diff --git a/xwords4/relay/Makefile b/xwords4/relay/Makefile index af2bbae6f..06fc29fa8 100644 --- a/xwords4/relay/Makefile +++ b/xwords4/relay/Makefile @@ -42,7 +42,7 @@ SRC = \ # STATIC ?= -static GITINFO = gitversion.txt -HASH=$(shell git describe) +HASH=$(shell git rev-parse --verify HEAD) OBJ = $(patsubst %.cpp,obj/%.o,$(SRC)) #LDFLAGS += -pthread -g -lmcheck $(STATIC) @@ -70,7 +70,7 @@ endif memdebug all: xwrelay rq -REQUIRED_DEBS = libpq-dev \ +REQUIRED_DEBS = libpq-dev g++ libglib2.0-dev postgresql \ .PHONY: debcheck debs_install diff --git a/xwords4/relay/cref.cpp b/xwords4/relay/cref.cpp index 86c8c8e8f..07dfa354e 100644 --- a/xwords4/relay/cref.cpp +++ b/xwords4/relay/cref.cpp @@ -875,13 +875,13 @@ putNetShort( uint8_t** bufpp, unsigned short s ) *bufpp += sizeof(s); } -void +int CookieRef::store_message( HostID dest, const uint8_t* buf, unsigned int len ) { logf( XW_LOGVERBOSE0, "%s: storing msg size %d for dest %d", __func__, len, dest ); - DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len ); + return DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len ); } void @@ -1044,6 +1044,7 @@ CookieRef::postCheckAllHere() void CookieRef::postDropDevice( HostID hostID ) { + logf( XW_LOGINFO, "%s(hostID=%d)", __func__, hostID ); CRefEvent evt( XWE_ACKTIMEOUT ); evt.u.ack.srcID = hostID; m_eventQueue.push_back( evt ); @@ -1192,21 +1193,16 @@ CookieRef::sendAnyStored( const CRefEvent* evt ) } typedef struct _StoreData { - string connName; - HostID dest; - uint8_t* buf; - int buflen; + int msgID; } StoreData; void CookieRef::storeNoAck( bool acked, uint32_t packetID, void* data ) { StoreData* sdata = (StoreData*)data; - if ( !acked ) { - DBMgr::Get()->StoreMessage( sdata->connName.c_str(), sdata->dest, - sdata->buf, sdata->buflen ); + if ( acked ) { + DBMgr::Get()->RemoveStoredMessages( &sdata->msgID, 1 ); } - free( sdata->buf ); delete sdata; } @@ -1237,17 +1233,13 @@ CookieRef::forward_or_store( const CRefEvent* evt ) } uint32_t packetID = 0; + int msgID = store_message( dest, buf, buflen ); if ( (NULL == destAddr) || !send_with_length( destAddr, dest, buf, buflen, true, &packetID ) ) { - store_message( dest, buf, buflen ); - } else if ( 0 != packetID ) { // sent via UDP + } else if ( 0 != msgID && 0 != packetID ) { // sent via UDP StoreData* data = new StoreData; - data->connName = m_connName; - data->dest = dest; - data->buf = (uint8_t*)malloc( buflen ); - memcpy( data->buf, buf, buflen ); - data->buflen = buflen; + data->msgID = msgID; UDPAckTrack::setOnAck( storeNoAck, packetID, data ); } @@ -1376,20 +1368,16 @@ CookieRef::sendAllHere( bool initial ) through the vector each time. */ HostID dest; for ( dest = 1; dest <= m_nPlayersSought; ++dest ) { - bool sent = false; *idLoc = dest; /* write in this target's hostId */ { RWReadLock rrl( &m_socketsRWLock ); HostRec* hr = m_sockets[dest-1]; if ( !!hr ) { - sent = send_with_length( &hr->m_addr, dest, buf, - bufp-buf, true ); + (void)send_with_length( &hr->m_addr, dest, buf, bufp-buf, true ); } } - if ( !sent ) { - store_message( dest, buf, bufp-buf ); - } + (void)store_message( dest, buf, bufp-buf ); } } /* sendAllHere */ diff --git a/xwords4/relay/cref.h b/xwords4/relay/cref.h index 508a4485c..61a677976 100644 --- a/xwords4/relay/cref.h +++ b/xwords4/relay/cref.h @@ -275,8 +275,7 @@ class CookieRef { bool notInUse(void) { return m_cid == 0; } - void store_message( HostID dest, const uint8_t* buf, - unsigned int len ); + int store_message( HostID dest, const uint8_t* buf, unsigned int len ); void send_stored_messages( HostID dest, const AddrInfo* addr ); void printSeeds( const char* caller ); diff --git a/xwords4/relay/crefmgr.cpp b/xwords4/relay/crefmgr.cpp index c08a0d2c9..882f0eb27 100644 --- a/xwords4/relay/crefmgr.cpp +++ b/xwords4/relay/crefmgr.cpp @@ -337,7 +337,7 @@ CRefMgr::getMakeCookieRef( const char* connName, const char* cookie, } /* getMakeCookieRef */ CidInfo* -CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead ) +CRefMgr::getMakeCookieRef( const char* const connName, HostID hid, bool* isDead ) { CookieRef* cref = NULL; CidInfo* cinfo = NULL; @@ -347,7 +347,7 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead ) int nAlreadyHere = 0; for ( ; ; ) { /* for: see comment above */ - CookieID cid = m_db->FindGame( connName, curCookie, sizeof(curCookie), + CookieID cid = m_db->FindGame( connName, hid, curCookie, sizeof(curCookie), &curLangCode, &nPlayersT, &nAlreadyHere, isDead ); if ( 0 != cid ) { /* already open */ @@ -375,6 +375,48 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead ) return cinfo; } +CidInfo* +CRefMgr::getMakeCookieRef( const AddrInfo::ClientToken clientToken, HostID srcID ) +{ + CookieRef* cref = NULL; + CidInfo* cinfo = NULL; + char curCookie[MAX_INVITE_LEN+1]; + int curLangCode; + int nPlayersT = 0; + int nAlreadyHere = 0; + + for ( ; ; ) { /* for: see comment above */ + char connName[MAX_CONNNAME_LEN+1] = {0}; + CookieID cid = m_db->FindGame( clientToken, srcID, + connName, sizeof(connName), + curCookie, sizeof(curCookie), + &curLangCode, &nPlayersT, &nAlreadyHere ); + // &seed ); + if ( 0 != cid ) { /* already open */ + cinfo = m_cidlock->Claim( cid ); + if ( NULL == cinfo->GetRef() ) { + m_cidlock->Relinquish( cinfo, true ); + continue; + } + } else if ( nPlayersT == 0 ) { /* wasn't in the DB */ + /* do nothing; insufficient info to fake it */ + } else { + cinfo = m_cidlock->Claim(); + if ( !m_db->AddCID( connName, cinfo->GetCid() ) ) { + m_cidlock->Relinquish( cinfo, true ); + continue; + } + logf( XW_LOGINFO, "%s(): added cid???", __func__ ); + cref = AddNew( curCookie, connName, cinfo->GetCid(), curLangCode, + nPlayersT, nAlreadyHere ); + cinfo->SetRef( cref ); + } + break; + } + logf( XW_LOGINFO, "%s() => %p", __func__, cinfo ); + return cinfo; +} + void CRefMgr::RemoveSocketRefs( const AddrInfo* addr ) { @@ -672,13 +714,13 @@ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid, } /* ConnName case -- must exist (unless DB record's been removed */ -SafeCref::SafeCref( const char* const connName ) +SafeCref::SafeCref( const char* const connName, HostID hid ) : m_cinfo( NULL ) , m_mgr( CRefMgr::Get() ) , m_isValid( false ) { bool isDead = false; - CidInfo* cinfo = m_mgr->getMakeCookieRef( connName, &isDead ); + CidInfo* cinfo = m_mgr->getMakeCookieRef( connName, hid, &isDead ); if ( NULL != cinfo && NULL != cinfo->GetRef() ) { assert( cinfo->GetCid() == cinfo->GetRef()->GetCid() ); m_locked = cinfo->GetRef()->Lock(); @@ -722,6 +764,19 @@ SafeCref::SafeCref( const AddrInfo* addr ) } } +SafeCref::SafeCref( const AddrInfo::ClientToken clientToken, HostID srcID ) + : m_cinfo( NULL ) + , m_mgr( CRefMgr::Get() ) + , m_isValid( false ) +{ + CidInfo* cinfo = m_mgr->getMakeCookieRef( clientToken, srcID ); + if ( NULL != cinfo && NULL != cinfo->GetRef() ) { + m_locked = cinfo->GetRef()->Lock(); + m_cinfo = cinfo; + m_isValid = true; + } +} + SafeCref::~SafeCref() { if ( m_cinfo != NULL ) { diff --git a/xwords4/relay/crefmgr.h b/xwords4/relay/crefmgr.h index 315bce9c8..7694759dd 100644 --- a/xwords4/relay/crefmgr.h +++ b/xwords4/relay/crefmgr.h @@ -128,7 +128,8 @@ class CRefMgr { int nPlayersS, int seed, int langCode, bool isPublic, bool* isDead ); - CidInfo* getMakeCookieRef( const char* const connName, bool* isDead ); + CidInfo* getMakeCookieRef( const char* const connName, HostID hid, bool* isDead ); + CidInfo* getMakeCookieRef( const AddrInfo::ClientToken clientToken, HostID srcID ); CidInfo* getCookieRef( CookieID cid, bool failOk = false ); CidInfo* getCookieRef( const AddrInfo* addr ); @@ -179,9 +180,10 @@ class SafeCref { const AddrInfo* addr, int clientVersion, DevID* devID, int nPlayersH, int nPlayersS, unsigned short gameSeed, int clientIndx, int langCode, bool wantsPublic, bool makePublic ); - SafeCref( const char* const connName ); + SafeCref( const char* const connName, HostID hid ); SafeCref( CookieID cid, bool failOk = false ); SafeCref( const AddrInfo* addr ); + SafeCref( const AddrInfo::ClientToken clientToken, HostID srcID ); /* SafeCref( CookieRef* cref ); */ ~SafeCref(); diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp index 95625a9b6..99492a87a 100644 --- a/xwords4/relay/dbmgr.cpp +++ b/xwords4/relay/dbmgr.cpp @@ -70,20 +70,6 @@ DBMgr::DBMgr() pthread_mutex_init( &m_haveNoMessagesMutex, NULL ); - /* Now figure out what the largest cid currently is. There must be a way - to get postgres to do this for me.... */ - /* const char* query = "SELECT cid FROM games ORDER BY cid DESC LIMIT 1"; */ - /* PGresult* result = PQexec( m_pgconn, query ); */ - /* if ( 0 == PQntuples( result ) ) { */ - /* m_nextCID = 1; */ - /* } else { */ - /* char* value = PQgetvalue( result, 0, 0 ); */ - /* m_nextCID = 1 + atoi( value ); */ - /* } */ - /* PQclear(result); */ - /* logf( XW_LOGINFO, "%s: m_nextCID=%d", __func__, m_nextCID ); */ - - // I've seen rand returning the same series several times.... srand( time( NULL ) ); } @@ -107,7 +93,7 @@ DBMgr::AddNew( const char* cookie, const char* connName, CookieID cid, qb.appendQueryf( "INSERT INTO " GAMES_TABLE " (cid, room, connName, nTotal, lang, pub)" " VALUES( $$, $$, $$, $$, $$, $$ )" ) - .appendParam(cid) + .appendParam(cid) .appendParam(cookie) .appendParam(connName) .appendParam(nPlayersT) @@ -136,7 +122,7 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen, { bool found = false; - const char* fmt = "SELECT cid, room, lang, nPerDevice, dead FROM " + const char* fmt = "SELECT cid, room, lang, dead FROM " GAMES_TABLE " WHERE connName = '%s' AND nTotal = %d " "AND %d = seeds[%d] AND 'A' = ack[%d] " ; @@ -148,10 +134,11 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen, assert( 1 >= PQntuples( result ) ); found = 1 == PQntuples( result ); if ( found ) { - *cidp = atoi( PQgetvalue( result, 0, 0 ) ); - snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) ); - *langP = atoi( PQgetvalue( result, 0, 2 ) ); - *isDead = 't' == PQgetvalue( result, 0, 4 )[0]; + int col = 0; + *cidp = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) ); + *langP = atoi( PQgetvalue( result, 0, col++ ) ); + *isDead = 't' == PQgetvalue( result, 0, col++ )[0]; } PQclear( result ); @@ -160,28 +147,29 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen, } /* FindGameFor */ CookieID -DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen, +DBMgr::FindGame( const char* connName, HostID hid, char* roomBuf, int roomBufLen, int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead ) { CookieID cid = 0; - const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice, dead FROM " + const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice[%d], dead FROM " GAMES_TABLE " WHERE connName = '%s'" // " LIMIT 1" ; StrWPF query; - query.catf( fmt, connName ); + query.catf( fmt, hid, connName ); logf( XW_LOGINFO, "query: %s", query.c_str() ); PGresult* result = PQexec( getThreadConn(), query.c_str() ); assert( 1 >= PQntuples( result ) ); if ( 1 == PQntuples( result ) ) { - cid = atoi( PQgetvalue( result, 0, 0 ) ); - snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) ); - *langP = atoi( PQgetvalue( result, 0, 2 ) ); - *nPlayersTP = atoi( PQgetvalue( result, 0, 3 ) ); - *nPlayersHP = atoi( PQgetvalue( result, 0, 4 ) ); - *isDead = 't' == PQgetvalue( result, 0, 5 )[0]; + int col = 0; + cid = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( roomBuf, roomBufLen, "%s", PQgetvalue( result, 0, col++ ) ); + *langP = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersTP = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) ); + *isDead = 't' == PQgetvalue( result, 0, col++ )[0]; } PQclear( result ); @@ -189,6 +177,40 @@ DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen, return cid; } /* FindGame */ +CookieID +DBMgr::FindGame( const AddrInfo::ClientToken clientToken, HostID hid, + char* connNameBuf, int connNameBufLen, + char* roomBuf, int roomBufLen, + int* langP, int* nPlayersTP, int* nPlayersHP ) +{ + CookieID cid = 0; + const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice[%d], connname FROM " + GAMES_TABLE " WHERE tokens[%d] = %d and NOT dead"; + // " LIMIT 1" + ; + StrWPF query; + query.catf( fmt, hid, hid, clientToken ); + logf( XW_LOGINFO, "query: %s", query.c_str() ); + + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + if ( 1 == PQntuples( result ) ) { + int col = 0; + cid = atoi( PQgetvalue( result, 0, col++ ) ); + // room + snprintf( roomBuf, roomBufLen, "%s", PQgetvalue( result, 0, col++ ) ); + // lang + *langP = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersTP = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( connNameBuf, connNameBufLen, "%s", PQgetvalue( result, 0, col++ ) ); + } + PQclear( result ); + + logf( XW_LOGINFO, "%s(ct=%d,hid=%d) => %d (connname=%s)", __func__, clientToken, + hid, cid, connNameBuf ); + return cid; +} + bool DBMgr::FindPlayer( DevIDRelay relayID, AddrInfo::ClientToken token, string& connName, HostID* hidp, unsigned short* seed ) @@ -294,11 +316,13 @@ DBMgr::SeenSeed( const char* cookie, unsigned short seed, NULL, NULL, 0 ); bool found = 1 == PQntuples( result ); if ( found ) { - *cid = atoi( PQgetvalue( result, 0, 0 ) ); - *nPlayersHP = here_less_seed( PQgetvalue( result, 0, 2 ), - atoi( PQgetvalue( result, 0, 3 ) ), - seed ); - snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) ); + int col = 0; + *cid = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) ); + + const char* seeds = PQgetvalue( result, 0, col++ ); + int perDeviceSum = atoi( PQgetvalue( result, 0, col++ ) ); + *nPlayersHP = here_less_seed( seeds, perDeviceSum, seed ); } PQclear( result ); logf( XW_LOGINFO, "%s(%4X)=>%s", __func__, seed, found?"true":"false" ); @@ -333,9 +357,10 @@ DBMgr::FindOpen( const char* cookie, int lang, int nPlayersT, int nPlayersH, NULL, NULL, 0 ); CookieID cid = 0; if ( 1 == PQntuples( result ) ) { - cid = atoi( PQgetvalue( result, 0, 0 ) ); - snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) ); - *nPlayersHP = atoi( PQgetvalue( result, 0, 2 ) ); + int col = 0; + cid = atoi( PQgetvalue( result, 0, col++ ) ); + snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) ); + *nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) ); /* cid may be 0, but should use game anyway */ } PQclear( result ); @@ -699,9 +724,11 @@ DBMgr::RecordSent( const int* msgIDs, int nMsgIDs ) if ( PGRES_TUPLES_OK == PQresultStatus( result ) ) { int ntuples = PQntuples( result ); for ( int ii = 0; ii < ntuples; ++ii ) { - RecordSent( PQgetvalue( result, ii, 0 ), - atoi( PQgetvalue( result, ii, 1 ) ), - atoi( PQgetvalue( result, ii, 2 ) ) ); + int col = 0; + const char* const connName = PQgetvalue( result, ii, col++ ); + HostID hid = atoi( PQgetvalue( result, ii, col++ ) ); + int nBytes = atoi( PQgetvalue( result, ii, col++ ) ); + RecordSent( connName, hid, nBytes ); } } PQclear( result ); @@ -1014,15 +1041,16 @@ DBMgr::CountStoredMessages( DevIDRelay relayID ) return getCountWhere( MSGS_TABLE, test ); } -void +int DBMgr::StoreMessage( DevIDRelay destDevID, const uint8_t* const buf, int len ) { + int msgID = 0; clearHasNoMessages( destDevID ); size_t newLen; const char* fmt = "INSERT INTO " MSGS_TABLE " " - "(devid, %s, msglen) VALUES(%d, %s'%s', %d)"; + "(devid, %s, msglen) VALUES(%d, %s'%s', %d) RETURNING id"; StrWPF query; if ( m_useB64 ) { @@ -1038,13 +1066,20 @@ DBMgr::StoreMessage( DevIDRelay destDevID, const uint8_t* const buf, } logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - execSql( query ); + + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + if ( 1 == PQntuples( result ) ) { + msgID = atoi( PQgetvalue( result, 0, 0 ) ); + } + PQclear( result ); + return msgID; } -void +int DBMgr::StoreMessage( const char* const connName, int destHid, const uint8_t* buf, int len ) { + int msgID = 0; clearHasNoMessages( connName, destHid ); DevIDRelay devID = getDevID( connName, destHid ); @@ -1074,7 +1109,7 @@ DBMgr::StoreMessage( const char* const connName, int destHid, #ifdef HAVE_STIME " AND stime='epoch'" #endif - " );", connName, destHid, b64 ); + " )", connName, destHid, b64 ); g_free( b64 ); } else { uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf, @@ -1085,9 +1120,17 @@ DBMgr::StoreMessage( const char* const connName, int destHid, "E", bytes, len ); PQfreemem( bytes ); } + query.catf(" RETURNING id;"); logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() ); - execSql( query ); + PGresult* result = PQexec( getThreadConn(), query.c_str() ); + if ( 1 == PQntuples( result ) ) { + msgID = atoi( PQgetvalue( result, 0, 0 ) ); + } else { + logf( XW_LOGINFO, "Not stored; duplicate?" ); + } + PQclear( result ); + return msgID; } void diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h index 690ca5c39..d23622c7e 100644 --- a/xwords4/relay/dbmgr.h +++ b/xwords4/relay/dbmgr.h @@ -75,9 +75,13 @@ class DBMgr { bool FindRelayIDFor( const char* connName, HostID hid, unsigned short seed, const DevID* host, DevIDRelay* devID ); - CookieID FindGame( const char* connName, char* cookieBuf, int bufLen, + CookieID FindGame( const char* connName, HostID hid, char* cookieBuf, int bufLen, int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead ); + CookieID FindGame( const AddrInfo::ClientToken clientToken, HostID hid, + char* connNameBuf, int connNameBufLen, + char* cookieBuf, int cookieBufLen, + int* langP, int* nPlayersTP, int* nPlayersHP ); bool FindGameFor( const char* connName, char* cookieBuf, int bufLen, unsigned short seed, HostID hid, @@ -137,10 +141,10 @@ class DBMgr { /* message storage -- different DB */ int CountStoredMessages( const char* const connName ); int CountStoredMessages( DevIDRelay relayID ); - void StoreMessage( DevIDRelay destRelayID, const uint8_t* const buf, - int len ); - void StoreMessage( const char* const connName, int destHid, - const uint8_t* const buf, int len ); + int StoreMessage( DevIDRelay destRelayID, const uint8_t* const buf, + int len ); + int StoreMessage( const char* const connName, int destHid, + const uint8_t* const buf, int len ); void GetStoredMessages( DevIDRelay relayID, vector& msgs ); void GetStoredMessages( const char* const connName, HostID hid, vector& msgs ); @@ -171,6 +175,7 @@ class DBMgr { int clientVersion, const char* const model, const char* const osVers, DevIDRelay relayID ); + PGconn* getThreadConn( void ); void clearThreadConn(); diff --git a/xwords4/relay/scripts/showinplay.sh b/xwords4/relay/scripts/showinplay.sh index f4c7eeec7..8a593ea02 100755 --- a/xwords4/relay/scripts/showinplay.sh +++ b/xwords4/relay/scripts/showinplay.sh @@ -54,13 +54,14 @@ echo "; relay pid[s]: $(pidof xwrelay)" echo "Row count:" $(psql -t xwgames -c "select count(*) FROM games $QUERY;") # Games -echo "SELECT dead as d,connname,cid,room,lang as lg,clntVers as cv ,ntotal as t,nperdevice as nPerDev,nsents as snts, seeds,devids,tokens,ack, mtimes "\ +echo "SELECT dead as d,connname,cid,room,lang as lg,clntVers as cv ,ntotal as t,nperdevice as npd,nsents as snts, seeds,devids,tokens,ack, mtimes "\ "FROM games $QUERY ORDER BY NOT dead, ctime DESC LIMIT $LIMIT;" \ | psql xwgames # Messages -echo "SELECT * "\ - "FROM msgs WHERE connname IN (SELECT connname from games $QUERY) "\ +echo "Unack'd msgs count:" $(psql -t xwgames -c "select count(*) FROM msgs where stime = 'epoch' AND connname IN (SELECT connname from games $QUERY);") +echo "SELECT id,connName,hid as h,token,ctime,stime,devid,msg64 "\ + "FROM msgs WHERE stime = 'epoch' AND connname IN (SELECT connname from games $QUERY) "\ "ORDER BY ctime DESC, connname LIMIT $LIMIT;" \ | psql xwgames diff --git a/xwords4/relay/xwrelay.cpp b/xwords4/relay/xwrelay.cpp index 666bee3f3..def2b44f1 100644 --- a/xwords4/relay/xwrelay.cpp +++ b/xwords4/relay/xwrelay.cpp @@ -550,18 +550,18 @@ assemble_packet( vector& packet, uint32_t* packetIDP, XWRelayReg cmd, } #ifdef LOG_UDP_PACKETS - gsize size = 0; - gint state = 0; - gint save = 0; - gchar out[1024]; - for ( unsigned int ii = 0; ii < iocount; ++ii ) { - size += g_base64_encode_step( (const guchar*)vec[ii].iov_base, - vec[ii].iov_len, - FALSE, &out[size], &state, &save ); - } - size += g_base64_encode_close( FALSE, &out[size], &state, &save ); - assert( size < sizeof(out) ); - out[size] = '\0'; + // gsize size = 0; + // gint state = 0; + // gint save = 0; + // gchar out[1024]; + // for ( unsigned int ii = 0; ii < iocount; ++ii ) { + // size += g_base64_encode_step( (const guchar*)vec[ii].iov_base, + // vec[ii].iov_len, + // FALSE, &out[size], &state, &save ); + // } + // size += g_base64_encode_close( FALSE, &out[size], &state, &save ); + // assert( size < sizeof(out) ); + // out[size] = '\0'; #endif } @@ -640,8 +640,10 @@ send_via_udp_impl( int sock, const struct sockaddr* dest_addr, #ifdef LOG_UDP_PACKETS gchar* b64 = g_base64_encode( (uint8_t*)dest_addr, sizeof(*dest_addr) ); + gchar* out = g_base64_encode( packet.data(), packet.size() ); logf( XW_LOGINFO, "%s()=>%d; addr='%s'; msg='%s'", __func__, nSent, b64, out ); + g_free( out ); g_free( b64 ); #else logf( XW_LOGINFO, "%s()=>%d", __func__, nSent ); @@ -761,13 +763,17 @@ send_havemsgs( const AddrInfo* addr ) class MsgClosure { public: MsgClosure( DevIDRelay dest, const vector* packet, - OnMsgAckProc proc, void* procClosure ) + int msgID, OnMsgAckProc proc, void* procClosure ) { + assert(m_msgID != 0); m_destDevID = dest; m_packet = *packet; m_proc = proc; m_procClosure = procClosure; + m_msgID = msgID; } + int getMsgID() { return m_msgID; } + int m_msgID; DevIDRelay m_destDevID; vector m_packet; OnMsgAckProc m_proc; @@ -778,9 +784,14 @@ static void onPostedMsgAcked( bool acked, uint32_t packetID, void* data ) { MsgClosure* mc = (MsgClosure*)data; - if ( !acked ) { - DBMgr::Get()->StoreMessage( mc->m_destDevID, mc->m_packet.data(), - mc->m_packet.size() ); + int msgID = mc->getMsgID(); + if ( acked ) { + DBMgr::Get()->RemoveStoredMessages( &msgID, 1 ); + } else { + assert( msgID != 0 ); + // So we only store after ack fails? Change that!!! + // DBMgr::Get()->StoreMessage( mc->m_destDevID, mc->m_packet.data(), + // mc->m_packet.size() ); } if ( NULL != mc->m_proc ) { (*mc->m_proc)( acked, mc->m_destDevID, packetID, mc->m_procClosure ); @@ -793,6 +804,8 @@ static bool post_or_store( DevIDRelay destDevID, vector& packet, uint32_t packetID, OnMsgAckProc proc, void* procClosure ) { + int msgID = DBMgr::Get()->StoreMessage( destDevID, packet.data(), packet.size() ); + const AddrInfo::AddrUnion* addru = DevMgr::Get()->get( destDevID ); bool canSendNow = !!addru; @@ -804,16 +817,13 @@ post_or_store( DevIDRelay destDevID, vector& packet, uint32_t packetID, if ( get_addr_info_if( &addr, &sock, &dest_addr ) ) { sent = 0 < send_packet_via_udp_impl( packet, sock, dest_addr ); - if ( sent ) { - MsgClosure* mc = new MsgClosure( destDevID, &packet, + if ( sent && msgID != 0 ) { + MsgClosure* mc = new MsgClosure( destDevID, &packet, msgID, proc, procClosure ); UDPAckTrack::setOnAck( onPostedMsgAcked, packetID, (void*)mc ); } } } - if ( !sent ) { - DBMgr::Get()->StoreMessage( destDevID, packet.data(), packet.size() ); - } return sent; } @@ -988,13 +998,13 @@ processReconnect( const uint8_t* bufp, int bufLen, const AddrInfo* addr ) } /* processReconnect */ static bool -processAck( const uint8_t* bufp, int bufLen, const AddrInfo* addr ) +processAck( const uint8_t* bufp, int bufLen, AddrInfo::ClientToken clientToken ) { bool success = false; const uint8_t* end = bufp + bufLen; HostID srcID; if ( getNetByte( &bufp, end, &srcID ) ) { - SafeCref scr( addr ); + SafeCref scr( clientToken, srcID ); success = scr.HandleAck( srcID ); } return success; @@ -1084,7 +1094,8 @@ forwardMessage( const uint8_t* buf, int buflen, const AddrInfo* addr ) } /* forwardMessage */ static bool -processMessage( const uint8_t* buf, int bufLen, const AddrInfo* addr ) +processMessage( const uint8_t* buf, int bufLen, const AddrInfo* addr, + AddrInfo::ClientToken clientToken ) { bool success = false; /* default is failure */ XWRELAY_Cmd cmd = *buf; @@ -1099,7 +1110,11 @@ processMessage( const uint8_t* buf, int bufLen, const AddrInfo* addr ) success = processReconnect( buf+1, bufLen-1, addr ); break; case XWRELAY_ACK: - success = processAck( buf+1, bufLen-1, addr ); + if ( clientToken != 0 ) { + success = processAck( buf+1, bufLen-1, clientToken ); + } else { + logf( XW_LOGERROR, "%s(): null client token", __func__ ); + } break; case XWRELAY_GAME_DISCONNECT: success = processDisconnect( buf+1, bufLen-1, addr ); @@ -1334,6 +1349,9 @@ handleMsgsMsg( const AddrInfo* addr, bool sendFull, logf( XW_LOGVERBOSE0, "%s: wrote %d bytes", __func__, nwritten ); if ( sendFull && nwritten >= 0 && (size_t)nwritten == out.size() ) { dbmgr->RecordSent( &msgIDs[0], msgIDs.size() ); + // This is wrong: should be removed when ACK returns and not + // before. But for some reason if I make that change apps wind up + // stalling. dbmgr->RemoveStoredMessages( msgIDs ); } } @@ -1438,7 +1456,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const uint8_t* bufp, } unsigned short nMsgs; if ( getNetShort( &bufp, end, &nMsgs ) ) { - SafeCref scr( connName ); + SafeCref scr( connName, hid ); while ( scr.IsValid() && nMsgs-- > 0 ) { unsigned short len; if ( getNetShort( &bufp, end, &len ) ) { @@ -1460,7 +1478,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const uint8_t* bufp, static void game_thread_proc( UdpThreadClosure* utc ) { - if ( !processMessage( utc->buf(), utc->len(), utc->addr() ) ) { + if ( !processMessage( utc->buf(), utc->len(), utc->addr(), 0 ) ) { XWThreadPool::GetTPool()->CloseSocket( utc->addr() ); } } @@ -1528,7 +1546,7 @@ proxy_thread_proc( UdpThreadClosure* utc ) sizeof( connName ), &hid ) ) { break; } - SafeCref scr( connName ); + SafeCref scr( connName, hid ); scr.DeviceGone( hid, seed ); } } @@ -1748,7 +1766,7 @@ handle_udp_packet( UdpThreadClosure* utc ) clientToken = ntohl( clientToken ); if ( AddrInfo::NULL_TOKEN != clientToken ) { AddrInfo addr( g_udpsock, clientToken, utc->saddr() ); - (void)processMessage( ptr, end - ptr, &addr ); + (void)processMessage( ptr, end - ptr, &addr, clientToken ); } else { logf( XW_LOGERROR, "%s: dropping packet with token of 0", __func__ ); @@ -1766,7 +1784,7 @@ handle_udp_packet( UdpThreadClosure* utc ) logf( XW_LOGERROR, "parse failed!!!" ); break; } - SafeCref scr( connName ); + SafeCref scr( connName, hid ); if ( scr.IsValid() ) { AddrInfo addr( g_udpsock, clientToken, utc->saddr() ); handlePutMessage( scr, hid, &addr, end - ptr, &ptr, end ); @@ -1833,7 +1851,7 @@ handle_udp_packet( UdpThreadClosure* utc ) string connName; if ( DBMgr::Get()->FindPlayer( devID.asRelayID(), clientToken, connName, &hid, &seed ) ) { - SafeCref scr( connName.c_str() ); + SafeCref scr( connName.c_str(), hid ); scr.DeviceGone( hid, seed ); } } @@ -1980,7 +1998,7 @@ maint_str_loop( int udpsock, const char* str ) } // maint_str_loop static uint32_t -getIPAddr( void ) +getUDPIPAddr( void ) { uint32_t result = INADDR_ANY; char iface[16] = {0}; @@ -2215,7 +2233,7 @@ main( int argc, char** argv ) struct sockaddr_in saddr; g_udpsock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); saddr.sin_family = PF_INET; - saddr.sin_addr.s_addr = getIPAddr(); + saddr.sin_addr.s_addr = getUDPIPAddr(); saddr.sin_port = htons(udpport); int err = bind( g_udpsock, (struct sockaddr*)&saddr, sizeof(saddr) ); if ( 0 == err ) { From b77ea0aaadd6e3bac006dd9ad07b989dc2179881 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 12 Nov 2017 20:29:15 -0800 Subject: [PATCH 5/7] copied from relay_via_http branch (was at: ./xwords4/android/scripts/relay.py) This is the web API clients can use to talk to the relay. --- xwords4/relay/scripts/relay.py | 250 +++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100755 xwords4/relay/scripts/relay.py diff --git a/xwords4/relay/scripts/relay.py b/xwords4/relay/scripts/relay.py new file mode 100755 index 000000000..7ca6c2fa6 --- /dev/null +++ b/xwords4/relay/scripts/relay.py @@ -0,0 +1,250 @@ +#!/usr/bin/python + +import base64, json, mod_python, socket, struct, sys +import psycopg2, random + +PROTOCOL_VERSION = 0 +PRX_DEVICE_GONE = 3 +PRX_GET_MSGS = 4 + +# try: +# from mod_python import apache +# apacheAvailable = True +# except ImportError: +# apacheAvailable = False + +# Joining a game. Basic idea is you have stuff to match on (room, +# number in game, language) and when somebody wants to join you add to +# an existing matching game if there's space otherwise create a new +# one. Problems are the unreliablity of transport: if you give a space +# and the device doesn't get the message you can't hold it forever. So +# device provides a seed that holds the space. If it asks again for a +# space with the same seed it gets the same space. If it never asks +# again (app deleted, say), the space needs eventually to be given to +# somebody else. I think that's done by adding a timestamp array and +# treating the space as available if TIME has expired. Need to think +# about this: what if app fails to ACK for TIME, then returns with +# seed to find it given away. Let's do a 30 minute reservation for +# now? [Note: much of this is PENDING] + +def join(req, devID, room, seed, hid = 0, lang = 1, nInGame = 2, nHere = 1, inviteID = None): + assert hid <= 4 + seed = int(seed) + assert seed != 0 + nInGame = int(nInGame) + nHere = int(nHere) + assert nHere <= nInGame + assert nInGame <= 4 + + devID = int(devID, 16) + + connname = None + logs = [] # for debugging + # logs.append('vers: ' + platform.python_version()) + + con = psycopg2.connect(database='xwgames') + cur = con.cursor() + # cur.execute('LOCK TABLE games IN ACCESS EXCLUSIVE MODE') + + # First see if there's a game with a space for me. Must match on + # room, lang and size. Must have room OR must have already given a + # spot for a seed equal to mine, in which case I get it + # back. Assumption is I didn't ack in time. + + query = "SELECT connname, seeds, nperdevice FROM games " + query += "WHERE lang = %s AND nTotal = %s AND room = %s " + query += "AND (njoined + %s <= ntotal OR %s = ANY(seeds)) " + query += "LIMIT 1" + cur.execute( query, (lang, nInGame, room, nHere, seed)) + for row in cur: + (connname, seeds, nperdevice) = row + print('found', connname, seeds, nperdevice) + break # should be only one! + + # If we've found a match, we either need to UPDATE or, if the + # seeds match, remind the caller of where he belongs. If a hid's + # been specified, we honor it by updating if the slot's available; + # otherwise a new game has to be created. + if connname: + if seed in seeds and nHere == nperdevice[seeds.index(seed)]: + hid = seeds.index(seed) + 1 + print('resusing seed case; outta here!') + else: + if hid == 0: + # Any gaps? Assign it + if None in seeds: + hid = seeds.index(None) + 1 + else: + hid = len(seeds) + 1 + print('set hid to', hid, 'based on ', seeds) + else: + print('hid already', hid) + query = "UPDATE games SET njoined = njoined + %s, " + query += "devids[%d] = %%s, " % hid + query += "seeds[%d] = %%s, " % hid + query += "jtimes[%d] = 'now', " % hid + query += "nperdevice[%d] = %%s " % hid + query += "WHERE connname = %s " + print(query) + params = (nHere, devID, seed, nHere, connname) + cur.execute(query, params) + + # If nothing was found, add a new game and add me. Honor my hid + # preference if specified + if not connname: + # This requires python3, which likely requires mod_wsgi + # ts = datetime.datetime.utcnow().timestamp() + # connname = '%s:%d:1' % (xwconfig.k_HOSTNAME, int(ts * 1000)) + connname = '%s:%d:1' % (xwconfig.k_HOSTNAME, random.randint(0, 10000000000)) + useHid = hid == 0 and 1 or hid + print('not found case; inserting using hid:', useHid) + query = "INSERT INTO games (connname, room, lang, ntotal, njoined, " + \ + "devids[%d], seeds[%d], jtimes[%d], nperdevice[%d]) " % (4 * (useHid,)) + query += "VALUES (%s, %s, %s, %s, %s, %s, %s, 'now', %s) " + query += "RETURNING connname, array_length(seeds,1); " + cur.execute(query, (connname, room, lang, nInGame, nHere, devID, seed, nHere)) + for row in cur: + connname, gothid = row + break + if hid == 0: hid = gothid + + con.commit() + con.close() + + result = {'connname': connname, 'hid' : hid, 'log' : ':'.join(logs)} + + return json.dumps(result) + +def kill(req, params): + print(params) + params = json.loads(params) + count = len(params) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('127.0.0.1', 10998)) + + header = struct.Struct('!BBh') + strLens = 0 + for ii in range(count): + strLens += len(params[ii]['relayID']) + 1 + size = header.size + (2*count) + strLens + sock.send(struct.Struct('!h').pack(size)) + sock.send(header.pack(PROTOCOL_VERSION, PRX_DEVICE_GONE, count)) + + for ii in range(count): + elem = params[ii] + asBytes = bytes(elem['relayID']) + sock.send(struct.Struct('!H%dsc' % (len(asBytes))).pack(elem['seed'], asBytes, '\n')) + sock.close() + + result = {'err': 0} + return json.dumps(result) + +# winds up in handle_udp_packet() in xwrelay.cpp +def post(req, params): + err = 'none' + params = json.loads(params) + data = params['data'] + timeoutSecs = 'timeoutSecs' in params and params['timeoutSecs'] or 1.0 + binData = [base64.b64decode(datum) for datum in data] + + udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + udpSock.settimeout(float(timeoutSecs)) # seconds + addr = ("127.0.0.1", 10997) + for binDatum in binData: + udpSock.sendto(binDatum, addr) + + responses = [] + while True: + try: + data, server = udpSock.recvfrom(1024) + responses.append(base64.b64encode(data)) + except socket.timeout: + #If data is not received back from server, print it has timed out + err = 'timeout' + break + + result = {'err' : err, 'data' : responses} + return json.dumps(result) + +def query(req, params): + print('params', params) + params = json.loads(params) + ids = params['ids'] + timeoutSecs = 'timeoutSecs' in params and float(params['timeoutSecs']) or 2.0 + + idsLen = 0 + for id in ids: idsLen += len(id) + + tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + tcpSock.settimeout(timeoutSecs) + tcpSock.connect(('127.0.0.1', 10998)) + + lenShort = 2 + idsLen + len(ids) + 2 + print(lenShort, PROTOCOL_VERSION, PRX_GET_MSGS, len(ids)) + header = struct.Struct('!hBBh') + assert header.size == 6 + tcpSock.send(header.pack(lenShort, PROTOCOL_VERSION, PRX_GET_MSGS, len(ids))) + + for id in ids: tcpSock.send(id + '\n') + + msgsLists = {} + try: + shortUnpacker = struct.Struct('!H') + resLen, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) # not getting all bytes + nameCount, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) + resLen -= shortUnpacker.size + print('resLen:', resLen, 'nameCount:', nameCount) + if nameCount == len(ids) and resLen > 0: + print('nameCount', nameCount) + for ii in range(nameCount): + perGame = [] + countsThisGame, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) # problem + print('countsThisGame:', countsThisGame) + for jj in range(countsThisGame): + msgLen, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) + print('msgLen:', msgLen) + msgs = [] + if msgLen > 0: + msg = tcpSock.recv(msgLen) + print('msg len:', len(msg)) + msg = base64.b64encode(msg) + msgs.append(msg) + perGame.append(msgs) + msgsLists[ids[ii]] = perGame + except: + None + + return json.dumps(msgsLists) + +def main(): + result = None + if len(sys.argv) > 1: + cmd = sys.argv[1] + args = sys.argv[2:] + if cmd == 'query' and len(args) > 0: + result = query(None, json.dumps({'ids':args})) + elif cmd == 'post': + # Params = { 'data' : 'V2VkIE9jdCAxOCAwNjowNDo0OCBQRFQgMjAxNwo=' } + # params = json.dumps(params) + # print(post(None, params)) + pass + elif cmd == 'join': + if len(args) == 6: + result = join(None, 1, args[0], int(args[1]), int(args[2]), int(args[3]), int(args[4]), int(args[5])) + elif cmd == 'kill': + result = kill( None, json.dumps([{'relayID': args[0], 'seed':int(args[1])}]) ) + + if result: + print '->', result + else: + print 'USAGE: query [connname/hid]*' + print ' join ' + print ' query [connname/hid]*' + # print ' post ' + print ' kill ' + print ' join ' + +############################################################################## +if __name__ == '__main__': + main() From c2eff7d3f2fdfb067756cc1142259608c42428d7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 12 Nov 2017 22:31:48 -0800 Subject: [PATCH 6/7] add "archive" option to dialog on opening done game Create the group if needed. Name's not user-editable at this point. Should be a preference, and that preference should be changed if user renames it. --- .../eehouse/android/xw4/BoardDelegate.java | 25 +++++++++++++++ .../java/org/eehouse/android/xw4/DBUtils.java | 32 +++++++++++++++++-- .../android/xw4/GamesListDelegate.java | 5 +-- .../app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java index 35052ec90..71f60acf4 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardDelegate.java @@ -204,6 +204,21 @@ public class BoardDelegate extends DelegateBase } }; ab.setNegativeButton( R.string.button_rematch, lstnr ); + + // If we're not already in the "archive" group, offer to move + final String archiveName = LocUtils + .getString( m_activity, R.string.group_name_archive ); + final long archiveGroup = DBUtils.getGroup( m_activity, archiveName ); + long curGroup = DBUtils.getGroupForGame( m_activity, m_rowid ); + if ( curGroup != archiveGroup ) { + lstnr = new OnClickListener() { + public void onClick( DialogInterface dlg, + int whichButton ) { + archiveAndClose( archiveName, archiveGroup ); + } + }; + ab.setNeutralButton( R.string.button_archive, lstnr ); + } } else if ( DlgID.DLG_CONNSTAT == dlgID && BuildConfig.DEBUG && null != m_connTypes && (m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY ) @@ -2575,6 +2590,16 @@ public class BoardDelegate extends DelegateBase return wordsArray; } + private void archiveAndClose( String archiveName, long groupID ) + { + if ( DBUtils.GROUPID_UNSPEC == groupID ) { + groupID = DBUtils.addGroup( m_activity, archiveName ); + } + DBUtils.moveGame( m_activity, m_rowid, groupID ); + waitCloseGame( false ); + finish(); + } + // For now, supported if standalone or either BT or SMS used for transport private boolean rematchSupported( boolean showMulti ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java index ec7782026..9f287b36b 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBUtils.java @@ -81,7 +81,9 @@ public class DBUtils { private static long s_cachedRowID = ROWID_NOTFOUND; private static byte[] s_cachedBytes = null; - public static enum GameChangeType { GAME_CHANGED, GAME_CREATED, GAME_DELETED }; + public static enum GameChangeType { GAME_CHANGED, GAME_CREATED, + GAME_DELETED, GAME_MOVED, + }; public static interface DBChangeListener { public void gameSaved( long rowid, GameChangeType change ); @@ -1701,6 +1703,29 @@ public class DBUtils { return result; } + public static long getGroup( Context context, String name ) + { + long result = GROUPID_UNSPEC; + String[] columns = { ROW_ID }; + String selection = DBHelper.GROUPNAME + " = ?"; + String[] selArgs = { name }; + + initDB( context ); + synchronized( s_dbHelper ) { + Cursor cursor = s_db.query( DBHelper.TABLE_NAME_GROUPS, columns, + selection, selArgs, + null, // groupBy + null, // having + null // orderby + ); + if ( cursor.moveToNext() ) { + result = cursor.getLong( cursor.getColumnIndex( ROW_ID ) ); + } + cursor.close(); + } + return result; + } + public static long addGroup( Context context, String name ) { long rowid = GROUPID_UNSPEC; @@ -1759,13 +1784,14 @@ public class DBUtils { } // Change group id of a game - public static void moveGame( Context context, long gameid, long groupID ) + public static void moveGame( Context context, long rowid, long groupID ) { Assert.assertTrue( GROUPID_UNSPEC != groupID ); ContentValues values = new ContentValues(); values.put( DBHelper.GROUPID, groupID ); - updateRow( context, DBHelper.TABLE_NAME_SUM, gameid, values ); + updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values ); invalGroupsCache(); + notifyListeners( rowid, GameChangeType.GAME_MOVED ); } private static String getChatHistoryStr( Context context, long rowid ) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java index 90a1dc2b1..06300270b 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java @@ -1060,8 +1060,6 @@ public class GamesListDelegate extends ListDelegateBase invalidateOptionsMenuIf(); setTitle(); } - - mkListAdapter(); } public void invalidateOptionsMenuIf() @@ -1133,6 +1131,9 @@ public class GamesListDelegate extends ListDelegateBase mkListAdapter(); setSelGame( rowid ); break; + case GAME_MOVED: + mkListAdapter(); + break; default: Assert.fail(); break; diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 4ba195973..c8342bb3a 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2158,6 +2158,8 @@ game with the same players and parameters as the one that just ended. --> Rematch + Archive\u200C + Archive Reconnect From 607567cd35f85c93129e995f0524b355754d20da Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 12 Nov 2017 22:50:45 -0800 Subject: [PATCH 7/7] don't allow duplicate group names If group already exists, warn rather than create another with the same name. --- .../eehouse/android/xw4/GamesListDelegate.java | 15 ++++++++++++--- .../android/app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java index 06300270b..41ffeeaea 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java @@ -755,9 +755,18 @@ public class GamesListDelegate extends ListDelegateBase lstnr = new OnClickListener() { public void onClick( DialogInterface dlg, int item ) { String name = namer.getName(); - DBUtils.addGroup( m_activity, name ); - mkListAdapter(); - showNewGroupIf(); + long hasName = DBUtils.getGroup( m_activity, name ); + if ( DBUtils.GROUPID_UNSPEC == hasName ) { + DBUtils.addGroup( m_activity, name ); + mkListAdapter(); + showNewGroupIf(); + } else { + String msg = LocUtils + .getString( m_activity, + R.string.duplicate_group_name_fmt, + name ); + makeOkOnlyBuilder( msg ).show(); + } } }; lstnr2 = new OnClickListener() { diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index c8342bb3a..4a5a7eb29 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2161,6 +2161,8 @@ Archive\u200C Archive + The group \"%1$s\" already exists. + Reconnect Square rack tiles