merge from android_branch

This commit is contained in:
Eric House 2012-09-24 07:39:44 -07:00
commit 140c982d41
54 changed files with 963 additions and 404 deletions

View file

@ -22,7 +22,7 @@
to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eehouse.android.xw4sms"
android:versionCode="43"
android:versionCode="44"
android:versionName="@string/app_version"
>

View file

@ -6,15 +6,21 @@
</head>
<body>
<b>CrossW-SMS 4.4 beta 51 release</b>
<b>CrossW-SMS 4.4 beta 52 release</b>
<p>This is first release of this variant of Crosswords featuring the
ability to play via SMS</p>
<ul>Other changes
<li>Add auto-update for wordlists and the app itself (when side-loaded)</li>
<li>Remember wordlist browser position, word size, etc.</li>
<li>Fix wordlist browser bugs for languages with more than one
letter on a tile</li>
<li>New word lookup URLs for Catalan language lists</li>
<li>Display wordlist comment if present</li>
</ul>
<p>Please remember that this is beta software. Please let me know (at

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_version">4.4 beta 51</string>
<string name="app_version">4.4 beta 52</string>
</resources>

View file

@ -22,7 +22,7 @@
to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eehouse.android.xw4"
android:versionCode="43"
android:versionCode="44"
android:versionName="@string/app_version"
>

View file

@ -141,7 +141,7 @@ andMakeBitmap( AndDictionaryCtxt* ctxt, XP_U8 const** ptrp,
JNIEnv* env = ctxt->env;
jobject tmp = and_util_makeJBitmap( ctxt->jniutil, nCols, nRows, colors );
bitmap = (*env)->NewGlobalRef( env, tmp );
(*env)->DeleteLocalRef( env, tmp );
deleteLocalRef( env, tmp );
XP_FREE( ctxt->super.mpool, colors );
#endif
}
@ -242,13 +242,13 @@ splitFaces_via_java( JNIEnv* env, AndDictionaryCtxt* ctxt, const XP_U8* ptr,
XP_MEMCPY( &facesBuf[indx], bytes, nBytes );
}
(*env)->ReleaseStringUTFChars( env, jstr, bytes );
(*env)->DeleteLocalRef( env, jstr );
deleteLocalRef( env, jstr );
indx += nBytes;
facesBuf[indx++] = '\0';
XP_ASSERT( indx < VSIZE(facesBuf) );
}
(*env)->DeleteLocalRef( env, jstrarr );
deleteLocalRef( env, jstrarr );
XP_UCHAR* faces = (XP_UCHAR*)XP_CALLOC( ctxt->super.mpool, indx );
const XP_UCHAR** ptrs = (const XP_UCHAR**)
@ -340,6 +340,28 @@ parseDict( AndDictionaryCtxt* ctxt, XP_U8 const* ptr, XP_U32 dictLength,
goto error;
}
if ( NULL == ctxt->super.md5Sum
#ifdef DEBUG
|| XP_TRUE
#endif
) {
JNIEnv* env = ctxt->env;
jstring jsum = and_util_getMD5SumFor( ctxt->jniutil, ctxt->super.name,
NULL, 0 );
if ( NULL == jsum ) {
jsum = and_util_getMD5SumFor( ctxt->jniutil, ctxt->super.name,
ptr, end - ptr );
}
XP_UCHAR* md5Sum = getStringCopy( MPPARM(ctxt->super.mpool) env, jsum );
deleteLocalRef( env, jsum );
if ( NULL == ctxt->super.md5Sum ) {
ctxt->super.md5Sum = md5Sum;
} else {
XP_ASSERT( 0 == XP_STRCMP( ctxt->super.md5Sum, md5Sum ) );
XP_FREE( ctxt->super.mpool, md5Sum );
}
}
ctxt->super.nodeSize = nodeSize;
if ( !isUTF8 ) {
@ -517,12 +539,9 @@ makeDicts( MPFORMAL JNIEnv *env, JNIUtilCtxt* jniutil,
dict = makeDict( MPPARM(mpool) env, jniutil, jname, jdict,
jpath, jlang, false );
XP_ASSERT( !!dict );
(*env)->DeleteLocalRef( env, jdict );
(*env)->DeleteLocalRef( env, jname );
}
if ( NULL != jpath) {
(*env)->DeleteLocalRef( env, jpath );
deleteLocalRefs( env, jdict, jname, DELETE_NO_REF );
}
deleteLocalRef( env, jpath );
}
if ( 0 == ii ) {
*dictp = dict;

View file

@ -84,7 +84,7 @@ getInt( JNIEnv* env, jobject obj, const char* name )
jfieldID fid = (*env)->GetFieldID( env, cls, name, "I");
XP_ASSERT( !!fid );
int result = (*env)->GetIntField( env, obj, fid );
(*env)->DeleteLocalRef( env, cls );
deleteLocalRef( env, cls );
return result;
}
@ -96,7 +96,7 @@ setInt( JNIEnv* env, jobject obj, const char* name, int value )
jfieldID fid = (*env)->GetFieldID( env, cls, name, "I");
XP_ASSERT( !!fid );
(*env)->SetIntField( env, obj, fid, value );
(*env)->DeleteLocalRef( env, cls );
deleteLocalRef( env, cls );
}
bool
@ -109,7 +109,7 @@ setBool( JNIEnv* env, jobject obj, const char* name, bool value )
(*env)->SetBooleanField( env, obj, fid, value );
success = true;
}
(*env)->DeleteLocalRef( env, cls );
deleteLocalRef( env, cls );
return success;
}
@ -120,13 +120,13 @@ setString( JNIEnv* env, jobject obj, const char* name, const XP_UCHAR* value )
bool success = false;
jclass cls = (*env)->GetObjectClass( env, obj );
jfieldID fid = (*env)->GetFieldID( env, cls, name, "Ljava/lang/String;" );
(*env)->DeleteLocalRef( env, cls );
deleteLocalRef( env, cls );
if ( 0 != fid ) {
jstring str = (*env)->NewStringUTF( env, value );
(*env)->SetObjectField( env, obj, fid, str );
success = true;
(*env)->DeleteLocalRef( env, str );
deleteLocalRef( env, str );
}
return success;
@ -148,11 +148,11 @@ getString( JNIEnv* env, jobject obj, const char* name, XP_UCHAR* buf,
const char* chars = (*env)->GetStringUTFChars( env, jstr, NULL );
XP_MEMCPY( buf, chars, len );
(*env)->ReleaseStringUTFChars( env, jstr, chars );
(*env)->DeleteLocalRef( env, jstr );
deleteLocalRef( env, jstr );
}
buf[len] = '\0';
(*env)->DeleteLocalRef( env, cls );
deleteLocalRef( env, cls );
}
XP_UCHAR*
@ -180,7 +180,7 @@ getObject( JNIEnv* env, jobject obj, const char* name, const char* sig,
*ret = (*env)->GetObjectField( env, obj, fid );
XP_ASSERT( !!*ret );
(*env)->DeleteLocalRef( env, cls );
deleteLocalRef( env, cls );
return true;
}
@ -194,7 +194,7 @@ setObject( JNIEnv* env, jobject obj, const char* name, const char* sig,
XP_ASSERT( !!fid );
(*env)->SetObjectField( env, obj, fid, val );
(*env)->DeleteLocalRef( env, cls );
deleteLocalRef( env, cls );
}
bool
@ -206,7 +206,7 @@ getBool( JNIEnv* env, jobject obj, const char* name )
jfieldID fid = (*env)->GetFieldID( env, cls, name, "Z");
XP_ASSERT( !!fid );
result = (*env)->GetBooleanField( env, obj, fid );
(*env)->DeleteLocalRef( env, cls );
deleteLocalRef( env, cls );
return result;
}
@ -238,6 +238,17 @@ makeByteArray( JNIEnv *env, int siz, const jbyte* vals )
return array;
}
jbyteArray
streamToBArray( JNIEnv *env, XWStreamCtxt* stream )
{
int nBytes = stream_getSize( stream );
jbyteArray result = (*env)->NewByteArray( env, nBytes );
jbyte* jelems = (*env)->GetByteArrayElements( env, result, NULL );
stream_getBytes( stream, jelems, nBytes );
(*env)->ReleaseByteArrayElements( env, result, jelems, 0 );
return result;
}
void
setBoolArray( JNIEnv* env, jbooleanArray jarr, int count,
const jboolean* vals )
@ -266,7 +277,7 @@ getIntFromArray( JNIEnv* env, jintArray arr, bool del )
int result = ints[0];
(*env)->ReleaseIntArrayElements( env, arr, ints, 0);
if ( del ) {
(*env)->DeleteLocalRef( env, arr );
deleteLocalRef( env, arr );
}
return result;
}
@ -277,14 +288,13 @@ makeStringArray( JNIEnv *env, int siz, const XP_UCHAR** vals )
jclass clas = (*env)->FindClass(env, "java/lang/String");
jstring empty = (*env)->NewStringUTF( env, "" );
jobjectArray jarray = (*env)->NewObjectArray( env, siz, clas, empty );
(*env)->DeleteLocalRef( env, clas );
(*env)->DeleteLocalRef( env, empty );
deleteLocalRefs( env, clas, empty, DELETE_NO_REF );
int ii;
for ( ii = 0; !!vals && ii < siz; ++ii ) {
jstring jstr = (*env)->NewStringUTF( env, vals[ii] );
(*env)->SetObjectArrayElement( env, jarray, ii, jstr );
(*env)->DeleteLocalRef( env, jstr );
deleteLocalRef( env, jstr );
}
return jarray;
@ -311,7 +321,7 @@ getMethodID( JNIEnv* env, jobject obj, const char* proc, const char* sig )
XP_ASSERT( !!cls );
jmethodID mid = (*env)->GetMethodID( env, cls, proc, sig );
XP_ASSERT( !!mid );
(*env)->DeleteLocalRef( env, cls );
deleteLocalRef( env, cls );
return mid;
}
@ -400,8 +410,7 @@ jenumFieldToInt( JNIEnv* env, jobject j_gi, const char* field,
XP_ASSERT( !!jenum );
jint result = jEnumToInt( env, jenum );
(*env)->DeleteLocalRef( env, clazz );
(*env)->DeleteLocalRef( env, jenum );
deleteLocalRefs( env, clazz, jenum, DELETE_NO_REF );
return result;
}
@ -415,7 +424,7 @@ intToJenumField( JNIEnv* env, jobject jobj, int val, const char* field,
snprintf( buf, sizeof(buf), "L%s;", fieldSig );
jfieldID fid = (*env)->GetFieldID( env, clazz, field, buf );
XP_ASSERT( !!fid ); /* failed */
(*env)->DeleteLocalRef( env, clazz );
deleteLocalRef( env, clazz );
jobject jenum = (*env)->GetObjectField( env, jobj, fid );
if ( !jenum ) { /* won't exist in new object */
@ -426,13 +435,13 @@ intToJenumField( JNIEnv* env, jobject jobj, int val, const char* field,
jenum = (*env)->NewObject( env, clazz, mid );
XP_ASSERT( !!jenum );
(*env)->SetObjectField( env, jobj, fid, jenum );
(*env)->DeleteLocalRef( env, clazz );
deleteLocalRef( env, clazz );
}
jobject jval = intToJEnum( env, val, fieldSig );
XP_ASSERT( !!jval );
(*env)->SetObjectField( env, jobj, fid, jval );
(*env)->DeleteLocalRef( env, jval );
deleteLocalRef( env, jval );
} /* intToJenumField */
/* Cons up a new enum instance and set its value */
@ -455,8 +464,7 @@ intToJEnum( JNIEnv* env, int val, const char* enumSig )
jenum = (*env)->GetObjectArrayElement( env, jvalues, val );
XP_ASSERT( !!jenum );
(*env)->DeleteLocalRef( env, jvalues );
(*env)->DeleteLocalRef( env, clazz );
deleteLocalRefs( env, jvalues, clazz, DELETE_NO_REF );
return jenum;
} /* intToJEnum */
@ -476,6 +484,28 @@ and_empty_stream( MPFORMAL AndGlobals* globals )
return stream;
}
void deleteLocalRef( JNIEnv* env, jobject jobj )
{
if ( NULL != jobj ) {
(*env)->DeleteLocalRef( env, jobj );
}
}
void
deleteLocalRefs( JNIEnv* env, jobject jobj, ... )
{
va_list ap;
va_start( ap, jobj );
for ( ; ; ) {
jobject jnext = va_arg( ap, jobject );
if ( DELETE_NO_REF == jnext ) {
break;
}
deleteLocalRef( env, jnext );
}
va_end( ap );
}
#ifdef DEBUG
void
android_debugf( const char* format, ... )

View file

@ -57,6 +57,7 @@ void setBoolArray( JNIEnv* env, jbooleanArray jarr, int count,
jobjectArray makeStringArray( JNIEnv *env, int size, const XP_UCHAR** vals );
jstring streamToJString( JNIEnv* env, XWStreamCtxt* stream );
jbyteArray streamToBArray( JNIEnv *env, XWStreamCtxt* stream );
/* Note: jmethodID can be cached. Should not look up more than once. */
jmethodID getMethodID( JNIEnv* env, jobject obj, const char* proc,
@ -70,4 +71,8 @@ void intToJenumField( JNIEnv* env, jobject jobj, int val, const char* field,
const char* fieldSig );
jobject intToJEnum( JNIEnv* env, int val, const char* enumSig );
jint jEnumToInt( JNIEnv* env, jobject jenum );
void deleteLocalRef( JNIEnv* env, jobject jobj );
void deleteLocalRefs( JNIEnv* env, jobject jobj, ... );
# define DELETE_NO_REF ((jobject)-1) /* terminates above varargs list */
#endif

View file

@ -59,10 +59,8 @@ makeJRect( AndDraw* draw, int indx, const XP_Rect* rect )
robj = (*env)->NewObject( env, rclass, initId, rect->left, rect->top,
right, bottom );
(*env)->DeleteLocalRef( env, rclass );
draw->jCache[indx] = (*env)->NewGlobalRef( env, robj );
(*env)->DeleteLocalRef( env, robj );
deleteLocalRefs( env, robj, rclass, DELETE_NO_REF );
robj = draw->jCache[indx];
} else {
setInt( env, robj, "left", rect->left );
@ -94,7 +92,6 @@ makeJRects( AndDraw* draw, int indx, XP_U16 nPlayers, const XP_Rect rects[] )
jclass rclass = (*env)->FindClass( env, "android/graphics/Rect");
jrects = (*env)->NewObjectArray( env, nPlayers, rclass, NULL );
draw->jCache[indx] = (*env)->NewGlobalRef( env, jrects );
(*env)->DeleteLocalRef( env, jrects );
jrects = draw->jCache[indx];
jmethodID initId = (*env)->GetMethodID( env, rclass, "<init>",
@ -103,10 +100,10 @@ makeJRects( AndDraw* draw, int indx, XP_U16 nPlayers, const XP_Rect rects[] )
for ( ii = 0; ii < nPlayers; ++ii ) {
jobject jrect = (*env)->NewObject( env, rclass, initId );
(*env)->SetObjectArrayElement( env, jrects, ii, jrect );
(*env)->DeleteLocalRef( env, jrect );
deleteLocalRef( env, jrect );
}
(*env)->DeleteLocalRef( env, rclass );
deleteLocalRefs( env, rclass, jrects, DELETE_NO_REF );
}
if ( NULL != rects ) {
@ -131,17 +128,17 @@ makeDSIs( AndDraw* draw, int indx, XP_U16 nPlayers, const DrawScoreInfo dsis[] )
jclass clas = (*env)->FindClass( env, PKG_PATH("jni/DrawScoreInfo") );
dsiobjs = (*env)->NewObjectArray( env, nPlayers, clas, NULL );
draw->jCache[indx] = (*env)->NewGlobalRef( env, dsiobjs );
(*env)->DeleteLocalRef( env, dsiobjs );
deleteLocalRef( env, dsiobjs );
dsiobjs = draw->jCache[indx];
jmethodID initId = (*env)->GetMethodID( env, clas, "<init>", "()V" );
for ( ii = 0; ii < nPlayers; ++ii ) {
jobject dsiobj = (*env)->NewObject( env, clas, initId );
(*env)->SetObjectArrayElement( env, dsiobjs, ii, dsiobj );
(*env)->DeleteLocalRef( env, dsiobj );
deleteLocalRef( env, dsiobj );
}
(*env)->DeleteLocalRef( env, clas );
deleteLocalRef( env, clas );
}
for ( ii = 0; ii < nPlayers; ++ii ) {
@ -173,10 +170,9 @@ makeDSI( AndDraw* draw, int indx, const DrawScoreInfo* dsi )
jclass rclass = (*env)->FindClass( env, PKG_PATH("jni/DrawScoreInfo") );
jmethodID initId = (*env)->GetMethodID( env, rclass, "<init>", "()V" );
dsiobj = (*env)->NewObject( env, rclass, initId );
(*env)->DeleteLocalRef( env, rclass );
draw->jCache[indx] = (*env)->NewGlobalRef( env, dsiobj );
(*env)->DeleteLocalRef( env, dsiobj );
deleteLocalRefs( env, rclass, dsiobj, DELETE_NO_REF );
dsiobj = draw->jCache[indx];
}
@ -219,7 +215,7 @@ and_draw_scoreBegin( DrawCtx* dctx, const XP_Rect* rect,
result = (*env)->CallBooleanMethod( env, draw->jdraw, mid,
jrect, numPlayers, jscores, remCount );
(*env)->DeleteLocalRef( env, jscores );
deleteLocalRef( env, jscores );
return result;
}
@ -389,9 +385,7 @@ and_draw_drawCell( DrawCtx* dctx, const XP_Rect* rect, const XP_UCHAR* text,
jrect, jtext, tile, value,
owner, bonus, hintAtts,
flags );
if ( !!jtext ) {
(*env)->DeleteLocalRef( env, jtext );
}
deleteLocalRef( env, jtext );
return result;
}
@ -444,9 +438,7 @@ and_draw_drawTile( DrawCtx* dctx, const XP_Rect* rect, const XP_UCHAR* text,
(*env)->CallVoidMethod( env, draw->jdraw, mid,
jrect, jtext, val, flags );
if ( !!jtext ) {
(*env)->DeleteLocalRef( env, jtext );
}
deleteLocalRef( env, jtext );
}
static void
@ -466,9 +458,7 @@ and_draw_drawTileMidDrag( DrawCtx* dctx, const XP_Rect* rect,
(*env)->CallVoidMethod( env, draw->jdraw, mid,
jrect, jtext, val, owner, flags );
if ( !!jtext ) {
(*env)->DeleteLocalRef( env, jtext );
}
deleteLocalRef( env, jtext );
}
static void
@ -550,7 +540,7 @@ and_draw_getMiniWText( DrawCtx* dctx, XWMiniTextType textHint )
const char* str = (*env)->GetStringUTFChars( env, jstr, NULL );
snprintf( draw->miniTextBuf, VSIZE(draw->miniTextBuf), "%s", str );
(*env)->ReleaseStringUTFChars( env, jstr, str );
(*env)->DeleteLocalRef( env, jstr );
deleteLocalRef( env, jstr );
return draw->miniTextBuf;
}
@ -567,7 +557,7 @@ and_draw_measureMiniWText( DrawCtx* dctx, const XP_UCHAR* textP,
(*env)->CallVoidMethod( env, draw->jdraw, mid,
jstr, widthArray, heightArray );
(*env)->DeleteLocalRef( env, jstr );
deleteLocalRef( env, jstr );
*width = getIntFromArray( env, widthArray, true );
*height = getIntFromArray( env, heightArray, true );
}
@ -585,7 +575,7 @@ and_draw_drawMiniWindow( DrawCtx* dctx, const XP_UCHAR* text,
(*env)->CallVoidMethod( env, draw->jdraw, mid,
jstr, jrect );
(*env)->DeleteLocalRef( env, jstr );
deleteLocalRef( env, jstr );
}
#endif

View file

@ -68,7 +68,7 @@ and_util_makeJBitmap( JNIUtilCtxt* jniutil, int nCols, int nRows,
bitmap = (*env)->CallObjectMethod( env, jniutil->jjniutil, mid,
nCols, nRows, jcolors );
(*env)->DeleteLocalRef( env, jcolors );
deleteLocalRef( env, jcolors );
return bitmap;
}
@ -84,14 +84,26 @@ and_util_splitFaces( JNIUtilCtxt* jniutil, const XP_U8* bytes, jsize len,
= getMethodID( env, jniutil->jjniutil, "splitFaces",
"([BZ)[Ljava/lang/String;" );
jbyteArray jbytes = (*env)->NewByteArray( env, len );
jbyteArray jbytes = makeByteArray( env, len, (jbyte*)bytes );
strarray =
(*env)->CallObjectMethod( env, jniutil->jjniutil, mid, jbytes, isUTF8 );
deleteLocalRef( env, jbytes );
jbyte* jp = (*env)->GetByteArrayElements( env, jbytes, NULL );
XP_MEMCPY( jp, bytes, len );
(*env)->ReleaseByteArrayElements( env, jbytes, jp, 0 );
strarray = (*env)->CallObjectMethod( env, jniutil->jjniutil, mid, jbytes,
isUTF8 );
(*env)->DeleteLocalRef( env, jbytes );
return strarray;
}
jstring
and_util_getMD5SumFor( JNIUtilCtxt* jniutil, const XP_UCHAR* name,
const XP_U8* bytes, jsize len )
{
JNIEnv* env = *jniutil->envp;
jmethodID mid = getMethodID( env, jniutil->jjniutil, "getMD5SumFor",
"(Ljava/lang/String;[B)Ljava/lang/String;" );
jstring jname = (*env)->NewStringUTF( env, name );
jbyteArray jbytes = NULL == bytes? NULL
: makeByteArray( env, len, (jbyte*)bytes );
jstring result =
(*env)->CallObjectMethod( env, jniutil->jjniutil, mid, jname, jbytes );
deleteLocalRefs( env, jname, jbytes, DELETE_NO_REF );
return result;
}

View file

@ -35,5 +35,7 @@ jobject and_util_makeJBitmap( JNIUtilCtxt* jniu, int nCols, int nRows,
const jboolean* colors );
jobject and_util_splitFaces( JNIUtilCtxt* jniu, const XP_U8* bytes, int len,
XP_Bool isUTF8 );
jstring and_util_getMD5SumFor( JNIUtilCtxt* jniutil, const XP_UCHAR* name,
const XP_U8* bytes, jsize len );
#endif

View file

@ -129,9 +129,7 @@ and_util_userQuery( XW_UtilCtxt* uc, UtilQueryID id, XWStreamCtxt* stream )
jstr = streamToJString( env, stream );
}
result = (*env)->CallBooleanMethod( env, util->jutil, mid, id, jstr );
if ( NULL != jstr ) {
(*env)->DeleteLocalRef( env, jstr );
}
deleteLocalRef( env, jstr );
UTIL_CBK_TAIL();
return result;
}
@ -143,7 +141,7 @@ and_util_confirmTrade( XW_UtilCtxt* uc, const XP_UCHAR** tiles, XP_U16 nTiles )
UTIL_CBK_HEADER("confirmTrade", "([Ljava/lang/String;)Z" );
jobjectArray jtiles = makeStringArray( env, nTiles, tiles );
result = (*env)->CallBooleanMethod( env, util->jutil, mid, jtiles );
(*env)->DeleteLocalRef( env, jtiles );
deleteLocalRef( env, jtiles );
UTIL_CBK_TAIL();
return result;
}
@ -160,7 +158,7 @@ and_util_userPickTileBlank( XW_UtilCtxt* uc, XP_U16 playerNum,
result = (*env)->CallIntMethod( env, util->jutil, mid,
playerNum, jtexts );
(*env)->DeleteLocalRef( env, jtexts );
deleteLocalRef( env, jtexts );
UTIL_CBK_TAIL();
return result;
}
@ -178,8 +176,7 @@ and_util_userPickTileTray( XW_UtilCtxt* uc, const PickInfo* pi,
result = (*env)->CallIntMethod( env, util->jutil, mid,
playerNum, jtexts, jcurtiles,
pi->thisPick );
(*env)->DeleteLocalRef( env, jtexts );
(*env)->DeleteLocalRef( env, jcurtiles );
deleteLocalRefs( env, jtexts, jcurtiles, DELETE_NO_REF );
UTIL_CBK_TAIL();
return result;
@ -195,7 +192,7 @@ and_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name,
jstring jname = (*env)->NewStringUTF( env, name );
jstring jstr = (*env)->CallObjectMethod( env, util->jutil, mid,
jname );
(*env)->DeleteLocalRef( env, jname );
deleteLocalRef( env, jname );
if ( NULL != jstr ) { /* null means user cancelled */
jsize jsiz = (*env)->GetStringUTFLength( env, jstr );
@ -207,7 +204,7 @@ and_util_askPassword( XW_UtilCtxt* uc, const XP_UCHAR* name,
*len = jsiz;
result = XP_TRUE;
}
(*env)->DeleteLocalRef( env, jstr );
deleteLocalRef( env, jstr );
}
UTIL_CBK_TAIL();
@ -253,10 +250,7 @@ and_util_informMove( XW_UtilCtxt* uc, XWStreamCtxt* expl, XWStreamCtxt* words )
jstring jexpl = streamToJString( env, expl );
jstring jwords = !!words ? streamToJString( env, words ) : NULL;
(*env)->CallVoidMethod( env, util->jutil, mid, jexpl, jwords );
(*env)->DeleteLocalRef( env, jexpl );
if ( !!jwords ) {
(*env)->DeleteLocalRef( env, jwords );
}
deleteLocalRefs( env, jexpl, jwords, DELETE_NO_REF );
UTIL_CBK_TAIL();
}
@ -415,7 +409,7 @@ and_util_getUserString( XW_UtilCtxt* uc, XP_U16 stringCode )
XP_MEMCPY( buf, jchars, len );
buf[len] = '\0';
(*env)->ReleaseStringUTFChars( env, jresult, jchars );
(*env)->DeleteLocalRef( env, jresult );
deleteLocalRef( env, jresult );
util->userStrings[index] = buf;
}
@ -440,8 +434,7 @@ and_util_warnIllegalWord( XW_UtilCtxt* uc, BadWordInfo* bwi,
jstring jname = (*env)->NewStringUTF( env, bwi->dictName );
result = (*env)->CallBooleanMethod( env, util->jutil, mid,
jname, jwords, turn, turnLost );
(*env)->DeleteLocalRef( env, jwords );
(*env)->DeleteLocalRef( env, jname );
deleteLocalRefs( env, jwords, jname, DELETE_NO_REF );
}
UTIL_CBK_TAIL();
return result;
@ -453,7 +446,7 @@ and_util_showChat( XW_UtilCtxt* uc, const XP_UCHAR const* msg )
UTIL_CBK_HEADER("showChat", "(Ljava/lang/String;)V" );
jstring jmsg = (*env)->NewStringUTF( env, msg );
(*env)->CallVoidMethod( env, util->jutil, mid, jmsg );
(*env)->DeleteLocalRef( env, jmsg );
deleteLocalRef( env, jmsg );
UTIL_CBK_TAIL();
}
@ -495,8 +488,7 @@ and_util_phoneNumbersSame( XW_UtilCtxt* uc, const XP_UCHAR* p1,
jstring js1 = (*env)->NewStringUTF( env, p1 );
jstring js2 = (*env)->NewStringUTF( env, p2 );
same = (*env)->CallBooleanMethod( env, util->jutil, mid, js1, js2 );
(*env)->DeleteLocalRef( env, js1 );
(*env)->DeleteLocalRef( env, js2 );
deleteLocalRefs( env, js1, js2, DELETE_NO_REF );
UTIL_CBK_TAIL();
}
return same;
@ -511,7 +503,7 @@ and_util_cellSquareHeld( XW_UtilCtxt* uc, XWStreamCtxt* words )
UTIL_CBK_HEADER( "cellSquareHeld", "(Ljava/lang/String;)V" );
jstring jwords = streamToJString( env, words );
(*env)->CallVoidMethod( env, util->jutil, mid, jwords );
(*env)->DeleteLocalRef( env, jwords );
deleteLocalRef( env, jwords );
UTIL_CBK_TAIL();
}
}
@ -528,7 +520,7 @@ and_util_informMissing(XW_UtilCtxt* uc, XP_Bool isServer,
jobject jtyp = intToJEnum( env, connType,
PKG_PATH("jni/CommsAddrRec$CommsConnType") );
(*env)->CallVoidMethod( env, util->jutil, mid, isServer, jtyp, nMissing );
(*env)->DeleteLocalRef( env, jtyp );
deleteLocalRef( env, jtyp );
UTIL_CBK_TAIL();
}

View file

@ -45,7 +45,7 @@ makeJAddr( JNIEnv* env, const CommsAddrRec* addr )
setJAddrRec( env, jaddr, addr );
(*env)->DeleteLocalRef( env, clazz );
deleteLocalRef( env, clazz );
}
return jaddr;
}
@ -82,11 +82,7 @@ and_xport_send( const XP_U8* buf, XP_U16 len, const CommsAddrRec* addr,
result = (*env)->CallIntMethod( env, aprocs->jxport, mid,
jbytes, jaddr, gameID );
if ( NULL != jaddr ) {
(*env)->DeleteLocalRef( env, jaddr );
}
(*env)->DeleteLocalRef( env, jbytes );
deleteLocalRefs( env, jaddr, jbytes, DELETE_NO_REF );
}
LOG_RETURNF( "%d", result );
return result;
@ -105,7 +101,7 @@ and_xport_relayStatus( void* closure, CommsRelayState newState )
jobject jenum = intToJEnum( env, newState,
PKG_PATH("jni/TransportProcs$CommsRelayState") );
(*env)->CallVoidMethod( env, aprocs->jxport, mid, jenum );
(*env)->DeleteLocalRef( env, jenum );
deleteLocalRef( env, jenum );
}
}
@ -122,7 +118,7 @@ and_xport_relayConnd( void* closure, XP_UCHAR* const room, XP_Bool reconnect,
jstring str = (*env)->NewStringUTF( env, room );
(*env)->CallVoidMethod( env, aprocs->jxport, mid,
str, devOrder, allHere, nMissing );
(*env)->DeleteLocalRef( env, str );
deleteLocalRef( env, str );
}
}
@ -142,8 +138,7 @@ and_xport_sendNoConn( const XP_U8* buf, XP_U16 len,
jstring str = (*env)->NewStringUTF( env, relayID );
result = (*env)->CallBooleanMethod( env, aprocs->jxport, mid,
jbytes, str );
(*env)->DeleteLocalRef( env, jbytes );
(*env)->DeleteLocalRef( env, str );
deleteLocalRefs( env, jbytes, str, DELETE_NO_REF );
}
return result;
}
@ -163,7 +158,7 @@ and_xport_relayError( void* closure, XWREASON relayErr )
PKG_PATH("jni/TransportProcs$XWRELAY_ERROR") );
(*env)->CallVoidMethod( env, aprocs->jxport, mid, jenum );
(*env)->DeleteLocalRef( env, jenum );
deleteLocalRef( env, jenum );
}
}

View file

@ -100,9 +100,9 @@ makeGI( MPFORMAL JNIEnv* env, jobject j_gi )
lp->secondsUsed = 0;
(*env)->DeleteLocalRef( env, jlp );
deleteLocalRef( env, jlp );
}
(*env)->DeleteLocalRef( env, jplayers );
deleteLocalRef( env, jplayers );
} else {
XP_ASSERT(0);
}
@ -147,9 +147,9 @@ setJGI( JNIEnv* env, jobject jgi, const CurGameInfo* gi )
setString( env, jlp, "dictName", lp->dictName );
setInt( env, jlp, "secondsUsed", lp->secondsUsed );
(*env)->DeleteLocalRef( env, jlp );
deleteLocalRef( env, jlp );
}
(*env)->DeleteLocalRef( env, jplayers );
deleteLocalRef( env, jplayers );
} else {
XP_ASSERT(0);
}
@ -185,9 +185,9 @@ loadCommonPrefs( JNIEnv* env, CommonPrefs* cp, jobject j_cp )
static XWStreamCtxt*
streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream )
{
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) vtMgr,
NULL, 0, NULL );
int len = (*env)->GetArrayLength( env, jstream );
XWStreamCtxt* stream = mem_stream_make_sized( MPPARM(mpool) vtMgr,
len, NULL, 0, NULL );
jbyte* jelems = (*env)->GetByteArrayElements( env, jstream, NULL );
stream_putBytes( stream, jelems, len );
(*env)->ReleaseByteArrayElements( env, jstream, jelems, 0 );
@ -211,14 +211,10 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1to_1stream
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) vtMgr,
NULL, 0, NULL );
game_saveToStream( NULL, gi, stream );
game_saveToStream( NULL, gi, stream, 0 );
destroyGI( MPPARM(mpool) &gi );
int nBytes = stream_getSize( stream );
result = (*env)->NewByteArray( env, nBytes );
jbyte* jelems = (*env)->GetByteArrayElements( env, result, NULL );
stream_getBytes( stream, jelems, nBytes );
(*env)->ReleaseByteArrayElements( env, result, jelems, 0 );
result = streamToBArray( env, stream );
stream_destroy( stream );
vtmgr_destroy( MPPARM(mpool) vtMgr );
@ -285,7 +281,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getUUID
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo
( JNIEnv* env, jclass C, jbyteArray jDictBytes, jstring jpath,
( JNIEnv* env, jclass C, jbyteArray jDictBytes, jstring jname, jstring jpath,
jobject jniu, jboolean check, jobject jinfo )
{
jboolean result = false;
@ -293,7 +289,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo
MemPoolCtx* mpool = mpool_make();
#endif
JNIUtilCtxt* jniutil = makeJNIUtil( MPPARM(mpool) &env, jniu );
DictionaryCtxt* dict = makeDict( MPPARM(mpool) env, jniutil, NULL,
DictionaryCtxt* dict = makeDict( MPPARM(mpool) env, jniutil, jname,
jDictBytes, jpath, NULL, check );
if ( NULL != dict ) {
if ( NULL != jinfo ) {
@ -344,6 +340,8 @@ typedef struct _JNIState {
XWGame game;
JNIEnv* env;
AndGlobals globals;
XP_U16 curSaveCount;
XP_U16 lastSavedSize;
#ifdef DEBUG
const char* envSetterFunc;
#endif
@ -533,26 +531,33 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1saveToStream
ours should -- changes like remote players being added. */
CurGameInfo* gi =
(NULL == jgi) ? globals->gi : makeGI( MPPARM(mpool) env, jgi );
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globals->vtMgr,
NULL, 0, NULL );
XWStreamCtxt* stream = mem_stream_make_sized( MPPARM(mpool) globals->vtMgr,
state->lastSavedSize,
NULL, 0, NULL );
game_saveToStream( &state->game, gi, stream );
game_saveToStream( &state->game, gi, stream, ++state->curSaveCount );
if ( NULL != jgi ) {
destroyGI( MPPARM(mpool) &gi );
}
int nBytes = stream_getSize( stream );
result = (*env)->NewByteArray( env, nBytes );
jbyte* jelems = (*env)->GetByteArrayElements( env, result, NULL );
stream_getBytes( stream, jelems, nBytes );
(*env)->ReleaseByteArrayElements( env, result, jelems, 0 );
state->lastSavedSize = stream_getSize( stream );
result = streamToBArray( env, stream );
stream_destroy( stream );
XWJNI_END();
return result;
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_game_1saveSucceeded
( JNIEnv* env, jclass C, jint gamePtr )
{
XWJNI_START();
game_saveSucceeded( &state->game, state->curSaveCount );
XWJNI_END();
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_board_1invalAll
( JNIEnv *env, jclass C, jint gamePtr )
@ -1039,9 +1044,9 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getAddrs
jobject jaddr = (*env)->NewObject( env, clas, initId );
setJAddrRec( env, jaddr, &addrs[ii] );
(*env)->SetObjectArrayElement( env, result, ii, jaddr );
(*env)->DeleteLocalRef( env, jaddr );
deleteLocalRef( env, jaddr );
}
(*env)->DeleteLocalRef( env, clas );
deleteLocalRef( env, clas );
XWJNI_END();
return result;
@ -1149,7 +1154,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1summarize
jobjectArray jaddrs = makeStringArray( env, count, addrps );
setObject( env, jsummary, "remoteDevs", "[Ljava/lang/String;",
jaddrs );
(*env)->DeleteLocalRef( env, jaddrs );
deleteLocalRef( env, jaddrs );
#endif
}
}
@ -1170,7 +1175,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1summarize
}
jintArray jarr = makeIntArray( env, nPlayers, jvals );
setObject( env, jsummary, "scores", "[I", jarr );
(*env)->DeleteLocalRef( env, jarr );
deleteLocalRef( env, jarr );
XWJNI_END();
}
@ -1369,7 +1374,8 @@ typedef struct _DictIterData {
JNIEXPORT jint JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1init
(JNIEnv* env, jclass C, jbyteArray jDictBytes, jstring jpath, jobject jniu )
( JNIEnv* env, jclass C, jbyteArray jDictBytes, jstring jname,
jstring jpath, jobject jniu )
{
jint closure = 0;
#ifdef MEM_DEBUG
@ -1378,7 +1384,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1init
DictIterData* data = XP_CALLOC( mpool, sizeof(*data) );
data->env = env;
JNIUtilCtxt* jniutil = makeJNIUtil( MPPARM(mpool) &data->env, jniu );
DictionaryCtxt* dict = makeDict( MPPARM(mpool) env, jniutil, NULL,
DictionaryCtxt* dict = makeDict( MPPARM(mpool) env, jniutil, jname,
jDictBytes, jpath, NULL, false );
if ( !!dict ) {
data->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
@ -1525,7 +1531,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1getPrefixes
depth, buf, VSIZE(buf) );
jstring jstr = (*env)->NewStringUTF( env, buf );
(*env)->SetObjectArrayElement( env, result, ii, jstr );
(*env)->DeleteLocalRef( env, jstr );
deleteLocalRef( env, jstr );
}
}
return result;
@ -1622,10 +1628,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_base64Decode
XP_U8 out[inlen];
XP_U16 outlen = VSIZE(out);
if ( smsToBin( out, &outlen, instr, inlen ) ) {
result = (*env)->NewByteArray( env, outlen );
jbyte* jelems = (*env)->GetByteArrayElements( env, result, NULL );
XP_MEMCPY( jelems, out, outlen );
(*env)->ReleaseByteArrayElements( env, result, jelems, 0 );
result = makeByteArray( env, outlen, (jbyte*)out );
} else {
XP_ASSERT(0);
}

View file

@ -5,9 +5,16 @@
</style>
</head>
<body>
<b>Crosswords 4.4 beta 51 release</b>
<b>Crosswords 4.4 beta 52 release</b>
<ul>
<li>Add auto-update for wordlists and the app itself (when side-loaded)</li>
<li>Remember wordlist browser position, word size, etc.</li>
<li>Fix wordlist browser bugs for languages with more than one
letter on a tile</li>
<li>New word lookup URLs for Catalan language lists</li>
<li>Display wordlist comment if present</li>
</ul>
<p>Please remember that this is beta software. Please let me know (at

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_version">4.4 beta 51</string>
<string name="app_version">4.4 beta 52</string>
</resources>

View file

@ -241,6 +241,10 @@
<item>:ca:</item>
<item>http://ca.wiktionary.org/w/index.php?search=%2$s</item>
<!-- -->
<item>DISC lookup</item>
<item>:ca:</item>
<item>http://escrable.montane.cat/diccionari/cercador/?mot=%2$s</item>
<!-- -->
<item>Google</item>
<item></item> <!-- means all supported -->
<item>http://www.google.com/search?nl=%1$s\u0026q=%2$s</item>

View file

@ -448,7 +448,7 @@ public class BoardActivity extends XWActivity
}
m_utils = new BoardUtilCtxt();
m_jniu = JNIUtilsImpl.get();
m_jniu = JNIUtilsImpl.get( this );
setContentView( R.layout.board );
m_timers = new TimerRunnable[4]; // needs to be in sync with
// XWTimerReason

View file

@ -28,8 +28,10 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String TABLE_NAME_SUM = "summaries";
public static final String TABLE_NAME_OBITS = "obits";
public static final String TABLE_NAME_DICTBROWSE = "dictbrowse";
public static final String TABLE_NAME_DICTINFO = "dictinfo";
private static final String DB_NAME = "xwdb";
private static final int DB_VERSION = 12;
private static final int DB_VERSION = 13;
public static final String GAME_NAME = "GAME_NAME";
public static final String NUM_MOVES = "NUM_MOVES";
@ -60,6 +62,19 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String RELAYID = "RELAYID";
public static final String SEED = "SEED";
public static final String SMSPHONE = "SMSPHONE";
public static final String DICTNAME = "DICTNAME";
public static final String MD5SUM = "MD5SUM";
public static final String WORDCOUNT = "WORDCOUNT";
public static final String WORDCOUNTS = "WORDCOUNTS";
public static final String LANGCODE = "LANGCODE";
public static final String LOC = "LOC";
public static final String ITERMIN = "ITERMIN";
public static final String ITERMAX = "ITERMAX";
public static final String ITERPOS = "ITERPOS";
public static final String ITERTOP = "ITERTOP";
public static final String ITERPREFIX = "ITERPREFIX";
// not used yet
public static final String CREATE_TIME = "CREATE_TIME";
// not used yet
@ -118,8 +133,30 @@ public class DBHelper extends SQLiteOpenHelper {
{
db.execSQL( "CREATE TABLE " + TABLE_NAME_OBITS + " ("
+ RELAYID + " TEXT,"
+ SEED + " INTEGER"
+ ");" );
+ SEED + " INTEGER);"
);
}
private void onCreateDictsDB( SQLiteDatabase db )
{
db.execSQL( "CREATE TABLE " + TABLE_NAME_DICTINFO + "("
+ DICTNAME + " TEXT,"
+ LOC + " UNSIGNED INTEGER(1),"
+ MD5SUM + " TEXT(32),"
+ WORDCOUNT + " INTEGER,"
+ LANGCODE + " INTEGER);"
);
db.execSQL( "CREATE TABLE " + TABLE_NAME_DICTBROWSE + "("
+ DICTNAME + " TEXT,"
+ LOC + " UNSIGNED INTEGER(1),"
+ WORDCOUNTS + " TEXT,"
+ ITERMIN + " INTEGER(4),"
+ ITERMAX + " INTEGER(4),"
+ ITERPOS + " INTEGER,"
+ ITERTOP + " INTEGER,"
+ ITERPREFIX + " TEXT);"
);
}
@Override
@ -127,6 +164,7 @@ public class DBHelper extends SQLiteOpenHelper {
{
onCreateSum( db );
onCreateObits( db );
onCreateDictsDB( db );
}
@Override
@ -154,6 +192,7 @@ public class DBHelper extends SQLiteOpenHelper {
case 11:
addColumn( db, REMOTEDEVS, "TEXT" );
case 12:
onCreateDictsDB( db );
// nothing yet
break;
default:

View file

@ -45,6 +45,7 @@ import java.util.StringTokenizer;
import junit.framework.Assert;
import org.eehouse.android.xw4.jni.*;
import org.eehouse.android.xw4.DictUtils.DictLoc;
public class DBUtils {
@ -54,6 +55,8 @@ public class DBUtils {
private static final String ROW_ID = "rowid";
private static final String ROW_ID_FMT = "rowid=%d";
private static final String NAME_FMT = "%s='%s'";
private static final String NAMELOC_FMT = "%s='%s' AND %s=%d";
private static long s_cachedRowID = -1;
private static byte[] s_cachedBytes = null;
@ -84,6 +87,15 @@ public class DBUtils {
boolean sourceLocal;
}
public static class DictBrowseState {
public int m_minShown;
public int m_maxShown;
public int m_pos;
public int m_top;
public String m_prefix;
public int[] m_counts;
}
public static GameSummary getSummary( Context context, long rowid,
long maxMillis )
{
@ -938,6 +950,210 @@ public class DBUtils {
return success;
}
/////////////////////////////////////////////////////////////////
// DictsDB stuff
/////////////////////////////////////////////////////////////////
public static DictBrowseState dictsGetOffset( Context context, String name,
DictLoc loc )
{
Assert.assertTrue( DictLoc.UNKNOWN != loc );
DictBrowseState result = null;
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
String[] columns = { DBHelper.ITERPOS, DBHelper.ITERTOP,
DBHelper.ITERMIN, DBHelper.ITERMAX,
DBHelper.WORDCOUNTS, DBHelper.ITERPREFIX };
String selection =
String.format( NAMELOC_FMT, DBHelper.DICTNAME,
name, DBHelper.LOC, loc.ordinal() );
Cursor cursor = db.query( DBHelper.TABLE_NAME_DICTBROWSE, columns,
selection, null, null, null, null );
if ( 1 >= cursor.getCount() && cursor.moveToFirst() ) {
result = new DictBrowseState();
result.m_pos = cursor.getInt( cursor
.getColumnIndex(DBHelper.ITERPOS));
result.m_top = cursor.getInt( cursor
.getColumnIndex(DBHelper.ITERTOP));
result.m_minShown =
cursor.getInt( cursor
.getColumnIndex(DBHelper.ITERMIN));
result.m_maxShown =
cursor.getInt( cursor
.getColumnIndex(DBHelper.ITERMAX));
result.m_prefix =
cursor.getString( cursor
.getColumnIndex(DBHelper.ITERPREFIX));
String counts =
cursor.getString( cursor.getColumnIndex(DBHelper.WORDCOUNTS));
if ( null != counts ) {
String[] nums = TextUtils.split( counts, ":" );
int[] ints = new int[nums.length];
for ( int ii = 0; ii < nums.length; ++ii ) {
ints[ii] = Integer.parseInt( nums[ii] );
}
result.m_counts = ints;
}
}
cursor.close();
db.close();
}
return result;
}
public static void dictsSetOffset( Context context, String name,
DictLoc loc, DictBrowseState state )
{
Assert.assertTrue( DictLoc.UNKNOWN != loc );
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
String selection =
String.format( NAMELOC_FMT, DBHelper.DICTNAME,
name, DBHelper.LOC, loc.ordinal() );
ContentValues values = new ContentValues();
values.put( DBHelper.ITERPOS, state.m_pos );
values.put( DBHelper.ITERTOP, state.m_top );
values.put( DBHelper.ITERMIN, state.m_minShown );
values.put( DBHelper.ITERMAX, state.m_maxShown );
values.put( DBHelper.ITERPREFIX, state.m_prefix );
if ( null != state.m_counts ) {
String[] nums = new String[state.m_counts.length];
for ( int ii = 0; ii < nums.length; ++ii ) {
nums[ii] = String.format( "%d", state.m_counts[ii] );
}
values.put( DBHelper.WORDCOUNTS, TextUtils.join( ":", nums ) );
}
int result = db.update( DBHelper.TABLE_NAME_DICTBROWSE,
values, selection, null );
if ( 0 == result ) {
values.put( DBHelper.DICTNAME, name );
values.put( DBHelper.LOC, loc.ordinal() );
db.insert( DBHelper.TABLE_NAME_DICTBROWSE, null, values );
}
db.close();
}
}
public static String dictsGetMD5Sum( Context context, String name )
{
DictInfo info = dictsGetInfo( context, name );
String result = null == info? null : info.md5Sum;
return result;
}
public static void dictsSetMD5Sum( Context context, String name, String sum )
{
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
String selection = String.format( NAME_FMT, DBHelper.DICTNAME, name );
ContentValues values = new ContentValues();
values.put( DBHelper.MD5SUM, sum );
int result = db.update( DBHelper.TABLE_NAME_DICTINFO,
values, selection, null );
if ( 0 == result ) {
values.put( DBHelper.DICTNAME, name );
db.insert( DBHelper.TABLE_NAME_DICTINFO, null, values );
}
db.close();
}
}
public static DictInfo dictsGetInfo( Context context, String name )
{
DictInfo result = null;
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getReadableDatabase();
String[] columns = { DBHelper.LANGCODE,
DBHelper.WORDCOUNT,
DBHelper.MD5SUM,
DBHelper.LOC };
String selection = String.format( NAME_FMT, DBHelper.DICTNAME, name );
Cursor cursor = db.query( DBHelper.TABLE_NAME_DICTINFO, columns,
selection, null, null, null, null );
if ( 1 == cursor.getCount() && cursor.moveToFirst() ) {
result = new DictInfo();
result.name = name;
result.langCode =
cursor.getInt( cursor.getColumnIndex(DBHelper.LANGCODE));
result.wordCount =
cursor.getInt( cursor.getColumnIndex(DBHelper.WORDCOUNT));
result.md5Sum =
cursor.getString( cursor.getColumnIndex(DBHelper.MD5SUM));
}
cursor.close();
db.close();
}
return result;
}
public static void dictsSetInfo( Context context, DictUtils.DictAndLoc dal,
DictInfo info )
{
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
String selection =
String.format( NAME_FMT, DBHelper.DICTNAME, dal.name );
ContentValues values = new ContentValues();
values.put( DBHelper.LANGCODE, info.langCode );
values.put( DBHelper.WORDCOUNT, info.wordCount );
values.put( DBHelper.MD5SUM, info.md5Sum );
values.put( DBHelper.LOC, dal.loc.ordinal() );
int result = db.update( DBHelper.TABLE_NAME_DICTINFO,
values, selection, null );
if ( 0 == result ) {
values.put( DBHelper.DICTNAME, dal.name );
db.insert( DBHelper.TABLE_NAME_DICTINFO, null, values );
}
db.close();
}
}
public static void dictsMoveInfo( Context context, String name,
DictLoc fromLoc, DictLoc toLoc )
{
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
String selection =
String.format( NAMELOC_FMT, DBHelper.DICTNAME,
name, DBHelper.LOC, fromLoc.ordinal() );
ContentValues values = new ContentValues();
values.put( DBHelper.LOC, toLoc.ordinal() );
db.update( DBHelper.TABLE_NAME_DICTINFO, values, selection, null );
db.update( DBHelper.TABLE_NAME_DICTBROWSE, values, selection, null);
db.close();
}
}
public static void dictsRemoveInfo( Context context,
DictUtils.DictAndLoc dal )
{
initDB( context );
synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase();
String selection =
String.format( NAMELOC_FMT, DBHelper.DICTNAME,
dal.name, DBHelper.LOC, dal.loc.ordinal() );
db.delete( DBHelper.TABLE_NAME_DICTINFO, selection, null );
db.delete( DBHelper.TABLE_NAME_DICTBROWSE, selection, null );
db.close();
}
}
public static boolean gameDBExists( Context context )
{
String name = DBHelper.getDBName();
File sdcardDB = new File( Environment.getExternalStorageDirectory(),
name );
return sdcardDB.exists();
}
private static void copyGameDB( Context context, boolean toSDCard )
{
String name = DBHelper.getDBName();
@ -987,6 +1203,8 @@ public class DBUtils {
{
if ( null == s_dbHelper ) {
s_dbHelper = new DBHelper( context );
// force any upgrade
s_dbHelper.getWritableDatabase().close();
}
}

View file

@ -32,6 +32,7 @@ import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SectionIndexer;
import android.widget.Spinner;
import android.widget.TextView;
@ -47,10 +48,8 @@ import org.eehouse.android.xw4.jni.XwJNI;
public class DictBrowseActivity extends XWListActivity
implements View.OnClickListener, OnItemSelectedListener {
public static final String DICT_NAME = "DICT_NAME";
public static final String DICT_MIN = "DICT_MIN";
public static final String DICT_MAX = "DICT_MAX";
public static final String DICT_COUNTS = "DICT_COUNTS";
private static final String DICT_NAME = "DICT_NAME";
private static final String DICT_LOC = "DICT_LOC";
private static final int MIN_LEN = 2;
private static final int FINISH_ACTION = 1;
@ -58,13 +57,12 @@ public class DictBrowseActivity extends XWListActivity
private int m_dictClosure = 0;
private int m_lang;
private String m_name;
private DictUtils.DictLoc m_loc;
private Spinner m_minSpinner;
private Spinner m_maxSpinner;
private int m_minShown;
private int m_maxShown;
private DBUtils.DictBrowseState m_browseState;
private int m_minAvail;
private int m_maxAvail;
private int[] m_counts;
// - Steps to reproduce the problem:
@ -84,13 +82,15 @@ public class DictBrowseActivity extends XWListActivity
{
super();
XwJNI.dict_iter_setMinMax( m_dictClosure, m_minShown, m_maxShown );
XwJNI.dict_iter_setMinMax( m_dictClosure, m_browseState.m_minShown,
m_browseState.m_maxShown );
m_nWords = XwJNI.dict_iter_wordCount( m_dictClosure );
int format = m_minShown == m_maxShown ?
int format = m_browseState.m_minShown == m_browseState.m_maxShown ?
R.string.dict_browse_title1f : R.string.dict_browse_titlef;
setTitle( Utils.format( DictBrowseActivity.this, format,
m_name, m_nWords, m_minShown, m_maxShown ));
m_name, m_nWords, m_browseState.m_minShown,
m_browseState.m_maxShown ));
String desc = XwJNI.dict_iter_getDesc( m_dictClosure );
if ( null != desc ) {
@ -162,19 +162,29 @@ public class DictBrowseActivity extends XWListActivity
finish();
} else {
m_name = name;
m_loc =
DictUtils.DictLoc.values()[intent.getIntExtra( DICT_LOC, 0 )];
m_lang = DictLangCache.getDictLangCode( this, name );
String[] names = { name };
DictUtils.DictPairs pairs = DictUtils.openDicts( this, names );
m_dictClosure = XwJNI.dict_iter_init( pairs.m_bytes[0],
pairs.m_paths[0],
JNIUtilsImpl.get() );
m_dictClosure = XwJNI.dict_iter_init( pairs.m_bytes[0],
name, pairs.m_paths[0],
JNIUtilsImpl.get(this) );
m_counts = intent.getIntArrayExtra( DICT_COUNTS );
if ( null == m_counts ) {
m_counts = XwJNI.dict_iter_getCounts( m_dictClosure );
m_browseState = DBUtils.dictsGetOffset( this, name, m_loc );
boolean newState = null == m_browseState;
if ( newState ) {
m_browseState = new DBUtils.DictBrowseState();
m_browseState.m_pos = 0;
m_browseState.m_top = 0;
}
if ( null == m_counts ) {
if ( null == m_browseState.m_counts ) {
m_browseState.m_counts =
XwJNI.dict_iter_getCounts( m_dictClosure );
}
if ( null == m_browseState.m_counts ) {
// empty dict? Just close down for now. Later if
// this is extended to include tile info -- it should
// be -- then use an empty list elem and disable
@ -183,7 +193,11 @@ public class DictBrowseActivity extends XWListActivity
name );
showOKOnlyDialogThen( msg, FINISH_ACTION );
} else {
figureMinMax();
figureMinMax( m_browseState.m_counts );
if ( newState ) {
m_browseState.m_minShown = m_minAvail;
m_browseState.m_maxShown = m_maxAvail;
}
setContentView( R.layout.dict_browser );
@ -195,14 +209,40 @@ public class DictBrowseActivity extends XWListActivity
}
} );
m_minShown = intent.getIntExtra( DICT_MIN, m_minAvail );
m_maxShown = intent.getIntExtra( DICT_MAX, m_maxAvail );
setUpSpinners();
setListAdapter( new DictListAdapter() );
getListView().setFastScrollEnabled( true );
getListView().setSelectionFromTop( m_browseState.m_pos,
m_browseState.m_top );
}
}
} // onCreate
@Override
protected void onPause()
{
if ( null != m_browseState ) { // already saved?
ListView list = getListView();
m_browseState.m_pos = list.getFirstVisiblePosition();
View view = list.getChildAt( 0 );
m_browseState.m_top = (view == null) ? 0 : view.getTop();
m_browseState.m_prefix = getFindText();
DBUtils.dictsSetOffset( this, m_name, m_loc, m_browseState );
m_browseState = null;
}
super.onPause();
}
@Override
protected void onResume()
{
super.onResume();
if ( null == m_browseState ) {
m_browseState = DBUtils.dictsGetOffset( this, m_name, m_loc );
}
setFindText( m_browseState.m_prefix );
}
@Override
@ -247,9 +287,9 @@ public class DictBrowseActivity extends XWListActivity
TextView text = (TextView)view;
int newval = Integer.parseInt( text.getText().toString() );
if ( parent == m_minSpinner ) {
setMinMax( newval, m_maxShown );
setMinMax( newval, m_browseState.m_maxShown );
} else if ( parent == m_maxSpinner ) {
setMinMax( m_minShown, newval );
setMinMax( m_browseState.m_minShown, newval );
}
}
@ -268,9 +308,29 @@ public class DictBrowseActivity extends XWListActivity
}
private void findButtonClicked()
{
String text = getFindText();
if ( null != text && 0 < text.length() ) {
m_browseState.m_prefix = text;
showPrefix();
}
}
private String getFindText()
{
EditText edit = (EditText)findViewById( R.id.word_edit );
String text = edit.getText().toString();
return edit.getText().toString();
}
private void setFindText( String text )
{
EditText edit = (EditText)findViewById( R.id.word_edit );
edit.setText( text );
}
private void showPrefix()
{
String text = m_browseState.m_prefix;
if ( null != text && 0 < text.length() ) {
int pos = XwJNI.dict_iter_getStartsWith( m_dictClosure, text );
if ( 0 <= pos ) {
@ -289,26 +349,32 @@ public class DictBrowseActivity extends XWListActivity
// adapter/making it recognized a changed dataset. So, as a
// workaround, relaunch the activity with different
// parameters.
if ( m_minShown != min || m_maxShown != max ) {
Intent intent = getIntent();
intent.putExtra( DICT_MIN, min );
intent.putExtra( DICT_MAX, max );
intent.putExtra( DICT_COUNTS, m_counts );
startActivity( intent );
if ( m_browseState.m_minShown != min ||
m_browseState.m_maxShown != max ) {
m_browseState.m_pos = 0;
m_browseState.m_top = 0;
m_browseState.m_minShown = min;
m_browseState.m_maxShown = max;
m_browseState.m_prefix = getFindText();
DBUtils.dictsSetOffset( this, m_name, m_loc, m_browseState );
m_browseState = null;
startActivity( getIntent() );
finish();
}
}
private void figureMinMax()
private void figureMinMax( int[] counts )
{
Assert.assertTrue( m_counts.length == XwJNI.MAX_COLS_DICT + 1 );
Assert.assertTrue( counts.length == XwJNI.MAX_COLS_DICT + 1 );
m_minAvail = 0;
while ( 0 == m_counts[m_minAvail] ) {
while ( 0 == counts[m_minAvail] ) {
++m_minAvail;
}
m_maxAvail = XwJNI.MAX_COLS_DICT;
while ( 0 == m_counts[m_maxAvail] ) { //
while ( 0 == counts[m_maxAvail] ) { //
--m_maxAvail;
}
}
@ -342,19 +408,29 @@ public class DictBrowseActivity extends XWListActivity
// current max the largest min allowed, and the current
// min the smallest max allowed.
m_minSpinner = (Spinner)findViewById( R.id.wordlen_min );
makeAdapter( m_minSpinner, m_minAvail, m_maxShown, m_minShown );
makeAdapter( m_minSpinner, m_minAvail, m_browseState.m_maxShown,
m_browseState.m_minShown );
m_minSpinner.setOnItemSelectedListener( this );
m_maxSpinner = (Spinner)findViewById( R.id.wordlen_max );
makeAdapter( m_maxSpinner, m_minShown, m_maxAvail, m_maxShown );
makeAdapter( m_maxSpinner, m_browseState.m_minShown,
m_maxAvail, m_browseState.m_maxShown );
m_maxSpinner.setOnItemSelectedListener( this );
}
public static void launch( Context caller, String name )
public static void launch( Context caller, String name,
DictUtils.DictLoc loc )
{
Intent intent = new Intent( caller, DictBrowseActivity.class );
intent.putExtra( DICT_NAME, name );
intent.putExtra( DICT_LOC, loc.ordinal() );
caller.startActivity( intent );
}
public static void launch( Context caller, String name )
{
DictUtils.DictLoc loc = DictUtils.getDictLoc( caller, name );
launch( caller, name, loc );
}
}

View file

@ -39,8 +39,6 @@ import org.eehouse.android.xw4.jni.DictInfo;
import org.eehouse.android.xw4.jni.CommonPrefs;
public class DictLangCache {
private static final HashMap<DictAndLoc,DictInfo> s_nameToLang =
new HashMap<DictAndLoc,DictInfo>();
private static String[] s_langNames;
private static int m_adaptedLang = -1;
@ -235,7 +233,8 @@ public class DictLangCache {
DictUtils.DictLoc loc, boolean added )
{
DictAndLoc dal = new DictAndLoc( name, loc );
s_nameToLang.remove( dal );
DBUtils.dictsRemoveInfo( context, dal );
if ( added ) {
getInfo( context, dal );
}
@ -357,15 +356,7 @@ public class DictLangCache {
private static DictInfo getInfo( Context context, String name )
{
DictInfo result = null;
Set<DictAndLoc> keys = s_nameToLang.keySet();
for ( DictAndLoc key : keys ) {
if ( key.name.equals(name) ) {
result = s_nameToLang.get( key );
break;
}
}
DictInfo result = DBUtils.dictsGetInfo( context, name );
if ( null == result ) {
DictUtils.DictLoc loc = DictUtils.getDictLoc( context, name );
result = getInfo( context, new DictAndLoc( name, loc ) );
@ -375,28 +366,20 @@ public class DictLangCache {
private static DictInfo getInfo( Context context, DictAndLoc dal )
{
DictInfo info;
if ( s_nameToLang.containsKey( dal ) ) {
info = s_nameToLang.get( dal );
} else {
DictInfo info = DBUtils.dictsGetInfo( context, dal.name );
if ( null == info ) {
String[] names = { dal.name };
DictUtils.DictPairs pairs = DictUtils.openDicts( context, names );
info = new DictInfo();
// It should not be possible for dict_getInfo to fail
// unless DictUtils.dictList() isn't doing its job and
// puts unchecked dicts on the list. Should probably
// assert that this returns true. Open question: do I
// always trust dicts in the BUILTIN and INTERNAL
// locations? Files can get damaged....
if ( XwJNI.dict_getInfo( pairs.m_bytes[0], pairs.m_paths[0],
JNIUtilsImpl.get(),
if ( XwJNI.dict_getInfo( pairs.m_bytes[0], dal.name,
pairs.m_paths[0],
JNIUtilsImpl.get( context ),
DictUtils.DictLoc.DOWNLOAD == dal.loc,
info ) ) {
info.name = dal.name;
s_nameToLang.put( dal, info );
DBUtils.dictsSetInfo( context, dal, info );
} else {
info = null;
DbgUtils.logf( "getInfo(): unable to open dict %s", dal.name );

View file

@ -114,14 +114,14 @@ public class DictUtils {
// changes?
}
private static void tryDir( File dir, boolean strict, DictLoc loc,
ArrayList<DictAndLoc> al )
private static void tryDir( Context context, File dir, boolean strict,
DictLoc loc, ArrayList<DictAndLoc> al )
{
if ( null != dir ) {
String[] list = dir.list();
if ( null != list ) {
for ( String file : list ) {
if ( isDict( file, strict? dir : null ) ) {
if ( isDict( context, file, strict? dir : null ) ) {
al.add( new DictAndLoc( removeDictExtn( file ), loc ) );
}
}
@ -135,21 +135,21 @@ public class DictUtils {
ArrayList<DictAndLoc> al = new ArrayList<DictAndLoc>();
for ( String file : getAssets( context ) ) {
if ( isDict( file, null ) ) {
if ( isDict( context, file, null ) ) {
al.add( new DictAndLoc( removeDictExtn( file ),
DictLoc.BUILT_IN ) );
}
}
for ( String file : context.fileList() ) {
if ( isDict( file, null ) ) {
if ( isDict( context, file, null ) ) {
al.add( new DictAndLoc( removeDictExtn( file ),
DictLoc.INTERNAL ) );
}
}
tryDir( getSDDir( context ), false, DictLoc.EXTERNAL, al );
tryDir( getDownloadDir(), true, DictLoc.DOWNLOAD, al );
tryDir( context, getSDDir( context ), false, DictLoc.EXTERNAL, al );
tryDir( context, getDownloadDir(), true, DictLoc.DOWNLOAD, al );
s_dictListCache =
al.toArray( new DictUtils.DictAndLoc[al.size()] );
@ -480,13 +480,13 @@ public class DictUtils {
return file.endsWith( XWConstants.GAME_EXTN );
}
private static boolean isDict( String file, File dir )
private static boolean isDict( Context context, String file, File dir )
{
boolean ok = file.endsWith( XWConstants.DICT_EXTN );
if ( ok && null != dir ) {
String fullPath = new File( dir, file ).getPath();
ok = XwJNI.dict_getInfo( null, fullPath, JNIUtilsImpl.get(),
true, null );
ok = XwJNI.dict_getInfo( null, removeDictExtn( file ), fullPath,
JNIUtilsImpl.get(context), true, null );
}
return ok;
}
@ -527,60 +527,6 @@ public class DictUtils {
// want this later? Environment.MEDIA_MOUNTED_READ_ONLY
}
private static String figureMD5Sum( Context context, DictAndLoc dandl )
{
byte[] digest = null;
String result = null;
String name = dandl.name;
File path = getDictFile( context, addDictExtn( name ), dandl.loc );
try {
InputStream fis = new FileInputStream( path );
byte[] buffer = new byte[1024];
MessageDigest md = MessageDigest.getInstance("MD5");
for ( ; ; ) {
int nRead = fis.read( buffer );
if ( 0 > nRead ) {
break;
}
md.update( buffer, 0, nRead );
}
fis.close();
digest = md.digest();
} catch ( java.io.FileNotFoundException fnfe ) {
DbgUtils.loge( fnfe );
} catch( java.security.NoSuchAlgorithmException nsae ) {
DbgUtils.loge( nsae );
} catch( java.io.IOException ioe ) {
DbgUtils.loge( ioe );
}
if ( null != digest ) {
final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9',
'a','b','c','d','e','f'};
char[] chars = new char[digest.length * 2];
for ( int ii = 0; ii < digest.length; ii++ ) {
int byt = digest[ii] & 0xFF;
chars[ii * 2] = hexArray[byt >> 4];
chars[ii * 2 + 1] = hexArray[byt & 0x0F];
}
result = new String(chars);
}
return result;
} // figureMD5Sum
public static String getMD5SumFor( Context context, DictAndLoc dandl )
{
String sum = null; // DBUtils.getDictMD5Sum( context, dandl.name,
// dandl.loc.ordinal() );
if ( null == sum ) {
sum = figureMD5Sum( context, dandl );
// DBUtils.setDictMD5Sum( context, dandl.name,
// dandl.loc.ordinal(), sum );
}
return sum;
}
private static File getSDDir( Context context )
{
File result = null;

View file

@ -306,6 +306,9 @@ public class DictsActivity extends ExpandableListActivity
rowView.setComment( m_locNames[toLoc.ordinal()] );
rowView.cache( toLoc );
rowView.invalidate();
DBUtils.dictsMoveInfo( DictsActivity.this,
rowView.getText(),
m_moveFromLoc, toLoc );
} else {
DbgUtils.logf( "moveDict(%s) failed",
rowView.getText() );
@ -473,7 +476,8 @@ public class DictsActivity extends ExpandableListActivity
askStartDownload( 0, null );
} else {
XWListItem item = (XWListItem)view;
DictBrowseActivity.launch( this, item.getText() );
DictBrowseActivity.launch( this, item.getText(),
(DictUtils.DictLoc)item.getCached() );
}
}

View file

@ -220,7 +220,7 @@ public class GameUtils {
XwJNI.game_dispose( gamePtr );
gamePtr = XwJNI.initJNI();
XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(),
XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get( context ),
CommonPrefs.get( context ), dictNames,
pairs.m_bytes, pairs.m_paths, gi.langName() );
@ -360,11 +360,11 @@ public class GameUtils {
XwJNI.game_makeFromStream( gamePtr, stream, gi,
dictNames, pairs.m_bytes,
pairs.m_paths, langName,
util, JNIUtilsImpl.get(),
util, JNIUtilsImpl.get( context ),
CommonPrefs.get(context),
tp);
if ( !madeGame ) {
XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(),
XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(context),
CommonPrefs.get(context), dictNames,
pairs.m_bytes, pairs.m_paths,
langName );
@ -773,7 +773,7 @@ public class GameUtils {
int gamePtr = XwJNI.initJNI();
XwJNI.game_makeFromStream( gamePtr, stream, gi, dictNames,
pairs.m_bytes, pairs.m_paths,
gi.langName(), JNIUtilsImpl.get(),
gi.langName(), JNIUtilsImpl.get(context),
CommonPrefs.get( context ) );
// second time required as game_makeFromStream can overwrite
gi.replaceDicts( newDict );
@ -816,11 +816,12 @@ public class GameUtils {
new CurGameInfo(context),
dictNames, pairs.m_bytes,
pairs.m_paths, langName,
JNIUtilsImpl.get(), cp );
JNIUtilsImpl.get(context),
cp );
}
if ( forceNew || !madeGame ) {
XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(),
XwJNI.game_makeNewGame( gamePtr, gi, JNIUtilsImpl.get(context),
cp, dictNames, pairs.m_bytes,
pairs.m_paths, langName );
}
@ -886,9 +887,11 @@ public class GameUtils {
private static void tellRelayDied( Context context, GameSummary summary,
boolean informNow )
{
DBUtils.addDeceased( context, summary.relayID, summary.seed );
if ( informNow ) {
NetUtils.informOfDeaths( context );
if ( null != summary.relayID ) {
DBUtils.addDeceased( context, summary.relayID, summary.seed );
if ( informNow ) {
NetUtils.informOfDeaths( context );
}
}
}

View file

@ -545,6 +545,12 @@ public class GamesList extends XWListActivity
MenuItem item = menu.findItem( id );
item.setVisible( visible );
}
if ( visible && !DBUtils.gameDBExists( this ) ) {
MenuItem item = menu.findItem( R.id.gamel_menu_loaddb );
item.setVisible( false );
}
return super.onPrepareOptionsMenu( menu );
}

View file

@ -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";

View file

@ -253,7 +253,7 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
JSONObject params = new JSONObject();
int lang = DictLangCache.getDictLangCode( context, dal );
String langStr = DictLangCache.getLangName( context, lang );
String sum = DictUtils.getMD5SumFor( context, dal );
String sum = DictLangCache.getDictMD5Sum( context, dal.name );
try {
params.put( k_NAME, dal.name );
params.put( k_LANG, langStr );

View file

@ -345,6 +345,23 @@ public class Utils {
return context.getString( id, args );
}
public static String digestToString( byte[] digest )
{
String result = null;
if ( null != digest ) {
final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9',
'a','b','c','d','e','f'};
char[] chars = new char[digest.length * 2];
for ( int ii = 0; ii < digest.length; ii++ ) {
int byt = digest[ii] & 0xFF;
chars[ii * 2] = hexArray[byt >> 4];
chars[ii * 2 + 1] = hexArray[byt & 0x0F];
}
result = new String(chars);
}
return result;
}
private static void setFirstBootStatics( Context context )
{
int thisVersion = 0;

View file

@ -277,6 +277,8 @@ public class JNIThread extends Thread {
byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, null );
GameUtils.saveGame( m_context, state, m_lock, false );
DBUtils.saveSummary( m_context, m_lock, summary );
// There'd better be no way for saveGame above to fail!
XwJNI.game_saveSucceeded( m_jniGamePtr );
}
@SuppressWarnings("fallthrough")
@ -357,7 +359,6 @@ public class JNIThread extends Thread {
(byte[])args[0],
(CommsAddrRec)args[1]);
handle( JNICmd.CMD_DO );
handle( JNICmd.CMD_ACKANY );
if ( draw ) {
handle( JNICmd.CMD_SAVE );
}

View file

@ -26,4 +26,5 @@ public interface JNIUtils {
// Stuff I can't do in C....
String[] splitFaces( byte[] chars, boolean isUTF8 );
String getMD5SumFor( String dictName, byte[] bytes );
}

View file

@ -20,25 +20,29 @@
package org.eehouse.android.xw4.jni;
import android.graphics.drawable.BitmapDrawable;
import android.content.Context;
import android.graphics.Bitmap;
import java.util.ArrayList;
import android.graphics.drawable.BitmapDrawable;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.util.ArrayList;
import org.eehouse.android.xw4.*;
public class JNIUtilsImpl implements JNIUtils {
private static JNIUtils s_impl = null;
private static JNIUtilsImpl s_impl = null;
private Context m_context;
private JNIUtilsImpl(){}
public static JNIUtils get()
public static JNIUtils get( Context context )
{
if ( null == s_impl ) {
s_impl = new JNIUtilsImpl();
}
s_impl.m_context = context;
return s_impl;
}
@ -86,4 +90,35 @@ public class JNIUtilsImpl implements JNIUtils {
String[] result = al.toArray( new String[al.size()] );
return result;
}
}
public String getMD5SumFor( String dictName, byte[] bytes )
{
String result = null;
if ( null == bytes ) {
result = DBUtils.dictsGetMD5Sum( m_context, dictName );
} else {
byte[] digest = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] buf = new byte[128];
int nLeft = bytes.length;
int offset = 0;
while ( 0 < nLeft ) {
int len = Math.min( buf.length, nLeft );
System.arraycopy( bytes, offset, buf, 0, len );
md.update( buf, 0, len );
nLeft -= len;
offset += len;
}
digest = md.digest();
} catch ( java.security.NoSuchAlgorithmException nsae ) {
DbgUtils.loge( nsae );
}
result = Utils.digestToString( digest );
// Is this needed? Caller might be doing it anyway.
DBUtils.dictsSetMD5Sum( m_context, dictName, result );
}
return result;
}
}

View file

@ -127,6 +127,7 @@ public class XwJNI {
public static native void game_summarize( int gamePtr, GameSummary summary );
public static native byte[] game_saveToStream( int gamePtr,
CurGameInfo gi );
public static native void game_saveSucceeded( int gamePtr );
public static native void game_getGi( int gamePtr, CurGameInfo gi );
public static native void game_getState( int gamePtr,
JNIThread.GameStateInfo gsi );
@ -238,15 +239,15 @@ public class XwJNI {
// Dicts
public static native boolean dict_tilesAreSame( int dictPtr1, int dictPtr2 );
public static native String[] dict_getChars( int dictPtr );
public static native boolean dict_getInfo( byte[] dict, String path,
JNIUtils jniu, boolean check,
DictInfo info );
public static native boolean dict_getInfo( byte[] dict, String name,
String path, JNIUtils jniu,
boolean check, DictInfo info );
public static native int dict_getTileValue( int dictPtr, int tile );
// Dict iterator
public final static int MAX_COLS_DICT = 15; // from dictiter.h
public static native int dict_iter_init( byte[] dict, String path,
JNIUtils jniu );
public static native int dict_iter_init( byte[] dict, String name,
String path, JNIUtils jniu );
public static native void dict_iter_setMinMax( int closure,
int min, int max );
public static native void dict_iter_destroy( int closure );

View file

@ -1,6 +1,7 @@
#!/usr/bin/python
# Script meant to be installed on eehouse.org.
import logging, shelve, hashlib, sys, json
import logging, shelve, hashlib, sys, json, subprocess
try:
from mod_python import apache
apacheAvailable = True
@ -18,6 +19,7 @@ k_DICTS = 'dicts'
k_LANG = 'lang'
k_MD5SUM = 'md5sum'
k_INDEX = 'index'
k_ISUM = 'isum'
k_SUCCESS = 'success'
k_URL = 'url'
@ -26,7 +28,7 @@ k_COUNT = 'count'
k_suffix = '.xwd'
k_filebase = "/var/www/"
k_shelfFile = k_filebase + 'xw4/info_shelf'
k_shelfFile = k_filebase + 'xw4/info_shelf_2'
k_urlbase = "http://eehouse.org/"
k_versions = { 'org.eehouse.android.xw4': {
'version' : 43,
@ -49,8 +51,8 @@ k_versions_dbg = { 'org.eehouse.android.xw4': {
'org.eehouse.android.xw4sms' : {
'version' : 43,
k_AVERS : 43,
k_GVERS : 'android_beta_51',
k_URL : 'xw4/android/sms/XWords4-release_android_beta_51.apk',
k_GVERS : 'android_beta_51-50-g0ccc233',
k_URL : 'xw4/android/sms/XWords4-release_android_beta_51-50-g0ccc233.apk',
},
}
s_shelf = None
@ -63,24 +65,43 @@ logging.basicConfig(level=logging.DEBUG
# ,filemode='w')
# This seems to be required to prime the pump somehow.
logging.debug( "loaded...." )
# logging.debug( "loaded...." )
def md5Checksum(sums, filePath):
def getInternalSum( filePath ):
filePath = k_filebase + "and_wordlists/" + filePath
proc = subprocess.Popen(['/usr/bin/perl',
'--',
'/var/www/xw4/dawg2dict.pl',
'-get-sum',
'-dict', filePath ],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
return proc.communicate()[0].strip()
def md5Checksums( sums, filePath ):
if not filePath.endswith(k_suffix): filePath += k_suffix
if not filePath in sums:
logging.debug( "opening %s" % (k_filebase + "and_wordlists/" + filePath))
file = open( k_filebase + "and_wordlists/" + filePath, 'rb' )
md5 = hashlib.md5()
while True:
buffer = file.read(128)
if not buffer: break
md5.update( buffer )
sums[filePath] = md5.hexdigest()
sums[filePath] = [ md5.hexdigest(), getInternalSum( filePath ) ]
logging.debug( "figured sum for %s" % filePath )
return sums[filePath]
def getDictSums():
global s_shelf
s_shelf = shelve.open(k_shelfFile)
# shelve will fail if permissions are wrong. That's ok for some
# testing: just make a fake shelf and test before saving it later.
try:
s_shelf = shelve.open(k_shelfFile)
except:
s_shelf = {}
if not k_SUMS in s_shelf: s_shelf[k_SUMS] = {}
if not k_COUNT in s_shelf: s_shelf[k_COUNT] = 0
s_shelf[k_COUNT] += 1
@ -115,16 +136,16 @@ def dictVersion( req, name, lang, md5sum ):
dictSums = getDictSums()
path = lang + "/" + name
if not path in dictSums:
sum = md5Checksum( dictSums, path )
if sum:
dictSums[path] = sum
sums = md5Checksums( dictSums, path )
if sums:
dictSums[path] = sums
s_shelf[k_SUMS] = dictSums
if path in dictSums:
if dictSums[path] != md5sum:
if not md5sum in dictSums[path]:
result[k_URL] = k_urlbase + "and_wordlists/" + path
else:
logging.debug( path + " not known" )
s_shelf.close()
if 'close' in s_shelf: s_shelf.close()
return json.dumps( result )
def getApp( params ):
@ -137,6 +158,7 @@ def getApp( params ):
gvers = params[k_GVERS]
if k_INSTALLER in params: installer = params[k_INSTALLER]
else: installer = ''
logging.debug( "name: %s; avers: %s; installer: %s; gvers: %s"
% (name, avers, installer, gvers) )
if k_DEVOK in params and params[k_DEVOK]: versions = k_versions_dbg
@ -166,18 +188,19 @@ def getDicts( params ):
if not name.endswith(k_suffix): name += k_suffix
path = lang + "/" + name
if not path in dictSums:
sum = md5Checksum( dictSums, path )
if sum:
dictSums[path] = sum
sums = md5Checksums( dictSums, path )
if sums:
dictSums[path] = sums
s_shelf[k_SUMS] = dictSums
if path in dictSums:
if dictSums[path] != md5sum:
if not md5sum in dictSums[path]:
cur = { k_URL : k_urlbase + "and_wordlists/" + path,
k_INDEX : index }
k_INDEX : index, k_ISUM: dictSums[path][1] }
result.append( cur )
else:
logging.debug( path + " not known" )
if 'close' in s_shelf: s_shelf.close()
if 0 == len(result): result = None
return result
@ -201,7 +224,8 @@ def clearShelf():
def usage():
print "usage:", sys.argv[0], '--get-sums [lang/dict]*'
print " | --test-get-app app <org.eehouse.app.name> avers gvers"
print ' | --test-get-app app <org.eehouse.app.name> avers gvers'
print ' | --test-get-dicts name lang curSum'
print ' | --clear-shelf'
sys.exit(-1)
@ -213,9 +237,9 @@ def main():
elif arg == '--get-sums':
dictSums = getDictSums()
for arg in sys.argv[2:]:
print arg, md5Checksum(dictSums, arg)
print arg, md5Checksums(dictSums, arg)
s_shelf[k_SUMS] = dictSums
s_shelf.close()
if 'close' in s_shelf: s_shelf.close()
elif arg == '--test-get-app':
if not 5 == len(sys.argv): usage()
params = { k_NAME: sys.argv[2],
@ -223,6 +247,14 @@ def main():
k_GVERS: sys.argv[4],
}
print getApp( params )
elif arg == '--test-get-dicts':
if not 5 == len(sys.argv): usage()
params = { k_NAME: sys.argv[2],
k_LANG : sys.argv[3],
k_MD5SUM : sys.argv[4],
k_INDEX : 0,
}
print getDicts( [params] )
else:
usage()

View file

@ -66,8 +66,15 @@ typedef struct AddressRecord {
struct AddressRecord* next;
CommsAddrRec addr;
MsgID nextMsgID; /* on a per-channel basis */
MsgID lastMsgRcd; /* on a per-channel basis */
MsgID lastMsgAckd; /* on a per-channel basis */
/* lastMsgRcd is the numerically highest MsgID we've seen. Because once
* it's sent in message as an ACK the other side will delete messages
* based on it, we don't send a number higher than has actually been
* written out successfully. lastMsgSaved is that number.
*/
MsgID lastMsgRcd;
MsgID lastMsgSaved;
/* only used if COMMS_HEARTBEAT set except for serialization (to_stream) */
XP_PlayerAddr channelNo;
struct {
@ -109,6 +116,7 @@ struct CommsCtxt {
XP_Bool hbTimerPending;
XP_Bool reconTimerPending;
#endif
XP_U16 lastSaveToken;
/* The following fields, down to isServer, are only used if
XWFEATURE_RELAY is defined, but I'm leaving them in here so apps built
@ -575,7 +583,7 @@ comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util,
addrFromStream( &rec->addr, stream );
rec->nextMsgID = stream_getU16( stream );
rec->lastMsgRcd = stream_getU16( stream );
rec->lastMsgSaved = rec->lastMsgRcd = stream_getU16( stream );
if ( version >= STREAM_VERS_BLUETOOTH2 ) {
rec->lastMsgAckd = stream_getU16( stream );
}
@ -713,7 +721,8 @@ addrToStream( XWStreamCtxt* stream, const CommsAddrRec* addrP )
} /* addrToStream */
void
comms_writeToStream( const CommsCtxt* comms, XWStreamCtxt* stream )
comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream,
XP_U16 saveToken )
{
XP_U16 nAddrRecs;
AddressRecord* rec;
@ -762,8 +771,26 @@ comms_writeToStream( const CommsCtxt* comms, XWStreamCtxt* stream )
stream_putBytes( stream, msg->msg, msg->len );
}
comms->lastSaveToken = saveToken;
} /* comms_writeToStream */
void
comms_saveSucceeded( CommsCtxt* comms, XP_U16 saveToken )
{
XP_LOGF( "%s(saveToken=%d)", __func__, saveToken );
XP_ASSERT( !!comms );
if ( saveToken == comms->lastSaveToken ) {
XP_LOGF( "%s: lastSave matches", __func__ );
AddressRecord* rec;
for ( rec = comms->recs; !!rec; rec = rec->next ) {
rec->lastMsgSaved = rec->lastMsgRcd;
}
#ifdef XWFEATURE_COMMSACK
comms_ackAny( comms ); /* might not want this for all transports */
#endif
}
}
void
comms_getAddr( const CommsCtxt* comms, CommsAddrRec* addr )
{
@ -902,7 +929,7 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
{
XP_U16 headerLen;
XP_U16 streamSize = NULL == stream? 0 : stream_getSize( stream );
MsgID lastMsgRcd = (!!rec)? rec->lastMsgRcd : 0;
MsgID lastMsgSaved = (!!rec)? rec->lastMsgSaved : 0;
MsgQueueElem* newMsgElem;
XWStreamCtxt* msgStream;
@ -924,10 +951,10 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
stream_putU16( msgStream, channelNo );
stream_putU32( msgStream, msgID );
XP_LOGF( "put lastMsgRcd: %ld", lastMsgRcd );
stream_putU32( msgStream, lastMsgRcd );
XP_LOGF( "put lastMsgSaved: %ld", lastMsgSaved );
stream_putU32( msgStream, lastMsgSaved );
if ( !!rec ) {
rec->lastMsgAckd = lastMsgRcd;
rec->lastMsgAckd = lastMsgSaved;
}
headerLen = stream_getSize( msgStream );
@ -1734,6 +1761,7 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
XP_LOGF( "%s: got channelNo=%d;msgID=%ld;len=%d", __func__,
channelNo & CHANNEL_MASK, msgID, payloadSize );
rec->lastMsgRcd = msgID;
comms->lastSaveToken = 0; /* lastMsgRcd no longer valid */
stream_setAddress( stream, channelNo );
messageValid = payloadSize > 0;
}

View file

@ -198,7 +198,9 @@ CommsCtxt* comms_makeFromStream( MPFORMAL XWStreamCtxt* stream,
XW_UtilCtxt* util,
const TransportProcs* procs );
void comms_start( CommsCtxt* comms );
void comms_writeToStream( const CommsCtxt* comms, XWStreamCtxt* stream );
void comms_writeToStream( CommsCtxt* comms, XWStreamCtxt* stream,
XP_U16 saveToken );
void comms_saveSucceeded( CommsCtxt* comms, XP_U16 saveToken );
XP_S16 comms_send( CommsCtxt* comms, XWStreamCtxt* stream );
XP_Bool comms_resendAll( CommsCtxt* comms );

View file

@ -16,11 +16,11 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
COMMON_INCS = -I ./$(PLATFORM) -I../common -I../relay
COMMON_INCS = -I ../common -I../relay
INCLUDES += $(COMMON_INCS) -I./
COMMONDIR ?= ../common
COMMONOBJDIR = ../common/$(PLATFORM)
COMMONOBJDIR = $(PLATFORM)/common
COMMONSRC = \
$(COMMONDIR)/board.c \

View file

@ -259,7 +259,7 @@ game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game,
void
game_saveToStream( const XWGame* game, const CurGameInfo* gi,
XWStreamCtxt* stream )
XWStreamCtxt* stream, XP_U16 saveToken )
{
stream_putU8( stream, CUR_STREAM_VERS );
stream_setVersion( stream, CUR_STREAM_VERS );
@ -267,12 +267,13 @@ game_saveToStream( const XWGame* game, const CurGameInfo* gi,
gi_writeToStream( stream, gi );
if ( !!game ) {
XP_ASSERT( 0 != saveToken );
stream_putU8( stream, (XP_U8)!!game->comms );
#ifdef XWFEATURE_STANDALONE_ONLY
XP_ASSERT( !game->comms );
#endif
if ( !!game->comms ) {
comms_writeToStream( game->comms, stream );
comms_writeToStream( game->comms, stream, saveToken );
}
model_writeToStream( game->model, stream );
@ -281,6 +282,14 @@ game_saveToStream( const XWGame* game, const CurGameInfo* gi,
}
} /* game_saveToStream */
void
game_saveSucceeded( const XWGame* game, XP_U16 saveToken )
{
if ( !!game->comms ) {
comms_saveSucceeded( game->comms, saveToken );
}
}
void
game_getState( const XWGame* game, GameStateInfo* gsi )
{

View file

@ -103,7 +103,8 @@ XP_Bool game_makeFromStream( MPFORMAL XWStreamCtxt* stream, XWGame* game,
const TransportProcs* procs );
void game_saveToStream( const XWGame* game, const CurGameInfo* gi,
XWStreamCtxt* stream );
XWStreamCtxt* stream, XP_U16 saveToken );
void game_saveSucceeded( const XWGame* game, XP_U16 saveToken );
void game_dispose( XWGame* game );
void game_getState( const XWGame* game, GameStateInfo* gsi );

View file

@ -70,10 +70,8 @@ mem_stream_make( MPFORMAL VTableMgr* vtmgr, void* closure,
XP_PlayerAddr channelNo, MemStreamCloseCallback onClose )
{
StreamCtxVTable* vtable;
MemStreamCtxt* result = (MemStreamCtxt*)XP_MALLOC( mpool,
MemStreamCtxt* result = (MemStreamCtxt*)XP_CALLOC( mpool,
sizeof(*result) );
XP_MEMSET( result, 0, sizeof(*result) );
MPASSIGN(result->mpool, mpool);
vtable = (StreamCtxVTable*)vtmgr_getVTable( vtmgr, VTABLE_MEM_STREAM );
@ -92,6 +90,21 @@ mem_stream_make( MPFORMAL VTableMgr* vtmgr, void* closure,
return (XWStreamCtxt*)result;
} /* make_mem_stream */
XWStreamCtxt*
mem_stream_make_sized( MPFORMAL VTableMgr* vtmgr, XP_U16 startSize,
void* closure, XP_PlayerAddr channelNo,
MemStreamCloseCallback onClose )
{
MemStreamCtxt* result =
(MemStreamCtxt*)mem_stream_make( MPPARM(mpool) vtmgr, closure,
channelNo, onClose );
if ( 0 < startSize ) {
result->buf = (XP_U8*)XP_CALLOC( mpool, startSize );
result->nBytesAllocated = startSize;
}
return (XWStreamCtxt*)result;
}
static void
mem_stream_getBytes( XWStreamCtxt* p_sctx, void* where, XP_U16 count )
{

View file

@ -38,6 +38,11 @@ XWStreamCtxt* mem_stream_make( MPFORMAL VTableMgr* vtmgr,
subclass */
MemStreamCloseCallback onCloseWritten );
XWStreamCtxt* mem_stream_make_sized( MPFORMAL VTableMgr* vtmgr,
XP_U16 initialSize,
void* closure, XP_PlayerAddr addr,
MemStreamCloseCallback onCloseWritten );
#ifdef CPLUS
}

View file

@ -20,7 +20,7 @@ LANGCODE=ca_ES
TARGET_TYPE ?= WINCE
ENC = UTF-8
DICTNOTE = "Built from DISC 2.0.5.\nSee http://escrable.montane.cat/diccionari for more information."
DICTNOTE = "Built from DISC 2.0.6.\nSee http://escrable.montane.cat/diccionari for more information."
ifeq ($(TARGET_TYPE),PALM)

View file

@ -1,4 +1,4 @@
#!/usr/bin/perl -CS
#!/usr/bin/perl -C
#
# Copyright 2004 - 2009 by Eric House (xwords@eehouse.org)
#
@ -24,10 +24,11 @@ use strict;
use Fcntl;
use Encode 'from_to';
use Encode;
use Digest::MD5;
my $gInFile;
my $gSumOnly = 0;
my $gSum;
my $gSum = '';
my $gDescOnly = 0;
my $gDesc;
my $gDoRaw = 0;
@ -95,24 +96,37 @@ sub readXWDFaces($$$) {
$nChars = unpack( 'c', $buf );
printf STDERR "nChars of faces: %d\n", $nChars;
binmode( $fh, ":encoding(utf8)" ) or die "binmode(:utf-8) failed\n";
sysread( $fh, $buf, $nChars );
length($buf) == $nChars or die "didn't read expected number of bytes\n";
binmode( $fh ) or die "binmode failed\n";
# At this point $fh is pointing at the start of data
if ( $gSumOnly ) {
my $sum = sumRestOfFile( $fh );
if ( $sum eq $gSum ) {
print STDERR "got: $sum, $gSum twice!!!\n";
} elsif ( !$gSum ) {
$gSum = $sum;
} else {
print STDERR "disagreement: $sum vs $gSum\n";
exit( -1 );
}
} else {
binmode( $fh, ":encoding(utf8)" ) or die "binmode(:utf-8) failed\n";
sysread( $fh, $buf, $nChars );
length($buf) == $nChars or die "didn't read expected number of bytes\n";
binmode( $fh ) or die "binmode failed\n";
print STDERR "string now: $buf\n";
my @faces;
for ( my $ii = 0; $ii < $nChars; ++$ii ) {
my $chr = substr( $buf, $ii, 1 );
print STDERR "pushing $chr \n";
push( @faces, $chr );
print STDERR "string now: $buf\n";
my @faces;
for ( my $ii = 0; $ii < $nChars; ++$ii ) {
my $chr = substr( $buf, $ii, 1 );
print STDERR "pushing $chr \n";
push( @faces, $chr );
}
printf STDERR "at 0x%x after reading faces\n", systell($fh);
${$nSpecials} = countSpecials( \@faces );
@{$facRef} = @faces;
printf STDERR "readXWDFaces=>%d\n", $nChars;
}
printf STDERR "at 0x%x after reading faces\n", systell($fh);
${$nSpecials} = countSpecials( \@faces );
@{$facRef} = @faces;
printf STDERR "readXWDFaces=>%d\n", $nChars;
return $nChars;
} # readXWDFaces
@ -169,17 +183,14 @@ sub readNodesToEnd($) {
sub printHeader($$) {
my ( $buf, $len ) = @_;
printf STDERR "skipped %d bytes of header:\n", $len + 2;
my $asStr = Encode::decode_utf8($buf);
my @strs = split( '\0', $asStr );
# There are variable numbers of strings showing up in this thing.
# Need to figure out the right way to unpack the thing.
$gDesc = $strs[1];
$gSum = $strs[2];
foreach my $str (@strs) {
if ( 0 < length($str) ) {
print STDERR 'Got: ', $str, "\n";
}
my $count;
if ( $len == 4 ) {
($count) = unpack( 'N', $buf );
} else {
print STDERR 'unpacking...\n';
($count, $gDesc, $gSum) = unpack( 'N Z* Z*', $buf );
}
printf STDERR "has %d words\n", $count;
}
sub nodeSizeFromFlags($$) {
@ -198,7 +209,7 @@ sub nodeSizeFromFlags($$) {
if ( $flags == 2 || $ flags == 4 ) {
return 3;
} elsif ( $flags == 5 ) {
} elsif ( $flags == 3 || $flags == 5 ) {
return 4;
} else {
die "invalid dict flags $flags";
@ -216,6 +227,21 @@ sub mergeSpecials($$) {
}
}
sub sumRestOfFile($) {
my ( $fh ) = @_;
my $buf;
my $count = 0;
my $md5 = Digest::MD5->new;
for ( ; ; ) {
my $nRead = sysread( $fh, $buf, 128 );
0 == $nRead && last;
$count += $nRead;
$md5->add( $buf );
}
# print STDERR "read $count bytes\n";
return $md5->hexdigest();
}
sub prepXWD($$$$) {
my ( $fh, $facRef, $nodesRef, $startRef ) = @_;
my $done = 1;
@ -227,18 +253,14 @@ sub prepXWD($$$$) {
$gNodeSize = nodeSizeFromFlags( $fh, $flags );
my $nSpecials;
my $faceCount = readXWDFaces( $fh, $facRef, \$nSpecials ); # does checksum
if ( $gSumOnly ) {
print STDOUT $gSum, "\n";
} elsif( $gDescOnly ) {
print STDOUT $gDesc, "\n";
} else {
$done = 0;
}
if ( !$done ) {
my $nSpecials;
my $faceCount = readXWDFaces( $fh, $facRef, \$nSpecials );
printf STDERR "at 0x%x before header read\n", systell($fh);
# skip xloc header
$nRead = sysread( $fh, $buf, 2 );

View file

@ -16,15 +16,17 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
BUILD_DIR ?= .
ifeq ($(MEMDEBUG),TRUE)
DEFINES = -DMEM_DEBUG -DDEBUG -DENABLE_LOGGING -DNUMBER_KEY_AS_INDEX
DEFINES += -DCOMMS_CHECKSUM
CFLAGS += -g $(GPROFFLAG) -Wall -Wunused-parameter -Wcast-align -Werror -O0
CFLAGS += -DDEBUG_TS -rdynamic
PLATFORM = obj_linux_memdbg
PLATFORM = $(BUILD_DIR)/obj_linux_memdbg
else
DEFINES =
PLATFORM = obj_linux_rel
PLATFORM = $(BUILD_DIR)/obj_linux_rel
# Not shipping this! Always build with symbols etc
CFLAGS += -g $(GPROFFLAG) -Wall -Wunused-parameter -Wcast-align -Werror -O0
#CFLAGS += -Os -Werror -Wunused

View file

@ -1904,22 +1904,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
}
#endif
}
if ( !!g_globals.cGlobals.params->fileName ) {
XWStreamCtxt* outStream;
outStream =
mem_stream_make( MPPARM(g_globals.cGlobals.params->util->mpool)
g_globals.cGlobals.params->vtMgr,
&g_globals.cGlobals, 0, writeToFile );
stream_open( outStream );
game_saveToStream( &g_globals.cGlobals.game,
&g_globals.cGlobals.params->gi,
outStream );
stream_destroy( outStream );
sync();
}
saveGame( &g_globals.cGlobals );
game_dispose( &g_globals.cGlobals.game ); /* takes care of the dict */
gi_disposePlayerInfo( MEMPOOL &g_globals.cGlobals.params->gi );

View file

@ -378,6 +378,8 @@ relay_error_gtk( void* closure, XWREASON relayErr )
"relay says another device deleted game.",
GTK_BUTTONS_OK, 1000 );
break;
case XWRELAY_ERROR_DEADGAME:
break;
default:
assert(0);
break;
@ -678,19 +680,7 @@ quit( void )
static void
cleanup( GtkAppGlobals* globals )
{
if ( !!globals->cGlobals.params->fileName ) {
XWStreamCtxt* outStream;
outStream = mem_stream_make( MEMPOOL globals->cGlobals.params->vtMgr,
globals, 0, writeToFile );
stream_open( outStream );
game_saveToStream( &globals->cGlobals.game,
&globals->cGlobals.params->gi,
outStream );
stream_destroy( outStream );
}
saveGame( &globals->cGlobals );
game_dispose( &globals->cGlobals.game ); /* takes care of the dict */
gi_disposePlayerInfo( MEMPOOL &globals->cGlobals.params->gi );
@ -2105,6 +2095,9 @@ newConnectionInput( GIOChannel *source,
redraw =
server_receiveMessage(globals->cGlobals.game.server,
inboundS );
if ( redraw ) {
saveGame( &globals->cGlobals );
}
}
stream_destroy( inboundS );
}

View file

@ -86,7 +86,7 @@ linux_dictionary_make( MPFORMAL const LaunchParams* params,
} /* gtk_dictionary_make */
static XP_UCHAR*
getNullTermParam( LinuxDictionaryCtxt* dctx, const XP_U8** ptr,
getNullTermParam( LinuxDictionaryCtxt* XP_UNUSED_DBG(dctx), const XP_U8** ptr,
XP_U16* headerLen )
{
XP_U16 len = 1 + XP_STRLEN( (XP_UCHAR*)*ptr );

View file

@ -236,6 +236,41 @@ strFromStream( XWStreamCtxt* stream )
return buf;
} /* strFromStream */
void
saveGame( CommonGlobals* cGlobals )
{
if ( !!cGlobals->params->fileName ) {
XP_Bool doSave = XP_TRUE;
if ( 0 < cGlobals->params->saveFailPct
/* don't fail to save first time! */
&& file_exists( cGlobals->params->fileName ) ) {
XP_U16 pct = XP_RANDOM() % 100;
doSave = pct >= cGlobals->params->saveFailPct;
}
if ( doSave ) {
XWStreamCtxt* outStream;
outStream = mem_stream_make_sized( cGlobals->params->util->mpool,
cGlobals->params->vtMgr,
cGlobals->lastStreamSize,
cGlobals, 0, writeToFile );
stream_open( outStream );
game_saveToStream( &cGlobals->game,
&cGlobals->params->gi,
outStream, ++cGlobals->curSaveToken );
cGlobals->lastStreamSize = stream_getSize( outStream );
stream_destroy( outStream );
game_saveSucceeded( &cGlobals->game, cGlobals->curSaveToken );
XP_LOGF( "%s: saved", __func__ );
} else {
XP_LOGF( "%s: simulating save failure", __func__ );
}
}
}
static void
handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs,
int fdin )
@ -462,6 +497,7 @@ typedef enum {
,CMD_SEED
,CMD_GAMESEED
,CMD_GAMEFILE
,CMD_SAVEFAIL_PCT
#ifdef USE_SQLITE
,CMD_GAMEDB_FILE
,CMD_GAMEDB_ID
@ -554,6 +590,7 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_SEED, true, "seed", "random seed" }
,{ CMD_GAMESEED, true, "game-seed", "game seed (for relay play)" }
,{ CMD_GAMEFILE, true, "file", "file to save to/read from" }
,{ CMD_SAVEFAIL_PCT, true, "savefail-pct", "How often, at random, does save fail?" }
#ifdef USE_SQLITE
,{ CMD_GAMEDB_FILE, true, "game-db-file",
"sqlite3 file, android format, holding game" }
@ -1597,8 +1634,18 @@ main( int argc, char** argv )
case CMD_GAMEFILE:
mainParams.fileName = optarg;
break;
case CMD_SAVEFAIL_PCT:
mainParams.saveFailPct = atoi( optarg );
break;
#ifdef USE_SQLITE
case CMD_GAMEDB_FILE:
/* Android isn't using XWFEATURE_SEARCHLIMIT, and it writes to
stream, so to read an android DB is to invite mayhem. */
# ifdef XWFEATURE_SEARCHLIMIT
usage( argv[0], "Don't open android DBs without "
"disabling XWFEATURE_SEARCHLIMIT" );
# endif
mainParams.dbFileName = optarg;
case CMD_GAMEDB_ID:
mainParams.dbFileID = atoi(optarg);

View file

@ -68,6 +68,7 @@ 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 );

View file

@ -474,6 +474,16 @@ linux_getErrString( UtilErrID id, XP_Bool* silent )
message = "You tried to supply more players than the host expected.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_DELETED:
message = "Game deleted .";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_NORECONN:
message = "Cannot reconnect.";
break;
case ERR_RELAY_BASE + XWRELAY_ERROR_DEADGAME:
message = "Game is listed as dead on relay.";
break;
default:
XP_LOGF( "no code for error: %d", id );
message = "<unrecognized error code reported>";

View file

@ -50,6 +50,7 @@ typedef struct LaunchParams {
PlayerDicts dicts;
GSList* dictDirs;
char* fileName;
XP_U16 saveFailPct;
const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS];
#ifdef USE_SQLITE
char* dbFileName;
@ -163,6 +164,7 @@ struct CommonGlobals {
XWGame game;
XP_U16 lastNTilesToUse;
XP_U16 lastStreamSize;
SocketChangedFunc socketChanged;
void* socketChangedClosure;
@ -194,6 +196,8 @@ struct CommonGlobals {
#endif
TimerInfo timerInfo[NUM_TIMERS_PLUS_ONE];
XP_U16 curSaveToken;
};
#endif

View file

@ -74,9 +74,9 @@ print_cmdline() {
function pick_ndevs() {
local NDEVS=2
local RNUM=$((RANDOM % 100))
if [ $RNUM -gt 90 ]; then
if [ $RNUM -gt 90 -a $MAXDEVS -ge 4 ]; then
NDEVS=4
elif [ $RNUM -gt 75 ]; then
elif [ $RNUM -gt 75 -a $MAXDEVS -ge 3 ]; then
NDEVS=3
fi
echo $NDEVS
@ -185,6 +185,7 @@ build_cmds() {
PARAMS="$PARAMS --game-dict $DICT --port $PORT --host $HOST "
PARAMS="$PARAMS --file $FILE --slow-robot 1:3 --skip-confirm"
PARAMS="$PARAMS --drop-nth-packet $DROP_N $PLAT_PARMS"
# PARAMS="$PARAMS --savefail-pct 10"
[ -n "$SEED" ] && PARAMS="$PARAMS --seed $RANDOM"
PARAMS="$PARAMS $PUBLIC"
ARGS[$COUNTER]=$PARAMS
@ -563,7 +564,7 @@ done
[ -z "$PORT" ] && PORT=10997
[ -z "$TIMEOUT" ] && TIMEOUT=$((NGAMES*60+500))
[ -z "$SAVE_GOOD" ] && SAVE_GOOD=YES
[ -z "$RESIGN_RATIO" ] && RESIGN_RATIO=1000
[ -z "$RESIGN_RATIO" -a "$NGAMES" -gt 1 ] && RESIGN_RATIO=1000 || RESIGN_RATIO=0
[ -z "$DROP_N" ] && DROP_N=0
[ -z "$USE_GTK" ] && USE_GTK=FALSE
[ -z "$UPGRADE_ODDS" ] && UPGRADE_ODDS=10

View file

@ -186,7 +186,8 @@ class SafeCref {
/* SafeCref( CookieRef* cref ); */
~SafeCref();
bool Forward( HostID src, in_addr& addr, HostID dest, unsigned char* buf, int buflen ) {
bool Forward( HostID src, in_addr& addr, HostID dest, unsigned char* buf,
int buflen ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
@ -206,11 +207,13 @@ class SafeCref {
}
}
bool Connect( int socket, int nPlayersH, int nPlayersS, int seed, in_addr& addr ) {
bool Connect( int socket, int nPlayersH, int nPlayersS, int seed,
in_addr& addr ) {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
return cref->_Connect( socket, m_clientVersion, nPlayersH, nPlayersS, seed,
return cref->_Connect( socket, m_clientVersion, nPlayersH,
nPlayersS, seed,
m_seenSeed, addr );
} else {
return false;
@ -226,8 +229,9 @@ class SafeCref {
if ( m_dead ) {
*errp = XWRELAY_ERROR_DEADGAME;
} else {
success = cref->_Reconnect( socket, m_clientVersion, srcID, nPlayersH,
nPlayersS, seed, addr, m_dead );
success = cref->_Reconnect( socket, m_clientVersion, srcID,
nPlayersH, nPlayersS, seed, addr,
m_dead );
}
}
return success;

View file

@ -30,3 +30,8 @@ echo "SELECT dead,connname,cid,room,lang,clntVers,ntotal,nperdevice,seeds,ack,ns
"FROM games $QUERY ORDER BY NOT dead, connname LIMIT $LIMIT;" \
| psql xwgames
echo "SELECT connname, hid, count(*), sum(msglen) "\
"FROM msgs where connname in (SELECT connname from games where not games.dead group by connname)" \
"GROUP BY connname, hid ORDER BY connname;" \
| psql xwgames