mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2024-12-29 10:26:36 +01:00
merge from android_branch
This commit is contained in:
commit
140c982d41
54 changed files with 963 additions and 404 deletions
|
@ -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"
|
||||
>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, ... )
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 );
|
||||
|
||||
|
|
|
@ -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>";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue