mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-18 22:26:30 +01:00
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:
parent
1c58ab3d99
commit
13cc6c79f1
7 changed files with 170 additions and 37 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue