diff --git a/xwords4/android/XWords4/jni/utilwrapper.c b/xwords4/android/XWords4/jni/utilwrapper.c index 4d9e29364..461bc28a4 100644 --- a/xwords4/android/XWords4/jni/utilwrapper.c +++ b/xwords4/android/XWords4/jni/utilwrapper.c @@ -262,6 +262,27 @@ and_util_informUndo( XW_UtilCtxt* uc ) UTIL_CBK_TAIL(); } +static void +and_util_informNetDict( XW_UtilCtxt* uc, const XP_UCHAR* oldName, + const XP_UCHAR* newName, const XP_UCHAR* newSum, + XWPhoniesChoice phoniesAction ) +{ + LOG_FUNC(); + UTIL_CBK_HEADER( "informNetDict", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;L" + PKG_PATH("jni/CurGameInfo$XWPhoniesChoice") ";)V" ); + jstring jnew = (*env)->NewStringUTF( env, newName ); + jstring jsum = (*env)->NewStringUTF( env, newSum ); + jstring jold = (*env)->NewStringUTF( env, oldName ); + jobject jphon = intToJEnum( env, phoniesAction, + PKG_PATH("jni/CurGameInfo$XWPhoniesChoice") ); + + (*env)->CallVoidMethod( env, util->jutil, mid, jold, jnew, jsum, jphon ); + deleteLocalRefs( env, jnew, jold, jsum, jphon, DELETE_NO_REF ); + + UTIL_CBK_TAIL(); +} + static void and_util_notifyGameOver( XW_UtilCtxt* uc ) { @@ -400,14 +421,17 @@ and_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi, XP_U16 turn, XP_Bool turnLost ) { jboolean result = XP_FALSE; - UTIL_CBK_HEADER("warnIllegalWord", "([Ljava/lang/String;IZ)Z" ); + UTIL_CBK_HEADER("warnIllegalWord", + "(Ljava/lang/String;[Ljava/lang/String;IZ)Z" ); XP_ASSERT( bwi->nWords > 0 ); if ( bwi->nWords > 0 ) { jobjectArray jwords = makeStringArray( env, bwi->nWords, (const XP_UCHAR**)bwi->words ); + XP_ASSERT( !!bwi->dictName ); + jstring jname = (*env)->NewStringUTF( env, bwi->dictName ); result = (*env)->CallBooleanMethod( env, util->jutil, mid, - jwords, turn, turnLost ); - deleteLocalRef( env, jwords ); + jname, jwords, turn, turnLost ); + deleteLocalRefs( env, jwords, jname, DELETE_NO_REF ); } UTIL_CBK_TAIL(); return result; @@ -578,6 +602,7 @@ makeUtil( MPFORMAL JNIEnv** envp, jobject jutil, CurGameInfo* gi, #endif SET_PROC(informMove); SET_PROC(informUndo); + SET_PROC(informNetDict); SET_PROC(notifyGameOver); SET_PROC(hiliteCell); SET_PROC(engineProgressCallback); diff --git a/xwords4/android/XWords4/res/values/strings.xml b/xwords4/android/XWords4/res/values/strings.xml index 1f865790b..61769251d 100644 --- a/xwords4/android/XWords4/res/values/strings.xml +++ b/xwords4/android/XWords4/res/values/strings.xml @@ -629,8 +629,8 @@ phonies_disallow is the current setting and a "phony" is played. One of the two following strings will be appended --> - Word[s] %s not found in - wordlist. + Word[s] %1$s not found in + wordlist %2$s. diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java index d5ce62292..a4e26134b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardActivity.java @@ -1472,6 +1472,22 @@ public class BoardActivity extends XWActivity nonBlockingDialog( DLG_OKONLY, getString( R.string.remote_undone ) ); } + @Override + public void informNetDict( String oldName, String newName, String newSum, + CurGameInfo.XWPhoniesChoice phonies ) + { + // If it's same dict and same sum, we're good. That + // should be the normal case. Otherwise: if same name but + // different sum, notify and offer to upgrade. If + // different name, offer to install. + String oldSum = DictLangCache.getDictMD5Sum( BoardActivity.this, + oldName ); + String str = String.format( "informNetDict(%s, %s, %s, %s, %s)", + oldName, oldSum, newName, newSum, + phonies.toString() ); + nonBlockingDialog( DLG_OKONLY, str ); + } + @Override public void notifyGameOver() { @@ -1484,10 +1500,10 @@ public class BoardActivity extends XWActivity // m_view.setVerticalScrollBarEnabled( maxOffset > 0 ); // } @Override - public boolean warnIllegalWord( String[] words, int turn, + public boolean warnIllegalWord( String dict, String[] words, int turn, boolean turnLost ) { - DbgUtils.logf( "warnIllegalWord" ); + DbgUtils.logf( "warnIllegalWord(dict=%s)", dict ); boolean accept = turnLost; StringBuffer sb = new StringBuffer(); @@ -1499,7 +1515,8 @@ public class BoardActivity extends XWActivity sb.append( "; " ); } - String message = getString( R.string.ids_badwords, sb.toString() ); + String message = + getString( R.string.ids_badwords, sb.toString(), dict ); if ( turnLost ) { nonBlockingDialog( DLG_BADWORDS, diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictLangCache.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictLangCache.java index 6bdca3580..d29ba24e9 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictLangCache.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/DictLangCache.java @@ -197,7 +197,12 @@ public class DictLangCache { public static String getDictMD5Sum( Context context, String dict ) { - return getInfo( context, dict ).md5Sum; + String result = null; + DictInfo info = getInfo( context, dict ); + if ( null != info ) { + result = info.md5Sum; + } + return result; } public static int getDictLangCode( Context context, String dict ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index 3e8a2e3ee..8a9e1c854 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -53,7 +53,7 @@ import org.eehouse.android.xw4.jni.XwJNI; public class SMSService extends Service { - private static final String INSTALL_URL = "http://eehouse.org/_/aa.htm "; + private static final String INSTALL_URL = "http://eehouse.org/_/a.py/a "; private static final int MAX_SMS_LEN = 140; // ??? differs by network private static final String MSG_SENT = "MSG_SENT"; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java index dfae6d311..8926a3a1e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxt.java @@ -116,6 +116,9 @@ public interface UtilCtxt { void informMove( String expl, String words ); void informUndo(); + void informNetDict( String oldName, String newName, String newSum, + CurGameInfo.XWPhoniesChoice phonies ); + void informMissing( boolean isServer, CommsAddrRec.CommsConnType connType, int nMissingPlayers ); @@ -123,7 +126,8 @@ public interface UtilCtxt { // Don't need this unless we have a scroll thumb to indicate position //void yOffsetChange( int maxOffset, int oldOffset, int newOffset ); - boolean warnIllegalWord( String[] words, int turn, boolean turnLost ); + boolean warnIllegalWord( String dict, String[] words, int turn, + boolean turnLost ); void showChat( String msg ); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java index 63762ccf1..a070d552e 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/UtilCtxtImpl.java @@ -222,6 +222,12 @@ public class UtilCtxtImpl implements UtilCtxt { subclassOverride( "informUndo" ); } + public void informNetDict( String oldName, String newName, String newSum, + CurGameInfo.XWPhoniesChoice phonies ) + { + subclassOverride( "informNetDict" ); + } + public void informMissing( boolean isServer, CommsAddrRec.CommsConnType connType, int nMissingPlayers ) @@ -236,7 +242,8 @@ public class UtilCtxtImpl implements UtilCtxt { subclassOverride( "notifyGameOver" ); } - public boolean warnIllegalWord( String[] words, int turn, boolean turnLost ) + public boolean warnIllegalWord( String dict, String[] words, int turn, + boolean turnLost ) { subclassOverride( "warnIllegalWord" ); return false; diff --git a/xwords4/common/board.c b/xwords4/common/board.c index cbc13eda3..23d0bc253 100644 --- a/xwords4/common/board.c +++ b/xwords4/common/board.c @@ -735,6 +735,7 @@ hideMiniWindow( BoardCtxt* board, XP_Bool destroy, MiniWindowType winType ) static XP_Bool warnBadWords( const XP_UCHAR* word, XP_Bool isLegal, + const DictionaryCtxt* XP_UNUSED(dict), #ifdef XWFEATURE_BOARDWORDS const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start), XP_U16 XP_UNUSED(end), diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h index 3af241f2b..b806fe124 100644 --- a/xwords4/common/comtypes.h +++ b/xwords4/common/comtypes.h @@ -47,6 +47,7 @@ #endif #define MAX_COLS MAX_ROWS +#define STREAM_VERS_DICTNAME 0x15 #ifdef HASH_STREAM # define STREAM_VERS_HASHSTREAM 0x14 #endif @@ -81,13 +82,7 @@ #define STREAM_VERS_41B4 0x02 #define STREAM_VERS_405 0x01 -#ifdef STREAM_VERS_HASHSTREAM -# define CUR_STREAM_VERS STREAM_VERS_HASHSTREAM -#elif MAX_COLS > 16 -# define CUR_STREAM_VERS STREAM_VERS_BIGBOARD -#else -# define CUR_STREAM_VERS STREAM_VERS_BLUETOOTH2 -#endif +#define CUR_STREAM_VERS STREAM_VERS_DICTNAME typedef struct XP_Rect { XP_S16 left; diff --git a/xwords4/common/model.c b/xwords4/common/model.c index e0137d8d2..200ff744e 100644 --- a/xwords4/common/model.c +++ b/xwords4/common/model.c @@ -78,6 +78,7 @@ static void writePlayerCtxt( const ModelCtxt* model, XWStreamCtxt* stream, const PlayerCtxt* pc ); static XP_U16 model_getRecentPassCount( ModelCtxt* model ); static XP_Bool recordWord( const XP_UCHAR* word, XP_Bool isLegal, + const DictionaryCtxt* dict, #ifdef XWFEATURE_BOARDWORDS const MoveInfo* movei, XP_U16 start, XP_U16 end, #endif @@ -2147,6 +2148,7 @@ typedef struct _FirstWordData { static XP_Bool getFirstWord( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), + const DictionaryCtxt* XP_UNUSED(dict), #ifdef XWFEATURE_BOARDWORDS const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start), XP_U16 XP_UNUSED(end), @@ -2246,6 +2248,7 @@ appendWithCR( XWStreamCtxt* stream, const XP_UCHAR* word, XP_U16* counter ) static XP_Bool recordWord( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), + const DictionaryCtxt* XP_UNUSED(dict), #ifdef XWFEATURE_BOARDWORDS const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start), XP_U16 XP_UNUSED(end), @@ -2277,6 +2280,7 @@ typedef struct _ListWordsThroughInfo { static XP_Bool listWordsThrough( const XP_UCHAR* word, XP_Bool XP_UNUSED(isLegal), + const DictionaryCtxt* XP_UNUSED(dict), const MoveInfo* movei, XP_U16 start, XP_U16 end, void* closure ) { diff --git a/xwords4/common/model.h b/xwords4/common/model.h index 548a64b35..28634005c 100644 --- a/xwords4/common/model.h +++ b/xwords4/common/model.h @@ -248,6 +248,7 @@ void model_countAllTrayTiles( ModelCtxt* model, XP_U16* counts, /********************* scoring ********************/ typedef XP_Bool (*WordNotifierProc)( const XP_UCHAR* word, XP_Bool isLegal, + const DictionaryCtxt* dict, #ifdef XWFEATURE_BOARDWORDS const MoveInfo* movei, XP_U16 start, XP_U16 end, diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c index cf2b07f76..79788131c 100644 --- a/xwords4/common/mscore.c +++ b/xwords4/common/mscore.c @@ -674,7 +674,7 @@ scoreWord( const ModelCtxt* model, XP_U16 turn, XP_UCHAR buf[(MAX_ROWS*2)+1]; dict_tilesToString( dict, checkWordBuf, len, buf, sizeof(buf) ); - (void)(*notifyInfo->proc)( buf, legal, + (void)(*notifyInfo->proc)( buf, legal, dict, #ifdef XWFEATURE_BOARDWORDS movei, start, end, #endif diff --git a/xwords4/common/server.c b/xwords4/common/server.c index f739af65b..ac1f8bb9d 100644 --- a/xwords4/common/server.c +++ b/xwords4/common/server.c @@ -1231,8 +1231,6 @@ client_readInitialMessage( ServerCtxt* server, XWStreamCtxt* stream ) gi_readFromStream( MPPARM(server->mpool) stream, &localGI ); localGI.serverRole = SERVER_ISCLIENT; - /* so it's not lost (HACK!). Without this, a client won't have a default - dict name when a new game is started. */ localGI.dictName = copyString( server->mpool, gi->dictName ); gi_copy( MPPARM(server->mpool) gi, &localGI ); @@ -1241,6 +1239,17 @@ client_readInitialMessage( ServerCtxt* server, XWStreamCtxt* stream ) newDict = util_makeEmptyDict( server->vol.util ); dict_loadFromStream( newDict, stream ); +#ifdef STREAM_VERS_BIGBOARD + if ( STREAM_VERS_DICTNAME <= streamVersion ) { + XP_UCHAR name[128]; + XP_UCHAR sum[128]; + stringFromStreamHere( stream, name, VSIZE(name) ); + stringFromStreamHere( stream, sum, VSIZE(sum) ); + util_informNetDict( server->vol.util, gi->dictName, name, + sum, localGI.phoniesAction ); + } +#endif + channelNo = stream_getAddress( stream ); XP_ASSERT( channelNo != 0 ); server->nv.addresses[0].channelNo = channelNo; @@ -1373,7 +1382,12 @@ server_sendInitialMessage( ServerCtxt* server ) gi_writeToStream( stream, &localGI ); dict_writeToStream( dict, stream ); - +#ifdef STREAM_VERS_BIGBOARD + if ( STREAM_VERS_DICTNAME <= addr->streamVersion ) { + stringToStream( stream, dict_getShortName(dict) ); + stringToStream( stream, dict_getMd5Sum(dict) ); + } +#endif /* send tiles currently in tray */ for ( ii = 0; ii < nPlayers; ++ii ) { model_trayToStream( model, ii, stream ); @@ -1393,9 +1407,9 @@ freeBWI( MPFORMAL BadWordInfo* bwi ) { XP_U16 nWords = bwi->nWords; + XP_FREEP( mpool, &bwi->dictName ); while ( nWords-- ) { - XP_FREE( mpool, (XP_UCHAR*)bwi->words[nWords] ); - bwi->words[nWords] = (XP_UCHAR*)NULL; + XP_FREEP( mpool, &bwi->words[nWords] ); } bwi->nWords = 0; @@ -1409,7 +1423,9 @@ bwiToStream( XWStreamCtxt* stream, BadWordInfo* bwi ) const XP_UCHAR** sp; stream_putBits( stream, 4, nWords ); - + if ( STREAM_VERS_DICTNAME <= stream_getVersion( stream ) ) { + stringToStream( stream, bwi->dictName ); + } for ( sp = bwi->words; nWords > 0; --nWords, ++sp ) { stringToStream( stream, *sp ); } @@ -1423,6 +1439,8 @@ bwiFromStream( MPFORMAL XWStreamCtxt* stream, BadWordInfo* bwi ) 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 ); } @@ -1861,6 +1879,7 @@ server_setGameOverListener( ServerCtxt* server, GameOverListener gol, static XP_Bool storeBadWords( const XP_UCHAR* word, XP_Bool isLegal, + const DictionaryCtxt* dict, #ifdef XWFEATURE_BOARDWORDS const MoveInfo* XP_UNUSED(movei), XP_U16 XP_UNUSED(start), XP_U16 XP_UNUSED(end), @@ -1869,9 +1888,12 @@ storeBadWords( const XP_UCHAR* word, XP_Bool isLegal, { if ( !isLegal ) { ServerCtxt* server = (ServerCtxt*)closure; + const XP_UCHAR* name = dict_getShortName( dict ); - XP_LOGF( "storeBadWords called with \"%s\"", word ); - + XP_LOGF( "storeBadWords called with \"%s\" (name=%s)", word, name ); + if ( NULL == server->illegalWordInfo.dictName ) { + server->illegalWordInfo.dictName = copyString( server->mpool, name ); + } server->illegalWordInfo.words[server->illegalWordInfo.nWords++] = copyString( server->mpool, word ); } diff --git a/xwords4/common/util.h b/xwords4/common/util.h index 9dc3a8a41..20f80c506 100644 --- a/xwords4/common/util.h +++ b/xwords4/common/util.h @@ -82,6 +82,7 @@ typedef struct PickInfo { typedef struct BadWordInfo { XP_U16 nWords; + XP_UCHAR* dictName; const XP_UCHAR* words[MAX_TRAY_TILES+1]; /* can form in both directions */ } BadWordInfo; @@ -129,6 +130,11 @@ typedef struct UtilVtable { void (*m_util_informMove)( XW_UtilCtxt* uc, XWStreamCtxt* expl, XWStreamCtxt* words ); void (*m_util_informUndo)( XW_UtilCtxt* uc ); + void (*m_util_informNetDict)( XW_UtilCtxt* uc, const XP_UCHAR* oldName, + const XP_UCHAR* newName, + const XP_UCHAR* newSum, + XWPhoniesChoice phoniesAction ); + void (*m_util_notifyGameOver)( XW_UtilCtxt* uc ); XP_Bool (*m_util_hiliteCell)( XW_UtilCtxt* uc, XP_U16 col, XP_U16 row ); @@ -244,6 +250,8 @@ struct XW_UtilCtxt { (uc)->vtable->m_util_informMove( (uc),(e),(w)) #define util_informUndo(uc) \ (uc)->vtable->m_util_informUndo( (uc)) +#define util_informNetDict(uc, on, nn, ns, pa ) \ + (uc)->vtable->m_util_informNetDict( (uc), (on), (nn), (ns), (pa) ) #define util_notifyGameOver( uc ) \ (uc)->vtable->m_util_notifyGameOver((uc)) diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c index 3380d5fc6..0db241b5e 100644 --- a/xwords4/linux/cursesmain.c +++ b/xwords4/linux/cursesmain.c @@ -394,6 +394,16 @@ curses_util_notifyGameOver( XW_UtilCtxt* uc ) } } /* curses_util_notifyGameOver */ +static void +curses_util_informNetDict( XW_UtilCtxt* uc, const XP_UCHAR* oldName, + const XP_UCHAR* newName, const XP_UCHAR* newSum, + XWPhoniesChoice phoniesAction ) +{ + XP_USE(uc); + XP_USE(phoniesAction); + XP_LOGF( "%s: %s => %s (cksum: %s)", __func__, oldName, newName, newSum ); +} + static XP_Bool curses_util_hiliteCell( XW_UtilCtxt* uc, XP_U16 XP_UNUSED(col), XP_U16 XP_UNUSED(row) ) @@ -1516,6 +1526,7 @@ setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util ) util->vtable->m_util_informMove = curses_util_informMove; util->vtable->m_util_informUndo = curses_util_informUndo; util->vtable->m_util_notifyGameOver = curses_util_notifyGameOver; + util->vtable->m_util_informNetDict = curses_util_informNetDict; util->vtable->m_util_hiliteCell = curses_util_hiliteCell; util->vtable->m_util_engineProgressCallback = curses_util_engineProgressCallback; diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c index fe50008f2..9dd3eef83 100644 --- a/xwords4/linux/gtkmain.c +++ b/xwords4/linux/gtkmain.c @@ -1458,6 +1458,24 @@ gtk_util_notifyGameOver( XW_UtilCtxt* uc ) } } /* gtk_util_notifyGameOver */ +static void +gtk_util_informNetDict( XW_UtilCtxt* uc, const XP_UCHAR* oldName, + const XP_UCHAR* newName, const XP_UCHAR* newSum, + XWPhoniesChoice phoniesAction ) +{ + GtkAppGlobals* globals = (GtkAppGlobals*)uc->closure; + gchar buf[512]; + + int offset = snprintf( buf, VSIZE(buf), + "dict changing from %s to %s (sum=%s).", + oldName, newName, newSum ); + if ( PHONIES_DISALLOW == phoniesAction ) { + snprintf( &buf[offset], VSIZE(buf)-offset, "%s", + "\nPHONIES_DISALLOW is set so this may lead to some surprises." ); + } + (void)gtkask( globals->window, buf, GTK_BUTTONS_OK ); +} + /* define this to prevent user events during debugging from stopping the engine */ /* #define DONT_ABORT_ENGINE */ @@ -1983,6 +2001,7 @@ setupGtkUtilCallbacks( GtkAppGlobals* globals, XW_UtilCtxt* util ) util->vtable->m_util_informMove = gtk_util_informMove; util->vtable->m_util_informUndo = gtk_util_informUndo; util->vtable->m_util_notifyGameOver = gtk_util_notifyGameOver; + util->vtable->m_util_informNetDict = gtk_util_informNetDict; util->vtable->m_util_hiliteCell = gtk_util_hiliteCell; util->vtable->m_util_altKeyDown = gtk_util_altKeyDown; util->vtable->m_util_engineProgressCallback = diff --git a/xwords4/linux/linuxdict.c b/xwords4/linux/linuxdict.c index 068c09372..0c9987ec6 100644 --- a/xwords4/linux/linuxdict.c +++ b/xwords4/linux/linuxdict.c @@ -49,6 +49,7 @@ typedef struct LinuxDictionaryCtxt { /************************ Prototypes ***********************/ static XP_Bool initFromDictFile( LinuxDictionaryCtxt* dctx, + const LaunchParams* params, const char* fileName ); static void linux_dictionary_destroy( DictionaryCtxt* dict ); static const XP_UCHAR* linux_dict_getShortName( const DictionaryCtxt* dict ); @@ -57,7 +58,8 @@ static const XP_UCHAR* linux_dict_getShortName( const DictionaryCtxt* dict ); * ****************************************************************************/ DictionaryCtxt* -linux_dictionary_make( MPFORMAL const char* dictFileName, XP_Bool useMMap ) +linux_dictionary_make( MPFORMAL const LaunchParams* params, + const char* dictFileName, XP_Bool useMMap ) { LinuxDictionaryCtxt* result = (LinuxDictionaryCtxt*)XP_MALLOC(mpool, sizeof(*result)); @@ -69,7 +71,7 @@ linux_dictionary_make( MPFORMAL const char* dictFileName, XP_Bool useMMap ) result->useMMap = useMMap; if ( !!dictFileName ) { - XP_Bool success = initFromDictFile( result, dictFileName ); + XP_Bool success = initFromDictFile( result, params, dictFileName ); if ( success ) { result->super.destructor = linux_dictionary_destroy; result->super.func_dict_getShortName = linux_dict_getShortName; @@ -219,7 +221,8 @@ dict_splitFaces( DictionaryCtxt* dict, const XP_U8* utf8, } /* dict_splitFaces */ static XP_Bool -initFromDictFile( LinuxDictionaryCtxt* dctx, const char* fileName ) +initFromDictFile( LinuxDictionaryCtxt* dctx, const LaunchParams* params, + const char* fileName ) { XP_Bool formatOk = XP_TRUE; long curPos, dictLength; @@ -231,15 +234,20 @@ initFromDictFile( LinuxDictionaryCtxt* dctx, const char* fileName ) XP_Bool isUTF8 = XP_FALSE; XP_Bool hasHeader = XP_FALSE; const XP_U8* ptr; + char path[256]; + if ( !getDictPath( params, fileName, path, VSIZE(path) ) ) { + XP_LOGF( "%s: path=%s", __func__, path ); + goto closeAndExit; + } struct stat statbuf; - if ( 0 != stat( fileName, &statbuf ) || 0 == statbuf.st_size ) { + if ( 0 != stat( path, &statbuf ) || 0 == statbuf.st_size ) { goto closeAndExit; } dctx->dictLength = statbuf.st_size; { - FILE* dictF = fopen( fileName, "r" ); + FILE* dictF = fopen( path, "r" ); XP_ASSERT( !!dictF ); if ( dctx->useMMap ) { dctx->dictBase = mmap( NULL, dctx->dictLength, PROT_READ, diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c index 4ae698aa0..4db5659e8 100644 --- a/xwords4/linux/linuxmain.c +++ b/xwords4/linux/linuxmain.c @@ -18,11 +18,13 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include +#include #include #include #include #include #include +#include #include /* gethostbyname */ #include @@ -74,11 +76,12 @@ #define DEFAULT_LISTEN_PORT 4998 XP_Bool -file_exists( const char* fileName ) +file_exists( const char* fileName ) { struct stat statBuf; int statResult = stat( fileName, &statBuf ); + XP_LOGF( "%s(%s)=>%d", __func__, fileName, statResult == 0 ); return statResult == 0; } /* file_exists */ @@ -489,6 +492,7 @@ typedef enum { ,CMD_TESTPRFX ,CMD_TESTMINMAX #endif + ,CMD_DICTDIR ,CMD_PLAYERDICT ,CMD_SEED ,CMD_GAMESEED @@ -581,6 +585,7 @@ static CmdInfoRec CmdInfoRecs[] = { ,{ CMD_TESTPRFX, true, "test-prefix", "list first word starting with this" } ,{ CMD_TESTMINMAX, true, "test-minmax", "M:M -- include only words whose len in range" } #endif + ,{ CMD_DICTDIR, true, "dict-dir", "path to dir in which dicts will be sought" } ,{ CMD_PLAYERDICT, true, "player-dict", "dictionary name for player (in sequence)" } ,{ CMD_SEED, true, "seed", "random seed" } ,{ CMD_GAMESEED, true, "game-seed", "game seed (for relay play)" } @@ -1382,7 +1387,7 @@ walk_dict_test_all( const LaunchParams* params, GSList* testDicts, for ( ii = 0; ii < count; ++ii ) { gchar* name = (gchar*)g_slist_nth_data( testDicts, ii ); DictionaryCtxt* dict = - linux_dictionary_make( MPPARM(params->util->mpool) name, + linux_dictionary_make( MPPARM(params->util->mpool) params, name, params->useMmap ); if ( NULL != dict ) { XP_LOGF( "walk_dict_test(%s)", name ); @@ -1393,6 +1398,55 @@ walk_dict_test_all( const LaunchParams* params, GSList* testDicts, } #endif +static void +trimDictPath( const char* input, char* buf, int bufsiz, char** path, char** dict ) +{ + struct stat statBuf; + int statResult = stat( input, &statBuf ); + if ( 0 == statResult && S_ISLNK(statBuf.st_mode) ) { + ssize_t nWritten = readlink( input, buf, bufsiz ); + buf[nWritten] = '\0'; + } else { + snprintf( buf, bufsiz, "%s", input ); + } + + char* result = strrchr( buf, '/' ); + if ( !!result ) { /* is is a full path */ + *path = buf; + *result = '\0'; /* null-terminate it */ + *dict = 1 + result; + } else { + *path = NULL; + *dict = buf; + } + char* dot = strrchr( *dict, '.' ); + if ( !!dot && 0 == strcmp(dot, ".xwd") ) { + *dot = '\0'; + } + XP_LOGF( "%s=> dict: %s; path: %s", __func__, *dict, *path ); +} + +XP_Bool +getDictPath( const LaunchParams *params, const char* name, + char* result, int resultLen ) +{ + XP_Bool success = XP_FALSE; + GSList* dictDirs; + result[0] = '\0'; + for ( dictDirs = params->dictDirs; !!dictDirs; dictDirs = dictDirs->next ) { + const char* path = dictDirs->data; + char buf[256]; + int len = snprintf( buf, VSIZE(buf), "%s/%s.xwd", path, name ); + if ( len < VSIZE(buf) && file_exists( buf ) ) { + snprintf( result, resultLen, "%s", buf ); + success = XP_TRUE; + break; + } + } + LOG_RETURNF( "%d", success ); + return success; +} + int main( int argc, char** argv ) { @@ -1413,6 +1467,9 @@ main( int argc, char** argv ) GSList* testPrefixes = NULL; char* testMinMax = NULL; #endif + char dictbuf[256]; + char* dict; + char* path; /* install a no-op signal handler. Later curses- or gtk-specific code will install one that does the right thing in that context */ @@ -1487,6 +1544,19 @@ main( int argc, char** argv ) mainParams.showRobotScores = XP_FALSE; mainParams.useMmap = XP_TRUE; + char* envDictPath = getenv( "XW_DICTSPATH" ); + if ( !!envDictPath ) { + char *saveptr; + for ( ; ; ) { + char* path = strtok_r( envDictPath, ":", &saveptr ); + if ( !path ) { + break; + } + mainParams.dictDirs = g_slist_append( mainParams.dictDirs, path ); + envDictPath = NULL; + } + } + /* serverName = mainParams.info.clientInfo.serverName = "localhost"; */ #if defined PLATFORM_GTK @@ -1526,8 +1596,12 @@ main( int argc, char** argv ) conType = COMMS_CONN_IP_DIRECT; break; case CMD_DICT: - mainParams.gi.dictName = copyString( mainParams.util->mpool, - (XP_UCHAR*)optarg ); + trimDictPath( optarg, dictbuf, VSIZE(dictbuf), &path, &dict ); + mainParams.gi.dictName = copyString( mainParams.util->mpool, dict ); + if ( !path ) { + path = "."; + } + mainParams.dictDirs = g_slist_append( mainParams.dictDirs, path ); break; #ifdef XWFEATURE_WALKDICT case CMD_TESTDICT: @@ -1540,8 +1614,16 @@ main( int argc, char** argv ) testMinMax = optarg; break; #endif + case CMD_DICTDIR: + mainParams.dictDirs = g_slist_append( mainParams.dictDirs, optarg ); + break; case CMD_PLAYERDICT: - mainParams.playerDictNames[nPlayerDicts++] = optarg; + trimDictPath( optarg, dictbuf, VSIZE(dictbuf), &path, &dict ); + mainParams.playerDictNames[nPlayerDicts++] = dict; + if ( !path ) { + path = "."; + } + mainParams.dictDirs = g_slist_append( mainParams.dictDirs, path ); break; case CMD_SEED: seed = atoi(optarg); @@ -1804,9 +1886,11 @@ main( int argc, char** argv ) } if ( !!mainParams.gi.dictName ) { + /* char path[256]; */ + /* getDictPath( &mainParams, mainParams.gi.dictName, path, VSIZE(path) ); */ mainParams.dict = - linux_dictionary_make( MPPARM(mainParams.util->mpool) - mainParams.gi.dictName, + linux_dictionary_make( MPPARM(mainParams.util->mpool) &mainParams, + mainParams.gi.dictName, mainParams.useMmap ); XP_ASSERT( !!mainParams.dict ); mainParams.gi.dictLang = dict_getLangCode( mainParams.dict ); @@ -1834,8 +1918,8 @@ main( int argc, char** argv ) const XP_UCHAR* name = mainParams.playerDictNames[ii]; if ( !!name ) { mainParams.dicts.dicts[ii] = - linux_dictionary_make( MPPARM(mainParams.util->mpool) name, - mainParams.useMmap ); + linux_dictionary_make( MPPARM(mainParams.util->mpool) + &mainParams, name, mainParams.useMmap ); } } diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h index 876329e37..f5afc7d21 100644 --- a/xwords4/linux/linuxmain.h +++ b/xwords4/linux/linuxmain.h @@ -66,6 +66,8 @@ XWStreamCtxt* streamFromFile( CommonGlobals* cGlobals, char* name, void* closure ); XWStreamCtxt* streamFromDB( CommonGlobals* cGlobals, void* closure ); void writeToFile( XWStreamCtxt* stream, void* closure ); +XP_Bool getDictPath( const LaunchParams *params, const char* name, + char* result, int resultLen ); void saveGame( CommonGlobals* cGlobals ); int blocking_read( int fd, unsigned char* buf, int len ); diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c index 65db504ec..ef8e79c5c 100644 --- a/xwords4/linux/linuxutl.c +++ b/xwords4/linux/linuxutl.c @@ -89,7 +89,7 @@ static DictionaryCtxt* linux_util_makeEmptyDict( XW_UtilCtxt* XP_UNUSED_DBG(uctx) ) { XP_DEBUGF( "linux_util_makeEmptyDict called" ); - return linux_dictionary_make( MPPARM(uctx->mpool) NULL, XP_FALSE ); + return linux_dictionary_make( MPPARM(uctx->mpool) NULL, NULL, XP_FALSE ); } /* linux_util_makeEmptyDict */ #define EM BONUS_NONE @@ -350,7 +350,6 @@ linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ) util->vtable->m_util_getSquareBonus = linux_util_getSquareBonus; util->vtable->m_util_getCurSeconds = linux_util_getCurSeconds; util->vtable->m_util_getUserString = linux_util_getUserString; - } void diff --git a/xwords4/linux/linuxutl.h b/xwords4/linux/linuxutl.h index 8685d33ea..781031a32 100644 --- a/xwords4/linux/linuxutl.h +++ b/xwords4/linux/linuxutl.h @@ -32,7 +32,9 @@ void linux_debugf(const char*, ...) __attribute__ ((format (printf, 1, 2))); #endif -DictionaryCtxt* linux_dictionary_make( MPFORMAL const char* dictFileName, XP_Bool useMmap ); +DictionaryCtxt* linux_dictionary_make( MPFORMAL const LaunchParams* mainParams, + const char* dictFileName, XP_Bool useMMap ); + void linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ); void linux_util_vt_destroy( XW_UtilCtxt* util ); diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h index 06432cec0..e3118d603 100644 --- a/xwords4/linux/main.h +++ b/xwords4/linux/main.h @@ -48,6 +48,7 @@ typedef struct LaunchParams { DictionaryCtxt* dict; CurGameInfo gi; PlayerDicts dicts; + GSList* dictDirs; char* fileName; XP_U16 saveFailPct; const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS];