add save/restore of partial messages

Handles case where the app receives only a subset of the SMS messages
into which a larger game-level message has been broken. Now when it
restarts and the remaining parts come in the whole can be reassembled.
This commit is contained in:
Eric House 2018-07-07 22:56:32 -07:00
parent 1c58ab3d99
commit 13cc6c79f1
7 changed files with 170 additions and 37 deletions

View file

@ -208,6 +208,8 @@ typedef enum {
BONUS_LAST
} XWBonusType;
#define PERSIST_KEY(str) __FILE__ ":" str
/* I need a way to communiate prefs to common/ code. For now, though, I'll
* leave storage of these values up to the platforms. First, because I don't
* want to deal with versioning in the common code. Second, becuase they

View file

@ -72,9 +72,9 @@ struct XW_DUtilCtxt {
#define dutil_getUserQuantityString( duc, c, q ) \
(duc)->vtable.m_dutil_getUserQuantityString((duc),(c),(q))
#define dutil_stor(duc, k, v) \
#define dutil_store(duc, k, v) \
(duc)->vtable.m_dutil_store((duc), (k), (v));
#define dutil_load(uc, k, s) \
#define dutil_load(duc, k, s) \
(duc)->vtable.m_dutil_load((duc), (k), (s));
#ifdef XWFEATURE_SMS

View file

@ -23,6 +23,7 @@
#include "util.h"
#include "smsproto.h"
#include "comtypes.h"
#include "strutils.h"
# define MAX_WAIT 5
// # define MAX_MSG_LEN 50 /* for testing */
@ -65,7 +66,7 @@ struct SMSProto {
XW_DUtilCtxt* dutil;
pthread_t creator;
XP_U16 nNextID;
int lastStoredSize;
XP_U16 nToPhones;
ToPhoneRec* toPhoneRecs;
@ -75,6 +76,8 @@ struct SMSProto {
MPSLOT;
};
#define KEY_PARTIALS PERSIST_KEY("partials")
static int nextMsgID( SMSProto* state );
static SMSMsgArray* toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld );
static ToPhoneRec* getForPhone( SMSProto* state, const XP_UCHAR* phone,
@ -85,6 +88,8 @@ static void addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID,
int indx, int count, const XP_U8* data, XP_U16 len );
static SMSMsgArray* completeMsgs( SMSProto* state, SMSMsgArray* arr,
const XP_UCHAR* fromPhone, int msgID );
static void savePartials( SMSProto* state );
static void restorePartials( SMSProto* state );
static void rmFromPhoneRec( SMSProto* state, int fromPhoneIndex );
static void freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex,
int msgIDIndex );
@ -104,6 +109,9 @@ smsproto_init( MPFORMAL XW_DUtilCtxt* dutil )
state->dutil = dutil;
// checkThread( state ); <-- Android's calling this on background thread now
MPASSIGN( state->mpool, mpool );
restorePartials( state );
return state;
}
@ -220,6 +228,7 @@ smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
/* __func__, len, fromPhone, proto, msgID, indx, count ); */
addMessage( state, fromPhone, msgID, indx, count, data + offset, len - offset );
result = completeMsgs( state, result, fromPhone, msgID );
savePartials( state );
}
break;
case SMS_PROTO_VERSION_COMBO:
@ -397,16 +406,20 @@ addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID, int indx,
int count, const XP_U8* data, XP_U16 len )
{
XP_LOGF( "phone=%s, msgID=%d, %d/%d", fromPhone, msgID, indx, count );
int fromPhoneIndex, msgIDIndex; /* ignored */
XP_ASSERT( 0 < len );
int ignore;
MsgIDRec* msgIDRec = getMsgIDRec( state, fromPhone, msgID, XP_TRUE,
&fromPhoneIndex, &msgIDIndex );
&ignore, &ignore );
/* if it's new, fill in missing fields */
if ( msgIDRec->count == 0 ) {
msgIDRec->count = count; /* in case it's new */
msgIDRec->parts = XP_CALLOC( state->mpool, count * sizeof(*msgIDRec->parts));
} else {
XP_ASSERT( count == msgIDRec->count );
}
XP_ASSERT( msgIDRec->parts[indx].len == 0 );
XP_ASSERT( msgIDRec->parts[indx].len == 0
|| msgIDRec->parts[indx].len == len ); /* replace with same ok */
msgIDRec->parts[indx].len = len;
msgIDRec->parts[indx].data = XP_MALLOC( state->mpool, len );
XP_MEMCPY( msgIDRec->parts[indx].data, data, len );
@ -460,6 +473,84 @@ freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex, int msgIDIndex
}
}
static void
savePartials( SMSProto* state )
{
checkThread( state );
XWStreamCtxt* stream
= mem_stream_make_raw( MPPARM(state->mpool)
dutil_getVTManager(state->dutil) );
stream_putU8( stream, 0 ); /* version */
stream_putU8( stream, state->nFromPhones );
for ( int ii = 0; ii < state->nFromPhones; ++ii ) {
const FromPhoneRec* rec = &state->fromPhoneRecs[ii];
stringToStream( stream, rec->phone );
stream_putU8( stream, rec->nMsgIDs );
for ( int jj = 0; jj < rec->nMsgIDs; ++jj ) {
MsgIDRec* mir = &rec->msgIDRecs[jj];
stream_putU16( stream, mir->msgID );
stream_putU8( stream, mir->count );
/* There's an array here. It may be sparse. Save a len of 0 */
for ( int kk = 0; kk < mir->count; ++kk ) {
int len = mir->parts[kk].len;
stream_putU8( stream, len );
stream_putBytes( stream, mir->parts[kk].data, len );
}
}
}
XP_U16 newSize = stream_getSize( stream );
if ( state->lastStoredSize == 2 && newSize == 2 ) {
XP_LOGF( "%s(): not storing empty again", __func__ );
} else {
dutil_store( state->dutil, KEY_PARTIALS, stream );
state->lastStoredSize = newSize;
}
stream_destroy( stream );
LOG_RETURN_VOID();
} /* savePartials */
static void
restorePartials( SMSProto* state )
{
// LOG_FUNC();
XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool)
dutil_getVTManager(state->dutil) );
dutil_load( state->dutil, KEY_PARTIALS, stream );
if ( stream_getSize( stream ) >= 1 ) {
XP_ASSERT( 0 == stream_getU8( stream ) );
int nFromPhones = stream_getU8( stream );
for ( int ii = 0; ii < nFromPhones; ++ii ) {
XP_UCHAR phone[32];
(void)stringFromStreamHere( stream, phone, VSIZE(phone) );
int nMsgIDs = stream_getU8( stream );
XP_LOGF( "%s(): got %d message records for phone %s", __func__,
nMsgIDs, phone );
for ( int jj = 0; jj < nMsgIDs; ++jj ) {
XP_U16 msgID = stream_getU16( stream );
int count = stream_getU8( stream );
XP_LOGF( "%s(): got %d records for msgID %d", __func__, count, msgID );
for ( int kk = 0; kk < count; ++kk ) {
int len = stream_getU8( stream );
if ( 0 < len ) {
XP_U8 buf[len];
stream_getBytes( stream, buf, len );
addMessage( state, phone, msgID, kk, count, buf, len );
}
}
}
}
}
stream_destroy( stream );
// LOG_RETURN_VOID();
}
static SMSMsgArray*
completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone,
int msgID )
@ -576,7 +667,7 @@ toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
}
return result;
}
} /* toMsgs */
static int
nextMsgID( SMSProto* state )
@ -716,6 +807,25 @@ smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
XP_ASSERT( !out );
smsproto_freeMsgArray( state, arr );
/* now a message that's unpacked across multiple sessions to test store/load */
XP_LOGF( "%s(): testing store/restore", __func__ );
arr = smsproto_prepOutbound( state, (XP_U8*)buf, 200, "33333", XP_TRUE, &waitSecs );
for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
SMSMsgArray* out = smsproto_prepInbound( state, "33333", arr->msgs[ii].data,
arr->msgs[ii].len );
if ( !!out ) {
XP_ASSERT( out->nMsgs == 1);
XP_LOGF( "%s(): got the message on the %dth loop", __func__, ii );
XP_ASSERT( out->msgs[0].len == 200 );
XP_ASSERT( 0 == memcmp( out->msgs[0].data, buf, 200 ) );
smsproto_freeMsgArray( state, out );
break;
}
smsproto_free( state ); /* give it a chance to store state */
state = smsproto_init( mpool, dutil );
}
smsproto_free( state );
LOG_RETURN_VOID();
}

View file

@ -497,6 +497,7 @@ loadInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid )
void
deleteGame( sqlite3* pDb, sqlite3_int64 rowid )
{
XP_ASSERT( !!pDb );
char query[256];
snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid );
sqlite3_stmt* ppStmt;
@ -511,10 +512,10 @@ deleteGame( sqlite3* pDb, sqlite3_int64 rowid )
void
db_store( sqlite3* pDb, const gchar* key, const gchar* value )
{
char buf[256];
snprintf( buf, sizeof(buf),
"INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')",
key, value );
XP_ASSERT( !!pDb );
gchar* buf =
g_strdup_printf( "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')",
key, value );
sqlite3_stmt *ppStmt;
int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL );
assertPrintResult( pDb, result, SQLITE_OK );
@ -522,11 +523,13 @@ db_store( sqlite3* pDb, const gchar* key, const gchar* value )
assertPrintResult( pDb, result, SQLITE_DONE );
XP_USE( result );
sqlite3_finalize( ppStmt );
g_free( buf );
}
FetchResult
db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen )
{
XP_ASSERT( !!pDb );
FetchResult result = NOT_THERE;
char query[256];
snprintf( query, sizeof(query),
@ -552,10 +555,11 @@ db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen )
}
XP_Bool
db_fetch_safe( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen )
db_fetch_safe( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen )
{
XP_ASSERT( !!pDb );
int tmp = buflen;
FetchResult result = db_fetch( dbp, key, buf, &tmp );
FetchResult result = db_fetch( pDb, key, buf, &tmp );
XP_ASSERT( result != BUFFER_TOO_SMALL );
return SUCCESS == result;
}
@ -563,6 +567,7 @@ db_fetch_safe( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen )
void
db_remove( sqlite3* pDb, const gchar* key )
{
XP_ASSERT( !!pDb );
char query[256];
snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key );
sqlite3_stmt *ppStmt;

View file

@ -47,7 +47,7 @@ typedef struct _GameInfo {
} GameInfo;
sqlite3* openGamesDB( const char* dbName );
void closeGamesDB( sqlite3* dbp );
void closeGamesDB( sqlite3* pDb );
void writeToDB( XWStreamCtxt* stream, void* closure );
sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb );
@ -55,15 +55,15 @@ sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb );
void summarize( CommonGlobals* cGlobals );
/* Return GSList whose data is (ptrs to) rowids */
GSList* listGames( sqlite3* dbp );
GSList* listGames( sqlite3* pDb );
/* free list and data allocated by above */
void freeGamesList( GSList* games );
/* Mapping of relayID -> rowid */
GHashTable* getRelayIDsToRowsMap( sqlite3* pDb );
XP_Bool getGameInfo( sqlite3* dbp, sqlite3_int64 rowid, GameInfo* gib );
void getRowsForGameID( sqlite3* dbp, XP_U32 gameID, sqlite3_int64* rowids,
XP_Bool getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib );
void getRowsForGameID( sqlite3* pDb, XP_U32 gameID, sqlite3_int64* rowids,
int* nRowIDs );
XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid );
void saveInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb,
@ -78,12 +78,12 @@ void deleteGame( sqlite3* pDb, sqlite3_int64 rowid );
#define KEY_SMSPORT "SMSPORT"
#define KEY_WIN_LOC "WIN_LOC"
void db_store( sqlite3* dbp, const gchar* key, const gchar* value );
void db_remove( sqlite3* dbp, const gchar* key );
void db_store( sqlite3* pDb, const gchar* key, const gchar* value );
void db_remove( sqlite3* pDb, const gchar* key );
typedef enum { NOT_THERE, BUFFER_TOO_SMALL, SUCCESS } FetchResult;
FetchResult db_fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint* buflen );
XP_Bool db_fetch_safe( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen );
FetchResult db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen );
XP_Bool db_fetch_safe( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen );
#endif

View file

@ -861,14 +861,6 @@ gtkmain( LaunchParams* params )
g_globals_for_signal = &apg;
if ( params->runSMSTest ) {
CommonGlobals cGlobals = {.params = params };
setupUtil( &cGlobals );
smsproto_runTests( params->mpool, cGlobals.params->dutil );
linux_util_vt_destroy( cGlobals.util );
free( cGlobals.util );
}
struct sigaction act = { .sa_handler = handle_sigintterm };
sigaction( SIGINT, &act, NULL );
sigaction( SIGTERM, &act, NULL );
@ -928,7 +920,13 @@ gtkmain( LaunchParams* params )
XP_LOGF( "not activating SMS: I don't have a phone" );
}
if ( params->runSMSTest ) {
CommonGlobals cGlobals = {.params = params };
setupUtil( &cGlobals );
smsproto_runTests( params->mpool, cGlobals.params->dutil );
linux_util_vt_destroy( cGlobals.util );
free( cGlobals.util );
}
#endif
makeGamesWindow( &apg );
} else if ( !!params->dbFileName ) {

View file

@ -180,19 +180,37 @@ linux_dutil_getUserQuantityString( XW_DUtilCtxt* duc, XP_U16 code,
}
static void
linux_dutil_store( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* data )
linux_dutil_store( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* stream )
{
XP_LOGF( "%s(key=%s)", __func__, key );
XP_USE( duc );
XP_USE( data );
LaunchParams* params = (LaunchParams*)duc->closure;
sqlite3* pDb = params->pDb;
gchar* b64 = g_base64_encode( stream_getPtr( stream ), stream_getSize( stream ) );
db_store( pDb, key, b64 );
g_free( b64 );
}
static void
linux_dutil_load( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* inOut )
{
XP_LOGF( "%s(key=%s)", __func__, key );
XP_USE( duc );
XP_USE( inOut );
LaunchParams* params = (LaunchParams*)duc->closure;
sqlite3* pDb = params->pDb;
gint buflen = 0;
FetchResult res = db_fetch( pDb, key, NULL, &buflen );
if ( res == BUFFER_TOO_SMALL ) {
gchar buf[buflen];
res = db_fetch( pDb, key, buf, &buflen );
XP_ASSERT( res == SUCCESS );
gsize out_len;
guchar* txt = g_base64_decode( (const gchar*)buf, &out_len );
stream_putBytes( inOut, txt, out_len );
g_free( txt );
}
XP_LOGF( "%s(key=%s) => len: %d", __func__, key, stream_getSize(inOut) );
}
#ifdef XWFEATURE_SMS