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];