From af4785c09f7a522e2eeeebfbb625efae9b6b9544 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 07:17:22 -0700 Subject: [PATCH 01/71] fix crash creating light-colored arrow Changing it to a VectorDrawable meant it crashed code that assumed otherwise. Fix by removing the assumption. --- .../java/org/eehouse/android/xw4/BoardCanvas.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java index b78ca6ee5..fa8a5d67d 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java @@ -919,17 +919,21 @@ public class BoardCanvas extends Canvas implements DrawCtx { Drawable arrow = res.getDrawable( resID ); if ( !useDark ) { - Bitmap src = ((BitmapDrawable)arrow).getBitmap(); - Bitmap bitmap = src.copy( Bitmap.Config.ARGB_8888, true ); + Bitmap bitmap = Bitmap.createBitmap( arrow.getIntrinsicWidth(), + arrow.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888 ); + Canvas canvas = new Canvas( bitmap ); + arrow.setBounds( 0, 0, canvas.getWidth(), canvas.getHeight() ); + arrow.draw( canvas ); + for ( int xx = 0; xx < bitmap.getWidth(); ++xx ) { - for( int yy = 0; yy < bitmap.getHeight(); ++yy ) { + for ( int yy = 0; yy < bitmap.getHeight(); ++yy ) { if ( BLACK == bitmap.getPixel( xx, yy ) ) { bitmap.setPixel( xx, yy, WHITE ); } } } - - arrow = new BitmapDrawable(bitmap); + arrow = new BitmapDrawable( bitmap ); } return arrow; } From 87e04ec667a375c4a1cc9b220c047d7052adf925 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 08:37:25 -0700 Subject: [PATCH 02/71] default to allowing duplicate invites for DEBUG --- xwords4/android/app/src/main/res/xml/xwprefs.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/res/xml/xwprefs.xml b/xwords4/android/app/src/main/res/xml/xwprefs.xml index 7892f5df3..a6c5d697f 100644 --- a/xwords4/android/app/src/main/res/xml/xwprefs.xml +++ b/xwords4/android/app/src/main/res/xml/xwprefs.xml @@ -401,7 +401,7 @@ Date: Sat, 11 Apr 2020 10:56:54 -0700 Subject: [PATCH 03/71] add another change to changelist --- xwords4/android/app/src/main/assets/changes.html | 3 ++- xwords4/android/jni/andutils.c | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index 662561108..d1eb73655 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -25,9 +25,10 @@

New with this release

    +
  • Show name in scoreboard as "Not here yet" until invitee connects
  • +
  • Fix crash drawing arrows on dark-background boards
  • Fix so game list entries collapse correctly again
  • Fix so bringing app to front keeps the open game open
  • -
  • Show name in scoreboard as "Not here yet" until invitee connects
  • Include more Norwegian translations
diff --git a/xwords4/android/jni/andutils.c b/xwords4/android/jni/andutils.c index c81f28571..573a7edd7 100644 --- a/xwords4/android/jni/andutils.c +++ b/xwords4/android/jni/andutils.c @@ -424,10 +424,13 @@ setIntInArray( JNIEnv* env, jintArray arr, int index, int val ) jobjectArray makeStringArray( JNIEnv *env, const int count, const XP_UCHAR** vals ) { - jclass clas = (*env)->FindClass(env, "java/lang/String"); - jstring empty = (*env)->NewStringUTF( env, "" ); - jobjectArray jarray = (*env)->NewObjectArray( env, count, clas, empty ); - deleteLocalRefs( env, clas, empty, DELETE_NO_REF ); + jobjectArray jarray; + { + jclass clas = (*env)->FindClass(env, "java/lang/String"); + jstring empty = (*env)->NewStringUTF( env, "" ); + jarray = (*env)->NewObjectArray( env, count, clas, empty ); + deleteLocalRefs( env, clas, empty, DELETE_NO_REF ); + } for ( int ii = 0; !!vals && ii < count; ++ii ) { jstring jstr = (*env)->NewStringUTF( env, vals[ii] ); From b81bd46645481975e01e66fcce5e49d699aa1e02 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 11:44:09 -0700 Subject: [PATCH 04/71] remove assertion when message mis-delivered I don't know why, but in my tests the relay seems to be delivering messages to the wrong device. The linux device detects this. It used to assert, but now just drops the message. If this is happening on Android it might be why I'm seeing crashes... --- xwords4/common/pool.c | 3 ++- xwords4/linux/cursesboard.c | 16 +++++++++++----- xwords4/linux/cursesboard.h | 5 +++-- xwords4/linux/cursesmain.c | 4 +--- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/xwords4/common/pool.c b/xwords4/common/pool.c index 6279f8c9d..ca778e26d 100644 --- a/xwords4/common/pool.c +++ b/xwords4/common/pool.c @@ -231,7 +231,8 @@ pool_containsTiles( const PoolContext* pool, const TrayTileSet* tiles ) XP_U16 pool_getNTilesLeft( const PoolContext* pool ) { - return pool->numTilesLeft; + XP_ASSERT( !!pool ); + return NULL == pool ? 0 : pool->numTilesLeft; } /* pool_remainingTileCount */ XP_U16 diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c index 799dd2be8..66784412a 100644 --- a/xwords4/linux/cursesboard.c +++ b/xwords4/linux/cursesboard.c @@ -640,16 +640,21 @@ findOrOpen( CursesBoardState* cbState, sqlite3_int64 rowid, return result; } -XP_U16 -cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, +bool +cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, XP_U16 expectSeed, const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ) { LOG_FUNC(); CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowid, NULL, NULL ); CommonGlobals* cGlobals = &bGlobals->cGlobals; - gameGotBuf( cGlobals, XP_TRUE, buf, len, from ); XP_U16 seed = comms_getChannelSeed( cGlobals->game.comms ); - return seed; + bool success = 0 == expectSeed || seed == expectSeed; + if ( success ) { + gameGotBuf( cGlobals, XP_TRUE, buf, len, from ); + } else { + XP_LOGFF( "msg for seed %d but I opened %d", expectSeed, seed ); + } + return success; } void @@ -663,7 +668,8 @@ cb_feedGame( CursesBoardState* cbState, XP_U32 gameID, getRowsForGameID( params->pDb, gameID, rowids, &nRows ); XP_LOGF( "%s(): found %d rows for gameID %d", __func__, nRows, gameID ); for ( int ii = 0; ii < nRows; ++ii ) { - (void)cb_feedRow( cbState, rowids[ii], buf, len, from ); + bool success = cb_feedRow( cbState, rowids[ii], 0, buf, len, from ); + XP_ASSERT( success ); } } diff --git a/xwords4/linux/cursesboard.h b/xwords4/linux/cursesboard.h index 03840752d..a38dbf692 100644 --- a/xwords4/linux/cursesboard.h +++ b/xwords4/linux/cursesboard.h @@ -44,8 +44,9 @@ bool cb_new( CursesBoardState* cbState, const cb_dims* dims ); void cb_newFor( CursesBoardState* cbState, const NetLaunchInfo* nli, const CommsAddrRec* returnAddr, const cb_dims* dims ); -XP_U16 cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, - const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ); +bool cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, + XP_U16 expectSeed, const XP_U8* buf, XP_U16 len, + const CommsAddrRec* from ); void cb_feedGame( CursesBoardState* cbState, XP_U32 gameID, const XP_U8* buf, XP_U16 len, const CommsAddrRec* from ); void cb_closeAll( CursesBoardState* cbState ); diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 4c7436f2d..c4734039d 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -1201,9 +1201,7 @@ cursesGotBuf( void* closure, const CommsAddrRec* addr, rowidFromToken( XP_NTOHL( clientToken ), &rowid, &gotSeed ); /* Figure out if the device is live, or we need to open the game */ - XP_U16 seed = cb_feedRow( aGlobals->cbState, rowid, buf, len, addr ); - XP_ASSERT( seed == 0 || gotSeed == seed ); - XP_USE( seed ); + cb_feedRow( aGlobals->cbState, rowid, gotSeed, buf, len, addr ); /* if ( seed == comms_getChannelSeed( cGlobals->game.comms ) ) { */ /* gameGotBuf( cGlobals, XP_TRUE, buf, len, addr ); */ From effbc3ef00e40fd34a1dbfee2d19c009b3392aa3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 07:17:22 -0700 Subject: [PATCH 05/71] fix crash creating light-colored arrow Changing it to a VectorDrawable meant it crashed code that assumed otherwise. Fix by removing the assumption. --- .../java/org/eehouse/android/xw4/BoardCanvas.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java index b78ca6ee5..fa8a5d67d 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/BoardCanvas.java @@ -919,17 +919,21 @@ public class BoardCanvas extends Canvas implements DrawCtx { Drawable arrow = res.getDrawable( resID ); if ( !useDark ) { - Bitmap src = ((BitmapDrawable)arrow).getBitmap(); - Bitmap bitmap = src.copy( Bitmap.Config.ARGB_8888, true ); + Bitmap bitmap = Bitmap.createBitmap( arrow.getIntrinsicWidth(), + arrow.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888 ); + Canvas canvas = new Canvas( bitmap ); + arrow.setBounds( 0, 0, canvas.getWidth(), canvas.getHeight() ); + arrow.draw( canvas ); + for ( int xx = 0; xx < bitmap.getWidth(); ++xx ) { - for( int yy = 0; yy < bitmap.getHeight(); ++yy ) { + for ( int yy = 0; yy < bitmap.getHeight(); ++yy ) { if ( BLACK == bitmap.getPixel( xx, yy ) ) { bitmap.setPixel( xx, yy, WHITE ); } } } - - arrow = new BitmapDrawable(bitmap); + arrow = new BitmapDrawable( bitmap ); } return arrow; } From f5808e0514c47b242c1ab87eff9540c6c9340d98 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 12:08:03 -0700 Subject: [PATCH 06/71] up release constants so can install --- xwords4/android/app/build.gradle | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 730d3986f..cfc75daf6 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -1,6 +1,6 @@ def INITIAL_CLIENT_VERS = 9 -def VERSION_CODE_BASE = 151 -def VERSION_NAME = '4.4.155' +def VERSION_CODE_BASE = 152 +def VERSION_NAME = '4.4.156' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") def BUILD_INFO_NAME = "build-info.txt" @@ -220,7 +220,8 @@ android { debug { debuggable true resValue "bool", "DEBUG", "true" - minifyEnabled true // for testing + // Drop this. Takes too long to build + // minifyEnabled true // for testing proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // This doesn't work on marshmallow: duplicate permission error // applicationIdSuffix ".debug" From 67759c883aac3db3e3bddaa98d5dc9a429782619 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 5 Apr 2020 19:48:36 -0700 Subject: [PATCH 07/71] show pending name in scoreboard when remote hasn't arrived, show same string in scoreboard as in games list rather than the local player's default name -- duh. --- .../java/org/eehouse/android/xw4/jni/DUtilCtxt.java | 5 +++++ xwords4/android/jni/LocalizedStrIncludes.h | 4 ++-- xwords4/common/scorebdp.c | 10 +++++++++- xwords4/linux/LocalizedStrIncludes.h | 12 ++++++------ xwords4/linux/lindutil.c | 6 ++---- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java index 4f2eb923e..a3ea4b68d 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java @@ -121,6 +121,7 @@ public class DUtilCtxt { private static final int STR_DUP_MOVED = 27; private static final int STRD_DUP_TRADED = 28; private static final int STRSD_DUP_ONESCORE = 29; + private static final int STR_PENDING_PLAYER = 30; public String getUserString( int stringCode ) { @@ -207,6 +208,10 @@ public class DUtilCtxt { id = R.string.dup_onescore_fmt; break; + case STR_PENDING_PLAYER: + id = R.string.missing_player; + break; + default: Log.w( TAG, "no such stringCode: %d", stringCode ); } diff --git a/xwords4/android/jni/LocalizedStrIncludes.h b/xwords4/android/jni/LocalizedStrIncludes.h index d9dc9e96e..1eaf98af4 100644 --- a/xwords4/android/jni/LocalizedStrIncludes.h +++ b/xwords4/android/jni/LocalizedStrIncludes.h @@ -33,6 +33,6 @@ # define STR_DUP_MOVED 27 # define STRD_DUP_TRADED 28 # define STRSD_DUP_ONESCORE 29 - -# define N_AND_USER_STRINGS 29 +# define STR_PENDING_PLAYER 30 +# define N_AND_USER_STRINGS 30 #endif diff --git a/xwords4/common/scorebdp.c b/xwords4/common/scorebdp.c index 63b920bc1..42d57ea9d 100644 --- a/xwords4/common/scorebdp.c +++ b/xwords4/common/scorebdp.c @@ -23,6 +23,7 @@ #include "game.h" #include "strutils.h" #include "dbgutil.h" +#include "LocalizedStrIncludes.h" #ifdef CPLUS extern "C" { @@ -244,8 +245,10 @@ drawScoreBoard( BoardCtxt* board ) /* figure spacing for each scoreboard entry */ XP_MEMSET( &datum, 0, sizeof(datum) ); totalDim = 0; + XP_U16 missingPlayers = server_getMissingPlayers( board->server ); for ( dp = datum, ii = 0; ii < nPlayers; ++ii, ++dp ) { LocalPlayer* lp = &board->gi->players[ii]; + XP_Bool isMissing = 0 != ((1 << ii) & missingPlayers); /* This is a hack! */ dp->dsi.lsc = board_ScoreCallback; @@ -258,11 +261,16 @@ drawScoreBoard( BoardCtxt* board ) dp->dsi.playerNum = ii; dp->dsi.totalScore = scores.arr[ii]; dp->dsi.isTurn = server_isPlayersTurn( board->server, ii ); - dp->dsi.name = emptyStringIfNull(lp->name); dp->dsi.selected = board->trayVisState != TRAY_HIDDEN && ii==selPlayer; dp->dsi.isRobot = LP_IS_ROBOT(lp); dp->dsi.isRemote = !lp->isLocal; + XP_ASSERT( !isMissing || dp->dsi.isRemote ); + if ( dp->dsi.isRemote && isMissing ) { + dp->dsi.name = dutil_getUserString( board->dutil, STR_PENDING_PLAYER ); + } else { + dp->dsi.name = emptyStringIfNull( lp->name ); + } dp->dsi.nTilesLeft = (nTilesInPool > 0)? -1: model_getNumTilesTotal( model, ii ); diff --git a/xwords4/linux/LocalizedStrIncludes.h b/xwords4/linux/LocalizedStrIncludes.h index a508b4215..30bcb2a2e 100644 --- a/xwords4/linux/LocalizedStrIncludes.h +++ b/xwords4/linux/LocalizedStrIncludes.h @@ -30,8 +30,7 @@ enum { STR_SUBMIT_CONFIRM, STRD_TURN_SCORE, STR_BONUS_ALL, - STR_NONLOCAL_NAME, - STR_LOCAL_NAME, + STR_PENDING_PLAYER, STRD_TIME_PENALTY_SUB, STRD_CUMULATIVE_SCORE, @@ -46,10 +45,6 @@ enum { STR_ROBOT_MOVED, STRS_REMOTE_MOVED, - STR_LOCALPLAYERS, - STR_TOTALPLAYERS, - STR_REMOTE, - STRS_VALUES_HEADER, STRD_REMAINS_HEADER, STRD_REMAINS_EXPL, @@ -64,6 +59,11 @@ enum { STRD_DUP_TRADED, STRSD_DUP_ONESCORE, + /* These three aren't in Android */ + STR_LOCALPLAYERS, + STR_TOTALPLAYERS, + STR_REMOTE, + STR_LAST }; diff --git a/xwords4/linux/lindutil.c b/xwords4/linux/lindutil.c index d4c372640..492c8238b 100644 --- a/xwords4/linux/lindutil.c +++ b/xwords4/linux/lindutil.c @@ -146,10 +146,8 @@ linux_dutil_getUserString( XW_DUtilCtxt* XP_UNUSED(uc), XP_U16 code ) return (XP_UCHAR*)"Score for turn: %d\n"; case STR_BONUS_ALL: return (XP_UCHAR*)"Bonus for using all tiles: 50\n"; - case STR_LOCAL_NAME: - return (XP_UCHAR*)"%s"; - case STR_NONLOCAL_NAME: - return (XP_UCHAR*)"%s (remote)"; + case STR_PENDING_PLAYER: + return (XP_UCHAR*)"(remote)"; case STRD_TIME_PENALTY_SUB: return (XP_UCHAR*)" - %d [time]"; /* added.... */ From 40479b6fe1cfde196e0e09186aa1e0d25b0786d5 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 9 Apr 2020 16:50:31 -0700 Subject: [PATCH 08/71] fix failure to collapse The problem appears to be that the new .xml drawable has a greater height for purposes of layout. Specifying dimensions seems to be the way to go, though devices may vary. --- xwords4/android/app/src/main/res/layout/game_list_item.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 33481df67..6d2d35847 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 @@ -39,8 +39,9 @@ android:paddingRight="8dip" > Date: Sat, 11 Apr 2020 12:17:14 -0700 Subject: [PATCH 09/71] remove mistaken '+' from layout file --- xwords4/android/app/src/main/res/layout/lookup.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/res/layout/lookup.xml b/xwords4/android/app/src/main/res/layout/lookup.xml index 1a3733291..4375ef1c8 100644 --- a/xwords4/android/app/src/main/res/layout/lookup.xml +++ b/xwords4/android/app/src/main/res/layout/lookup.xml @@ -13,7 +13,7 @@ android:paddingLeft="8dp" /> - Date: Sat, 11 Apr 2020 12:30:28 -0700 Subject: [PATCH 10/71] update changelog --- .../android/app/src/main/assets/changes.html | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index e32fe9a7b..162aacede 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -13,9 +13,9 @@ -

CrossWords 4.4.155 release

+

CrossWords 4.4.156 release

-

This release fixes stalls that sometimes follow use of the Undo feature

+

This release fixes a crash drawing on a dark-background board

Please take @@ -25,16 +25,13 @@

New with this release

    -
  • Fix stall that was sometimes triggered by an ill-timed Undo
  • -
  • Make some on-board icons smoother
  • -
  • Fix crash receiving in-game chat message that's too long
  • -
  • Make the board look a bit better on tall&narrow screens
  • -
  • Tweak the game-configure dialog
  • -
  • Make some changes you shouldn't see getting ready to - support "Duplicate" play
  • -
  • New translations (via Weblate) for Catalan, Dutch, French, - German, Polish and Portuguese, and especially for Japanese - and Spanish
  • +
  • The last release introduced a crash for those who had + customized their board to have a dark background. It's now + fixed.
  • +
  • Show "Not here yet" in scoreboard for invitees who have not + arrived yet
  • +
  • Fix new failure of game list items to shrink when + collapsed

(The full changelog From c5f1d2bf3b646eaaf54014fd4e6e02f35136e3f3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 15:04:02 -0700 Subject: [PATCH 11/71] release lock when BoardDelegate paused The lock was leaking when sometimes the OS would call onStop() without isFinishing() being true, then never use the fragment again. And never call onDestroy(). Releasing the lock in onStop and regetting it in onResume seems to fix, but this needs some testing and time. --- .../eehouse/android/xw4/BoardDelegate.java | 10 +++++++-- .../org/eehouse/android/xw4/XWFragment.java | 21 ++++++++++--------- 2 files changed, 19 insertions(+), 12 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 91c9717de..54dd0afb4 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 @@ -614,7 +614,10 @@ public class BoardDelegate extends DelegateBase Log.i( TAG, "opening rowid %d", m_rowid ); m_haveInvited = args.getBoolean( GameUtils.INVITED, false ); m_overNotShown = true; + } // init + private void getLock() + { GameLock.getLockThen( m_rowid, 100L, new Handler(), // this doesn't unlock new GameLock.GotLockProc() { @Override @@ -648,7 +651,7 @@ public class BoardDelegate extends DelegateBase } } } ); - } // init + } // getLock @Override protected void onStart() @@ -670,6 +673,7 @@ public class BoardDelegate extends DelegateBase doResume( false ); } else { m_resumeSkipped = true; + getLock(); } } @@ -688,9 +692,11 @@ public class BoardDelegate extends DelegateBase @Override protected void onStop() { - if ( isFinishing() && null != m_jniThreadRef ) { + if ( null != m_jniThreadRef ) { m_jniThreadRef.release(); m_jniThreadRef = null; + } else { + Log.d( TAG, "onStop(): m_jniThreadRef already null" ); } super.onStop(); } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWFragment.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWFragment.java index 750d198c4..1be63005e 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWFragment.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWFragment.java @@ -80,6 +80,7 @@ abstract class XWFragment extends Fragment implements Delegator { protected void onCreate( DelegateBase dlgt, Bundle sis, boolean hasOptionsMenu ) { + Log.d( TAG, "%H/%s.onCreate() called", this, getClass().getSimpleName() ); m_hasOptionsMenu = hasOptionsMenu; this.onCreate( dlgt, sis ); } @@ -87,7 +88,7 @@ abstract class XWFragment extends Fragment implements Delegator { @Override public void onSaveInstanceState( Bundle outState ) { - Log.d( TAG, "%s.onSaveInstanceState() called", getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onSaveInstanceState() called", this, getClass().getSimpleName() ); Assert.assertNotNull( m_parentName ); outState.putString( PARENT_NAME, m_parentName ); outState.putInt( COMMIT_ID, m_commitID ); @@ -97,7 +98,7 @@ abstract class XWFragment extends Fragment implements Delegator { protected void onCreate( DelegateBase dlgt, Bundle sis ) { - Log.d( TAG, "%s.onCreate() called", getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onCreate() called", this, getClass().getSimpleName() ); super.onCreate( sis ); if ( null != sis ) { m_parentName = sis.getString( PARENT_NAME ); @@ -122,7 +123,7 @@ abstract class XWFragment extends Fragment implements Delegator { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) { - Log.d( TAG, "%s.onCreateView() called", getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onCreateView() called", this, getClass().getSimpleName() ); sActiveFrags.add(this); return m_dlgt.inflateView( inflater, container ); } @@ -130,7 +131,7 @@ abstract class XWFragment extends Fragment implements Delegator { @Override public void onActivityCreated( Bundle savedInstanceState ) { - Log.d( TAG, "%s.onActivityCreated() called", getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onActivityCreated() called", this, getClass().getSimpleName() ); m_dlgt.init( savedInstanceState ); super.onActivityCreated( savedInstanceState ); if ( m_hasOptionsMenu ) { @@ -141,7 +142,7 @@ abstract class XWFragment extends Fragment implements Delegator { @Override public void onPause() { - Log.d( TAG, "%s.onPause() called", getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onPause() called", this, getClass().getSimpleName() ); m_dlgt.onPause(); super.onPause(); } @@ -149,7 +150,7 @@ abstract class XWFragment extends Fragment implements Delegator { @Override public void onResume() { - Log.d( TAG, "%s.onResume() called", getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onResume() called", this, getClass().getSimpleName() ); super.onResume(); m_dlgt.onResume(); } @@ -157,7 +158,7 @@ abstract class XWFragment extends Fragment implements Delegator { @Override public void onStart() { - Log.d( TAG, "%s.onStart() called", getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onStart() called", this, getClass().getSimpleName() ); super.onStart(); m_dlgt.onStart(); } @@ -165,7 +166,7 @@ abstract class XWFragment extends Fragment implements Delegator { @Override public void onStop() { - Log.d( TAG, "%s.onStop() called", getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onStop() called", this, getClass().getSimpleName() ); m_dlgt.onStop(); super.onStop(); } @@ -173,7 +174,7 @@ abstract class XWFragment extends Fragment implements Delegator { @Override public void onDestroy() { - Log.d( TAG, "%s.onDestroy() called", getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onDestroy() called", this, getClass().getSimpleName() ); m_dlgt.onDestroy(); sActiveFrags.remove( this ); super.onDestroy(); @@ -182,7 +183,7 @@ abstract class XWFragment extends Fragment implements Delegator { @Override public void onActivityResult( int requestCode, int resultCode, Intent data ) { - Log.d( TAG, "%s.onActivityResult() called", getClass().getSimpleName() ); + Log.d( TAG, "%P/%s.onActivityResult() called", this, getClass().getSimpleName() ); m_dlgt.onActivityResult( RequestCode.values()[requestCode], resultCode, data ); } From ca6a0868c996221b4358aa94ddb4be20276d5bff Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 15:51:58 -0700 Subject: [PATCH 12/71] show last-move as an alert rather than a toast it gets lost the other way. --- .../src/main/java/org/eehouse/android/xw4/BoardDelegate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 54dd0afb4..8fa5a2033 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 @@ -1874,7 +1874,7 @@ public class BoardDelegate extends DelegateBase post( new Runnable() { @Override public void run() { - showToast( text ); + makeOkOnlyBuilder( text ).show(); } } ); } From 29ed46e12bdc0d947d6eda9d510e7cea781696fb Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 19:23:20 -0700 Subject: [PATCH 13/71] fix so upgrade of release builds works --- xwords4/android/scripts/info.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xwords4/android/scripts/info.py b/xwords4/android/scripts/info.py index e74828eb7..387e396ed 100755 --- a/xwords4/android/scripts/info.py +++ b/xwords4/android/scripts/info.py @@ -204,7 +204,7 @@ def getOrderedApks( path, appID, debug ): files = ((os.stat(apk).st_mtime, apk) for apk in glob.glob(pattern)) for mtime, file in sorted(files, reverse=True): info = getAAPTInfo(file) - if info['appID'] == appID: + if info and 'appID' in info and info['appID'] == appID: apkToCode[file] = info['versionCode'] apkToMtime[file] = mtime result = sorted(apkToCode.keys(), reverse=True, key=lambda file: (apkToCode[file], apkToMtime[file])) @@ -326,7 +326,7 @@ def getApp( params, name = None, debug = False): apache.log_error( "name: %s; installer: %s; gvers: %s" % (name, installer, vers) ) print "name: %s; installer: %s; vers: %s" % (name, installer, vers) - dir = k_filebase + k_apkDir + 'rel/' + dir = k_filebase + k_apkDir apk = getNextAfter( dir, name, vers, debug ) if apk: apk = apk[len(k_filebase):] # strip fs path @@ -569,7 +569,7 @@ def getUpdates( req, params ): # result[k_XLATEINFO] = xlateResult; result = json.dumps( result ) - # apache.log_error( result ) + apache.log_error( 'getUpdates() => ' + result ) return result def clearShelf(): From 9fd4bb2a11faa6f2deac029cd4f278c730bf3d3e Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 19:54:02 -0700 Subject: [PATCH 14/71] enable relayid-invites for non-tagged release builds Too useful for testing --- xwords4/android/app/build.gradle | 5 ----- .../eehouse/android/xw4/InviteChoicesAlert.java | 2 +- .../eehouse/android/xw4/RelayInviteDelegate.java | 16 +++++++++------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 8539c7b41..f9fcf68ee 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -96,7 +96,6 @@ android { resValue "string", "app_name", "CrossWords" resValue "string", "nbs_port", "3344" buildConfigField "boolean", "WIDIR_ENABLED", "false" - buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false" buildConfigField "String", "VARIANT_NAME", "\"Google Play Store\"" buildConfigField "int", "VARIANT_CODE", "1" buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\"" @@ -112,7 +111,6 @@ android { resValue "string", "app_name", "CrossWords" resValue "string", "nbs_port", "3344" buildConfigField "boolean", "WIDIR_ENABLED", "false" - buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false" buildConfigField "String", "VARIANT_NAME", "\"F-Droid\"" buildConfigField "int", "VARIANT_CODE", "2" buildConfigField "boolean", "FOR_FDROID", "true" @@ -129,7 +127,6 @@ android { resValue "string", "app_name", "CrossDbg" resValue "string", "nbs_port", "3345" buildConfigField "boolean", "WIDIR_ENABLED", "true" - buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true" buildConfigField "String", "VARIANT_NAME", "\"Dev/Debug\"" buildConfigField "int", "VARIANT_CODE", "3" buildConfigField "boolean", "REPORT_LOCKS", "true" @@ -148,7 +145,6 @@ android { resValue "string", "app_name", "CrossDbg" resValue "string", "nbs_port", "3345" buildConfigField "boolean", "WIDIR_ENABLED", "true" - buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true" buildConfigField "String", "VARIANT_NAME", "\"Dev/Debug NoSMS\"" buildConfigField "int", "VARIANT_CODE", "4" buildConfigField "boolean", "REPORT_LOCKS", "true" @@ -165,7 +161,6 @@ android { resValue "string", "app_name", "CrossWords" resValue "string", "nbs_port", "3344" buildConfigField "boolean", "WIDIR_ENABLED", "false" - buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false" buildConfigField "String", "VARIANT_NAME", "\"FOSS\"" buildConfigField "int", "VARIANT_CODE", "5" buildConfigField "String", "NFC_AID", "\"${NFC_AID_XW4}\"" diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java index 5810233c3..b9a532bfb 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/InviteChoicesAlert.java @@ -65,7 +65,7 @@ public class InviteChoicesAlert extends DlgDelegateAlert { if ( Utils.deviceSupportsNBS(context) ) { add( items, means, R.string.invite_choice_data_sms, InviteMeans.SMS_DATA ); } - if ( BuildConfig.RELAYINVITE_SUPPORTED ) { + if ( BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD ) { add( items, means, R.string.invite_choice_relay, InviteMeans.RELAY ); } if ( WiDirWrapper.enabled() ) { diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayInviteDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayInviteDelegate.java index 8dd4709af..1a42739f9 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayInviteDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayInviteDelegate.java @@ -57,6 +57,8 @@ import org.eehouse.android.xw4.DlgDelegate.Action; public class RelayInviteDelegate extends InviteDelegate { private static final String TAG = RelayInviteDelegate.class.getSimpleName(); private static final String RECS_KEY = "TAG" + "/recs"; + private static final boolean RELAYINVITE_SUPPORTED + = BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD; private static int[] BUTTONIDS = { R.id.button_relay_add, @@ -77,7 +79,7 @@ public class RelayInviteDelegate extends InviteDelegate { SentInvitesInfo info, RequestCode requestCode ) { - if ( BuildConfig.RELAYINVITE_SUPPORTED ) { + if ( RELAYINVITE_SUPPORTED ) { Intent intent = InviteDelegate.makeIntent( activity, RelayInviteActivity.class, nMissing, info ); @@ -94,7 +96,7 @@ public class RelayInviteDelegate extends InviteDelegate { @Override protected void init( Bundle savedInstanceState ) { - if ( BuildConfig.RELAYINVITE_SUPPORTED ) { + if ( RELAYINVITE_SUPPORTED ) { super.init( savedInstanceState ); String msg = getString( R.string.button_invite ); @@ -137,7 +139,7 @@ public class RelayInviteDelegate extends InviteDelegate { @Override protected void onBarButtonClicked( int id ) { - if ( BuildConfig.RELAYINVITE_SUPPORTED ) { + if ( RELAYINVITE_SUPPORTED ) { switch( id ) { case R.id.button_relay_add: Utils.notImpl( m_activity ); @@ -189,7 +191,7 @@ public class RelayInviteDelegate extends InviteDelegate { protected Dialog makeDialog( DBAlert alert, Object[] params ) { Dialog dialog = null; - if ( BuildConfig.RELAYINVITE_SUPPORTED ) { + if ( RELAYINVITE_SUPPORTED ) { DialogInterface.OnClickListener lstnr; switch( alert.getDlgID() ) { case GET_NUMBER: { @@ -334,7 +336,7 @@ public class RelayInviteDelegate extends InviteDelegate { @Override protected void tryEnable() { - if ( BuildConfig.RELAYINVITE_SUPPORTED ) { + if ( RELAYINVITE_SUPPORTED ) { super.tryEnable(); Button button = (Button)findViewById( R.id.button_clear ); @@ -353,7 +355,7 @@ public class RelayInviteDelegate extends InviteDelegate { public boolean onPosButton( Action action, Object[] params ) { boolean handled = true; - if ( BuildConfig.RELAYINVITE_SUPPORTED ) { + if ( RELAYINVITE_SUPPORTED ) { switch( action ) { case CLEAR_ACTION: clearSelectedImpl(); @@ -373,7 +375,7 @@ public class RelayInviteDelegate extends InviteDelegate { public boolean onDismissed( Action action, Object[] params ) { boolean handled = true; - if ( BuildConfig.RELAYINVITE_SUPPORTED ) { + if ( RELAYINVITE_SUPPORTED ) { if ( Action.USE_IMMOBILE_ACTION == action && m_immobileConfirmed ) { makeConfirmThenBuilder( R.string.warn_unlimited, Action.POST_WARNING_ACTION ) From c3b1423c363accb161e60f975f978b3d5a530bb6 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 11 Apr 2020 21:18:20 -0700 Subject: [PATCH 15/71] add (unused) method to copy apk to Downloads/ --- .../java/org/eehouse/android/xw4/DBUtils.java | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) 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 f5e7bf90c..a04b8dfbb 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 @@ -22,13 +22,15 @@ package org.eehouse.android.xw4; import android.content.ContentValues; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteConstraintException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; -import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; +import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Environment; import android.text.TextUtils; @@ -2563,6 +2565,32 @@ public class DBUtils { } } + // Copy my .apk to the Downloads directory, from which a user could more + // easily share it with somebody else. Should be blocked for apks + // installed from the Play store since viral distribution isn't allowed, + // but might be helpful in other cases. Need to figure out how to expose + // it, and how to recommend transmissions. E.g. gmail doesn't let me + // attach an .apk even if I rename it. + static void copyApkToDownloads( Context context ) + { + try { + String myName = context.getPackageName(); + PackageManager pm = context.getPackageManager(); + ApplicationInfo appInfo = pm.getApplicationInfo( myName, 0 ); + + File srcPath = new File( appInfo.publicSourceDir ); + File destPath = Environment + .getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS ); + destPath = new File( destPath, context.getString(R.string.app_name) + ".apk" ); + + FileInputStream src = new FileInputStream( srcPath ); + FileOutputStream dest = new FileOutputStream( destPath ); + copyFileStream( dest, src ); + } catch ( Exception ex ) { + Log.e( TAG, "copyApkToDownloads(): got ex: %s", ex ); + } + } + private static String getVariantDBName() { return String.format( "%s_%s", DBHelper.getDBName(), From b51e3d8d1eeaafb9b5a3083445ee417e4a677dd2 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 15 Apr 2020 19:59:03 -0700 Subject: [PATCH 16/71] remove unused jni declaration --- .../src/main/java/org/eehouse/android/xw4/jni/XwJNI.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java index 9343ab569..1db3931e9 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java @@ -308,8 +308,10 @@ public class XwJNI { // int timerHeight ); public static native boolean board_zoom( GamePtr gamePtr, int zoomBy, boolean[] canZoom ); - public static native boolean board_getActiveRect( GamePtr gamePtr, Rect rect, - int[] dims ); + + // Not available if XWFEATURE_ACTIVERECT not #defined in C + // public static native boolean board_getActiveRect( GamePtr gamePtr, Rect rect, + // int[] dims ); public static native boolean board_handlePenDown( GamePtr gamePtr, int xx, int yy, From 22f62d53e08c029868d188953d7f451a259a4be1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 16 Apr 2020 10:59:51 -0700 Subject: [PATCH 17/71] add DB versioning for linux --- xwords4/linux/gamesdb.c | 187 ++++++++++++++++++++++++++++++++-------- 1 file changed, 151 insertions(+), 36 deletions(-) diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index a48f60865..a7872e808 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -23,18 +23,43 @@ #include "gtkdraw.h" #include "linuxutl.h" #include "main.h" +#include "dbgutil.h" #define SNAP_WIDTH 150 #define SNAP_HEIGHT 150 +#define KEY_DB_VERSION "dbvers" + +#define VERS_0_TO_1 \ + "nPending INT" \ + ",role INT" \ + ",dictlang INT" \ + ",scores TEXT" \ + static XP_Bool getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int* len ); +static bool db_fetchInt( sqlite3* pDb, const gchar* key, int32_t* resultP ); +static void db_storeInt( sqlite3* pDb, const gchar* key, int32_t val ); +static void createTables( sqlite3* pDb ); +static bool gamesTableExists( sqlite3* pDb ); +static void upgradeTables( sqlite3* pDb, int32_t oldVersion ); +static void execNoResult( sqlite3* pDb, const gchar* query, bool errOK ); + #ifdef DEBUG static char* sqliteErr2str( int err ); #endif static void assertPrintResult( sqlite3* pDb, int result, int expect ); +/* Versioning: + * + * Version 0 is defined, and not having a version code means you're 0. For + * each subsequent version there needs to be a recipe for upgrading, whether + * it's adding new fields or whatever. + */ + +#define CUR_DB_VERSION 1 + sqlite3* openGamesDB( const char* dbName ) { @@ -45,6 +70,79 @@ openGamesDB( const char* dbName ) result = sqlite3_open( dbName, &pDb ); XP_ASSERT( SQLITE_OK == result ); + if ( gamesTableExists( pDb ) ) { + int32_t oldVersion; + if ( !db_fetchInt( pDb, KEY_DB_VERSION, &oldVersion ) ) { + oldVersion = 0; + XP_LOGFF( "no version found; assuming %d", oldVersion ); + } + if ( oldVersion < CUR_DB_VERSION ) { + upgradeTables( pDb, oldVersion ); + } + } else { + createTables( pDb ); + } + + return pDb; +} + +static void +upgradeTables( sqlite3* pDb, int32_t oldVersion ) +{ + gchar* newCols = NULL; + switch ( oldVersion ) { + case 0: + XP_ASSERT( 1 == CUR_DB_VERSION ); + newCols = VERS_0_TO_1; + break; + default: + XP_ASSERT(0); + break; + } + + if ( !!newCols ) { + gchar** strs = g_strsplit( newCols, ",", -1 ); + for ( int ii = 0; !!strs[ii]; ++ii ) { + gchar* str = strs[ii]; + if ( 0 < strlen(str) ) { + gchar* query = g_strdup_printf( "ALTER TABLE games ADD COLUMN %s", strs[ii] ); + XP_LOGFF( "query: \"%s\"", query ); + execNoResult( pDb, query, true ); + g_free( query ); + } + } + g_strfreev( strs ); + + db_storeInt( pDb, KEY_DB_VERSION, CUR_DB_VERSION ); + } +} + +static bool +gamesTableExists( sqlite3* pDb ) +{ + const gchar* query = "SELECT COUNT(*) FROM sqlite_master " + "WHERE type = 'table' AND name = 'games'"; + + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + assertPrintResult( pDb, result, SQLITE_OK ); + result = sqlite3_step( ppStmt ); + XP_ASSERT( SQLITE_ROW == result ); + bool exists = 1 == sqlite3_column_int( ppStmt, 0 ); + sqlite3_finalize( ppStmt ); + LOG_RETURNF( "%s", boolToStr(exists) ); + return exists; +} + +static void +createTables( sqlite3* pDb ) +{ + /* This can never change! Versioning counts on it. */ + const char* createValuesStr = + "CREATE TABLE pairs ( key TEXT UNIQUE, value TEXT )"; + int result = sqlite3_exec( pDb, createValuesStr, NULL, NULL, NULL ); + XP_LOGFF( "sqlite3_exec(%s)=>%d", createValuesStr, result ); + const char* createGamesStr = "CREATE TABLE games ( " "rowid INTEGER PRIMARY KEY AUTOINCREMENT" @@ -59,25 +157,17 @@ openGamesDB( const char* dbName ) ",local INT(1)" ",nmoves INT" ",seed INT" - ",nPending INT" - ",role INT" - ",dictlang INT" ",gameid INT" ",ntotal INT(2)" ",nmissing INT(2)" ",lastMoveTime INT" - ",scores TEXT" - ",dupTimerExpires INT" + ",dupTimerExpires INT" + ","VERS_0_TO_1 + // ",dupTimerExpires INT" ")"; result = sqlite3_exec( pDb, createGamesStr, NULL, NULL, NULL ); - const char* createValuesStr = - "CREATE TABLE pairs ( key TEXT UNIQUE,value TEXT )"; - result = sqlite3_exec( pDb, createValuesStr, NULL, NULL, NULL ); - XP_LOGFF( "sqlite3_exec=>%d", result ); - XP_USE( result ); - - return pDb; + db_storeInt( pDb, KEY_DB_VERSION, CUR_DB_VERSION ); } void @@ -558,30 +648,46 @@ 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; - int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); - assertPrintResult( pDb, result, SQLITE_OK ); - result = sqlite3_step( ppStmt ); - assertPrintResult( pDb, result, SQLITE_DONE ); - XP_USE( result ); - sqlite3_finalize( ppStmt ); + execNoResult( pDb, query, false ); } void db_store( sqlite3* pDb, const gchar* key, const gchar* value ) { XP_ASSERT( !!pDb ); - gchar* buf = + gchar* query = 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 ); - result = sqlite3_step( ppStmt ); - assertPrintResult( pDb, result, SQLITE_DONE ); - XP_USE( result ); - sqlite3_finalize( ppStmt ); - g_free( buf ); + execNoResult( pDb, query, false ); + g_free( query ); +} + +static bool +db_fetchInt( sqlite3* pDb, const gchar* key, int32_t* resultP ) +{ + gint buflen = 16; + gchar buf[buflen]; + FetchResult fr = db_fetch( pDb, key, buf, &buflen ); + bool gotIt = SUCCESS == fr; + if ( gotIt ) { + buf[buflen] = '\0'; + sscanf( buf, "%x", resultP ); + } + return gotIt; +} + +static void +db_storeInt( sqlite3* pDb, const gchar* key, int32_t val ) +{ + gchar buf[32]; + snprintf( buf, VSIZE(buf), "%x", val ); + db_store( pDb, key, buf ); + +#ifdef DEBUG + int32_t tmp; + bool worked = db_fetchInt( pDb, key, &tmp ); + XP_ASSERT( worked && tmp == val ); +#endif } FetchResult @@ -632,13 +738,7 @@ 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; - int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); - assertPrintResult( pDb, result, SQLITE_OK ); - result = sqlite3_step( ppStmt ); - assertPrintResult( pDb, result, SQLITE_DONE ); - XP_USE( result ); - sqlite3_finalize( ppStmt ); + execNoResult( pDb, query, false ); } static XP_Bool @@ -656,6 +756,21 @@ getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int *len ) return success; } +static void +execNoResult( sqlite3* pDb, const gchar* query, bool errOk ) +{ + sqlite3_stmt *ppStmt; + int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); + if ( ! errOk ) { + assertPrintResult( pDb, result, SQLITE_OK ); + } + result = sqlite3_step( ppStmt ); + if ( ! errOk ) { + assertPrintResult( pDb, result, SQLITE_DONE ); + } + sqlite3_finalize( ppStmt ); +} + #ifdef DEBUG # define CASESTR(c) case c: return #c static char* @@ -704,7 +819,7 @@ assertPrintResult( sqlite3* pDb, int XP_UNUSED_DBG(result), int expect ) int code = sqlite3_errcode( pDb ); XP_ASSERT( code == result ); /* do I need to pass it? */ if ( code != expect ) { - XP_LOGFF( "sqlite3 error: %s", sqlite3_errmsg( pDb ) ); + XP_LOGFF( "sqlite3 error: %d (%s)", code, sqlite3_errmsg( pDb ) ); XP_ASSERT(0); } } From 91e6e504803e5470223df776dfc90e47cc420d82 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 16 Apr 2020 12:23:32 -0700 Subject: [PATCH 18/71] stop broken upgrade early-launch case Wasn't checking for existance of script and so got and exception deleting what didn't exist. Instead I define nonexistance as being too early. --- xwords4/linux/scripts/discon_ok2.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py index cbd9bbd82..a9395ef2b 100755 --- a/xwords4/linux/scripts/discon_ok2.py +++ b/xwords4/linux/scripts/discon_ok2.py @@ -210,12 +210,16 @@ class Device(): def setApp(self, pct): if self.app == self.args.APP_OLD and not self.app == self.args.APP_NEW: - if pct >= random.randint(0, 99): - print('launch(): upgrading from ', self.app, ' to ', self.args.APP_NEW) + if os.path.exists(self.script) and pct >= random.randint(0, 99): + print('launch(): upgrading {} from {} to {}' \ + .format(self.devName(), self.app, self.args.APP_NEW)) self.app = self.args.APP_NEW # nuke script to force regeneration os.unlink(self.script) + def devName(self): + return 'dev_' + str(self.indx) + def logReaderMain(self): assert self and self.proc # print('logReaderMain called; opening:', self.logPath) @@ -679,7 +683,7 @@ def mkParser(): help = 'the app we\'ll upgrade from') parser.add_argument('--start-pct', dest = 'START_PCT', default = 50, type = int, help = 'odds of starting with the new app, 0 <= n < 100') - parser.add_argument('--upgrade-pct', dest = 'UPGRADE_PCT', default = 5, type = int, + parser.add_argument('--upgrade-pct', dest = 'UPGRADE_PCT', default = 20, type = int, help = 'odds of upgrading at any launch, 0 <= n < 100') parser.add_argument('--num-games', dest = 'NGAMES', type = int, default = 1, help = 'number of games') From 18711c1dd34151a8345cab99d98c23338c58f9aa Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 16 Apr 2020 22:54:05 -0700 Subject: [PATCH 19/71] show gtk dialog with lookup-words That way I can at least see it's working. --- xwords4/linux/gtkask.c | 3 ++- xwords4/linux/gtkboard.c | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/xwords4/linux/gtkask.c b/xwords4/linux/gtkask.c index 7aa362dfe..792de08af 100644 --- a/xwords4/linux/gtkask.c +++ b/xwords4/linux/gtkask.c @@ -46,10 +46,11 @@ gtkask( GtkWidget* parent, const gchar *message, GtkButtonsType buttons, } gint -gtkask_timeout( GtkWidget* parent, const gchar *message, +gtkask_timeout( GtkWidget* parent, const gchar* message, GtkButtonsType buttons, const AskPair* buttxts, XP_U16 timeout ) { + XP_LOGFF( "(msg: \"%s\")", message ); guint src = 0; GtkWidget* dlg = gtk_message_dialog_new( (GtkWindow*)parent, GTK_MESSAGE_QUESTION, diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 0c00f5fda..552bfea79 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1987,9 +1987,12 @@ gtk_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player ) static void gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) { - XP_USE( uc ); - catOnClose( words, NULL ); - fprintf( stderr, "\n" ); + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + const XP_U8* bytes = stream_getPtr( words ); + gchar* msg = g_strdup_printf( "words for lookup:\n%s", + (XP_UCHAR*)bytes ); + gtktell( globals->window, msg ); + g_free( msg ); } #endif From 5f7bb303074c854c58d8d60d3b87dbd28deb449c Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 16 Apr 2020 22:54:46 -0700 Subject: [PATCH 20/71] fix crash: don't offer to commit an illegal move! --- xwords4/common/board.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/common/board.c b/xwords4/common/board.c index e2c43609c..5e9c03ab8 100644 --- a/xwords4/common/board.c +++ b/xwords4/common/board.c @@ -1149,7 +1149,7 @@ board_commitTurn( BoardCtxt* board, XP_Bool phoniesConfirmed, bwl.bwi.dictName = dict_getShortName( model_getPlayerDict( model, selPlayer ) ); util_notifyIllegalWords( board->util, &bwl.bwi, selPlayer, XP_FALSE ); - } else { + } else if ( legal ) { /* Hide the tray so no peeking. Leave it hidden even if user cancels as otherwise another player could get around passwords and peek at tiles. */ From 388fc4f8714942639c759e07b552f848e4c3787d Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 06:52:36 -0700 Subject: [PATCH 21/71] don't offer to display empty list of words --- xwords4/common/board.c | 17 +++++++++++------ xwords4/common/model.c | 6 +++++- xwords4/common/model.h | 4 ++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/xwords4/common/board.c b/xwords4/common/board.c index 5e9c03ab8..203e807b9 100644 --- a/xwords4/common/board.c +++ b/xwords4/common/board.c @@ -1341,7 +1341,7 @@ timerFiredForPen( BoardCtxt* board ) if ( dragDropIsBeingDragged( board, col, row, NULL ) ) { /* even if we aren't calling dragDropSetAdd we want to avoid - putting up a sqare bonus if we're on a sqare with + putting up a square bonus if we're on a square with something that can be dragged */ #ifdef XWFEATURE_RAISETILE draw = dragDropSetAdd( board ); @@ -1351,20 +1351,25 @@ timerFiredForPen( BoardCtxt* board ) /* We calculate words even for a pending tile set, meaning dragDrop might be happening too. */ XP_Bool listWords = XP_FALSE; -#ifdef XWFEATURE_BOARDWORDS /* here it is */ +#ifdef XWFEATURE_BOARDWORDS XP_U16 modelCol, modelRow; flipIf( board, col, row, &modelCol, &modelRow ); listWords = model_getTile( board->model, modelCol, modelRow, XP_TRUE, board->selPlayer, NULL, NULL, NULL, NULL ); if ( listWords ) { - XP_LOGF( "%s(): listWords came back true", __func__ ); XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(board->mpool) dutil_getVTManager(board->dutil) ); - model_listWordsThrough( board->model, modelCol, modelRow, - board->selPlayer, stream ); - util_cellSquareHeld( board->util, stream ); + listWords = model_listWordsThrough( board->model, modelCol, modelRow, + board->selPlayer, stream ); + if ( listWords ) { + util_cellSquareHeld( board->util, stream ); + if ( dragDropInProgress( board ) ) { + XP_Bool dragged; + dragDropEnd( board, board->penDownX, board->penDownY, &dragged ); + } + } stream_destroy( stream ); } #endif diff --git a/xwords4/common/model.c b/xwords4/common/model.c index 36fa2ea46..a493a2d6a 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -2613,10 +2613,11 @@ listWordsThrough( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), /* List every word played that includes the tile on {col,row}. * * How? Undo backwards until we find the move that placed that tile.*/ -void +XP_Bool model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row, XP_S16 turn, XWStreamCtxt* stream ) { + XP_Bool found = XP_FALSE; ModelCtxt* tmpModel = makeTmpModel( model, NULL, NULL, NULL, NULL ); copyStack( model, tmpModel->vol.stack, model->vol.stack ); @@ -2667,9 +2668,12 @@ model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row, modelAddEntry( tmpModel, nEntriesAfter++, &entry, XP_FALSE, NULL, &ni, NULL, NULL, NULL ); } + XP_LOGFF( "nWords: %d", lwtInfo.nWords ); + found = 0 < lwtInfo.nWords; } model_destroy( tmpModel ); + return found; } /* model_listWordsThrough */ #endif diff --git a/xwords4/common/model.h b/xwords4/common/model.h index ebfe71a30..078dd9bf7 100644 --- a/xwords4/common/model.h +++ b/xwords4/common/model.h @@ -302,8 +302,8 @@ XP_S16 model_getPlayerScore( ModelCtxt* model, XP_S16 player ); XP_Bool model_getPlayersLastScore( ModelCtxt* model, XP_S16 player, LastMoveInfo* info ); #ifdef XWFEATURE_BOARDWORDS -void model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row, - XP_S16 turn, XWStreamCtxt* stream ); +XP_Bool model_listWordsThrough( ModelCtxt* model, XP_U16 col, XP_U16 row, + XP_S16 turn, XWStreamCtxt* stream ); #endif /* Have there been too many passes (so game should end)? */ From ab07c1c466c959b03253bab10e2cbd7e22da11c3 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 07:06:31 -0700 Subject: [PATCH 22/71] show bonus-square-held Again, so I can see what's going on. Use timeout so doesn't force lifting the mouse, closer to what happens on Android with Toasts. --- xwords4/common/board.c | 5 ++--- xwords4/common/dragdrpp.c | 6 ++++-- xwords4/linux/gtkask.c | 9 ++++----- xwords4/linux/gtkask.h | 2 +- xwords4/linux/gtkboard.c | 7 ++++--- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/xwords4/common/board.c b/xwords4/common/board.c index 203e807b9..90f893824 100644 --- a/xwords4/common/board.c +++ b/xwords4/common/board.c @@ -1366,8 +1366,7 @@ timerFiredForPen( BoardCtxt* board ) if ( listWords ) { util_cellSquareHeld( board->util, stream ); if ( dragDropInProgress( board ) ) { - XP_Bool dragged; - dragDropEnd( board, board->penDownX, board->penDownY, &dragged ); + dragDropEnd( board, board->penDownX, board->penDownY, NULL ); } } stream_destroy( stream ); @@ -3035,7 +3034,6 @@ handlePenUpInternal( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool isPen, XP_Bool altDown ) { XP_Bool draw = XP_FALSE; - XP_Bool dragged = XP_FALSE; BoardObjectType prevObj = board->penDownObject; /* prevent timer from firing after pen lifted. Set now rather than later @@ -3043,6 +3041,7 @@ handlePenUpInternal( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool isPen, exiting this function (which might give timer time to fire. */ board->penDownObject = OBJ_NONE; + XP_Bool dragged = XP_FALSE; if ( dragDropInProgress(board) ) { draw = dragDropEnd( board, xx, yy, &dragged ); } diff --git a/xwords4/common/dragdrpp.c b/xwords4/common/dragdrpp.c index 839124eac..28cd97d06 100644 --- a/xwords4/common/dragdrpp.c +++ b/xwords4/common/dragdrpp.c @@ -237,7 +237,7 @@ dragDropContinue( BoardCtxt* board, XP_U16 xx, XP_U16 yy ) } XP_Bool -dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* dragged ) +dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* draggedP ) { DragState* ds = &board->dragState; BoardObjectType newObj; @@ -245,7 +245,9 @@ dragDropEnd( BoardCtxt* board, XP_U16 xx, XP_U16 yy, XP_Bool* dragged ) XP_ASSERT( dragDropInProgress(board) ); (void)dragDropContinueImpl( board, xx, yy, &newObj ); - *dragged = ds->didMove; + if ( !!draggedP ) { + *draggedP = ds->didMove; + } /* If we've dropped on something, put the tile there! Since we don't remove it from its earlier location until it's dropped, diff --git a/xwords4/linux/gtkask.c b/xwords4/linux/gtkask.c index 792de08af..2da903ad4 100644 --- a/xwords4/linux/gtkask.c +++ b/xwords4/linux/gtkask.c @@ -48,18 +48,17 @@ gtkask( GtkWidget* parent, const gchar *message, GtkButtonsType buttons, gint gtkask_timeout( GtkWidget* parent, const gchar* message, GtkButtonsType buttons, const AskPair* buttxts, - XP_U16 timeout ) + uint32_t timeoutMS ) { - XP_LOGFF( "(msg: \"%s\")", message ); guint src = 0; GtkWidget* dlg = gtk_message_dialog_new( (GtkWindow*)parent, GTK_MESSAGE_QUESTION, GTK_DIALOG_MODAL, buttons, "%s", message ); - if ( timeout > 0 ) { - XP_LOGF( "%s(%s)", __func__, message ); /* log since times out... */ - src = g_timeout_add( timeout, timer_func, dlg ); + if ( timeoutMS > 0 ) { + XP_LOGF( "%s(\"%s\")", __func__, message ); /* log since times out... */ + src = g_timeout_add( timeoutMS, timer_func, dlg ); } while ( !!buttxts && !!buttxts->txt ) { diff --git a/xwords4/linux/gtkask.h b/xwords4/linux/gtkask.h index 32f8ccf63..20756b5c5 100644 --- a/xwords4/linux/gtkask.h +++ b/xwords4/linux/gtkask.h @@ -38,7 +38,7 @@ gint gtkask( GtkWidget* parent, const gchar *message, GtkButtonsType buttons, const AskPair* buttxts ); gint gtkask_timeout( GtkWidget* parent, const gchar *message, GtkButtonsType buttons, const AskPair* buttxts, - XP_U16 timeout ); + uint32_t timeoutMS ); #endif #endif /* PLATFORM_GTK */ diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 552bfea79..68af06791 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1961,9 +1961,10 @@ gtk_util_getTraySearchLimits( XW_UtilCtxt* XP_UNUSED(uc), static void gtk_util_bonusSquareHeld( XW_UtilCtxt* uc, XWBonusType bonus ) { - LOG_FUNC(); - XP_USE( uc ); - XP_USE( bonus ); + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + gchar* msg = g_strdup_printf( "bonusSquareHeld(bonus=%d)", bonus ); + gtkask_timeout( globals->window, msg, GTK_BUTTONS_OK, NULL, 1000 ); + g_free( msg ); } static void From ad694386077bc28de26315691d63150c44580f68 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 08:45:01 -0700 Subject: [PATCH 23/71] move the two allow-hints settings back together --- xwords4/android/app/src/main/res/xml/xwprefs.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/xwords4/android/app/src/main/res/xml/xwprefs.xml b/xwords4/android/app/src/main/res/xml/xwprefs.xml index a6c5d697f..e3121f213 100644 --- a/xwords4/android/app/src/main/res/xml/xwprefs.xml +++ b/xwords4/android/app/src/main/res/xml/xwprefs.xml @@ -62,18 +62,18 @@ android:defaultValue="true" /> - - + + Date: Fri, 17 Apr 2020 15:51:24 -0700 Subject: [PATCH 24/71] fix mis-formatting causing crash --- .../app/src/main/java/org/eehouse/android/xw4/XWFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWFragment.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWFragment.java index 1be63005e..ba80081a0 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWFragment.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWFragment.java @@ -183,7 +183,7 @@ abstract class XWFragment extends Fragment implements Delegator { @Override public void onActivityResult( int requestCode, int resultCode, Intent data ) { - Log.d( TAG, "%P/%s.onActivityResult() called", this, getClass().getSimpleName() ); + Log.d( TAG, "%H/%s.onActivityResult() called", this, getClass().getSimpleName() ); m_dlgt.onActivityResult( RequestCode.values()[requestCode], resultCode, data ); } From 36bf92e8e5f458d341ac9a193d712be971a9ff87 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 15:52:43 -0700 Subject: [PATCH 25/71] fix misspelled column type INTEGER I have no idea how this worked or how old. Might require a new DB version? Needs testing. --- .../app/src/main/java/org/eehouse/android/xw4/DBHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBHelper.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBHelper.java index 597d329b1..6734c5c7a 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBHelper.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DBHelper.java @@ -190,7 +190,7 @@ public class DBHelper extends SQLiteOpenHelper { { DICTNAME, "TEXT" } ,{ LOC, "UNSIGNED INTEGER(1)" } ,{ WORDCOUNTS, "TEXT" } - ,{ ITERMIN, "INTEGRE(4)" } + ,{ ITERMIN, "INTEGER(4)" } ,{ ITERMAX, "INTEGER(4)" } ,{ ITERPOS, "INTEGER" } ,{ ITERTOP, "INTEGER" } From cfa9b738337a9692350beadd0d9c74add82bfca7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 16:45:03 -0700 Subject: [PATCH 26/71] first cut at persisting logged messages Works, with support for writing them to file. BUT does not handle messages coming in from multiple threads at once so drops a lot of them. --- .../org/eehouse/android/xw4/DlgDelegate.java | 1 + .../android/xw4/GamesListDelegate.java | 32 +++ .../java/org/eehouse/android/xw4/Log.java | 221 +++++++++++++++++- .../java/org/eehouse/android/xw4/Utils.java | 4 +- .../java/org/eehouse/android/xw4/XWApp.java | 2 + .../app/src/main/res/menu/games_list_menu.xml | 12 + .../app/src/main/res/values/strings.xml | 16 ++ xwords4/android/jni/andutils.c | 68 +++++- xwords4/android/jni/andutils.h | 7 + xwords4/android/jni/xwjni.c | 58 ++++- 10 files changed, 400 insertions(+), 21 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java index fbed46ef4..0c9e325d4 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java @@ -51,6 +51,7 @@ public class DlgDelegate { DWNLD_LOC_DICT, NEW_GAME_DFLT_NAME, SEND_EMAIL, + CLEAR_LOG_DB, // BoardDelegate UNDO_LAST_ACTION, 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 25f264353..7f5fdf688 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 @@ -62,6 +62,7 @@ import org.eehouse.android.xw4.jni.GameSummary; import org.eehouse.android.xw4.jni.LastMoveInfo; import org.eehouse.android.xw4.loc.LocUtils; +import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; @@ -574,6 +575,10 @@ public class GamesListDelegate extends ListDelegateBase // R.id.games_menu_loaddb, R.id.games_menu_storedb, R.id.games_menu_writegit, + R.id.games_menu_enableLogStorage, + R.id.games_menu_disableLogStorage, + R.id.games_menu_clearLogStorage, + R.id.games_menu_dumpLogStorage, }; private static final int[] NOSEL_ITEMS = { R.id.games_menu_newgroup, @@ -1380,6 +1385,11 @@ public class GamesListDelegate extends ListDelegateBase Utils.emailAuthor( m_activity ); break; + case CLEAR_LOG_DB: + int nDumped = Log.clearStored(); + Utils.showToast( m_activity, R.string.logstore_cleared_fmt, nDumped ); + break; + case ASKED_PHONE_STATE: rematchWithNameAndPerm( true, params ); break; @@ -1664,6 +1674,28 @@ public class GamesListDelegate extends ListDelegateBase Utils.gitInfoToClip( m_activity ); break; + case R.id.games_menu_enableLogStorage: + Log.setStoreLogs( true ); + break; + case R.id.games_menu_disableLogStorage: + Log.setStoreLogs( false ); + break; + case R.id.games_menu_clearLogStorage: + makeConfirmThenBuilder( R.string.logstore_clear_confirm, + Action.CLEAR_LOG_DB ) + .setPosButton( R.string.button_delete ) + .show(); + break; + case R.id.games_menu_dumpLogStorage: + File logLoc = Log.dumpStored(); + if ( null != logLoc ) { + String dumpMsg = LocUtils + .getString( m_activity, R.string.logstore_dumped_fmt, + logLoc.getPath() ); + makeOkOnlyBuilder( dumpMsg ).show(); + } + break; + default: handled = handleSelGamesItem( itemID, selRowIDs ) || handleSelGroupsItem( itemID, getSelGroupIDs() ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java index 1cc2a5116..1def66a8c 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java @@ -1,6 +1,6 @@ /* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */ /* - * Copyright 2009 - 2017 by Eric House (xwords@eehouse.org). All rights + * Copyright 2009 - 2020 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -20,22 +20,87 @@ package org.eehouse.android.xw4; +import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Environment; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.ref.WeakReference; import java.util.Formatter; public class Log { + private static final String TAG = Log.class.getSimpleName(); private static final String PRE_TAG = BuildConfig.FLAVOR + "-"; + private static final String KEY_USE_DB = TAG + "/useDB"; private static final boolean LOGGING_ENABLED = BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD; private static final boolean ERROR_LOGGING_ENABLED = true; + private static final String LOGS_DB_NAME = "logs_db"; + private static final String LOGS_TABLE_NAME = "logs"; + private static final String COL_ENTRY = "entry"; + private static final String COL_ROWID = "rowid"; + private static final String COL_TAG = "tag"; + private static final String COL_LEVEL = "level"; + + private static final int DB_VERSION = 1; private static boolean sEnabled = BuildConfig.DEBUG; + private static boolean sUseDB; + private static WeakReference sContextRef; + + private static enum LOG_LEVEL { + INFO, + ERROR, + WARN, + DEBUG, + } + + public static void init( Context context ) + { + sContextRef = new WeakReference<>( context ); + sUseDB = DBUtils.getBoolFor( context, KEY_USE_DB, false ); + } + + public static void setStoreLogs( boolean enable ) + { + Context context = sContextRef.get(); + if ( null != context ) { + DBUtils.setBoolFor( context, KEY_USE_DB, enable ); + } + sUseDB = enable; + } public static void enable( boolean newVal ) { sEnabled = newVal; } + public static int clearStored() + { + int result = 0; + LogDBHelper helper = initDB(); + if ( null != helper ) { + result = helper.clear(); + } + return result; + } + + public static File dumpStored() + { + File result = null; + LogDBHelper helper = initDB(); + if ( null != helper ) { + result = helper.dumpToFile(); + } + return result; + } + public static void enable( Context context ) { boolean on = LOGGING_ENABLED || @@ -47,35 +112,54 @@ public class Log { public static void d( String tag, String fmt, Object... args ) { if ( sEnabled ) { - String str = new Formatter().format( fmt, args ).toString(); - android.util.Log.d( PRE_TAG + tag, str ); + dolog( LOG_LEVEL.DEBUG, tag, fmt, args ); } } public static void w( String tag, String fmt, Object... args ) { if ( sEnabled ) { - String str = new Formatter().format( fmt, args ).toString(); - android.util.Log.w( PRE_TAG + tag, str ); + dolog( LOG_LEVEL.WARN, tag, fmt, args ); } } public static void e( String tag, String fmt, Object... args ) { if ( ERROR_LOGGING_ENABLED ) { - String str = new Formatter().format( fmt, args ).toString(); - android.util.Log.e( PRE_TAG + tag, str ); + dolog( LOG_LEVEL.ERROR, tag, fmt, args ); } } public static void i( String tag, String fmt, Object... args ) { if ( sEnabled ) { - String str = new Formatter().format( fmt, args ).toString(); - android.util.Log.i( PRE_TAG + tag, str ); + dolog( LOG_LEVEL.INFO, tag, fmt, args ); } } + private static void dolog( LOG_LEVEL level, String tag, String fmt, Object[] args ) + { + String str = new Formatter().format( fmt, args ).toString(); + String fullTag = PRE_TAG + tag; + switch ( level ) { + case DEBUG: + android.util.Log.d( fullTag, str ); + break; + case ERROR: + android.util.Log.e( fullTag, str ); + break; + case WARN: + android.util.Log.w( fullTag, str ); + break; + case INFO: + android.util.Log.e( fullTag, str ); + break; + default: + Assert.failDbg(); + } + store( level, fullTag, str ); + } + public static void ex( String tag, Exception exception ) { if ( sEnabled ) { @@ -83,4 +167,123 @@ public class Log { DbgUtils.printStack( tag, exception.getStackTrace() ); } } + + private static void llog( String fmt, Object... args ) + { + String str = new Formatter().format( fmt, args ).toString(); + android.util.Log.d( TAG, str ); + } + + private static LogDBHelper s_dbHelper; + private synchronized static LogDBHelper initDB() + { + if ( null == s_dbHelper ) { + Context context = sContextRef.get(); + if ( null != context ) { + s_dbHelper = new LogDBHelper( context ); + // force any upgrade + s_dbHelper.getWritableDatabase().close(); + } + } + return s_dbHelper; + } + + // Called from jni + public static void store( String tag, String msg ) + { + llog( "store(%s) called from jni", msg ); + store( LOG_LEVEL.DEBUG, tag, msg ); + } + + private static void store( LOG_LEVEL level, String tag, String msg ) + { + if ( sUseDB ) { + LogDBHelper helper = initDB(); + if ( null != helper ) { + helper.store( level, tag, msg ); + } + } + } + + private static class LogDBHelper extends SQLiteOpenHelper { + private Context mContext; + + LogDBHelper( Context context ) + { + super( context, LOGS_DB_NAME, null, DB_VERSION ); + mContext = context; + } + + @Override + public void onCreate( SQLiteDatabase db ) + { + String query = "CREATE TABLE " + LOGS_TABLE_NAME + "(" + + COL_ROWID + " INTEGER PRIMARY KEY AUTOINCREMENT" + + "," + COL_ENTRY + " TEXT" + + "," + COL_TAG + " TEXT" + + "," + COL_LEVEL + " INTEGER(2)" + + ");"; + + db.execSQL( query ); + } + + @Override + @SuppressWarnings("fallthrough") + public void onUpgrade( SQLiteDatabase db, int oldVersion, int newVersion ) + { + String msg = String.format("onUpgrade(%s): old: %d; new: %d", db, oldVersion, newVersion ); + android.util.Log.i( TAG, msg ); + Assert.failDbg(); + } + + void store( LOG_LEVEL level, String tag, String msg ) + { + ContentValues values = new ContentValues(); + values.put( COL_ENTRY, msg ); + values.put( COL_TAG, tag ); + values.put( COL_LEVEL, level.ordinal() ); + long res = getWritableDatabase().insert( LOGS_TABLE_NAME, null, values ); + } + + File dumpToFile() + { + File storage = Environment.getExternalStorageDirectory(); + File db = new File( storage, LOGS_DB_NAME ); + + try { + OutputStream os = new FileOutputStream( db ); + OutputStreamWriter osw = new OutputStreamWriter(os); + + String[] columns = { COL_ENTRY, COL_TAG }; + String selection = null; + String orderBy = COL_ROWID; + Cursor cursor = getReadableDatabase().query( LOGS_TABLE_NAME, columns, + selection, null, null, null, + orderBy ); + llog( "dumpToFile(): got %d results", cursor.getCount() ); + int indx0 = cursor.getColumnIndex( columns[0] ); + int indx1 = cursor.getColumnIndex( columns[1] ); + while ( cursor.moveToNext() ) { + String data = cursor.getString(indx0); + String tag = cursor.getString(indx1); + osw.write( tag ); + osw.write( ":" ); + osw.write(data); + osw.write( "\n" ); + } + osw.close(); + } catch ( IOException ioe ) { + llog( "dumpToFile(): ioe: %s", ioe ); + } + return db; + } + + // Return the number of rows + int clear() + { + int result = getWritableDatabase() + .delete( LOGS_TABLE_NAME, "1", null ); + return result; + } + } } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java index c6899c6e1..9a07c43b5 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java @@ -65,6 +65,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.security.MessageDigest; +import java.util.Formatter; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -174,9 +175,10 @@ public class Utils { } } - public static void showToast( Context context, int id ) + public static void showToast( Context context, int id, Object... args ) { String msg = LocUtils.getString( context, id ); + msg = new Formatter().format( msg, args ).toString(); showToast( context, msg ); } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java index b4f45100f..77a3f8fb1 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWApp.java @@ -71,6 +71,8 @@ public class XWApp extends Application Assert.assertTrue( s_context == s_context.getApplicationContext() ); super.onCreate(); + Log.init( this ); + ProcessLifecycleOwner.get().getLifecycle().addObserver(this); android.util.Log.i( TAG, "onCreate(); git_rev=" diff --git a/xwords4/android/app/src/main/res/menu/games_list_menu.xml b/xwords4/android/app/src/main/res/menu/games_list_menu.xml index 08a679053..cea644131 100644 --- a/xwords4/android/app/src/main/res/menu/games_list_menu.xml +++ b/xwords4/android/app/src/main/res/menu/games_list_menu.xml @@ -122,4 +122,16 @@ android:title="@string/gamel_menu_writegit" /> + + + + diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 38b00986a..69a88dedb 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2519,4 +2519,20 @@ Paused by: %1$s. Message: %1$s. Auto-paused. + + + Enable log storage + + Diable log storage + + Clear stored logs + + Write stored logs to file + + Logs written to file %1$s + + %1$d log entries deleted + + Are you sure you want to + erase your debug logs? This action cannot be undone. diff --git a/xwords4/android/jni/andutils.c b/xwords4/android/jni/andutils.c index 573a7edd7..14c785dbb 100644 --- a/xwords4/android/jni/andutils.c +++ b/xwords4/android/jni/andutils.c @@ -30,6 +30,8 @@ void and_assert( const char* test, int line, const char* file, const char* func ) { + RAW_LOG( "assertion \"%s\" failed: line %d in %s() in %s", + test, line, func, file ); XP_LOGF( "assertion \"%s\" failed: line %d in %s() in %s", test, line, func, file ); __android_log_assert( test, "ASSERT", "line %d in %s() in %s", @@ -766,6 +768,46 @@ deleteLocalRefs( JNIEnv* env, ... ) } #ifdef DEBUG +static int g_log_count = 0; +static pthread_mutex_t g_log_count_lock = PTHREAD_MUTEX_INITIALIZER; + +/* A bunch of threads are generating log statements. */ +static void +passToJava( const char* tag, const char* msg ) +{ + if ( 1 ) { + pthread_mutex_lock( &g_log_count_lock ); + int lockCount = ++g_log_count; + RAW_LOG( "lockCount: %d", lockCount ); + pthread_mutex_unlock( &g_log_count_lock ); + + /* Now pass into Log.java for possible writing to DB */ + if ( 1 == lockCount ) { + JNIEnv* env = waitEnvFromGlobals(); + if ( !!env ) { + jstring jtag = (*env)->NewStringUTF( env, tag ); + jstring jbuf = (*env)->NewStringUTF( env, msg ); + jclass clazz = (*env)->FindClass( env, PKG_PATH("Log") ); + XP_ASSERT( !!clazz ); + jmethodID mid = (*env)->GetStaticMethodID( env, clazz, "store", + "(Ljava/lang/String;Ljava/lang/String;)V" ); + (*env)->CallStaticVoidMethod( env, clazz, mid, jtag, jbuf ); + deleteLocalRefs( env, clazz, jtag, jbuf, DELETE_NO_REF ); + + releaseEnvFromGlobals( env ); + } else { + RAW_LOG( "env is NULL; dropping" ); + } + } else { + RAW_LOG( "recursing! Skipping msg %s", msg ); + } + + pthread_mutex_lock( &g_log_count_lock ); + --g_log_count; + pthread_mutex_unlock( &g_log_count_lock ); + } +} + static void debugf( const char* format, va_list ap ) { @@ -783,8 +825,8 @@ debugf( const char* format, va_list ap ) if ( len < sizeof(buf) ) { vsnprintf( buf + len, sizeof(buf)-len, format, ap ); } - - (void)__android_log_write( ANDROID_LOG_DEBUG, + + const char* tag = # if defined VARIANT_xw4NoSMS || defined VARIANT_xw4fdroid || defined VARIANT_xw4SMS "xw4" # elif defined VARIANT_xw4d || defined VARIANT_xw4dNoSMS @@ -792,7 +834,27 @@ debugf( const char* format, va_list ap ) # elif defined VARIANT_xw4dup || defined VARIANT_xw4dupNoSMS "x4du" # endif - , buf ); + ; + + (void)__android_log_write( ANDROID_LOG_DEBUG, tag, buf ); + + passToJava( tag, buf ); +} + +void +raw_log( const char* func, const char* fmt, ... ) +{ + char buf[1024]; + int len = snprintf( buf, VSIZE(buf) - 1, "in %s(): %s", func, fmt ); + buf[len] = '\0'; + + va_list ap; + va_start( ap, fmt ); + char buf2[1024]; + len = vsnprintf( buf2, VSIZE(buf2) - 1, buf, ap ); + va_end( ap ); + + (void)__android_log_write( ANDROID_LOG_DEBUG, "raw", buf2 ); } void diff --git a/xwords4/android/jni/andutils.h b/xwords4/android/jni/andutils.h index a19eef58f..a2cc306b8 100644 --- a/xwords4/android/jni/andutils.h +++ b/xwords4/android/jni/andutils.h @@ -108,5 +108,12 @@ XP_U32 getCurSeconds( JNIEnv* env ); void deleteLocalRef( JNIEnv* env, jobject jobj ); void deleteLocalRefs( JNIEnv* env, ... ); + +JNIEnv* waitEnvFromGlobals(); +void releaseEnvFromGlobals( JNIEnv* env ); + +void raw_log( const char* func, const char* fmt, ... ); +#define RAW_LOG(...) raw_log( __func__, __VA_ARGS__ ) + # define DELETE_NO_REF ((jobject)-1) /* terminates above varargs list */ #endif diff --git a/xwords4/android/jni/xwjni.c b/xwords4/android/jni/xwjni.c index 4ed600296..5c49b67b7 100644 --- a/xwords4/android/jni/xwjni.c +++ b/xwords4/android/jni/xwjni.c @@ -118,7 +118,7 @@ typedef long GamePtrType; #ifdef LOG_MAPPING # ifdef DEBUG static int -countUsed(const EnvThreadInfo* ti) +countUsed( const EnvThreadInfo* ti ) { int count = 0; for ( int ii = 0; ii < ti->nEntries; ++ii ) { @@ -157,8 +157,8 @@ map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller ) found = true; if ( env != entry->env ) { /* this DOES happen!!! */ - XP_LOGF( "%s (ti=%p): replacing env %p with env %p for thread %x", - __func__, ti, entry->env, env, (int)self ); + RAW_LOG( "(ti=%p): replacing env %p with env %p for thread %x", + ti, entry->env, env, (int)self ); entry->env = env; } } @@ -180,7 +180,7 @@ map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller ) ti->entries = entries; ti->nEntries = nEntries; #ifdef LOG_MAPPING - XP_LOGF( "%s: num env entries now %d", __func__, nEntries ); + RAW_LOG( "num env entries now %d", nEntries ); #endif } @@ -191,9 +191,9 @@ map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller ) ++firstEmpty->refcount; #ifdef LOG_MAPPING firstEmpty->ownerFunc = caller; - XP_LOGF( "%s: entry %zu: mapped env %p to thread %x", __func__, + RAW_LOG( "entry %zu: mapped env %p to thread %x", firstEmpty - ti->entries, env, (int)self ); - XP_LOGF( "%s: num entries USED now %d", __func__, countUsed(ti) ); + RAW_LOG( "num entries USED now %d", countUsed(ti) ); #endif } @@ -221,9 +221,9 @@ map_remove_prv( EnvThreadInfo* ti, JNIEnv* env, const char* func ) if ( found ) { XP_ASSERT( pthread_self() == entry->owner ); #ifdef LOG_MAPPING - XP_LOGF( "%s: UNMAPPED env %p to thread %x (from %s; mapped by %s)", __func__, + RAW_LOG( "UNMAPPED env %p to thread %x (from %s; mapped by %s)", entry->env, (int)entry->owner, func, entry->ownerFunc ); - XP_LOGF( "%s: %d entries left", __func__, countUsed( ti ) ); + RAW_LOG( "%d entries left", countUsed( ti ) ); entry->ownerFunc = NULL; #endif XP_ASSERT( 1 == entry->refcount ); @@ -258,6 +258,46 @@ prvEnvForMe( EnvThreadInfo* ti ) return result; } +#ifdef DEBUG +static pthread_mutex_t g_globalStateLock = PTHREAD_MUTEX_INITIALIZER; +static JNIGlobalState* g_globalState = NULL; + +void setGlobalState( JNIGlobalState* state ) +{ + pthread_mutex_lock( &g_globalStateLock ); + g_globalState = state; + pthread_mutex_unlock( &g_globalStateLock ); +} + +JNIEnv* +waitEnvFromGlobals() /* hanging */ +{ + JNIEnv* result = NULL; + pthread_mutex_lock( &g_globalStateLock ); + JNIGlobalState* state = g_globalState; + if ( !!state ) { + result = prvEnvForMe( &state->ti ); + } + if ( !result ) { + pthread_mutex_unlock( &g_globalStateLock ); + } + return result; +} + +void +releaseEnvFromGlobals( JNIEnv* env ) +{ + XP_ASSERT( !!env ); + JNIGlobalState* state = g_globalState; + XP_ASSERT( !!state ); + XP_ASSERT( env == prvEnvForMe( &state->ti ) ); + pthread_mutex_unlock( &g_globalStateLock ); +} + +#else +# define setGlobalState(s) +#endif + JNIEnv* envForMe( EnvThreadInfo* ti, const char* caller ) { @@ -325,6 +365,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_initGlobals globalState->dictMgr = dmgr_make( MPPARM_NOCOMMA( mpool ) ); globalState->smsProto = smsproto_init( MPPARM( mpool ) globalState->dutil ); MPASSIGN( globalState->mpool, mpool ); + setGlobalState( globalState ); // LOG_RETURNF( "%p", globalState ); return (jlong)globalState; } @@ -335,6 +376,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_cleanGlobals { // LOG_FUNC(); if ( 0 != jniGlobalPtr ) { + setGlobalState( NULL ); JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG MemPoolCtx* mpool = GETMPOOL( globalState ); From 6560394478260aea400561eeb9a569695087bc7a Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 17:36:26 -0700 Subject: [PATCH 27/71] part two: add threadID and support multiple threads Looks like it's enough to just use a mutex so threads block until it's their turn to send logs to java. --- .../org/eehouse/android/xw4/DlgDelegate.java | 1 + .../android/xw4/GamesListDelegate.java | 29 +++++++++--- .../java/org/eehouse/android/xw4/Log.java | 33 +++++++++----- .../app/src/main/res/values/strings.xml | 2 + xwords4/android/jni/andutils.c | 44 ++++++------------- xwords4/android/jni/andutils.h | 6 ++- 6 files changed, 66 insertions(+), 49 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java index 0c9e325d4..8a1ebac0f 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java @@ -51,6 +51,7 @@ public class DlgDelegate { DWNLD_LOC_DICT, NEW_GAME_DFLT_NAME, SEND_EMAIL, + WRITE_LOG_DB, CLEAR_LOG_DB, // BoardDelegate 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 7f5fdf688..ce6da0e3e 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 @@ -1385,6 +1385,25 @@ public class GamesListDelegate extends ListDelegateBase Utils.emailAuthor( m_activity ); break; + case WRITE_LOG_DB: + final File logLoc = Log.dumpStored(); + post( new Runnable() { + @Override + public void run() { + String dumpMsg; + if ( null == logLoc ) { + dumpMsg = LocUtils.getString( m_activity, + R.string.logstore_notdumped ); + } else { + dumpMsg = LocUtils + .getString( m_activity, R.string.logstore_dumped_fmt, + logLoc.getPath() ); + } + makeOkOnlyBuilder( dumpMsg ).show(); + } + } ); + break; + case CLEAR_LOG_DB: int nDumped = Log.clearStored(); Utils.showToast( m_activity, R.string.logstore_cleared_fmt, nDumped ); @@ -1687,13 +1706,8 @@ public class GamesListDelegate extends ListDelegateBase .show(); break; case R.id.games_menu_dumpLogStorage: - File logLoc = Log.dumpStored(); - if ( null != logLoc ) { - String dumpMsg = LocUtils - .getString( m_activity, R.string.logstore_dumped_fmt, - logLoc.getPath() ); - makeOkOnlyBuilder( dumpMsg ).show(); - } + Perms23.tryGetPerms( this, Perm.STORAGE, null, + Action.WRITE_LOG_DB ); break; default: @@ -1907,6 +1921,7 @@ public class GamesListDelegate extends ListDelegateBase } else { dropSels = true; // will select the new game instead post( new Runnable() { + @Override public void run() { Activity self = m_activity; byte[] stream = diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java index 1def66a8c..146e280f6 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java @@ -42,9 +42,11 @@ public class Log { private static final boolean LOGGING_ENABLED = BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD; private static final boolean ERROR_LOGGING_ENABLED = true; - private static final String LOGS_DB_NAME = "logs_db"; + private static final String LOGS_DB_NAME = "xwlogs_db"; private static final String LOGS_TABLE_NAME = "logs"; private static final String COL_ENTRY = "entry"; + private static final String COL_THREAD = "tid"; + private static final String COL_PID = "pid"; private static final String COL_ROWID = "rowid"; private static final String COL_TAG = "tag"; private static final String COL_LEVEL = "level"; @@ -188,10 +190,10 @@ public class Log { return s_dbHelper; } - // Called from jni + // Called from jni. Keep name and signature in sync with what's in + // passToJava() in andutils.c public static void store( String tag, String msg ) { - llog( "store(%s) called from jni", msg ); store( LOG_LEVEL.DEBUG, tag, msg ); } @@ -220,6 +222,8 @@ public class Log { String query = "CREATE TABLE " + LOGS_TABLE_NAME + "(" + COL_ROWID + " INTEGER PRIMARY KEY AUTOINCREMENT" + "," + COL_ENTRY + " TEXT" + + "," + COL_THREAD + " INTEGER" + + "," + COL_PID + " INTEGER" + "," + COL_TAG + " TEXT" + "," + COL_LEVEL + " INTEGER(2)" + ");"; @@ -238,8 +242,11 @@ public class Log { void store( LOG_LEVEL level, String tag, String msg ) { + long tid = Thread.currentThread().getId(); + ContentValues values = new ContentValues(); values.put( COL_ENTRY, msg ); + values.put( COL_THREAD, tid ); values.put( COL_TAG, tag ); values.put( COL_LEVEL, level.ordinal() ); long res = getWritableDatabase().insert( LOGS_TABLE_NAME, null, values ); @@ -247,14 +254,15 @@ public class Log { File dumpToFile() { - File storage = Environment.getExternalStorageDirectory(); - File db = new File( storage, LOGS_DB_NAME ); + File dir = Environment.getExternalStorageDirectory(); + dir = new File( dir, Environment.DIRECTORY_DOWNLOADS ); + File db = new File( dir, LOGS_DB_NAME + ".txt" ); try { OutputStream os = new FileOutputStream( db ); OutputStreamWriter osw = new OutputStreamWriter(os); - String[] columns = { COL_ENTRY, COL_TAG }; + String[] columns = { COL_ENTRY, COL_TAG, COL_THREAD }; String selection = null; String orderBy = COL_ROWID; Cursor cursor = getReadableDatabase().query( LOGS_TABLE_NAME, columns, @@ -263,17 +271,22 @@ public class Log { llog( "dumpToFile(): got %d results", cursor.getCount() ); int indx0 = cursor.getColumnIndex( columns[0] ); int indx1 = cursor.getColumnIndex( columns[1] ); + int indx2 = cursor.getColumnIndex( columns[2] ); while ( cursor.moveToNext() ) { String data = cursor.getString(indx0); String tag = cursor.getString(indx1); - osw.write( tag ); - osw.write( ":" ); - osw.write(data); - osw.write( "\n" ); + long tid = cursor.getLong(indx2); + StringBuilder builder = new StringBuilder() + .append(tid).append(":") + .append(tag).append(":") + .append(data).append("\n") + ; + osw.write( builder.toString() ); } osw.close(); } catch ( IOException ioe ) { llog( "dumpToFile(): ioe: %s", ioe ); + db = null; } return db; } diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 69a88dedb..ba05ded9f 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2530,6 +2530,8 @@ Write stored logs to file Logs written to file %1$s + + Unable to write logs %1$d log entries deleted diff --git a/xwords4/android/jni/andutils.c b/xwords4/android/jni/andutils.c index 14c785dbb..272787f9d 100644 --- a/xwords4/android/jni/andutils.c +++ b/xwords4/android/jni/andutils.c @@ -768,43 +768,25 @@ deleteLocalRefs( JNIEnv* env, ... ) } #ifdef DEBUG -static int g_log_count = 0; -static pthread_mutex_t g_log_count_lock = PTHREAD_MUTEX_INITIALIZER; /* A bunch of threads are generating log statements. */ static void passToJava( const char* tag, const char* msg ) { - if ( 1 ) { - pthread_mutex_lock( &g_log_count_lock ); - int lockCount = ++g_log_count; - RAW_LOG( "lockCount: %d", lockCount ); - pthread_mutex_unlock( &g_log_count_lock ); + JNIEnv* env = waitEnvFromGlobals(); + if ( !!env ) { + jstring jtag = (*env)->NewStringUTF( env, tag ); + jstring jbuf = (*env)->NewStringUTF( env, msg ); + jclass clazz = (*env)->FindClass( env, PKG_PATH("Log") ); + XP_ASSERT( !!clazz ); + jmethodID mid = (*env)->GetStaticMethodID( env, clazz, "store", + "(Ljava/lang/String;Ljava/lang/String;)V" ); + (*env)->CallStaticVoidMethod( env, clazz, mid, jtag, jbuf ); + deleteLocalRefs( env, clazz, jtag, jbuf, DELETE_NO_REF ); - /* Now pass into Log.java for possible writing to DB */ - if ( 1 == lockCount ) { - JNIEnv* env = waitEnvFromGlobals(); - if ( !!env ) { - jstring jtag = (*env)->NewStringUTF( env, tag ); - jstring jbuf = (*env)->NewStringUTF( env, msg ); - jclass clazz = (*env)->FindClass( env, PKG_PATH("Log") ); - XP_ASSERT( !!clazz ); - jmethodID mid = (*env)->GetStaticMethodID( env, clazz, "store", - "(Ljava/lang/String;Ljava/lang/String;)V" ); - (*env)->CallStaticVoidMethod( env, clazz, mid, jtag, jbuf ); - deleteLocalRefs( env, clazz, jtag, jbuf, DELETE_NO_REF ); - - releaseEnvFromGlobals( env ); - } else { - RAW_LOG( "env is NULL; dropping" ); - } - } else { - RAW_LOG( "recursing! Skipping msg %s", msg ); - } - - pthread_mutex_lock( &g_log_count_lock ); - --g_log_count; - pthread_mutex_unlock( &g_log_count_lock ); + releaseEnvFromGlobals( env ); + } else { + RAW_LOG( "env is NULL; dropping" ); } } diff --git a/xwords4/android/jni/andutils.h b/xwords4/android/jni/andutils.h index a2cc306b8..07b14b38e 100644 --- a/xwords4/android/jni/andutils.h +++ b/xwords4/android/jni/andutils.h @@ -113,7 +113,11 @@ JNIEnv* waitEnvFromGlobals(); void releaseEnvFromGlobals( JNIEnv* env ); void raw_log( const char* func, const char* fmt, ... ); -#define RAW_LOG(...) raw_log( __func__, __VA_ARGS__ ) +#ifdef DEBUG +# define RAW_LOG(...) raw_log( __func__, __VA_ARGS__ ) +#else +# define RAW_LOG(...) +#endif # define DELETE_NO_REF ((jobject)-1) /* terminates above varargs list */ #endif From 0e5cc9f2b9d1cc7062c730af9b53400bb9464c16 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 18:07:50 -0700 Subject: [PATCH 28/71] log pid and tid roughly same as logcat does --- .../org/eehouse/android/xw4/GamesListDelegate.java | 2 +- .../src/main/java/org/eehouse/android/xw4/Log.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 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 ce6da0e3e..8b75dbe87 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 @@ -1702,7 +1702,7 @@ public class GamesListDelegate extends ListDelegateBase case R.id.games_menu_clearLogStorage: makeConfirmThenBuilder( R.string.logstore_clear_confirm, Action.CLEAR_LOG_DB ) - .setPosButton( R.string.button_delete ) + .setPosButton( R.string.loc_item_clear ) .show(); break; case R.id.games_menu_dumpLogStorage: diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java index 146e280f6..833443578 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java @@ -26,6 +26,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.os.Environment; +import android.os.Process; import java.io.File; import java.io.FileOutputStream; @@ -242,11 +243,13 @@ public class Log { void store( LOG_LEVEL level, String tag, String msg ) { - long tid = Thread.currentThread().getId(); + int tid = Process.myTid(); + int pid = Process.myPid(); ContentValues values = new ContentValues(); values.put( COL_ENTRY, msg ); values.put( COL_THREAD, tid ); + values.put( COL_PID, pid ); values.put( COL_TAG, tag ); values.put( COL_LEVEL, level.ordinal() ); long res = getWritableDatabase().insert( LOGS_TABLE_NAME, null, values ); @@ -262,7 +265,7 @@ public class Log { OutputStream os = new FileOutputStream( db ); OutputStreamWriter osw = new OutputStreamWriter(os); - String[] columns = { COL_ENTRY, COL_TAG, COL_THREAD }; + String[] columns = { COL_ENTRY, COL_TAG, COL_THREAD, COL_PID }; String selection = null; String orderBy = COL_ROWID; Cursor cursor = getReadableDatabase().query( LOGS_TABLE_NAME, columns, @@ -272,12 +275,14 @@ public class Log { int indx0 = cursor.getColumnIndex( columns[0] ); int indx1 = cursor.getColumnIndex( columns[1] ); int indx2 = cursor.getColumnIndex( columns[2] ); + int indx3 = cursor.getColumnIndex( columns[3] ); while ( cursor.moveToNext() ) { String data = cursor.getString(indx0); String tag = cursor.getString(indx1); - long tid = cursor.getLong(indx2); + int tid = cursor.getInt(indx2); + int pid = cursor.getInt(indx3); StringBuilder builder = new StringBuilder() - .append(tid).append(":") + .append(String.format("% 5d % 5d", pid, tid)).append(":") .append(tag).append(":") .append(data).append("\n") ; From 5d2f84acd83c210c2802f5358f6d4c8f35e7819d Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 18:18:52 -0700 Subject: [PATCH 29/71] move log-related menuitems into a submenu --- .../android/xw4/GamesListDelegate.java | 5 +--- .../app/src/main/res/menu/games_list_menu.xml | 30 +++++++++++-------- .../app/src/main/res/values/strings.xml | 1 + 3 files changed, 20 insertions(+), 16 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 8b75dbe87..d0746fb50 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 @@ -575,10 +575,7 @@ public class GamesListDelegate extends ListDelegateBase // R.id.games_menu_loaddb, R.id.games_menu_storedb, R.id.games_menu_writegit, - R.id.games_menu_enableLogStorage, - R.id.games_menu_disableLogStorage, - R.id.games_menu_clearLogStorage, - R.id.games_menu_dumpLogStorage, + R.id.games_submenu_logs, }; private static final int[] NOSEL_ITEMS = { R.id.games_menu_newgroup, diff --git a/xwords4/android/app/src/main/res/menu/games_list_menu.xml b/xwords4/android/app/src/main/res/menu/games_list_menu.xml index cea644131..a11be0b52 100644 --- a/xwords4/android/app/src/main/res/menu/games_list_menu.xml +++ b/xwords4/android/app/src/main/res/menu/games_list_menu.xml @@ -122,16 +122,22 @@ android:title="@string/gamel_menu_writegit" /> - - - - + +

+ + + + + + diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index ba05ded9f..61c5746a5 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2520,6 +2520,7 @@ Message: %1$s. Auto-paused. + Debug logs Enable log storage From b8ba5a98c14810a99411b4faa0fd125af881a55c Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 18:38:44 -0700 Subject: [PATCH 30/71] remove too-frequent logging --- .../app/src/main/java/org/eehouse/android/xw4/DBUtils.java | 2 +- .../src/main/java/org/eehouse/android/xw4/DupeModeTimer.java | 4 ++-- .../app/src/main/java/org/eehouse/android/xw4/NFCUtils.java | 2 +- .../src/main/java/org/eehouse/android/xw4/jni/JNIThread.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) 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 a04b8dfbb..bef650e05 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 @@ -1541,7 +1541,7 @@ public class DBUtils { } s_groupsCache = result; } - Log.d( TAG, "getGroups() => %s", result ); + // Log.d( TAG, "getGroups() => %s", result ); return result; } // getGroups diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DupeModeTimer.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DupeModeTimer.java index 55aab7c3d..db258a134 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DupeModeTimer.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DupeModeTimer.java @@ -73,7 +73,7 @@ public class DupeModeTimer extends BroadcastReceiver { public void gameSaved( Context context, long rowid, GameChangeType change ) { - Log.d( TAG, "gameSaved(rowid=%d,change=%s) called", rowid, change ); + // Log.d( TAG, "gameSaved(rowid=%d,change=%s) called", rowid, change ); switch( change ) { case GAME_CHANGED: case GAME_CREATED: @@ -81,7 +81,7 @@ public class DupeModeTimer extends BroadcastReceiver { if ( sDirtyVals.containsKey( rowid ) ) { sQueue.addOne( context, rowid ); } else { - Log.d( TAG, "skipping; not dirty" ); + // Log.d( TAG, "skipping; not dirty" ); } } break; diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCUtils.java index 371c24d46..6d675a00e 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCUtils.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/NFCUtils.java @@ -992,7 +992,7 @@ public class NFCUtils { mAdapter.disableReaderMode( mActivity ); } mInReadMode = wantReadMode; - Log.d( TAG, "run(): inReadMode now: %b", mInReadMode ); + // Log.d( TAG, "run(): inReadMode now: %b", mInReadMode ); // Now sleep. If we aren't going to want to toggle read // mode soon, sleep until interrupted by a state change, diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java index 46f02ef61..4aa9d05a5 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java @@ -401,7 +401,7 @@ public class JNIThread extends Thread implements AutoCloseable { // PENDING: once certain this is true, stop saving the full array and // instead save the hash. Also, update it after each save. if ( hashesEqual ) { - Log.d( TAG, "save_jni(): no change in game; can skip saving" ); + // Log.d( TAG, "save_jni(): no change in game; can skip saving" ); } else { // Don't need this!!!! this only runs on the run() thread synchronized( this ) { From 9f92bce8807d9b6bff42d86b4b17e9d25971426a Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 17 Apr 2020 20:29:47 -0700 Subject: [PATCH 31/71] name exported file based on FLAVOR --- .../app/src/main/java/org/eehouse/android/xw4/Log.java | 3 ++- .../src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java index 833443578..57fc6ee95 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Log.java @@ -43,6 +43,7 @@ public class Log { private static final boolean LOGGING_ENABLED = BuildConfig.DEBUG || !BuildConfig.IS_TAGGED_BUILD; private static final boolean ERROR_LOGGING_ENABLED = true; + private static final String LOGS_FILE_NAME = BuildConfig.FLAVOR + "_logsDB.txt"; private static final String LOGS_DB_NAME = "xwlogs_db"; private static final String LOGS_TABLE_NAME = "logs"; private static final String COL_ENTRY = "entry"; @@ -259,7 +260,7 @@ public class Log { { File dir = Environment.getExternalStorageDirectory(); dir = new File( dir, Environment.DIRECTORY_DOWNLOADS ); - File db = new File( dir, LOGS_DB_NAME + ".txt" ); + File db = new File( dir, LOGS_FILE_NAME ); try { OutputStream os = new FileOutputStream( db ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java index a3ea4b68d..31c4e504b 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java @@ -123,9 +123,8 @@ public class DUtilCtxt { private static final int STRSD_DUP_ONESCORE = 29; private static final int STR_PENDING_PLAYER = 30; - public String getUserString( int stringCode ) + public String getUserString( final int stringCode ) { - Log.d( TAG, "getUserString(%d)", stringCode ); int id = 0; switch( stringCode ) { case STR_ROBOT_MOVED: @@ -217,7 +216,7 @@ public class DUtilCtxt { } String result = (0 == id) ? "" : LocUtils.getString( m_context, id ); - Log.d( TAG, "getUserString() => %s", result ); + Log.d( TAG, "getUserString(%d) => %s", stringCode, result ); return result; } From 72cb12f38370eecb45488428bc080547861784df Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 06:14:29 -0700 Subject: [PATCH 32/71] fix crash anytime you used PHONIES_DISALLOW Yikes. I used to assert, in nextTurn(), that you were in the right state. On release builds that went away, and you were moved into the right state regardless. The bug happened when I changed that to exit nextTurn() without changing the state, meaning that for PHONIES_DISALLOW the host filled up its message queue trying to communicate that the latest move was ok, never getting out of the state that required sending that message. The fix is simply to change the state after sending and, guest-side, after receiving, that message. --- xwords4/common/server.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/xwords4/common/server.c b/xwords4/common/server.c index f1c0c4ed1..0bc695462 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -3843,11 +3843,12 @@ setTurn( ServerCtxt* server, XP_S16 turn ) static void tellMoveWasLegal( ServerCtxt* server ) { - XWStreamCtxt* stream; - - stream = messageStreamWithHeader( server, server->lastMoveSource, - XWPROTO_MOVE_CONFIRM ); + XWStreamCtxt* stream = + messageStreamWithHeader( server, server->lastMoveSource, + XWPROTO_MOVE_CONFIRM ); stream_destroy( stream ); + + SETSTATE( server, XWSTATE_INTURN ); } /* tellMoveWasLegal */ static XP_Bool @@ -3872,6 +3873,7 @@ handleMoveOk( ServerCtxt* server, XWStreamCtxt* XP_UNUSED(incoming) ) XP_ASSERT( server->vol.gi->serverRole == SERVER_ISCLIENT ); XP_ASSERT( server->nv.gameState == XWSTATE_MOVE_CONFIRM_WAIT ); + SETSTATE( server, XWSTATE_INTURN ); nextTurn( server, PICK_NEXT ); return accepted; From 2efc82f4e0967c1014753481acbd157a3610a798 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 06:41:21 -0700 Subject: [PATCH 33/71] add --phonies arg so this doesn't happen again --- xwords4/linux/scripts/discon_ok2.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py index a9395ef2b..f7db93ae5 100755 --- a/xwords4/linux/scripts/discon_ok2.py +++ b/xwords4/linux/scripts/discon_ok2.py @@ -478,9 +478,13 @@ def build_cmds(args): # it isn't a priority. # PARAMS += ['--seed', args.SEED] - if DEV == 1: PARAMS += ['--server'] if DEV == 1 or usePublic: PARAMS += ['--force-game'] - if DEV > 1: PARAMS += ['--force-channel', DEV - 1] + if DEV == 1: + if args.PHONIES == -1: phonies = GAME % 3 + else: phonies = args.PHONIES + PARAMS += ['--server', '--phonies', phonies ] + else: + PARAMS += ['--force-channel', DEV - 1] if useDupeMode: PARAMS += ['--duplicate-mode'] if usePublic: PARAMS += ['--make-public', '--join-public'] @@ -694,7 +698,10 @@ def mkParser(): parser.add_argument('--nochange-secs', dest = 'NO_CHANGE_SECS', default = 30, type = int, help = 'seconds without change after which to timeout') parser.add_argument('--log-root', dest='LOGROOT', default = '.', help = 'where logfiles go') - parser.add_argument('--dup-packets', dest = 'DUP_PACKETS', default = False, help = 'send all packet twice') + parser.add_argument('--dup-packets', dest = 'DUP_PACKETS', default = False, + help = 'send all packet twice') + parser.add_argument('--phonies', dest = 'PHONIES', default = -1, type = int, + help = '0 (ignore), 1 (warn)) or 2 (lose turn); default is pick at random') parser.add_argument('--use-gtk', dest = 'USE_GTK', default = False, action = 'store_true', help = 'run games using gtk instead of ncurses') From b82975179bb07a49ce3b2bdcfa53e12f0903a7d4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 07:08:00 -0700 Subject: [PATCH 34/71] remove mistaken assert It's ok to fail to open a game, as will happen e.g. when a message arrives for a game that was recently deleted. --- .../app/src/main/java/org/eehouse/android/xw4/DBUtils.java | 1 - 1 file changed, 1 deletion(-) 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 bef650e05..3e4236db5 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 @@ -1125,7 +1125,6 @@ public class DBUtils { } setCached( rowid, result ); } - Assert.assertTrueNR( null != result ); return result; } From 3a0294044ef9e2155fff3cf4bf2e9777fd40f871 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 07:31:03 -0700 Subject: [PATCH 35/71] fix crashes when actual bad words sent Same as before, needed to explicitly set the state back to XWSTATE_INTURN after doing the work the special state required. --- xwords4/common/server.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 0bc695462..3c31750ba 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -1,6 +1,6 @@ /* -*- compile-command: "cd ../linux && make -j3 MEMDEBUG=TRUE"; -*- */ /* - * Copyright 1997 - 2019 by Eric House (xwords@eehouse.org). All rights + * Copyright 1997 - 2020 by Eric House (xwords@eehouse.org). All rights * reserved. * * This program is free software; you can redistribute it and/or @@ -1589,7 +1589,7 @@ server_do( ServerCtxt* server ) #ifndef XWFEATURE_STANDALONE_ONLY sendBadWordMsgs( server ); #endif - nextTurn( server, PICK_NEXT ); /* sets server->nv.gameState */ + nextTurn( server, PICK_NEXT ); //moreToDo = XP_TRUE; /* why? */ break; @@ -1605,7 +1605,7 @@ server_do( ServerCtxt* server ) case XWSTATE_MOVE_CONFIRM_MUSTSEND: XP_ASSERT( server->vol.gi->serverRole == SERVER_ISSERVER ); - tellMoveWasLegal( server ); + tellMoveWasLegal( server ); /* sets state */ nextTurn( server, PICK_NEXT ); break; @@ -2125,6 +2125,7 @@ sendBadWordMsgs( ServerCtxt* server ) freeBWI( MPPARM(server->mpool) &server->illegalWordInfo ); } + SETSTATE( server, XWSTATE_INTURN ); } /* sendBadWordMsgs */ #endif @@ -2517,6 +2518,7 @@ nextTurn( ServerCtxt* server, XP_S16 nxtTurn ) XP_Bool moreToDo = XP_FALSE; if ( nxtTurn == PICK_NEXT ) { + XP_ASSERT( server->nv.gameState == XWSTATE_INTURN ); if ( server->nv.gameState != XWSTATE_INTURN ) { XP_LOGFF( "doing nothing; state %s != XWSTATE_INTURN", getStateStr(server->nv.gameState) ); @@ -3863,6 +3865,7 @@ handleIllegalWord( ServerCtxt* server, XWStreamCtxt* incoming ) freeBWI( MPPARM(server->mpool) &bwi ); + SETSTATE( server, XWSTATE_INTURN ); return XP_TRUE; } /* handleIllegalWord */ From c39bede5c1e96e8c01c1b2bae5e498f8e8968c2e Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 09:38:02 -0700 Subject: [PATCH 36/71] up version strings --- xwords4/android/app/build.gradle | 4 +-- .../android/app/src/main/assets/changes.html | 25 +++++++++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index f9fcf68ee..5cf5a144b 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -1,6 +1,6 @@ def INITIAL_CLIENT_VERS = 9 -def VERSION_CODE_BASE = 152 -def VERSION_NAME = '4.4.156' +def VERSION_CODE_BASE = 153 +def VERSION_NAME = '4.4.157' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") def BUILD_INFO_NAME = "build-info.txt" diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index f5c48bf7f..36c70f223 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -13,9 +13,14 @@ -

CrossWords 4.4.156 release

+

CrossWords 4.4.157 release

-

This release fixes a couple of minor UI issues

+

This release fixes a major crash introduced in 155. Thanks + for all the reports and debugging help!

+ +

Note that you may still crash if the app is trying to open + damaged games in the background. If that's happening sit tight and + wait for me to do another release.

Please take @@ -25,8 +30,14 @@

New with this release

    -
  • Fix so bringing app to front keeps the open game open
  • -
  • Include more Norwegian translations
  • +
  • Won't crash or hang when "Disallow phonies" setting is in use
  • +
  • Won't allow committing a malformed word
  • +
  • Fixes some problems with dragging tiles
  • +
  • Fixes occasional problem drawing board-arrow
  • +
  • Fixes game-list item collapse
  • +
  • Fixes so when you open app from Launcher you go back to your open game
  • +
  • Fixes to show invitee in scoreboard as "not here yet"
  • +
  • Fixes crash when chat message is too long

(The full changelog @@ -34,10 +45,8 @@

Next up

Please let me know From 8c50bb1895ae3a9f30a27fba25ddf5e0bf9b1a99 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 13:36:21 -0700 Subject: [PATCH 37/71] Avoid opening games that have crashed. On each open, increment a counter. And if we're able to close without a crash intervening, decrement. Once we're trying to open with a non-0 counter we have a bad game. Open only after warning the user. --- .../java/org/eehouse/android/xw4/DBUtils.java | 31 ++-- .../org/eehouse/android/xw4/DlgDelegate.java | 1 + .../android/xw4/GamesListDelegate.java | 34 ++++- .../org/eehouse/android/xw4/Quarantine.java | 143 ++++++++++++++++++ .../org/eehouse/android/xw4/jni/XwJNI.java | 3 + .../app/src/main/res/values/strings.xml | 5 + 6 files changed, 196 insertions(+), 21 deletions(-) create mode 100644 xwords4/android/app/src/main/java/org/eehouse/android/xw4/Quarantine.java 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 3e4236db5..96eeb533f 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 @@ -1106,24 +1106,27 @@ public class DBUtils { public static byte[] loadGame( Context context, GameLock lock ) { + byte[] result = null; long rowid = lock.getRowid(); Assert.assertTrue( ROWID_NOTFOUND != rowid ); - byte[] result = getCached( rowid ); - if ( null == result ) { - String[] columns = { DBHelper.SNAPSHOT }; - String selection = String.format( ROW_ID_FMT, rowid ); - initDB( context ); - synchronized( s_dbHelper ) { - Cursor cursor = query( TABLE_NAMES.SUM, columns, selection ); - if ( 1 == cursor.getCount() && cursor.moveToFirst() ) { - result = cursor.getBlob( cursor - .getColumnIndex(DBHelper.SNAPSHOT)); - } else { - Log.e( TAG, "loadGame: none for rowid=%d", rowid ); + if ( Quarantine.safeToOpen( rowid ) ) { + result = getCached( rowid ); + if ( null == result ) { + String[] columns = { DBHelper.SNAPSHOT }; + String selection = String.format( ROW_ID_FMT, rowid ); + initDB( context ); + synchronized( s_dbHelper ) { + Cursor cursor = query( TABLE_NAMES.SUM, columns, selection ); + if ( 1 == cursor.getCount() && cursor.moveToFirst() ) { + result = cursor.getBlob( cursor + .getColumnIndex(DBHelper.SNAPSHOT)); + } else { + Log.e( TAG, "loadGame: none for rowid=%d", rowid ); + } + cursor.close(); } - cursor.close(); + setCached( rowid, result ); } - setCached( rowid, result ); } return result; } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java index 8a1ebac0f..0792253a3 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java @@ -53,6 +53,7 @@ public class DlgDelegate { SEND_EMAIL, WRITE_LOG_DB, CLEAR_LOG_DB, + CLEAR_QUARANTINE, // BoardDelegate UNDO_LAST_ACTION, 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 d0746fb50..5e5bda7af 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 @@ -1226,6 +1226,25 @@ public class GamesListDelegate extends ListDelegateBase } ); } + private void openWithChecks( long rowid, GameSummary summary ) + { + if ( ! m_launchedGames.contains( rowid ) ) { + if ( Quarantine.safeToOpen( rowid ) ) { + makeNotAgainBuilder( R.string.not_again_newselect, + R.string.key_notagain_newselect, + Action.OPEN_GAME ) + .setParams( rowid, summary ) + .show(); + } else { + makeConfirmThenBuilder( R.string.unsafe_open_warning, + Action.CLEAR_QUARANTINE ) + .setPosButton( R.string.unsafe_open_disregard ) + .setParams( rowid, summary ) + .show(); + } + } + } + ////////////////////////////////////////////////////////////////////// // SelectableItem interface ////////////////////////////////////////////////////////////////////// @@ -1237,13 +1256,7 @@ public class GamesListDelegate extends ListDelegateBase // an empty room name. if ( clicked instanceof GameListItem ) { long rowid = ((GameListItem)clicked).getRowID(); - if ( ! m_launchedGames.contains( rowid ) ) { - makeNotAgainBuilder( R.string.not_again_newselect, - R.string.key_notagain_newselect, - Action.OPEN_GAME ) - .setParams( rowid, summary ) - .show(); - } + openWithChecks( rowid, summary ); } } @@ -1344,6 +1357,13 @@ public class GamesListDelegate extends ListDelegateBase case OPEN_GAME: doOpenGame( params ); break; + case CLEAR_QUARANTINE: + long rowid = (long)params[0]; + Quarantine.clear( rowid ); + GameSummary summary = (GameSummary)params[0]; + openWithChecks( rowid, summary ); + break; + case CLEAR_SELS: clearSelections(); break; diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Quarantine.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Quarantine.java new file mode 100644 index 000000000..94c1cd620 --- /dev/null +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Quarantine.java @@ -0,0 +1,143 @@ +/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */ +/* + * Copyright 2009-2020 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. + */ + +package org.eehouse.android.xw4; + +import android.content.Context; + +import java.util.HashMap; +import java.util.Map; +import java.io.Serializable; + +public class Quarantine { + private static final String TAG = Quarantine.class.getSimpleName(); + private static final String DATA_KEY = TAG + "/key"; + + public static boolean safeToOpen( long rowid ) + { + int count; + synchronized ( sDataRef ) { + count = get().getFor( rowid ); + } + boolean result = count == 0; // Not too strict? + Log.d( TAG, "safeToOpen(%d) => %b (count=%d)", rowid, result, count ); + return result; + } + + public static void clear( long rowid ) + { + synchronized ( sDataRef ) { + get().clear( rowid ); + store(); + } + } + + public static void recordOpened( long rowid ) + { + synchronized ( sDataRef ) { + get().increment( rowid ); + store(); + Log.d( TAG, "recordOpened(%d): %s", rowid, sDataRef[0].toString() ); + } + } + + public static void recordClosed( long rowid ) + { + synchronized ( sDataRef ) { + get().decrement( rowid ); + store(); + Log.d( TAG, "recordClosed(%d): %s", rowid, sDataRef[0].toString() ); + } + } + + private static class Data implements Serializable { + private HashMap mCounts = new HashMap<>(); + + synchronized void increment( long rowid ) { + if ( ! mCounts.containsKey(rowid) ) { + mCounts.put(rowid, 0); + } + mCounts.put( rowid, mCounts.get(rowid) + 1 ); + } + + synchronized void decrement( long rowid ) + { + Assert.assertTrue( mCounts.containsKey(rowid) ); + mCounts.put( rowid, mCounts.get(rowid) - 1 ); + Assert.assertTrueNR( mCounts.get(rowid) >= 0 ); + } + + synchronized int getFor( long rowid ) + { + int result = mCounts.containsKey(rowid) ? mCounts.get( rowid ) : 0; + return result; + } + + synchronized void clear( long rowid ) + { + mCounts.put( rowid, 0 ); + } + + @Override + synchronized public String toString() + { + StringBuilder sb = new StringBuilder().append("["); + synchronized ( mCounts ) { + for ( long rowid : mCounts.keySet() ) { + int count = mCounts.get(rowid); + sb.append( String.format("{%d: %d}", rowid, count ) ); + } + } + return sb.append("]").toString(); + } + } + + private static Data[] sDataRef = {null}; + + private static void store() + { + synchronized( sDataRef ) { + DBUtils.setSerializableFor( getContext(), DATA_KEY, sDataRef[0] ); + } + } + + private static Data get() + { + Data data; + synchronized ( sDataRef ) { + data = sDataRef[0]; + if ( null == data ) { + data = (Data)DBUtils.getSerializableFor( getContext(), DATA_KEY ); + if ( null == data ) { + data = new Data(); + } else { + Log.d( TAG, "loading existing: %s", data ); + } + sDataRef[0] = data; + } + } + return data; + } + + private static Context getContext() + { + return XWApp.getContext(); + } +} diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java index 1db3931e9..6e46a5338 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java @@ -28,6 +28,7 @@ import org.eehouse.android.xw4.Assert; import org.eehouse.android.xw4.BuildConfig; import org.eehouse.android.xw4.Log; import org.eehouse.android.xw4.NetLaunchInfo; +import org.eehouse.android.xw4.Quarantine; import org.eehouse.android.xw4.Utils; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; @@ -46,6 +47,7 @@ public class XwJNI { m_ptrGame = ptr; m_rowid = rowid; mStack = android.util.Log.getStackTraceString(new Exception()); + Quarantine.recordOpened( rowid ); } public synchronized long ptr() @@ -73,6 +75,7 @@ public class XwJNI { // getClass().getName(), this, m_rowid, m_refCount ); if ( 0 == m_refCount ) { if ( 0 != m_ptrGame ) { + Quarantine.recordClosed( m_rowid ); if ( haveEnv( getJNI().m_ptrGlobals ) ) { game_dispose( this ); // will crash if haveEnv fails } else { diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 61c5746a5..3e232ecde 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2520,6 +2520,11 @@ Message: %1$s. Auto-paused. + This game has caused CrossWords + to crash recently and is likely corrupt. Do you want to open it + anyway? + Open anyway + Debug logs Enable log storage From 5fce6d8e261a826cfca6f668b13b86d5d920b576 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 14:35:36 -0700 Subject: [PATCH 38/71] add delete option for suspect-corrupt files --- .../java/org/eehouse/android/xw4/DlgDelegate.java | 3 ++- .../org/eehouse/android/xw4/GamesListDelegate.java | 13 ++++++++++--- xwords4/android/app/src/main/res/values/strings.xml | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java index 0792253a3..a692573f6 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DlgDelegate.java @@ -53,7 +53,8 @@ public class DlgDelegate { SEND_EMAIL, WRITE_LOG_DB, CLEAR_LOG_DB, - CLEAR_QUARANTINE, + QUARANTINE_CLEAR, + QUARANTINE_DELETE, // BoardDelegate UNDO_LAST_ACTION, 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 5e5bda7af..11caabfe1 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 @@ -1237,8 +1237,10 @@ public class GamesListDelegate extends ListDelegateBase .show(); } else { makeConfirmThenBuilder( R.string.unsafe_open_warning, - Action.CLEAR_QUARANTINE ) + Action.QUARANTINE_CLEAR ) .setPosButton( R.string.unsafe_open_disregard ) + .setActionPair( Action.QUARANTINE_DELETE, + R.string.button_delete ) .setParams( rowid, summary ) .show(); } @@ -1357,13 +1359,18 @@ public class GamesListDelegate extends ListDelegateBase case OPEN_GAME: doOpenGame( params ); break; - case CLEAR_QUARANTINE: + case QUARANTINE_CLEAR: long rowid = (long)params[0]; Quarantine.clear( rowid ); - GameSummary summary = (GameSummary)params[0]; + GameSummary summary = (GameSummary)params[1]; openWithChecks( rowid, summary ); break; + case QUARANTINE_DELETE: + rowid = (long)params[0]; + deleteGames( new long[] {rowid}, true ); + break; + case CLEAR_SELS: clearSelections(); break; diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 3e232ecde..8d3879962 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2521,8 +2521,8 @@ Auto-paused. This game has caused CrossWords - to crash recently and is likely corrupt. Do you want to open it - anyway? + to crash recently and is likely damaged. Opening it might cause + another crash. Do you want to open it anyway? Open anyway Debug logs From b94ba0fd96b1acf56a2397dfb4de1c97848e47d0 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 14:42:47 -0700 Subject: [PATCH 39/71] add debug-only menuitem for marking a game corrupt --- .../java/org/eehouse/android/xw4/GamesListDelegate.java | 6 ++++++ .../android/app/src/main/res/menu/games_list_game_menu.xml | 4 +++- xwords4/android/app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) 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 11caabfe1..011186475 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 @@ -1793,6 +1793,7 @@ public class GamesListDelegate extends ListDelegateBase && (BuildConfig.DEBUG || XWPrefs.getDebugEnabled( m_activity )); } Utils.setItemVisible( menu, R.id.games_game_invites, enable ); + Utils.setItemVisible( menu, R.id.games_game_markbad, enable ); Utils.setItemVisible( menu, R.id.games_game_netstats, isMultiGame ); enable = !m_launchedGames.contains( rowID ); @@ -1989,6 +1990,11 @@ public class GamesListDelegate extends ListDelegateBase makeOkOnlyBuilder( msg ).show(); break; + // DEBUG only + case R.id.games_game_markbad: + Quarantine.recordOpened( selRowIDs[0] ); + break; + default: handled = false; } diff --git a/xwords4/android/app/src/main/res/menu/games_list_game_menu.xml b/xwords4/android/app/src/main/res/menu/games_list_game_menu.xml index 97bf85a39..6bf76aec9 100644 --- a/xwords4/android/app/src/main/res/menu/games_list_game_menu.xml +++ b/xwords4/android/app/src/main/res/menu/games_list_game_menu.xml @@ -38,5 +38,7 @@ - + diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 8d3879962..314b2867a 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2306,6 +2306,8 @@ Processing games Select De-select + + Mark corrupt You are using the default player name \"%1$s\". Would you like to personalize with your own name before you create this game? From d2fcbb5f9dca0fbb31de656b2cd27f3ea4172109 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 14:51:53 -0700 Subject: [PATCH 40/71] update changelog --- xwords4/android/app/src/main/assets/changes.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index 36c70f223..0422489cb 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -18,9 +18,9 @@

This release fixes a major crash introduced in 155. Thanks for all the reports and debugging help!

-

Note that you may still crash if the app is trying to open - damaged games in the background. If that's happening sit tight and - wait for me to do another release.

+

Note that some games may be have been permanently damaged and + may have to be deleted. I'm sorry to have missed this + one!

Please take @@ -38,6 +38,7 @@

  • Fixes so when you open app from Launcher you go back to your open game
  • Fixes to show invitee in scoreboard as "not here yet"
  • Fixes crash when chat message is too long
  • +
  • Detects and flags damaged games that crash the app
  • (The full changelog From 56b199020734917676bafd822d6376991523afe1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 17:06:40 -0700 Subject: [PATCH 41/71] another changelog tweak --- xwords4/android/app/src/main/assets/changes.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index 0422489cb..3a22a3371 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -46,8 +46,7 @@

    Next up

      -
    • Quarantine previously damaged games so app doesn't crash - opening them in background
    • +
    • Fix sometimes allowing same player to move twice

    Please let me know From 170b9a1ed61ff117a1d95f099436460ab927a1d4 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 20:59:15 -0700 Subject: [PATCH 42/71] set turn to -1 while waiting for phonies check When a guest sends a move to the server it's not the next player's turn until the server checks the move and rejects or accepts it. It was possible still to manipulate the board, playing new tiles, and even to attempt to commit them. And if the move came back rejected, the bogus committed one would be that was cleared. Bad. So I'm just setting the turn to -1, which disables board etc., and letting nextTurn() assigne it after the confirmation comes back. --- xwords4/common/server.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 3c31750ba..476b769d3 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -3598,6 +3598,7 @@ finishMove( ServerCtxt* server, TrayTileSet* newTiles, XP_U16 turn ) } else if (isClient && (gi->phoniesAction == PHONIES_DISALLOW) && nTilesMoved > 0 ) { SETSTATE( server, XWSTATE_MOVE_CONFIRM_WAIT ); + setTurn( server, -1 ); #endif } else { nextTurn( server, PICK_NEXT ); @@ -3877,6 +3878,7 @@ handleMoveOk( ServerCtxt* server, XWStreamCtxt* XP_UNUSED(incoming) ) XP_ASSERT( server->nv.gameState == XWSTATE_MOVE_CONFIRM_WAIT ); SETSTATE( server, XWSTATE_INTURN ); + setTurn( server, 0 ); nextTurn( server, PICK_NEXT ); return accepted; @@ -4125,6 +4127,7 @@ server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming ) case XWPROTO_BADWORD_INFO: accepted = handleIllegalWord( server, incoming ); if ( accepted && server->nv.gameState != XWSTATE_GAMEOVER ) { + setTurn( server, 0 ); nextTurn( server, PICK_NEXT ); } break; From ac376c5dd9138d70f24a48b0e53ec86d899ac858 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 18 Apr 2020 20:59:15 -0700 Subject: [PATCH 43/71] set turn to -1 while waiting for phonies check When a guest sends a move to the server it's not the next player's turn until the server checks the move and rejects or accepts it. It was possible still to manipulate the board, playing new tiles, and even to attempt to commit them. And if the move came back rejected, the bogus committed one would be that was cleared. Bad. So I'm just setting the turn to -1, which disables board etc., and letting nextTurn() assigne it after the confirmation comes back. --- xwords4/common/server.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 3c31750ba..476b769d3 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -3598,6 +3598,7 @@ finishMove( ServerCtxt* server, TrayTileSet* newTiles, XP_U16 turn ) } else if (isClient && (gi->phoniesAction == PHONIES_DISALLOW) && nTilesMoved > 0 ) { SETSTATE( server, XWSTATE_MOVE_CONFIRM_WAIT ); + setTurn( server, -1 ); #endif } else { nextTurn( server, PICK_NEXT ); @@ -3877,6 +3878,7 @@ handleMoveOk( ServerCtxt* server, XWStreamCtxt* XP_UNUSED(incoming) ) XP_ASSERT( server->nv.gameState == XWSTATE_MOVE_CONFIRM_WAIT ); SETSTATE( server, XWSTATE_INTURN ); + setTurn( server, 0 ); nextTurn( server, PICK_NEXT ); return accepted; @@ -4125,6 +4127,7 @@ server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming ) case XWPROTO_BADWORD_INFO: accepted = handleIllegalWord( server, incoming ); if ( accepted && server->nv.gameState != XWSTATE_GAMEOVER ) { + setTurn( server, 0 ); nextTurn( server, PICK_NEXT ); } break; From f0444c5c1ecdf6c9f93fc1ea57f4bcec516cb862 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 19 Apr 2020 07:33:12 -0700 Subject: [PATCH 44/71] make player edit dialog less ugly --- .../app/src/main/res/layout/player_edit.xml | 90 ++++++++++--------- .../app/src/main/res/values/strings.xml | 6 +- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/xwords4/android/app/src/main/res/layout/player_edit.xml b/xwords4/android/app/src/main/res/layout/player_edit.xml index 84679f3a8..cc025af57 100644 --- a/xwords4/android/app/src/main/res/layout/player_edit.xml +++ b/xwords4/android/app/src/main/res/layout/player_edit.xml @@ -22,7 +22,9 @@ + android:orientation="vertical" + android:padding="8dp" + > - - - + + - - - + + + + + + + + - + android:orientation="horizontal" + > - Wordlist (in %1$s) + Wordlist (%1$s) @@ -425,13 +425,13 @@ Robot player - Password + Password: + Block phonies + + + @string/phonies_ignore + @string/phonies_warn + @string/phonies_disallow + @string/phonies_block + + + diff --git a/xwords4/android/app/src/xw4dNoSMS/res/values/tmp_for_phony.xml b/xwords4/android/app/src/xw4dNoSMS/res/values/tmp_for_phony.xml new file mode 120000 index 000000000..6bd0cd04b --- /dev/null +++ b/xwords4/android/app/src/xw4dNoSMS/res/values/tmp_for_phony.xml @@ -0,0 +1 @@ +../../../xw4d/res/values/tmp_for_phony.xml \ No newline at end of file diff --git a/xwords4/android/jni/utilwrapper.c b/xwords4/android/jni/utilwrapper.c index e5ded2a1c..e472a44ff 100644 --- a/xwords4/android/jni/utilwrapper.c +++ b/xwords4/android/jni/utilwrapper.c @@ -706,6 +706,14 @@ and_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) UTIL_CBK_TAIL(); } +static void +and_util_informWordBlocked( XW_UtilCtxt* uc ) +{ + UTIL_CBK_HEADER( "informWordBlocked", "()V" ); + (*env)->CallVoidMethod( env, util->jutil, mid ); + UTIL_CBK_TAIL(); +} + #ifdef XWFEATURE_DEVID static const XP_UCHAR* and_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ ) @@ -908,6 +916,7 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi, #endif SET_PROC(getDevUtilCtxt); + SET_PROC(informWordBlocked); #undef SET_PROC assertTableFull( vtable, sizeof(*vtable), "util" ); diff --git a/xwords4/common/board.c b/xwords4/common/board.c index 4e0f067b9..dc039f1dc 100644 --- a/xwords4/common/board.c +++ b/xwords4/common/board.c @@ -1040,10 +1040,10 @@ typedef struct _BadWordList { } BadWordList; static void -saveBadWords( const WNParams* wnp ) +saveBadWords( const WNParams* wnp, void* closure ) { if ( !wnp->isLegal ) { - BadWordList* bwlp = (BadWordList*)wnp->closure; + BadWordList* bwlp = (BadWordList*)closure; bwlp->bwi.words[bwlp->bwi.nWords] = &bwlp->buf[bwlp->index]; XP_STRCAT( &bwlp->buf[bwlp->index], wnp->word ); bwlp->index += XP_STRLEN(wnp->word) + 1; diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index 7af0ee602..8ba981f5c 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -140,8 +140,12 @@ typedef XP_U8 DeviceRole; enum { PHONIES_IGNORE, + /* You can commit a phony after viewing a warning */ PHONIES_WARN, - PHONIES_DISALLOW + /* You can commit a phony, but you'll lose your turn */ + PHONIES_DISALLOW, + /* a phony is an illegal move, like tiles out-of-line */ + PHONIES_BLOCK, }; typedef XP_U8 XWPhoniesChoice; diff --git a/xwords4/common/engine.c b/xwords4/common/engine.c index de909d7f3..2950f4cda 100644 --- a/xwords4/common/engine.c +++ b/xwords4/common/engine.c @@ -1104,9 +1104,9 @@ considerMove( EngineCtxt* engine, Tile* tiles, XP_S16 tileLength, } /* considerMove */ static void -countWords( const WNParams* wnp ) +countWords( const WNParams* wnp, void* closure ) { - XP_U16* wcp = (XP_U16*)wnp->closure; + XP_U16* wcp = (XP_U16*)closure; if ( wnp->isLegal ) { ++*wcp; } diff --git a/xwords4/common/model.c b/xwords4/common/model.c index 50ad3ffeb..9fe4b4feb 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -77,7 +77,7 @@ static void loadPlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream, static void writePlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream, const PlayerCtxt* pc ); static XP_U16 model_getRecentPassCount( ModelCtxt* model ); -static void recordWord( const WNParams* wnp ); +static void recordWord( const WNParams* wnp, void *closure ); #ifdef DEBUG typedef struct _DiffTurnState { XP_S16 lastPlayerNum; @@ -2459,9 +2459,9 @@ typedef struct _FirstWordData { } FirstWordData; static void -getFirstWord( const WNParams* wnp ) +getFirstWord( const WNParams* wnp, void* closure ) { - FirstWordData* data = (FirstWordData*)wnp->closure; + FirstWordData* data = (FirstWordData*)closure; if ( '\0' == data->word[0] && '\0' != wnp->word[0] ) { XP_STRCAT( data->word, wnp->word ); } @@ -2548,9 +2548,9 @@ appendWithCR( XWStreamCtxt* stream, const XP_UCHAR* word, XP_U16* counter ) } static void -recordWord( const WNParams* wnp ) +recordWord( const WNParams* wnp, void* closure ) { - RecordWordsInfo* info = (RecordWordsInfo*)wnp->closure; + RecordWordsInfo* info = (RecordWordsInfo*)closure; appendWithCR( info->stream, wnp->word, &info->nWords ); } @@ -2572,9 +2572,9 @@ typedef struct _ListWordsThroughInfo { } ListWordsThroughInfo; static void -listWordsThrough( const WNParams* wnp ) +listWordsThrough( const WNParams* wnp, void* closure ) { - ListWordsThroughInfo* info = (ListWordsThroughInfo*)wnp->closure; + ListWordsThroughInfo* info = (ListWordsThroughInfo*)closure; const MoveInfo* movei = wnp->movei; XP_Bool contained = XP_FALSE; diff --git a/xwords4/common/model.h b/xwords4/common/model.h index 20074c963..c25d07a83 100644 --- a/xwords4/common/model.h +++ b/xwords4/common/model.h @@ -291,10 +291,9 @@ typedef struct _WNParams { XP_U16 start; XP_U16 end; #endif - void* closure; } WNParams; -typedef void (*WordNotifierProc)( const WNParams* wnp ); +typedef void (*WordNotifierProc)( const WNParams* wnp, void* closure ); typedef struct WordNotifierInfo { WordNotifierProc proc; void* closure; diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c index 10cdc293c..150594482 100644 --- a/xwords4/common/mscore.c +++ b/xwords4/common/mscore.c @@ -217,6 +217,24 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP, } } /* model_figureFinalScores */ +typedef struct _BlockCheckState { + WordNotifierInfo* chainNI; + XP_Bool allLegal; +} BlockCheckState; + +static void +blockCheck( const WNParams* wnp, void* closure ) +{ + BlockCheckState* bcs = (BlockCheckState*)closure; + + if ( !!bcs->chainNI ) { + (bcs->chainNI->proc)( wnp, bcs->chainNI->closure ); + } + if ( !wnp->isLegal ) { + bcs->allLegal = XP_FALSE; + } +} + /* checkScoreMove. * Negative score means illegal. */ @@ -238,17 +256,38 @@ checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine, formatSummary( stream, model, 0 ); } - } else if ( tilesInLine( model, turn, &isHorizontal ) ) { + } else if ( !tilesInLine( model, turn, &isHorizontal ) ) { + if ( !silent ) { /* tiles out of line */ + util_userError( model->vol.util, ERR_TILES_NOT_IN_LINE ); + } + } else { MoveInfo moveInfo; - normalizeMoves( model, turn, isHorizontal, &moveInfo ); if ( isLegalMove( model, &moveInfo, silent ) ) { - score = figureMoveScore( model, turn, &moveInfo, engine, stream, - notifyInfo ); + /* If I'm testing for blocking, I need to chain my test onto any + existing WordNotifierInfo. blockCheck() does that. */ + XP_Bool checkDict = PHONIES_BLOCK == model->vol.gi->phoniesAction; + WordNotifierInfo blockWNI; + BlockCheckState bcs; + if ( checkDict ) { + bcs.allLegal = XP_TRUE; + bcs.chainNI = notifyInfo; + blockWNI.proc = blockCheck; + blockWNI.closure = &bcs; + notifyInfo = &blockWNI; + } + + XP_S16 tmpScore = figureMoveScore( model, turn, &moveInfo, + engine, stream, notifyInfo ); + if ( checkDict && !bcs.allLegal ) { + if ( !silent ) { + util_informWordBlocked( model->vol.util ); + } + } else { + score = tmpScore; + } } - } else if ( !silent ) { /* tiles out of line */ - util_userError( model->vol.util, ERR_TILES_NOT_IN_LINE ); } return score; } /* checkScoreMove */ @@ -700,9 +739,8 @@ scoreWord( const ModelCtxt* model, XP_U16 turn, #ifdef XWFEATURE_BOARDWORDS .movei = movei, .start = start, .end = end, #endif - .closure = notifyInfo->closure, }; - (void)(*notifyInfo->proc)( &wnp ); + (void)(*notifyInfo->proc)( &wnp, notifyInfo->closure ); } if ( !!stream ) { diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 4541920a4..d4963b6bb 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -2613,10 +2613,10 @@ server_setGameOverListener( ServerCtxt* server, GameOverListener gol, } /* server_setGameOverListener */ static void -storeBadWords( const WNParams* wnp ) +storeBadWords( const WNParams* wnp, void* closure ) { if ( !wnp->isLegal ) { - ServerCtxt* server = (ServerCtxt*)wnp->closure; + ServerCtxt* server = (ServerCtxt*)closure; const XP_UCHAR* name = dict_getShortName( wnp->dict ); XP_LOGF( "storeBadWords called with \"%s\" (name=%s)", wnp->word, name ); diff --git a/xwords4/common/util.h b/xwords4/common/util.h index 0ba262005..f7905e292 100644 --- a/xwords4/common/util.h +++ b/xwords4/common/util.h @@ -173,6 +173,8 @@ typedef struct UtilVtable { void (*m_util_setIsServer)(XW_UtilCtxt* uc, XP_Bool isServer ); #endif + void (*m_util_informWordBlocked)( XW_UtilCtxt* uc ); + #ifdef XWFEATURE_SEARCHLIMIT XP_Bool (*m_util_getTraySearchLimits)(XW_UtilCtxt* uc, XP_U16* min, XP_U16* max ); @@ -308,6 +310,8 @@ struct XW_UtilCtxt { # define util_addrChange( uc, addro, addrn ) #endif +#define util_informWordBlocked(uc) (uc)->vtable->m_util_informWordBlocked( uc ) + #ifdef XWFEATURE_SEARCHLIMIT #define util_getTraySearchLimits(uc,min,max) \ (uc)->vtable->m_util_getTraySearchLimits((uc), (min), (max)) diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c index 3e06bda4f..857809753 100644 --- a/xwords4/linux/cursesboard.c +++ b/xwords4/linux/cursesboard.c @@ -1049,6 +1049,11 @@ curses_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) } #endif +static void +curses_util_informWordBlocked( XW_UtilCtxt* XP_UNUSED(uc) ) +{ + LOG_FUNC(); +} #ifndef XWFEATURE_STANDALONE_ONLY static XWStreamCtxt* @@ -1120,6 +1125,7 @@ setupCursesUtilCallbacks( CursesBoardGlobals* bGlobals, XW_UtilCtxt* util ) #ifdef XWFEATURE_BOARDWORDS SET_PROC(cellSquareHeld); #endif + SET_PROC(informWordBlocked); #ifdef XWFEATURE_SEARCHLIMIT SET_PROC(getTraySearchLimits); diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index d7c6677df..5accd757b 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1997,6 +1997,13 @@ gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) } #endif +static void +gtk_util_informWordBlocked( XW_UtilCtxt* uc ) +{ + GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; + gtkUserError( globals, "Word blocked by phonies setting" ); +} + static void gtk_util_userError( XW_UtilCtxt* uc, UtilErrID id ) { @@ -2229,7 +2236,7 @@ setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util ) #ifdef XWFEATURE_BOARDWORDS SET_PROC(cellSquareHeld); #endif - + SET_PROC(informWordBlocked); #undef SET_PROC assertTableFull( util->vtable, sizeof(*util->vtable), "gtk util" ); diff --git a/xwords4/linux/gtknewgame.c b/xwords4/linux/gtknewgame.c index 5506b3946..b6ce195f0 100644 --- a/xwords4/linux/gtknewgame.c +++ b/xwords4/linux/gtknewgame.c @@ -218,7 +218,7 @@ addPhoniesCombo( GtkNewGameState* state, GtkWidget* parent ) FALSE, TRUE, 0 ); GtkWidget* phoniesCombo = gtk_combo_box_text_new(); - const char* ptxts[] = { "IGNORE", "WARN", "DISALLOW" }; + const char* ptxts[] = { "IGNORE", "WARN", "LOSE TURN", "BLOCK" }; for ( int ii = 0; ii < VSIZE(ptxts); ++ii ) { gtk_combo_box_text_append_text( GTK_COMBO_BOX_TEXT(phoniesCombo), @@ -227,6 +227,8 @@ addPhoniesCombo( GtkNewGameState* state, GtkWidget* parent ) g_signal_connect( phoniesCombo, "changed", G_CALLBACK(phonies_combo_changed), state ); + XWPhoniesChoice startChoice = state->globals->cGlobals.params->pgi.phoniesAction; + gtk_combo_box_set_active( GTK_COMBO_BOX(phoniesCombo), startChoice ); gtk_widget_show( phoniesCombo ); gtk_box_pack_start( GTK_BOX(hbox), phoniesCombo, FALSE, TRUE, 0 ); gtk_widget_show( hbox ); diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index aaf34f9f5..fadbbee2f 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -1018,7 +1018,7 @@ static CmdInfoRec CmdInfoRecs[] = { ,{ CMD_ADVERTISEROOM, false, "make-public", "make room public on relay" } ,{ CMD_JOINADVERTISED, false, "join-public", "look for a public room" } ,{ CMD_PHONIES, true, "phonies", - "ignore (0, default), warn (1) or lose turn (2)" } + "ignore (0, default), warn (1), lose turn (2), or refuse to commit (3)" } ,{ CMD_BONUSFILE, true, "bonus-file", "provides bonus info: . + * ^ and ! are legal" } ,{ CMD_INVITEE_RELAYID, true, "invitee-relayid", "relayID to send any invitation to" } @@ -2823,8 +2823,11 @@ main( int argc, char** argv ) case 2: mainParams.pgi.phoniesAction = PHONIES_DISALLOW; break; + case 3: + mainParams.pgi.phoniesAction = PHONIES_BLOCK; + break; default: - usage( argv[0], "phonies takes 0 or 1 or 2" ); + usage( argv[0], "phonies takes 0 or 1 or 2 or 3" ); } break; case CMD_BONUSFILE: From 10f509ea1f154e1a1e89c0884b125200aabfb014 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sun, 19 Apr 2020 22:27:14 -0700 Subject: [PATCH 48/71] pass and display the blocked word --- .../java/org/eehouse/android/xw4/BoardDelegate.java | 6 +++--- .../java/org/eehouse/android/xw4/jni/UtilCtxt.java | 2 +- .../org/eehouse/android/xw4/jni/UtilCtxtImpl.java | 2 +- xwords4/android/jni/utilwrapper.c | 8 +++++--- xwords4/common/mscore.c | 12 ++++++------ xwords4/common/util.h | 4 ++-- xwords4/linux/cursesboard.c | 5 +++-- xwords4/linux/gtkboard.c | 6 ++++-- 8 files changed, 25 insertions(+), 20 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 089b7ca1d..1c6f2bccc 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 @@ -1862,13 +1862,13 @@ public class BoardDelegate extends DelegateBase } @Override - public void informWordBlocked() + public void informWordBlocked( final String word ) { runOnUiThread( new Runnable() { @Override public void run() { - makeOkOnlyBuilder( "Word blocked" ) - .show(); + String msg = String.format( "Word \"%s\" blocked", word ); + makeOkOnlyBuilder( msg ).show(); } } ); } diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java index 0febcc2b5..fe303148d 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -62,7 +62,7 @@ public interface UtilCtxt { void remSelected(); void timerSelected( boolean inDuplicateMode, boolean canPause ); void setIsServer( boolean isServer ); - void informWordBlocked(); + void informWordBlocked( String word ); void bonusSquareHeld( int bonus ); void playerScoreHeld( int player ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java index 1d59b347f..d9a42154e 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java @@ -107,7 +107,7 @@ public class UtilCtxtImpl implements UtilCtxt { } @Override - public void informWordBlocked() + public void informWordBlocked( String word ) { subclassOverride( "informWordBlocked" ); } diff --git a/xwords4/android/jni/utilwrapper.c b/xwords4/android/jni/utilwrapper.c index e472a44ff..7d63d3df2 100644 --- a/xwords4/android/jni/utilwrapper.c +++ b/xwords4/android/jni/utilwrapper.c @@ -707,10 +707,12 @@ and_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) } static void -and_util_informWordBlocked( XW_UtilCtxt* uc ) +and_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word ) { - UTIL_CBK_HEADER( "informWordBlocked", "()V" ); - (*env)->CallVoidMethod( env, util->jutil, mid ); + UTIL_CBK_HEADER( "informWordBlocked", "(Ljava/lang/String;)V" ); + jstring jword = (*env)->NewStringUTF( env, word ); + (*env)->CallVoidMethod( env, util->jutil, mid, jword ); + deleteLocalRef( env, jword ); UTIL_CBK_TAIL(); } diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c index 150594482..88191ece1 100644 --- a/xwords4/common/mscore.c +++ b/xwords4/common/mscore.c @@ -219,7 +219,7 @@ model_figureFinalScores( ModelCtxt* model, ScoresArray* finalScoresP, typedef struct _BlockCheckState { WordNotifierInfo* chainNI; - XP_Bool allLegal; + XP_UCHAR word[32]; } BlockCheckState; static void @@ -230,8 +230,8 @@ blockCheck( const WNParams* wnp, void* closure ) if ( !!bcs->chainNI ) { (bcs->chainNI->proc)( wnp, bcs->chainNI->closure ); } - if ( !wnp->isLegal ) { - bcs->allLegal = XP_FALSE; + if ( !wnp->isLegal && '\0' == bcs->word[0] ) { + XP_STRCAT( bcs->word, wnp->word ); } } @@ -271,7 +271,7 @@ checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine, WordNotifierInfo blockWNI; BlockCheckState bcs; if ( checkDict ) { - bcs.allLegal = XP_TRUE; + XP_MEMSET( &bcs, 0, sizeof(bcs) ); bcs.chainNI = notifyInfo; blockWNI.proc = blockCheck; blockWNI.closure = &bcs; @@ -280,9 +280,9 @@ checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine, XP_S16 tmpScore = figureMoveScore( model, turn, &moveInfo, engine, stream, notifyInfo ); - if ( checkDict && !bcs.allLegal ) { + if ( checkDict && '\0' != bcs.word[0] ) { if ( !silent ) { - util_informWordBlocked( model->vol.util ); + util_informWordBlocked( model->vol.util, bcs.word ); } } else { score = tmpScore; diff --git a/xwords4/common/util.h b/xwords4/common/util.h index f7905e292..2446164bf 100644 --- a/xwords4/common/util.h +++ b/xwords4/common/util.h @@ -173,7 +173,7 @@ typedef struct UtilVtable { void (*m_util_setIsServer)(XW_UtilCtxt* uc, XP_Bool isServer ); #endif - void (*m_util_informWordBlocked)( XW_UtilCtxt* uc ); + void (*m_util_informWordBlocked)( XW_UtilCtxt* uc, const XP_UCHAR* word ); #ifdef XWFEATURE_SEARCHLIMIT XP_Bool (*m_util_getTraySearchLimits)(XW_UtilCtxt* uc, @@ -310,7 +310,7 @@ struct XW_UtilCtxt { # define util_addrChange( uc, addro, addrn ) #endif -#define util_informWordBlocked(uc) (uc)->vtable->m_util_informWordBlocked( uc ) +#define util_informWordBlocked(uc, w) (uc)->vtable->m_util_informWordBlocked( (uc), (w) ) #ifdef XWFEATURE_SEARCHLIMIT #define util_getTraySearchLimits(uc,min,max) \ diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c index 857809753..8c46d3412 100644 --- a/xwords4/linux/cursesboard.c +++ b/xwords4/linux/cursesboard.c @@ -1050,9 +1050,10 @@ curses_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) #endif static void -curses_util_informWordBlocked( XW_UtilCtxt* XP_UNUSED(uc) ) +curses_util_informWordBlocked( XW_UtilCtxt* XP_UNUSED(uc), + const XP_UCHAR* XP_UNUSED_DBG(word) ) { - LOG_FUNC(); + XP_LOGFF( "(word=%s)", word ); } #ifndef XWFEATURE_STANDALONE_ONLY diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 5accd757b..ab6f6138b 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1998,10 +1998,12 @@ gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) #endif static void -gtk_util_informWordBlocked( XW_UtilCtxt* uc ) +gtk_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; - gtkUserError( globals, "Word blocked by phonies setting" ); + gchar* msg = g_strdup_printf( "Word \"%s\" blocked by phonies setting", word ); + gtkUserError( globals, msg ); + g_free( msg ); } static void From 54efffa6354bc4d1056541aa6ee77b55d3b2f1cb Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 20 Apr 2020 09:34:14 -0700 Subject: [PATCH 49/71] pass the wordlist name too --- .../main/java/org/eehouse/android/xw4/BoardDelegate.java | 6 ++++-- .../main/java/org/eehouse/android/xw4/jni/UtilCtxt.java | 2 +- .../java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java | 2 +- xwords4/android/app/src/main/res/values/strings.xml | 2 ++ xwords4/android/jni/utilwrapper.c | 9 +++++---- xwords4/common/mscore.c | 3 ++- xwords4/common/util.h | 6 ++++-- xwords4/linux/cursesboard.c | 5 +++-- xwords4/linux/gtkboard.c | 4 ++-- 9 files changed, 24 insertions(+), 15 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 1c6f2bccc..f518bf871 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 @@ -1862,12 +1862,14 @@ public class BoardDelegate extends DelegateBase } @Override - public void informWordBlocked( final String word ) + public void informWordBlocked( final String word, final String dict ) { runOnUiThread( new Runnable() { @Override public void run() { - String msg = String.format( "Word \"%s\" blocked", word ); + String msg = LocUtils + .getString( m_activity, R.string.word_blocked_by_phony, + word, dict ); makeOkOnlyBuilder( msg ).show(); } } ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java index fe303148d..49b600aca 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -62,7 +62,7 @@ public interface UtilCtxt { void remSelected(); void timerSelected( boolean inDuplicateMode, boolean canPause ); void setIsServer( boolean isServer ); - void informWordBlocked( String word ); + void informWordBlocked( String word, String dict ); void bonusSquareHeld( int bonus ); void playerScoreHeld( int player ); diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java index d9a42154e..cfd32dbbf 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java @@ -107,7 +107,7 @@ public class UtilCtxtImpl implements UtilCtxt { } @Override - public void informWordBlocked( String word ) + public void informWordBlocked( String word, String dict ) { subclassOverride( "informWordBlocked" ); } diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index f97aa3ac7..3f969ab82 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2527,6 +2527,8 @@ another crash. Do you want to open it anyway? Open anyway + Word %1$s not found in wordlist %2$s + Debug logs Enable log storage diff --git a/xwords4/android/jni/utilwrapper.c b/xwords4/android/jni/utilwrapper.c index 7d63d3df2..44af18e23 100644 --- a/xwords4/android/jni/utilwrapper.c +++ b/xwords4/android/jni/utilwrapper.c @@ -707,12 +707,13 @@ and_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer ) } static void -and_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word ) +and_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word, const XP_UCHAR* dict ) { - UTIL_CBK_HEADER( "informWordBlocked", "(Ljava/lang/String;)V" ); + UTIL_CBK_HEADER( "informWordBlocked", "(Ljava/lang/String;Ljava/lang/String;)V" ); jstring jword = (*env)->NewStringUTF( env, word ); - (*env)->CallVoidMethod( env, util->jutil, mid, jword ); - deleteLocalRef( env, jword ); + jstring jdict = (*env)->NewStringUTF( env, dict ); + (*env)->CallVoidMethod( env, util->jutil, mid, jword, jdict ); + deleteLocalRefs( env, jword, DELETE_NO_REF ); UTIL_CBK_TAIL(); } diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c index 88191ece1..931a1cec4 100644 --- a/xwords4/common/mscore.c +++ b/xwords4/common/mscore.c @@ -282,7 +282,8 @@ checkScoreMove( ModelCtxt* model, XP_S16 turn, EngineCtxt* engine, engine, stream, notifyInfo ); if ( checkDict && '\0' != bcs.word[0] ) { if ( !silent ) { - util_informWordBlocked( model->vol.util, bcs.word ); + DictionaryCtxt* dict = model_getPlayerDict( model, turn ); + util_informWordBlocked( model->vol.util, bcs.word, dict_getName( dict ) ); } } else { score = tmpScore; diff --git a/xwords4/common/util.h b/xwords4/common/util.h index 2446164bf..b411018be 100644 --- a/xwords4/common/util.h +++ b/xwords4/common/util.h @@ -173,7 +173,8 @@ typedef struct UtilVtable { void (*m_util_setIsServer)(XW_UtilCtxt* uc, XP_Bool isServer ); #endif - void (*m_util_informWordBlocked)( XW_UtilCtxt* uc, const XP_UCHAR* word ); + void (*m_util_informWordBlocked)( XW_UtilCtxt* uc, const XP_UCHAR* word, + const XP_UCHAR* dictName ); #ifdef XWFEATURE_SEARCHLIMIT XP_Bool (*m_util_getTraySearchLimits)(XW_UtilCtxt* uc, @@ -310,7 +311,8 @@ struct XW_UtilCtxt { # define util_addrChange( uc, addro, addrn ) #endif -#define util_informWordBlocked(uc, w) (uc)->vtable->m_util_informWordBlocked( (uc), (w) ) +#define util_informWordBlocked(uc, w, d) \ + (uc)->vtable->m_util_informWordBlocked( (uc), (w), (d) ) #ifdef XWFEATURE_SEARCHLIMIT #define util_getTraySearchLimits(uc,min,max) \ diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c index 8c46d3412..970dfabde 100644 --- a/xwords4/linux/cursesboard.c +++ b/xwords4/linux/cursesboard.c @@ -1051,9 +1051,10 @@ curses_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) static void curses_util_informWordBlocked( XW_UtilCtxt* XP_UNUSED(uc), - const XP_UCHAR* XP_UNUSED_DBG(word) ) + const XP_UCHAR* XP_UNUSED_DBG(word), + const XP_UCHAR* XP_UNUSED_DBG(dict) ) { - XP_LOGFF( "(word=%s)", word ); + XP_LOGFF( "(word=%s, dict=%s)", word, dict ); } #ifndef XWFEATURE_STANDALONE_ONLY diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index ab6f6138b..472e9694c 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1998,10 +1998,10 @@ gtk_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words ) #endif static void -gtk_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word ) +gtk_util_informWordBlocked( XW_UtilCtxt* uc, const XP_UCHAR* word, const XP_UCHAR* dict ) { GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure; - gchar* msg = g_strdup_printf( "Word \"%s\" blocked by phonies setting", word ); + gchar* msg = g_strdup_printf( "Word \"%s\" not found in %s", word, dict ); gtkUserError( globals, msg ); g_free( msg ); } From fe9eb9ae7c8fbb689499bdc9359ff53ccfcfb4df Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 20 Apr 2020 12:19:04 -0700 Subject: [PATCH 50/71] test for null back from refusal to load quarantined game --- .../java/org/eehouse/android/xw4/Assert.java | 3 +- .../org/eehouse/android/xw4/GameUtils.java | 90 +++++++++++-------- .../org/eehouse/android/xw4/jni/XwJNI.java | 1 + 3 files changed, 58 insertions(+), 36 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Assert.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Assert.java index 20e32d4f3..9fed16ba8 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Assert.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Assert.java @@ -32,7 +32,8 @@ public class Assert { assertTrue(! val); } - public static void assertTrue(boolean val) { + public static void assertTrue( boolean val ) + { if (! val) { Log.e( TAG, "firing assert!" ); DbgUtils.printStack( TAG ); 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 d59f698e7..aaee1ece1 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 @@ -386,6 +386,16 @@ public class GameUtils { return loadMakeGame( context, gi, util, tp, stream, lock.getRowid() ); } + private static CurGameInfo giFromStream( Context context, byte[] stream ) + { + CurGameInfo gi = null; + if ( null != stream ) { + gi = new CurGameInfo( context ); + XwJNI.gi_from_stream( gi, stream ); + } + return gi; + } + private static GamePtr loadMakeGame( Context context, CurGameInfo gi, UtilCtxt util, TransportProcs tp, byte[] stream, long rowid ) @@ -832,22 +842,28 @@ public class GameUtils { public static String[] dictNames( Context context, GameLock lock ) { + String[] result = null; byte[] stream = savedGame( context, lock ); - CurGameInfo gi = new CurGameInfo( context ); - XwJNI.gi_from_stream( gi, stream ); - return gi.dictNames(); + CurGameInfo gi = giFromStream( context, stream ); + if ( null != gi ) { + result = gi.dictNames(); + } + return result; } public static String[] dictNames( Context context, long rowid, int[] missingLang ) { + String[] result = null; byte[] stream = savedGame( context, rowid ); - CurGameInfo gi = new CurGameInfo( context ); - XwJNI.gi_from_stream( gi, stream ); - if ( null != missingLang ) { - missingLang[0] = gi.dictLang; + CurGameInfo gi = giFromStream( context, stream ); + if ( null != gi ) { + if ( null != missingLang ) { + missingLang[0] = gi.dictLang; + } + result = gi.dictNames(); } - return gi.dictNames(); + return result; } public static String[] dictNames( Context context, long rowid ) @@ -863,7 +879,7 @@ public class GameUtils { public static boolean gameDictsHere( Context context, GameLock lock ) { String[] gameDicts = dictNames( context, lock ); - return gameDictsHere( context, null, gameDicts ); + return null != gameDicts && gameDictsHere( context, null, gameDicts ); } // Return true if all dicts present. Return list of those that @@ -873,7 +889,8 @@ public class GameUtils { int[] missingLang ) { String[] gameDicts = dictNames( context, rowid, missingLang ); - return gameDictsHere( context, missingNames, gameDicts ); + return null != gameDicts + && gameDictsHere( context, missingNames, gameDicts ); } public static boolean gameDictsHere( Context context, @@ -1089,34 +1106,37 @@ public class GameUtils { boolean success; try ( GameLock lock = GameLock.lock( rowid, 300 ) ) { success = null != lock; - if ( success ) { - byte[] stream = savedGame( context, lock ); - CurGameInfo gi = new CurGameInfo( context ); - XwJNI.gi_from_stream( gi, stream ); - - // first time required so dictNames() will work - gi.replaceDicts( context, newDict ); - - String[] dictNames = gi.dictNames(); - DictUtils.DictPairs pairs = DictUtils.openDicts( context, - dictNames ); - - try ( GamePtr gamePtr = - XwJNI.initFromStream( rowid, stream, gi, dictNames, - pairs.m_bytes, pairs.m_paths, - gi.langName( context ), null, - null, CommonPrefs.get( context ), null ) ) { - // second time required as game_makeFromStream can overwrite - gi.replaceDicts( context, newDict ); - - saveGame( context, gamePtr, gi, lock, false ); - - summarize( context, lock, gamePtr, gi ); - } - } else { + if ( !success ) { DbgUtils.toastNoLock( TAG, context, rowid, "replaceDicts(): rowid %d", rowid ); + } else { + byte[] stream = savedGame( context, lock ); + CurGameInfo gi = giFromStream( context, stream ); + success = null != gi; + if ( !success ) { + Log.e( TAG, "replaceDicts(): unable to load rowid %d", rowid ); + } else { + // first time required so dictNames() will work + gi.replaceDicts( context, newDict ); + + String[] dictNames = gi.dictNames(); + DictUtils.DictPairs pairs = DictUtils.openDicts( context, + dictNames ); + + try ( GamePtr gamePtr = + XwJNI.initFromStream( rowid, stream, gi, dictNames, + pairs.m_bytes, pairs.m_paths, + gi.langName( context ), null, + null, CommonPrefs.get( context ), null ) ) { + // second time required as game_makeFromStream can overwrite + gi.replaceDicts( context, newDict ); + + saveGame( context, gamePtr, gi, lock, false ); + + summarize( context, lock, gamePtr, gi ); + } + } } } return success; diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java index 6e46a5338..921d320db 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java @@ -168,6 +168,7 @@ public class XwJNI { public static void gi_from_stream( CurGameInfo gi, byte[] stream ) { + Assert.assertNotNull( stream ); gi_from_stream( getJNI().m_ptrGlobals, gi, stream ); // called here } From 055eceffbaf374e48b7c6a25566bfa2ffb45e504 Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 20 Apr 2020 14:37:39 -0700 Subject: [PATCH 51/71] got back to singleTask Having intents launch second instances of GameList on top of open games is sucking. --- xwords4/android/app/src/main/AndroidManifest.xml | 2 +- .../app/src/main/java/org/eehouse/android/xw4/Quarantine.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/xwords4/android/app/src/main/AndroidManifest.xml b/xwords4/android/app/src/main/AndroidManifest.xml index bedb9bf1f..b53bde654 100644 --- a/xwords4/android/app/src/main/AndroidManifest.xml +++ b/xwords4/android/app/src/main/AndroidManifest.xml @@ -55,7 +55,7 @@ diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Quarantine.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Quarantine.java index 94c1cd620..46a3fa039 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Quarantine.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Quarantine.java @@ -37,7 +37,9 @@ public class Quarantine { count = get().getFor( rowid ); } boolean result = count == 0; // Not too strict? - Log.d( TAG, "safeToOpen(%d) => %b (count=%d)", rowid, result, count ); + if ( !result ) { + Log.d( TAG, "safeToOpen(%d) => %b (count=%d)", rowid, result, count ); + } return result; } From 5a28a7fc278fa7b63558147545c64508503bf4f0 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 21 Apr 2020 21:18:43 -0700 Subject: [PATCH 52/71] fix gtk games connecting to relay/each other I broke gtk back in February making curses changes --- xwords4/common/game.c | 42 ++++++++++++++++++++++++--------------- xwords4/common/gameinfo.h | 6 ++++-- xwords4/common/server.c | 5 +++-- xwords4/linux/gtkboard.c | 3 ++- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/xwords4/common/game.c b/xwords4/common/game.c index 03a38a11f..4d915681f 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -549,13 +549,22 @@ gi_setNPlayers( CurGameInfo* gi, XP_U16 nTotal, XP_U16 nHere ) } if ( nHere != curLocal ) { - /* This will happen when a device has more than on player. Not sure I + /* This will happen when a device has more than one player. Not sure I handle that correctly, but don't assert for now. */ XP_LOGFF( "nHere: %d; curLocal: %d; a problem?", nHere, curLocal ); - /* for ( XP_U16 ii = 0; ii < nTotal; ++ii ) { */ - /* gi->players[ii].isLocal = ii < nHere; */ - /* } */ + for ( XP_U16 ii = 0; ii < nTotal; ++ii ) { + if ( !gi->players[ii].isLocal ) { + gi->players[ii].isLocal = XP_TRUE; + XP_LOGFF( "making player #%d local when wasn't before", ii ); + ++curLocal; + XP_ASSERT( curLocal <= nHere ); + if ( curLocal == nHere ) { + break; + } + } + } } + LOGGI( gi, __func__ ); } XP_U16 @@ -762,20 +771,21 @@ player_timePenalty( CurGameInfo* gi, XP_U16 playerNum ) #ifdef DEBUG void -game_logGI( const CurGameInfo* gi, const char* msg ) +game_logGI( const CurGameInfo* gi, const char* msg, const char* func, int line ) { - XP_LOGFF( "msg: %s", msg ); - - XP_LOGF( " nPlayers: %d", gi->nPlayers ); - for ( XP_U16 ii = 0; ii < gi->nPlayers; ++ii ) { - const LocalPlayer* lp = &gi->players[ii]; - XP_LOGF( " player[%d]: local: %d; robotIQ: %d; name: %s", ii, - lp->isLocal, lp->robotIQ, lp->name ); + XP_LOGFF( "msg: %s from %s() line %d; addr: %p", msg, func, line, gi ); + if ( !!gi ) { + XP_LOGF( " nPlayers: %d", gi->nPlayers ); + for ( XP_U16 ii = 0; ii < gi->nPlayers; ++ii ) { + const LocalPlayer* lp = &gi->players[ii]; + XP_LOGF( " player[%d]: local: %d; robotIQ: %d; name: %s", ii, + lp->isLocal, lp->robotIQ, lp->name ); + } + XP_LOGF( " forceChannel: %d", gi->forceChannel ); + XP_LOGF( " serverRole: %d", gi->serverRole ); + XP_LOGF( " gameID: %d", gi->gameID ); + XP_LOGF( " dictName: %s", gi->dictName ); } - XP_LOGF( " forceChannel: %d", gi->forceChannel ); - XP_LOGF( " serverRole: %d", gi->serverRole ); - XP_LOGF( " gameID: %d", gi->gameID ); - XP_LOGF( " dictName: %s", gi->dictName ); } #endif diff --git a/xwords4/common/gameinfo.h b/xwords4/common/gameinfo.h index 0f169063e..3d5a3ce96 100644 --- a/xwords4/common/gameinfo.h +++ b/xwords4/common/gameinfo.h @@ -64,9 +64,11 @@ typedef struct CurGameInfo { } CurGameInfo; #ifdef DEBUG - void game_logGI( const CurGameInfo* gi, const char* msg ); +# define LOGGI( gip, msg ) game_logGI( (gip), (msg), __func__, __LINE__ ) + void game_logGI( const CurGameInfo* gi, const char* msg, + const char* func, int line ); #else -# define game_logGI(gi, msg) +# define LOGGI(gi, msg) #endif #ifdef CPLUS diff --git a/xwords4/common/server.c b/xwords4/common/server.c index d4963b6bb..de7961e1d 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -672,8 +672,9 @@ server_initClientConnection( ServerCtxt* server, XWStreamCtxt* stream ) nPlayers = gi->nPlayers; XP_ASSERT( nPlayers > 0 ); - stream_putBits( stream, NPLAYERS_NBITS, - gi_countLocalPlayers( gi, XP_FALSE) ); + XP_U16 localPlayers = gi_countLocalPlayers( gi, XP_FALSE); + XP_ASSERT( 0 < localPlayers ); + stream_putBits( stream, NPLAYERS_NBITS, localPlayers ); for ( lp = gi->players; nPlayers-- > 0; ++lp ) { XP_UCHAR* name; diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index 472e9694c..a35d6c825 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -583,7 +583,8 @@ createOrLoadObjects( GtkGameGlobals* globals ) TransportProcs procs; setTransportProcs( &procs, globals ); - if ( linuxOpenGame( cGlobals, &procs, NULL ) ) { + + if ( linuxOpenGame( cGlobals, &procs, &cGlobals->addr ) ) { if ( !params->fileName && !!params->dbName ) { XP_UCHAR buf[64]; From 410bc00d2e844f8443b33d21a615869f7d8fe9a7 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 21 Apr 2020 22:48:49 -0700 Subject: [PATCH 53/71] add test for being in background --- xwords4/linux/cursesboard.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c index 970dfabde..ee452df3d 100644 --- a/xwords4/linux/cursesboard.c +++ b/xwords4/linux/cursesboard.c @@ -809,13 +809,21 @@ curses_util_turnChanged( XW_UtilCtxt* uc, XP_S16 XP_UNUSED_DBG(newTurn) ) #endif static void -curses_util_notifyIllegalWords( XW_UtilCtxt* uc, - BadWordInfo* XP_UNUSED(bwi), - XP_U16 XP_UNUSED(player), - XP_Bool XP_UNUSED(turnLost) ) +curses_util_notifyIllegalWords( XW_UtilCtxt* uc, BadWordInfo* bwi, + XP_U16 player, XP_Bool turnLost ) { + gchar* strs = g_strjoinv( "\", \"", (gchar**)bwi->words ); + gchar* msg = g_strdup_printf( "Player %d played bad word[s]: \"%s\". " + "Turn lost: %s", player, strs, boolToStr(turnLost) ); + CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure; - ca_informf( bGlobals->boardWin, "%s() not implemented", __func__ ); + if ( !!bGlobals->boardWin ) { + ca_inform( bGlobals->boardWin, msg ); + } else { + XP_LOGFF( "msg: %s", msg ); + } + g_free( strs ); + g_free( msg ); } /* curses_util_notifyIllegalWord */ /* this needs to change!!! */ From 9d04b97ec8d270150689748a8317a0be60e8e6dc Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 21 Apr 2020 20:55:48 -0700 Subject: [PATCH 54/71] add option to have robot words reversed With reject-phonies set this will trigger the reject path. Also init CommonPrefs in jni land since its makePhonyPct, left unitialized, causes the robot to deliberately reverse every turn, firing an assertion that the robot's moves are legal. --- xwords4/android/jni/xwjni.c | 2 +- xwords4/common/comtypes.h | 1 + xwords4/common/model.c | 14 ++++++++++++++ xwords4/common/model.h | 3 ++- xwords4/common/server.c | 7 +++++++ xwords4/linux/cursesboard.c | 1 + xwords4/linux/gtkboard.c | 1 + xwords4/linux/linuxmain.c | 10 ++++++++++ xwords4/linux/main.h | 1 + xwords4/linux/scripts/discon_ok2.py | 7 +++++-- 10 files changed, 43 insertions(+), 4 deletions(-) diff --git a/xwords4/android/jni/xwjni.c b/xwords4/android/jni/xwjni.c index 5c49b67b7..c5f743690 100644 --- a/xwords4/android/jni/xwjni.c +++ b/xwords4/android/jni/xwjni.c @@ -1026,7 +1026,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame } globals->dctx = dctx; globals->xportProcs = makeXportProcs( MPPARM(mpool) ti, j_procs ); - CommonPrefs cp; + CommonPrefs cp = {0}; loadCommonPrefs( env, &cp, j_cp ); game_makeNewGame( MPPARM(mpool) &state->game, gi, globals->util, dctx, &cp, diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index 8ba981f5c..5b07b7220 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -237,6 +237,7 @@ typedef struct CommonPrefs { #ifdef XWFEATURE_CROSSHAIRS XP_Bool hideCrosshairs; /* applies to all games */ #endif + XP_U16 makePhonyPct; } CommonPrefs; typedef struct _PlayerDicts { diff --git a/xwords4/common/model.c b/xwords4/common/model.c index 9fe4b4feb..4f9cc6445 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -1253,6 +1253,20 @@ juggleMoveIfDebug( MoveInfo* move ) } } } + +/* Reverse the *letters on* the tiles */ +void +reverseTiles( MoveInfo* move ) +{ + MoveInfoTile* start = &move->tiles[0]; + MoveInfoTile* end = start + move->nTiles - 1; + while ( start < end ) { + Tile tmp = start->tile; + start->tile = end->tile; + end->tile = tmp; + --end; ++start; + } +} #endif void diff --git a/xwords4/common/model.h b/xwords4/common/model.h index c25d07a83..1e71d32fe 100644 --- a/xwords4/common/model.h +++ b/xwords4/common/model.h @@ -57,7 +57,7 @@ extern "C" { #define MAX_NUM_BLANKS 4 /* Used by scoring code and engine as fast representation of moves. */ -typedef struct MoveInfoTile { +typedef struct _MoveInfoTile { XP_U8 varCoord; /* 5 bits ok (0-16 for 17x17 board) */ Tile tile; /* 6 bits will do */ } MoveInfoTile; @@ -237,6 +237,7 @@ void model_makeTurnFromMoveInfo( ModelCtxt* model, XP_U16 playerNum, #ifdef DEBUG void juggleMoveIfDebug( MoveInfo* move ); +void reverseTiles( MoveInfo* move ); void model_dumpSelf( const ModelCtxt* model, const XP_UCHAR* msg ); #else # define juggleMoveIfDebug(newMove) diff --git a/xwords4/common/server.c b/xwords4/common/server.c index de7961e1d..4b094ec84 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -106,6 +106,7 @@ typedef struct ServerNonvolatiles { XP_U16 robotThinkMin, robotThinkMax; /* not saved (yet) */ XP_U16 robotTradePct; #endif + XP_U16 makePhonyPct; RemoteAddress addresses[MAX_NUM_PLAYERS]; XWStreamCtxt* prevMoveStream; /* save it to print later */ @@ -630,6 +631,7 @@ server_prefsChanged( ServerCtxt* server, const CommonPrefs* cp ) server->nv.robotThinkMax = cp->robotThinkMax; server->nv.robotTradePct = cp->robotTradePct; #endif + server->nv.makePhonyPct = cp->makePhonyPct; } /* server_prefsChanged */ XP_S16 @@ -1374,6 +1376,11 @@ makeRobotMove( ServerCtxt* server ) /* if canMove is false, this is a fake move, a pass */ if ( canMove || NPASSES_OK(server) ) { +#ifdef DEBUG + if ( server->nv.makePhonyPct > XP_RANDOM() % 100 ) { + reverseTiles( &newMove ); + } +#endif juggleMoveIfDebug( &newMove ); model_makeTurnFromMoveInfo( model, turn, &newMove ); XP_LOGFF( "robot making %d tile move for player %d", newMove.nTiles, turn ); diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c index ee452df3d..a19bba677 100644 --- a/xwords4/linux/cursesboard.c +++ b/xwords4/linux/cursesboard.c @@ -591,6 +591,7 @@ initNoDraw( CursesBoardState* cbState, sqlite3_int64 rowid, cGlobals->cp.robotThinkMax = params->robotThinkMax; cGlobals->cp.robotTradePct = params->robotTradePct; #endif + cGlobals->cp.makePhonyPct = params->makePhonyPct; if ( linuxOpenGame( cGlobals, &result->procs, returnAddr ) ) { result = ref( result ); diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index a35d6c825..b029896ef 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -2372,6 +2372,7 @@ initGlobalsNoDraw( GtkGameGlobals* globals, LaunchParams* params, cGlobals->cp.robotThinkMax = params->robotThinkMax; cGlobals->cp.robotTradePct = params->robotTradePct; #endif + cGlobals->cp.makePhonyPct = params->makePhonyPct; #ifdef XWFEATURE_CROSSHAIRS cGlobals->cp.hideCrosshairs = params->hideCrosshairs; #endif diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index fadbbee2f..d11409fa7 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -896,6 +896,7 @@ typedef enum { ,CMD_SLOWROBOT ,CMD_TRADEPCT #endif + ,CMD_MAKE_PHONY_PCT #ifdef USE_GLIBLOOP /* just because hard to implement otherwise */ ,CMD_UNDOPCT #endif @@ -1030,6 +1031,8 @@ static CmdInfoRec CmdInfoRecs[] = { ,{ CMD_SLOWROBOT, true, "slow-robot", "make robot slower to test network" } ,{ CMD_TRADEPCT, true, "trade-pct", "what pct of the time should robot trade" } #endif + ,{ CMD_MAKE_PHONY_PCT, true, "make-phony-pct", + "what pct of the time should robot play a bad word" } #ifdef USE_GLIBLOOP ,{ CMD_UNDOPCT, true, "undo-pct", "each second, what are the odds of doing an undo" } @@ -2944,6 +2947,13 @@ main( int argc, char** argv ) usage(argv[0], "must be 0 <= n <= 100" ); } break; + + case CMD_MAKE_PHONY_PCT: + mainParams.makePhonyPct = atoi( optarg ); + if ( mainParams.makePhonyPct < 0 || mainParams.makePhonyPct > 100 ) { + usage(argv[0], "must be 0 <= n <= 100" ); + } + break; #endif #ifdef USE_GLIBLOOP diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index b0635121e..b92e6c73e 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -129,6 +129,7 @@ typedef struct LaunchParams { XP_U16 robotThinkMin, robotThinkMax; XP_U16 robotTradePct; #endif + XP_U16 makePhonyPct; XP_Bool commsDisableds[COMMS_CONN_NTYPES][2]; DeviceRole serverRole; diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py index f7db93ae5..4e51fa6ae 100755 --- a/xwords4/linux/scripts/discon_ok2.py +++ b/xwords4/linux/scripts/discon_ok2.py @@ -422,6 +422,8 @@ def build_cmds(args): # make one in three games public usePublic = args.ADD_RELAY and random.randint(0, 3) == 0 useDupeMode = random.randint(0, 100) < args.DUP_PCT + if args.PHONIES == -1: phonies = GAME % 3 + else: phonies = args.PHONIES DEV = 0 for NLOCALS in LOCALS: DEV += 1 @@ -480,11 +482,10 @@ def build_cmds(args): if DEV == 1 or usePublic: PARAMS += ['--force-game'] if DEV == 1: - if args.PHONIES == -1: phonies = GAME % 3 - else: phonies = args.PHONIES PARAMS += ['--server', '--phonies', phonies ] else: PARAMS += ['--force-channel', DEV - 1] + if args.PHONY_PCT and phonies == 2: PARAMS += [ '--make-phony-pct', args.PHONY_PCT ] if useDupeMode: PARAMS += ['--duplicate-mode'] if usePublic: PARAMS += ['--make-public', '--join-public'] @@ -702,6 +703,8 @@ def mkParser(): help = 'send all packet twice') parser.add_argument('--phonies', dest = 'PHONIES', default = -1, type = int, help = '0 (ignore), 1 (warn)) or 2 (lose turn); default is pick at random') + parser.add_argument('--make-phony-pct', dest = 'PHONY_PCT', default = 0, type = int, + help = 'how often a robot should play a phony (only applies when --phonies==2') parser.add_argument('--use-gtk', dest = 'USE_GTK', default = False, action = 'store_true', help = 'run games using gtk instead of ncurses') From 0dc39b5d856c2c2e90390c98d963d9443cd1ef53 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 21 Apr 2020 22:28:20 -0700 Subject: [PATCH 55/71] clear pending move before applying phonies rejection If slow network traffic has given a guest time to move tiles to the board while the host decides its last move must be rejected, those tiles must be removed before the rejected turn can be undone. --- xwords4/common/model.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xwords4/common/model.c b/xwords4/common/model.c index 4f9cc6445..9be742dd0 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -902,6 +902,8 @@ model_rejectPreviousMove( ModelCtxt* model, PoolContext* pool, XP_U16* turn ) stack_popEntry( stack, &entry ); XP_ASSERT( entry.moveType == MOVE_TYPE ); + model_resetCurrentTurn( model, entry.playerNum ); + replaceNewTiles( model, pool, entry.playerNum, &entry.u.move.newTiles ); XP_ASSERT( !model->vol.gi->inDuplicateMode ); undoFromMove( model, entry.playerNum, blankTile, &entry.u.move ); From 29dbeebc94cb1430b89e76aaea5e38be0d24a0c9 Mon Sep 17 00:00:00 2001 From: Eric House Date: Tue, 21 Apr 2020 14:44:02 -0700 Subject: [PATCH 56/71] fix assert with reporting no bad words Several changes toward not allowing bad things to happen while waiting for a confirmation from the host --- xwords4/common/game.c | 3 ++- xwords4/common/server.c | 50 ++++++++++++++++++++-------------------- xwords4/common/util.h | 5 ++-- xwords4/linux/gtkboard.c | 2 +- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/xwords4/common/game.c b/xwords4/common/game.c index 4d915681f..48a347b34 100644 --- a/xwords4/common/game.c +++ b/xwords4/common/game.c @@ -536,6 +536,7 @@ gi_copy( MPFORMAL CurGameInfo* destGI, const CurGameInfo* srcGI ) void gi_setNPlayers( CurGameInfo* gi, XP_U16 nTotal, XP_U16 nHere ) { + LOGGI( gi, "before" ); XP_ASSERT( nTotal <= MAX_NUM_PLAYERS ); XP_ASSERT( nHere < nTotal ); @@ -564,7 +565,7 @@ gi_setNPlayers( CurGameInfo* gi, XP_U16 nTotal, XP_U16 nHere ) } } } - LOGGI( gi, __func__ ); + LOGGI( gi, "after" ); } XP_U16 diff --git a/xwords4/common/server.c b/xwords4/common/server.c index 4b094ec84..b2f3a6062 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -123,9 +123,7 @@ struct ServerCtxt { PoolContext* pool; BadWordInfo illegalWordInfo; -#ifndef XWFEATURE_STANDALONE_ONLY XP_U16 lastMoveSource; -#endif ServerPlayer players[MAX_NUM_PLAYERS]; XP_Bool serverDoing; @@ -159,8 +157,7 @@ static void nextTurn( ServerCtxt* server, XP_S16 nxtTurn ); static void doEndGame( ServerCtxt* server, XP_S16 quitter ); static void endGameInternal( ServerCtxt* server, GameEndReason why, XP_S16 quitter ); -static void badWordMoveUndoAndTellUser( ServerCtxt* server, - BadWordInfo* bwi ); +static void badWordMoveUndoAndTellUser( ServerCtxt* server, BadWordInfo* bwi ); static XP_Bool tileCountsOk( const ServerCtxt* server ); static void setTurn( ServerCtxt* server, XP_S16 turn ); static XWStreamCtxt* mkServerStream( ServerCtxt* server ); @@ -203,6 +200,7 @@ static void writeProto( const ServerCtxt* server, XWStreamCtxt* stream, #endif #define PICK_NEXT -1 +#define PICK_CUR -2 #if defined DEBUG && ! defined XWFEATURE_STANDALONE_ONLY static char* @@ -227,8 +225,7 @@ getStateStr( XW_State st ) } #endif -#if 0 -//def DEBUG +#ifdef DEBUG static void logNewState( XW_State old, XW_State newst, const char* caller ) { @@ -238,10 +235,10 @@ logNewState( XW_State old, XW_State newst, const char* caller ) XP_LOGFF( "state transition %s => %s (from %s())", oldStr, newStr, caller ); } } -# define SETSTATE( s, st ) { \ - XW_State old = (s)->nv.gameState; \ - (s)->nv.gameState = (st); \ - logNewState( old, st, __func__); \ +# define SETSTATE( server, st ) { \ + XW_State old = (server)->nv.gameState; \ + (server)->nv.gameState = (st); \ + logNewState( old, st, __func__); \ } #else # define SETSTATE( s, st ) (s)->nv.gameState = (st) @@ -405,6 +402,7 @@ putNV( XWStreamCtxt* stream, const ServerNonvolatiles* nv, XP_U16 nPlayers ) stream_putBits( stream, XWSTATE_NBITS, nv->stateAfterShow ); /* +1: make -1 (NOTURN) into a positive number */ + XP_ASSERT( -1 <= nv->currentTurn && nv->currentTurn < MAX_NUM_PLAYERS ); stream_putBits( stream, NPLAYERS_NBITS, nv->currentTurn+1 ); stream_putBits( stream, NPLAYERS_NBITS, nv->quitter+1 ); stream_putBits( stream, NPLAYERS_NBITS, nv->pendingRegistrations ); @@ -546,11 +544,7 @@ server_writeToStream( const ServerCtxt* server, XWStreamCtxt* stream ) } } -#ifndef XWFEATURE_STANDALONE_ONLY stream_putBits( stream, 2, server->lastMoveSource ); -#else - stream_putBits( stream, 2, 0 ); -#endif writeStreamIf( stream, server->nv.prevMoveStream ); writeStreamIf( stream, server->nv.prevWordsStream ); @@ -2057,14 +2051,14 @@ static void bwiFromStream( MPFORMAL XWStreamCtxt* stream, BadWordInfo* bwi ) { XP_U16 nWords = stream_getBits( stream, 4 ); - const XP_UCHAR** sp = bwi->words; bwi->nWords = nWords; bwi->dictName = ( STREAM_VERS_DICTNAME <= stream_getVersion( stream ) ) ? stringFromStream( mpool, stream ) : NULL; - for ( sp = bwi->words; nWords; ++sp, --nWords ) { - *sp = (const XP_UCHAR*)stringFromStream( mpool, stream ); + for ( int ii = 0; ii < nWords; ++ii ) { + bwi->words[ii] = (const XP_UCHAR*)stringFromStream( mpool, stream ); } + bwi->words[nWords] = NULL; } /* bwiFromStream */ #ifdef DEBUG @@ -2129,6 +2123,10 @@ sendBadWordMsgs( ServerCtxt* server ) bwiToStream( stream, &server->illegalWordInfo ); + /* XP_U32 hash = model_getHash( server->vol.model ); */ + /* stream_putU32( stream, hash ); */ + /* XP_LOGFF( "wrote hash: %X", hash ); */ + stream_destroy( stream ); freeBWI( MPPARM(server->mpool) &server->illegalWordInfo ); @@ -2521,11 +2519,12 @@ nextTurn( ServerCtxt* server, XP_S16 nxtTurn ) { XP_LOGFF( "(nxtTurn=%d)", nxtTurn ); CurGameInfo* gi = server->vol.gi; - XP_Bool playerTilesLeft = XP_FALSE; XP_S16 currentTurn = server->nv.currentTurn; XP_Bool moreToDo = XP_FALSE; - if ( nxtTurn == PICK_NEXT ) { + if ( nxtTurn == PICK_CUR ) { + nxtTurn = model_getNextTurn( server->vol.model ); + } else if ( nxtTurn == PICK_NEXT ) { XP_ASSERT( server->nv.gameState == XWSTATE_INTURN ); if ( server->nv.gameState != XWSTATE_INTURN ) { XP_LOGFF( "doing nothing; state %s != XWSTATE_INTURN", @@ -2533,7 +2532,6 @@ nextTurn( ServerCtxt* server, XP_S16 nxtTurn ) XP_ASSERT( !moreToDo ); goto exit; } else if ( currentTurn >= 0 ) { - playerTilesLeft = tileCountsOk( server ); if ( inDuplicateMode(server) ) { nxtTurn = dupe_nextTurn( server ); } else { @@ -2546,9 +2544,9 @@ nextTurn( ServerCtxt* server, XP_S16 nxtTurn ) /* We're doing an undo, and so won't bother figuring out who the previous turn was or how many tiles he had: it's a sure thing he "has" enough to be allowed to take the turn just undone. */ - playerTilesLeft = XP_TRUE; XP_ASSERT( nxtTurn == model_getNextTurn( server->vol.model ) ); } + XP_Bool playerTilesLeft = tileCountsOk( server ); SETSTATE( server, XWSTATE_INTURN ); /* even if game over, if undoing */ if ( playerTilesLeft && NPASSES_OK(server) ){ @@ -3835,6 +3833,9 @@ setTurn( ServerCtxt* server, XP_S16 turn ) if ( inDupMode || server->nv.currentTurn != turn || 1 == server->vol.gi->nPlayers ) { if ( DUP_PLAYER == turn && inDupMode ) { turn = dupe_nextTurn( server ); + } else if ( PICK_CUR == turn ) { + XP_ASSERT( !inDupMode ); + turn = model_getNextTurn( server->vol.model ); } else if ( 0 <= turn && !inDupMode ) { XP_ASSERT( turn == model_getNextTurn( server->vol.model ) ); } @@ -3851,6 +3852,7 @@ tellMoveWasLegal( ServerCtxt* server ) XWStreamCtxt* stream = messageStreamWithHeader( server, server->lastMoveSource, XWPROTO_MOVE_CONFIRM ); + stream_destroy( stream ); SETSTATE( server, XWSTATE_INTURN ); @@ -3880,8 +3882,7 @@ handleMoveOk( ServerCtxt* server, XWStreamCtxt* XP_UNUSED(incoming) ) XP_ASSERT( server->nv.gameState == XWSTATE_MOVE_CONFIRM_WAIT ); SETSTATE( server, XWSTATE_INTURN ); - setTurn( server, 0 ); - nextTurn( server, PICK_NEXT ); + nextTurn( server, PICK_CUR ); return accepted; } /* handleMoveOk */ @@ -4129,8 +4130,7 @@ server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming ) case XWPROTO_BADWORD_INFO: accepted = handleIllegalWord( server, incoming ); if ( accepted && server->nv.gameState != XWSTATE_GAMEOVER ) { - setTurn( server, 0 ); - nextTurn( server, PICK_NEXT ); + nextTurn( server, PICK_CUR ); } break; diff --git a/xwords4/common/util.h b/xwords4/common/util.h index b411018be..a4b7b9cfd 100644 --- a/xwords4/common/util.h +++ b/xwords4/common/util.h @@ -71,10 +71,11 @@ typedef struct PickInfo { XP_U16 thisPick; /* <= nTotal */ } PickInfo; -typedef struct BadWordInfo { +typedef struct _BadWordInfo { XP_U16 nWords; const XP_UCHAR* dictName; - const XP_UCHAR* words[MAX_TRAY_TILES+1]; /* can form in both directions */ + /* Null-terminated array of ptrs */ + const XP_UCHAR* words[MAX_TRAY_TILES+2]; /* can form in both directions */ } BadWordInfo; /* XWTimerProc returns true if redraw was necessitated by what the proc did */ diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c index b029896ef..aadedff35 100644 --- a/xwords4/linux/gtkboard.c +++ b/xwords4/linux/gtkboard.c @@ -1870,7 +1870,7 @@ gtk_util_notifyIllegalWords( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 player, XP_UCHAR* name = cGlobals->gi->players[player].name; XP_ASSERT( !!name ); - sprintf( buf, "Player %d (%s) played illegal word[s] %s; loses turn.", + sprintf( buf, "Player %d (%s) played illegal word[s] \"%s\"; loses turn.", player+1, name, strs ); if ( cGlobals->params->skipWarnings ) { From 5db0345ffd08289972df23938cc45ef8fc686508 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 22 Apr 2020 13:48:12 -0700 Subject: [PATCH 57/71] add util for logging NetLaunchInfo contents --- xwords4/common/nli.c | 18 ++++++++++++++++-- xwords4/common/nli.h | 8 ++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/xwords4/common/nli.c b/xwords4/common/nli.c index 59cd6d024..c85e8c1c9 100644 --- a/xwords4/common/nli.c +++ b/xwords4/common/nli.c @@ -33,14 +33,14 @@ void nli_init( NetLaunchInfo* nli, const CurGameInfo* gi, const CommsAddrRec* addr, - XP_U16 nPlayers, XP_U16 forceChannel ) + XP_U16 nPlayersH, XP_U16 forceChannel ) { XP_MEMSET( nli, 0, sizeof(*nli) ); nli->gameID = gi->gameID; XP_STRCAT( nli->dict, gi->dictName ); nli->lang = gi->dictLang; nli->nPlayersT = gi->nPlayers; - nli->nPlayersH = nPlayers; + nli->nPlayersH = nPlayersH; nli->forceChannel = forceChannel; nli->inDuplicateMode = gi->inDuplicateMode; @@ -202,4 +202,18 @@ nli_makeAddrRec( const NetLaunchInfo* nli, CommsAddrRec* addr ) } } } + +# ifdef DEBUG +void +logNLI( const NetLaunchInfo* nli, const char* callerFunc, const int callerLine ) +{ + XP_LOGFF( "called by %s(), line %d", callerFunc, callerLine ); + + XP_UCHAR buf[256]; + XP_SNPRINTF( buf, VSIZE(buf), "{nPlayersT: %d; nPlayersH: %d; " + "gameID: %d; inviteID: %s}", + nli->nPlayersT, nli->nPlayersH, nli->gameID, nli->inviteID ); + XP_LOGF( "%s", buf ); +} +# endif #endif diff --git a/xwords4/common/nli.h b/xwords4/common/nli.h index 9d9a7ddde..946e2183d 100644 --- a/xwords4/common/nli.h +++ b/xwords4/common/nli.h @@ -80,5 +80,13 @@ void nli_setDevID( NetLaunchInfo* invit, XP_U32 devID ); void nli_setInviteID( NetLaunchInfo* invit, const XP_UCHAR* inviteID ); void nli_setGameName( NetLaunchInfo* invit, const XP_UCHAR* gameName ); +# ifdef DEBUG +void logNLI( const NetLaunchInfo* nli, const char* callerFunc, const int callerLine ); +# define LOGNLI(nli) \ + logNLI( (nli), __func__, __LINE__ ) +# else +# define logNLI(nli) +# endif + #endif From da0916fdd183420f2aa0c57fd559e13d73d3700f Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 22 Apr 2020 14:24:39 -0700 Subject: [PATCH 58/71] Fix including empty messages. They're only there when somebody is trying to force an immediate send. Also, add a bunch of logging of the contents of message arrays being returned --- xwords4/common/smsproto.c | 61 ++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/xwords4/common/smsproto.c b/xwords4/common/smsproto.c index e1aefb5e7..5b250e813 100644 --- a/xwords4/common/smsproto.c +++ b/xwords4/common/smsproto.c @@ -108,6 +108,11 @@ static void freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex, static void freeForPhone( SMSProto* state, const XP_UCHAR* phone ); static void freeMsg( SMSProto* state, MsgRec** msg ); static void freeRec( SMSProto* state, ToPhoneRec* rec ); +#ifdef DEBUG +static void logResult( const SMSProto* state, const SMSMsgArray* result, const char* caller ); +#else +# define logResult( state, result, caller ) +#endif SMSProto* smsproto_init( MPFORMAL XW_DUtilCtxt* dutil ) @@ -216,8 +221,8 @@ smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID, #ifdef DEBUG XP_UCHAR* checksum = dutil_md5sum( state->dutil, buf, buflen ); - XP_LOGF( "%s(cmd=%d, gameID=%d): len=%d, sum=%s, toPhone=%s", __func__, cmd, - gameID, buflen, checksum, toPhone ); + XP_LOGFF( "(cmd=%d, gameID=%d): len=%d, sum=%s, toPhone=%s", cmd, + gameID, buflen, checksum, toPhone ); XP_FREEP( state->mpool, &checksum ); #endif @@ -225,7 +230,7 @@ smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID, /* First, add the new message (if present) to the array */ XP_U32 nowSeconds = dutil_getCurSeconds( state->dutil ); - if ( cmd != NONE ) { + if ( cmd != NONE && 0 < buflen ) { addToOutRec( state, rec, cmd, toPort, gameID, buf, buflen, nowSeconds ); } @@ -249,12 +254,13 @@ smsproto_prepOutbound( SMSProto* state, SMS_CMD cmd, XP_U32 gameID, } *waitSecsP = waitSecs; - XP_LOGF( "%s() => %p (len=%d, *waitSecs=%d)", __func__, result, + XP_LOGF( "%s() => %p (count=%d, *waitSecs=%d)", __func__, result, !!result ? result->nMsgs : 0, *waitSecsP ); pthread_mutex_unlock( &state->mutex ); + logResult( state, result, __func__ ); return result; -} +} /* smsproto_prepOutbound */ static SMSMsgArray* appendLocMsg( SMSProto* XP_UNUSED_DBG(state), SMSMsgArray* arr, SMSMsgLoc* msg ) @@ -292,7 +298,14 @@ SMSMsgArray* smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone, XP_U16 wantPort, const XP_U8* data, XP_U16 len ) { - XP_LOGF( "%s(): len=%d, fromPhone=%s", __func__, len, fromPhone ); + XP_LOGFF( "len=%d, fromPhone=%s", len, fromPhone ); + +#ifdef DEBUG + XP_UCHAR* checksum = dutil_md5sum( state->dutil, data, len ); + XP_LOGFF( "(fromPhone=%s, len=%d); sum=%s", fromPhone, len, checksum ); + XP_FREEP( state->mpool, &checksum ); +#endif + SMSMsgArray* result = NULL; pthread_mutex_lock( &state->mutex ); @@ -361,7 +374,8 @@ smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone, stream_destroy( stream ); - XP_LOGF( "%s() => %p (len=%d)", __func__, result, (!!result) ? result->nMsgs : 0 ); + XP_LOGFF( "=> %p (len=%d)", result, (!!result) ? result->nMsgs : 0 ); + logResult( state, result, __func__ ); pthread_mutex_unlock( &state->mutex ); return result; @@ -395,6 +409,39 @@ smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr ) pthread_mutex_unlock( &state->mutex ); } +#ifdef DEBUG +static void +logResult( const SMSProto* state, const SMSMsgArray* result, const char* caller ) +{ + if ( !!result ) { + for ( int ii = 0; ii < result->nMsgs; ++ii ) { + XP_U8* data; + XP_U16 len; + switch ( result->format ) { + case FORMAT_LOC: { + SMSMsgLoc* msgsLoc = &result->u.msgsLoc[ii]; + data = msgsLoc->data; + len = msgsLoc->len; + } + break; + case FORMAT_NET: { + SMSMsgNet* msgsNet = &result->u.msgsNet[ii]; + data = msgsNet->data; + len = msgsNet->len; + } + break; + default: + XP_ASSERT(0); + } + XP_ASSERT( 0 < len ); + XP_UCHAR* checksum = dutil_md5sum( state->dutil, data, len ); + XP_LOGFF( "%s() => datum[%d] sum: %s, len: %d", caller, ii, checksum, len ); + XP_FREEP( state->mpool, &checksum ); + } + } +} +#endif + static void freeMsg( SMSProto* XP_UNUSED_DBG(state), MsgRec** msgp ) { From e68e972396566ee0547a0d142671c4f8d83cb91d Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 22 Apr 2020 15:54:12 -0700 Subject: [PATCH 59/71] send NLI in stream format, not as raw bytes Bad programmer. And because the raw bytes form was so large it always caused the fake sms stuff to send immediately rather than waiting for a timer to expire -- which never happened when run by the test script. So I'm not allowing any timer for invitation-sends only. (Another problem is that there's no mechanism in the xplat code to retry invitation sends. That needs fixing.) --- xwords4/linux/linuxsms.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/xwords4/linux/linuxsms.c b/xwords4/linux/linuxsms.c index 368a3fb11..39ae94778 100644 --- a/xwords4/linux/linuxsms.c +++ b/xwords4/linux/linuxsms.c @@ -226,6 +226,17 @@ decodeAndDelete( LinSMSData* storage, const gchar* name, return nRead; } /* decodeAndDelete */ +static void +nliFromData( LaunchParams* params, const SMSMsgLoc* msg, NetLaunchInfo* nliOut ) +{ + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool) + params->vtMgr ); + stream_putBytes( stream, msg->data, msg->len ); + XP_Bool success = nli_makeFromStream( nliOut, stream ); + XP_ASSERT( success ); + stream_destroy( stream ); +} + static void parseAndDispatch( LaunchParams* params, uint8_t* buf, int len, CommsAddrRec* addr ) @@ -244,9 +255,12 @@ parseAndDispatch( LaunchParams* params, uint8_t* buf, int len, msg->gameID, msg->data, msg->len ); break; - case INVITE: + case INVITE: { + NetLaunchInfo nli = {0}; + nliFromData( params, msg, &nli ); (*storage->procs->inviteReceived)( storage->procClosure, - (NetLaunchInfo*)msg->data, addr ); + &nli, addr ); + } break; default: XP_ASSERT(0); /* implement me!! */ @@ -291,13 +305,21 @@ linux_sms_invite( LaunchParams* params, const NetLaunchInfo* nli, LOG_FUNC(); LinSMSData* storage = getStorage( params ); + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool) + params->vtMgr ); + nli_saveToStream( nli, stream ); + const XP_U8* ptr = stream_getPtr( stream ); + XP_U16 len = stream_getSize( stream ); + XP_U16 waitSecs; + const XP_Bool forceOld = XP_TRUE; /* Send NOW in case test app kills us */ SMSMsgArray* arr - = smsproto_prepOutbound( storage->protoState, INVITE, nli->gameID, nli, - sizeof(*nli), toPhone, toPort, XP_FALSE, - &waitSecs ); + = smsproto_prepOutbound( storage->protoState, INVITE, nli->gameID, ptr, + len, toPhone, toPort, forceOld, &waitSecs ); + XP_ASSERT( !!arr || !forceOld ); sendOrRetry( params, arr, INVITE, waitSecs, toPhone, toPort, nli->gameID, "invite" ); + stream_destroy( stream ); } XP_S16 From 4720ede1d70e6f2ccd36acd2b820d23646246ae2 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 22 Apr 2020 16:21:30 -0700 Subject: [PATCH 60/71] fix so curses app can invite two players on one device Turns out the host, when inviting a remote device, needs to know how many players are on it (since more than one is supported and the script currently generates that case.) So pass to --server devices an array, one per remote -- but don't bother when all entries are "1"; --- xwords4/common/nli.c | 2 ++ xwords4/linux/cursesboard.c | 10 ++++++---- xwords4/linux/linuxmain.c | 19 ++++++++++++++++++- xwords4/linux/main.h | 1 + xwords4/linux/scripts/discon_ok2.py | 3 +++ 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/xwords4/common/nli.c b/xwords4/common/nli.c index c85e8c1c9..10ef8c41b 100644 --- a/xwords4/common/nli.c +++ b/xwords4/common/nli.c @@ -89,6 +89,7 @@ nli_setInviteID( NetLaunchInfo* nli, const XP_UCHAR* inviteID ) void nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream ) { + LOGNLI( nli ); stream_putU8( stream, NLI_VERSION ); stream_putU16( stream, nli->_conTypes ); @@ -172,6 +173,7 @@ nli_makeFromStream( NetLaunchInfo* nli, XWStreamCtxt* stream ) XP_ASSERT( 0 == stream_getSize( stream ) ); LOG_RETURNF( "%s", boolToStr(success) ); + LOGNLI( nli ); return success; } diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c index a19bba677..5328e7c12 100644 --- a/xwords4/linux/cursesboard.c +++ b/xwords4/linux/cursesboard.c @@ -1213,10 +1213,12 @@ inviteList( CommonGlobals* cGlobals, CommsAddrRec* addr, GSList* invitees, if ( haveAddressees ) { LaunchParams* params = cGlobals->params; for ( int ii = 0; ii < g_slist_length(invitees); ++ii ) { - const XP_U16 nPlayers = 1; - gint forceChannel = ii + 1; + const XP_U16 nPlayersH = params->connInfo.inviteeCounts[ii]; + const gint forceChannel = ii + 1; + XP_LOGFF( "using nPlayersH of %d, forceChannel of %d for guest device %d", + nPlayersH, forceChannel, ii ); NetLaunchInfo nli = {0}; - nli_init( &nli, cGlobals->gi, addr, nPlayers, forceChannel ); + nli_init( &nli, cGlobals->gi, addr, nPlayersH, forceChannel ); if ( useRelay ) { uint64_t inviteeRelayID = (uint64_t)g_slist_nth_data( invitees, ii ); relaycon_invite( params, (XP_U32)inviteeRelayID, NULL, &nli ); @@ -1241,8 +1243,8 @@ handleInvite( void* closure, int XP_UNUSED(key) ) XP_ASSERT( comms ); comms_getAddr( comms, &addr ); - XP_U16 nPlayers = 1; gint forceChannel = 1; + const XP_U16 nPlayers = params->connInfo.inviteeCounts[forceChannel-1]; NetLaunchInfo nli = {0}; nli_init( &nli, cGlobals->gi, &addr, nPlayers, forceChannel ); diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index d11409fa7..187391eed 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -329,7 +329,7 @@ linuxOpenGame( CommonGlobals* cGlobals, const TransportProcs* procs, cGlobals->gi->allowHintRect = params->allowHintRect; #endif - if ( params->needsNewGame ) { + if ( params->needsNewGame && !opened ) { XP_ASSERT(0); // new_game_impl( globals, XP_FALSE ); } @@ -881,6 +881,7 @@ typedef enum { ,CMD_INVITEE_SMSNUMBER ,CMD_SMSPORT #endif + ,CMD_INVITEE_COUNTS #ifdef XWFEATURE_RELAY ,CMD_ROOMNAME ,CMD_ADVERTISEROOM @@ -1014,6 +1015,9 @@ static CmdInfoRec CmdInfoRecs[] = { ,{ CMD_INVITEE_SMSNUMBER, true, "invitee-sms-number", "number to send any invitation to" } ,{ CMD_SMSPORT, true, "sms-port", "this devices's sms port" } #endif + ,{ CMD_INVITEE_COUNTS, true, "invitee-counts", + "When invitations sent, how many on each device? e.g. \"1:2\" for a " + "three-dev game with two players on second guest" } #ifdef XWFEATURE_RELAY ,{ CMD_ROOMNAME, true, "room", "name of room on relay" } ,{ CMD_ADVERTISEROOM, false, "make-public", "make room public on relay" } @@ -2537,6 +2541,9 @@ main( int argc, char** argv ) initParams( &mainParams ); /* defaults */ + for ( int ii = 0; ii < VSIZE(mainParams.connInfo.inviteeCounts); ++ii ) { + mainParams.connInfo.inviteeCounts[ii] = 1; + } #ifdef XWFEATURE_RELAY mainParams.connInfo.relay.defaultSendPort = DEFAULT_PORT; mainParams.connInfo.relay.relayName = "localhost"; @@ -2737,6 +2744,16 @@ main( int argc, char** argv ) g_slist_append( mainParams.connInfo.sms.inviteePhones, optarg ); addr_addType( &mainParams.addr, COMMS_CONN_SMS ); break; + case CMD_INVITEE_COUNTS: { + gchar** strs = g_strsplit( optarg, ":", -1 ); + for ( int ii = 0; + !!strs[ii] && ii < VSIZE(mainParams.connInfo.inviteeCounts); + ++ii ) { + mainParams.connInfo.inviteeCounts[ii] = atoi(strs[ii]); + } + g_strfreev( strs ); + } + break; case CMD_SMSPORT: mainParams.connInfo.sms.port = atoi(optarg); addr_addType( &mainParams.addr, COMMS_CONN_SMS ); diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index b92e6c73e..a96642b53 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -136,6 +136,7 @@ typedef struct LaunchParams { CommsAddrRec addr; struct { + XP_U16 inviteeCounts[MAX_NUM_PLAYERS]; #ifdef XWFEATURE_RELAY struct { char* relayName; diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py index 4e51fa6ae..84e13a926 100755 --- a/xwords4/linux/scripts/discon_ok2.py +++ b/xwords4/linux/scripts/discon_ok2.py @@ -483,6 +483,9 @@ def build_cmds(args): if DEV == 1 or usePublic: PARAMS += ['--force-game'] if DEV == 1: PARAMS += ['--server', '--phonies', phonies ] + # IFF there are any non-1 player counts, tell inviter which + if sum(LOCALS) > NDEVS: + PARAMS += ['--invitee-counts', ":".join(str(n) for n in LOCALS[1:])] else: PARAMS += ['--force-channel', DEV - 1] if args.PHONY_PCT and phonies == 2: PARAMS += [ '--make-phony-pct', args.PHONY_PCT ] From e8c48e792aa15ede352a865704a80ff37ed195b1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 22 Apr 2020 18:18:07 -0700 Subject: [PATCH 61/71] fix tile picker dialog I wasn't exiting dialogs correctly. Probably broken since the switch to gtk3. --- xwords4/linux/gtkletterask.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/xwords4/linux/gtkletterask.c b/xwords4/linux/gtkletterask.c index dcab48ca2..14c2b6354 100644 --- a/xwords4/linux/gtkletterask.c +++ b/xwords4/linux/gtkletterask.c @@ -24,18 +24,21 @@ #include "gtkask.h" static void -set_bool_and_quit( GtkWidget* XP_UNUSED(widget), gpointer closure ) +set_bool_and_quit( GtkWidget* widget, gpointer closure ) { XP_Bool* whichSet = (XP_Bool*)closure; *whichSet = XP_TRUE; - gtk_main_quit(); + + GtkWidget* dialog = gtk_widget_get_toplevel( widget ); + gtk_dialog_response( GTK_DIALOG(dialog), 1000 ); } /* button_event */ #ifdef FEATURE_TRAY_EDIT static void -abort_button_event( GtkWidget* XP_UNUSED(widget), gpointer XP_UNUSED(closure) ) +abort_button_event( GtkWidget* widget, gpointer XP_UNUSED(closure) ) { - gtk_main_quit(); + GtkWidget* dialog = gtk_widget_get_toplevel( widget ); + gtk_dialog_response( GTK_DIALOG(dialog), 1000 ); } /* abort_button_event */ #endif @@ -51,7 +54,6 @@ gtkletterask( const TrayTileSet* curPick, XP_Bool forTray, const XP_UCHAR* name, GtkWidget* vbox; GtkWidget* hbox = NULL; char* txt; - XP_S16 ii; GtkWidget* button; XP_UCHAR buf[64]; XP_Bool backedUp = XP_FALSE; @@ -60,7 +62,7 @@ gtkletterask( const TrayTileSet* curPick, XP_Bool forTray, const XP_UCHAR* name, vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); - for ( ii = 0; ii < nTiles; ++ii ) { + for ( int ii = 0; ii < nTiles; ++ii ) { if ( ii % BUTTONS_PER_ROW == 0 ) { hbox = gtk_box_new( GTK_ORIENTATION_HORIZONTAL, 0 ); @@ -114,7 +116,7 @@ gtkletterask( const TrayTileSet* curPick, XP_Bool forTray, const XP_UCHAR* name, char curTilesBuf[64]; int len = snprintf( curTilesBuf, sizeof(curTilesBuf), "%s", "Tiles so far: " ); - for ( ii = 0; ii < curPick->nTiles; ++ii ) { + for ( int ii = 0; ii < curPick->nTiles; ++ii ) { Tile tile = curPick->tiles[ii]; len += snprintf( &curTilesBuf[len], sizeof(curTilesBuf) - len, "%s ", texts[tile] ); @@ -129,24 +131,25 @@ gtkletterask( const TrayTileSet* curPick, XP_Bool forTray, const XP_UCHAR* name, gtk_dialog_add_action_widget( GTK_DIALOG(dialog), vbox, 0 ); gtk_widget_show_all( dialog ); - gtk_dialog_run( GTK_DIALOG( dialog ) ); + // gint dlgResult = + (void)gtk_dialog_run( GTK_DIALOG( dialog ) ); gtk_widget_destroy( dialog ); + XP_S16 result; if ( backedUp ) { - ii = PICKER_BACKUP; + result = PICKER_BACKUP; } else { - for ( ii = 0; ii < nTiles; ++ii ) { + result = PICKER_PICKALL; + for ( int ii = 0; ii < nTiles; ++ii ) { if ( results[ii] ) { + result = ii; break; } } - if ( ii == nTiles ) { - ii = PICKER_PICKALL; - } } - return ii; + return result; } /* gtkletterask */ #endif /* PLATFORM_GTK */ From d01fd5de1cdf3f776ad17b3263ac4f88bbfb8f78 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 22 Apr 2020 19:43:57 -0700 Subject: [PATCH 62/71] inval score when face for blank chosen If I'm displaying real-time whether the score is legal, I need to recalc it after the user tells me what tile a blank is standing in for. --- xwords4/common/model.c | 3 +++ xwords4/common/mscore.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/xwords4/common/model.c b/xwords4/common/model.c index 9be742dd0..958c52b70 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -1572,6 +1572,9 @@ model_setBlankValue( ModelCtxt* model, XP_U16 turn, &nUsed, tfaces, tiles ); pt->tile = tiles[newIndex] | TILE_BLANK_BIT; + + /* force a recalc in case phonies==PHONIES_BLOCK */ + invalidateScore( model, turn ); } break; } diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c index 931a1cec4..29e643117 100644 --- a/xwords4/common/mscore.c +++ b/xwords4/common/mscore.c @@ -736,7 +736,7 @@ scoreWord( const ModelCtxt* model, XP_U16 turn, dict_tilesToString( dict, checkWordBuf, len, buf, sizeof(buf) ); - WNParams wnp = { .word = buf, .isLegal =legal, .dict = dict, + WNParams wnp = { .word = buf, .isLegal = legal, .dict = dict, #ifdef XWFEATURE_BOARDWORDS .movei = movei, .start = start, .end = end, #endif From f35136099d381e23e792c044556756a730b9fc45 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 23 Apr 2020 21:57:59 -0700 Subject: [PATCH 63/71] don't crash when wordlist provides no bitmap --- xwords4/linux/gtkdraw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/linux/gtkdraw.c b/xwords4/linux/gtkdraw.c index 76c2d2861..4d498fe65 100644 --- a/xwords4/linux/gtkdraw.c +++ b/xwords4/linux/gtkdraw.c @@ -779,7 +779,7 @@ gtkDrawTileImpl( DrawCtx* p_dctx, const XP_Rect* rect, const XP_UCHAR* textP, formatRect.width -= 6; if ( notEmpty ) { - if ( !!bitmaps ) { + if ( !!bitmaps && !!bitmaps->bmps[1] ) { drawBitmapFromLBS( dctx, bitmaps->bmps[1], &insetR ); } else if ( !!textP ) { if ( *textP != LETTER_NONE ) { /* blank */ From 2204d951a7b2fac5ba80466111f55ebe56aacc44 Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 23 Apr 2020 21:59:44 -0700 Subject: [PATCH 64/71] don't crash dumping empty wordlists --- xwords4/dawg/dawg2dict.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/xwords4/dawg/dawg2dict.py b/xwords4/dawg/dawg2dict.py index 9a58e50ae..c2ab89077 100755 --- a/xwords4/dawg/dawg2dict.py +++ b/xwords4/dawg/dawg2dict.py @@ -220,15 +220,16 @@ def process(args): nodes = loadNodes( dawg, nodeSize ) words = [] - expandDAWG( nodes, nodeSize, offset, data, words ) - assert len(words) == nWords + if nodes: + expandDAWG( nodes, nodeSize, offset, data, words ) + assert len(words) == nWords if args.DUMP_WORDS: for word in words: print(word) def mkParser(): parser = argparse.ArgumentParser() - parser.add_argument('--dict', dest = 'DAWG', type = str, required = True, + parser.add_argument('--dawg', dest = 'DAWG', type = str, required = True, help = 'the .xwd file to load') parser.add_argument('--dump-words', dest = 'DUMP_WORDS', default = False, action = 'store_true', help = 'write wordlist to stdout') From abc34f623f570e34fcdc2e6f39662e45661ea23a Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 24 Apr 2020 06:33:53 -0700 Subject: [PATCH 65/71] add Hungarian to display wordlist --- xwords4/android/app/src/main/res/values/common_rsrc.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xwords4/android/app/src/main/res/values/common_rsrc.xml b/xwords4/android/app/src/main/res/values/common_rsrc.xml index 9cc38af9c..e4ea7c8e3 100644 --- a/xwords4/android/app/src/main/res/values/common_rsrc.xml +++ b/xwords4/android/app/src/main/res/values/common_rsrc.xml @@ -242,6 +242,7 @@ Czech Greek Slovak + Hungarian @@ -265,6 +266,7 @@ cs el sk + hu From d4e71caed10f570e53b2444999133ac177fbf61a Mon Sep 17 00:00:00 2001 From: Eric House Date: Thu, 23 Apr 2020 22:04:20 -0700 Subject: [PATCH 66/71] fix warning --- xwords4/common/smsproto.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwords4/common/smsproto.c b/xwords4/common/smsproto.c index 5b250e813..dd1a1d9e5 100644 --- a/xwords4/common/smsproto.c +++ b/xwords4/common/smsproto.c @@ -416,7 +416,7 @@ logResult( const SMSProto* state, const SMSMsgArray* result, const char* caller if ( !!result ) { for ( int ii = 0; ii < result->nMsgs; ++ii ) { XP_U8* data; - XP_U16 len; + XP_U16 len = 0; switch ( result->format ) { case FORMAT_LOC: { SMSMsgLoc* msgsLoc = &result->u.msgsLoc[ii]; From cc285620619d19a70650289e7c3d3d1759b9c689 Mon Sep 17 00:00:00 2001 From: Eric House Date: Wed, 22 Apr 2020 21:54:59 -0700 Subject: [PATCH 67/71] files to create empty Hungarian wordlist --- xwords4/dawg/Hungarian/Makefile | 45 +++++++++++++++++++++++++++++ xwords4/dawg/Hungarian/info.txt | 50 +++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 xwords4/dawg/Hungarian/Makefile create mode 100644 xwords4/dawg/Hungarian/info.txt diff --git a/xwords4/dawg/Hungarian/Makefile b/xwords4/dawg/Hungarian/Makefile new file mode 100644 index 000000000..03547fbb4 --- /dev/null +++ b/xwords4/dawg/Hungarian/Makefile @@ -0,0 +1,45 @@ +# -*-mode: Makefile; -*- +# Copyright 2002-2020 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. + +XWLANG=Hungarian +LANGCODE=hu_HU +ENC = UTF-8 +TARGET_TYPE ?= WINCE + +DICTNOTE = "This wordlist contains the tile information for Hungarian but no words." + +LANG_SPECIAL_INFO = \ + "CS Cs cS cs" /dev/null /dev/null \ + "GY Gy gY gy" /dev/null /dev/null \ + "LY Ly lY ly" /dev/null /dev/null \ + "NY Ny nY ny" /dev/null /dev/null \ + "SZ Sz sZ sz" /dev/null /dev/null \ + "TY Ty tY ty" /dev/null /dev/null \ + "ZS Zs zS zs" /dev/null /dev/null \ + +include ../Makefile.langcommon + +# Empty dict +$(XWLANG)Main.dict.gz: + echo -n "" | gzip -c > $@ + +# Everything but creating of the Main.dict file is inherited from the +# "parent" Makefile.langcommon in the parent directory. + +clean: clean_common + rm -f $(XWLANG)Main.dict.gz *.bin $(XWLANG)*.pdb $(XWLANG)*.seb diff --git a/xwords4/dawg/Hungarian/info.txt b/xwords4/dawg/Hungarian/info.txt new file mode 100644 index 000000000..3768603a9 --- /dev/null +++ b/xwords4/dawg/Hungarian/info.txt @@ -0,0 +1,50 @@ +# -*- mode: conf; coding: utf-8; -*- + +LANGCODE:hu_HU +CHARSET: utf-8 + +# High bit means "official". Next 7 bits are an enum where +# Hungarian==0x14. Low byte is padding +XLOC_HEADER:0x9400 + + +2 0 {"_"} +6 1 'A|a' +4 1 'Á|á' +3 2 'B|b' +1 5 'C|c' +1 7 {"CS|cs"} +3 2 'D|d' +6 1 'E|e' +3 3 'É|é' +2 4 'F|f' +3 2 'G|g' +2 4 {"GY|gy"} +2 3 'H|h' +3 1 'I|i' +1 5 'Í|í' +2 4 'J|j' +6 1 'K|k' +4 1 'L|l' +1 8 {"LY|ly"} +3 1 'M|m' +4 1 'N|n' +1 5 {"NY|ny"} +3 1 'O|o' +3 2 'Ó|ó' +2 4 'Ö|ö' +1 7 'Ő|ö' +2 4 'P|p' +4 1 'R|r' +3 1 'S|s' +2 3 {"SZ|sz"} +5 1 'T|t' +1 10 {"TY|ty"} +2 4 'U|u' +1 7 'Ú|ú' +2 4 'Ü|ü' +1 7 'Ű|ű' +2 3 'V|v' +2 4 'Z|z' +1 8 {"ZS|zs"} + From 70479710bc358fe86749737ec8031f3e5d427644 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 24 Apr 2020 07:40:25 -0700 Subject: [PATCH 68/71] changelog for new release --- xwords4/android/app/build.gradle | 4 ++-- xwords4/android/app/src/main/assets/changes.html | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/xwords4/android/app/build.gradle b/xwords4/android/app/build.gradle index 5cf5a144b..8053ffa7b 100644 --- a/xwords4/android/app/build.gradle +++ b/xwords4/android/app/build.gradle @@ -1,6 +1,6 @@ def INITIAL_CLIENT_VERS = 9 -def VERSION_CODE_BASE = 153 -def VERSION_NAME = '4.4.157' +def VERSION_CODE_BASE = 154 +def VERSION_NAME = '4.4.158' def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") def BUILD_INFO_NAME = "build-info.txt" diff --git a/xwords4/android/app/src/main/assets/changes.html b/xwords4/android/app/src/main/assets/changes.html index 3a22a3371..5ad050aa4 100644 --- a/xwords4/android/app/src/main/assets/changes.html +++ b/xwords4/android/app/src/main/assets/changes.html @@ -13,7 +13,7 @@ -

    CrossWords 4.4.157 release

    +

    CrossWords 4.4.158 release

    This release fixes a major crash introduced in 155. Thanks for all the reports and debugging help!

    @@ -30,15 +30,9 @@

    New with this release

      -
    • Won't crash or hang when "Disallow phonies" setting is in use
    • -
    • Won't allow committing a malformed word
    • -
    • Fixes some problems with dragging tiles
    • -
    • Fixes occasional problem drawing board-arrow
    • -
    • Fixes game-list item collapse
    • -
    • Fixes so when you open app from Launcher you go back to your open game
    • -
    • Fixes to show invitee in scoreboard as "not here yet"
    • -
    • Fixes crash when chat message is too long
    • -
    • Detects and flags damaged games that crash the app
    • +
    • Fix timing problems when the "Phonies" setting is DISALLOW
    • +
    • Clean up "Edit player" dialog
    • +
    • Add support for Hungarian wordlists

    (The full changelog From e8a4c6e39ad6ad3e842269477f608317135ec899 Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 24 Apr 2020 08:38:31 -0700 Subject: [PATCH 69/71] don't run duplicate games by default Duplicate games get lots of crashes when mixed with phonies code. Will need to fix that before enabling duplicate mode. In general there are lots of assertion failures mixing trades and undos and phonies and running dozens of games at once. I don't think that's new so will ship now and fix later. --- xwords4/linux/scripts/discon_ok2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py index 84e13a926..53b7a020d 100755 --- a/xwords4/linux/scripts/discon_ok2.py +++ b/xwords4/linux/scripts/discon_ok2.py @@ -706,12 +706,12 @@ def mkParser(): help = 'send all packet twice') parser.add_argument('--phonies', dest = 'PHONIES', default = -1, type = int, help = '0 (ignore), 1 (warn)) or 2 (lose turn); default is pick at random') - parser.add_argument('--make-phony-pct', dest = 'PHONY_PCT', default = 0, type = int, + parser.add_argument('--make-phony-pct', dest = 'PHONY_PCT', default = 20, type = int, help = 'how often a robot should play a phony (only applies when --phonies==2') parser.add_argument('--use-gtk', dest = 'USE_GTK', default = False, action = 'store_true', help = 'run games using gtk instead of ncurses') - parser.add_argument('--duplicate-pct', dest = 'DUP_PCT', default = 50, type = int, + parser.add_argument('--dup-pct', dest = 'DUP_PCT', default = 0, type = int, help = 'this fraction played in duplicate mode') # # From f51030186ff303c396de0e8d0ad4aeb3f098230e Mon Sep 17 00:00:00 2001 From: Eric House Date: Fri, 24 Apr 2020 08:54:57 -0700 Subject: [PATCH 70/71] fix non-debug build warnings --- xwords4/common/nli.h | 2 +- xwords4/linux/cursesboard.c | 5 ++++- xwords4/linux/gamesdb.c | 17 +++++++++++++---- xwords4/linux/linuxsms.c | 5 ++++- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/xwords4/common/nli.h b/xwords4/common/nli.h index 946e2183d..fba479c34 100644 --- a/xwords4/common/nli.h +++ b/xwords4/common/nli.h @@ -85,7 +85,7 @@ void logNLI( const NetLaunchInfo* nli, const char* callerFunc, const int callerL # define LOGNLI(nli) \ logNLI( (nli), __func__, __LINE__ ) # else -# define logNLI(nli) +# define LOGNLI(nli) # endif diff --git a/xwords4/linux/cursesboard.c b/xwords4/linux/cursesboard.c index 5328e7c12..d306d9260 100644 --- a/xwords4/linux/cursesboard.c +++ b/xwords4/linux/cursesboard.c @@ -670,7 +670,10 @@ cb_feedGame( CursesBoardState* cbState, XP_U32 gameID, getRowsForGameID( params->pDb, gameID, rowids, &nRows ); XP_LOGF( "%s(): found %d rows for gameID %d", __func__, nRows, gameID ); for ( int ii = 0; ii < nRows; ++ii ) { - bool success = cb_feedRow( cbState, rowids[ii], 0, buf, len, from ); +#ifdef DEBUG + bool success = +#endif + cb_feedRow( cbState, rowids[ii], 0, buf, len, from ); XP_ASSERT( success ); } } diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c index a7872e808..be9ed4a49 100644 --- a/xwords4/linux/gamesdb.c +++ b/xwords4/linux/gamesdb.c @@ -63,11 +63,17 @@ static void assertPrintResult( sqlite3* pDb, int result, int expect ); sqlite3* openGamesDB( const char* dbName ) { - int result = sqlite3_initialize(); +#ifdef DEBUG + int result = +#endif + sqlite3_initialize(); XP_ASSERT( SQLITE_OK == result ); sqlite3* pDb = NULL; - result = sqlite3_open( dbName, &pDb ); +#ifdef DEBUG + result = +#endif + sqlite3_open( dbName, &pDb ); XP_ASSERT( SQLITE_OK == result ); if ( gamesTableExists( pDb ) ) { @@ -140,7 +146,10 @@ createTables( sqlite3* pDb ) /* This can never change! Versioning counts on it. */ const char* createValuesStr = "CREATE TABLE pairs ( key TEXT UNIQUE, value TEXT )"; - int result = sqlite3_exec( pDb, createValuesStr, NULL, NULL, NULL ); +#ifdef DEBUG + int result = +#endif + sqlite3_exec( pDb, createValuesStr, NULL, NULL, NULL ); XP_LOGFF( "sqlite3_exec(%s)=>%d", createValuesStr, result ); const char* createGamesStr = @@ -165,7 +174,7 @@ createTables( sqlite3* pDb ) ","VERS_0_TO_1 // ",dupTimerExpires INT" ")"; - result = sqlite3_exec( pDb, createGamesStr, NULL, NULL, NULL ); + (void)sqlite3_exec( pDb, createGamesStr, NULL, NULL, NULL ); db_storeInt( pDb, KEY_DB_VERSION, CUR_DB_VERSION ); } diff --git a/xwords4/linux/linuxsms.c b/xwords4/linux/linuxsms.c index 39ae94778..a9fe0d358 100644 --- a/xwords4/linux/linuxsms.c +++ b/xwords4/linux/linuxsms.c @@ -232,7 +232,10 @@ nliFromData( LaunchParams* params, const SMSMsgLoc* msg, NetLaunchInfo* nliOut ) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool) params->vtMgr ); stream_putBytes( stream, msg->data, msg->len ); - XP_Bool success = nli_makeFromStream( nliOut, stream ); +#ifdef DEBUG + XP_Bool success = +#endif + nli_makeFromStream( nliOut, stream ); XP_ASSERT( success ); stream_destroy( stream ); } From 83a5d32ed0806207e3844bed6e924b67bfa857ea Mon Sep 17 00:00:00 2001 From: Eric House Date: Mon, 20 Apr 2020 21:27:26 -0700 Subject: [PATCH 71/71] check for null --- .../main/java/org/eehouse/android/xw4/jni/JNIThread.java | 8 ++++++-- xwords4/android/app/src/main/res/values/strings.xml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java index 4aa9d05a5..b0dbb5163 100644 --- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java @@ -197,9 +197,13 @@ public class JNIThread extends Thread implements AutoCloseable { m_queue.clear(); } + boolean success = false; + DictUtils.DictPairs pairs = null; String[] dictNames = GameUtils.dictNames( context, m_lock ); - DictUtils.DictPairs pairs = DictUtils.openDicts( context, dictNames ); - boolean success = !pairs.anyMissing( dictNames ); + if ( null != dictNames ) { + pairs = DictUtils.openDicts( context, dictNames ); + success = !pairs.anyMissing( dictNames ); + } if ( success ) { byte[] stream = GameUtils.savedGame( context, m_lock ); diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml index 3f969ab82..670eb6505 100644 --- a/xwords4/android/app/src/main/res/values/strings.xml +++ b/xwords4/android/app/src/main/res/values/strings.xml @@ -2527,7 +2527,7 @@ another crash. Do you want to open it anyway? Open anyway - Word %1$s not found in wordlist %2$s + Word %1$s not found in wordlist %2$s. Debug logs