Merge branch 'android_branch' into gtk_multigame

Conflicts:
	xwords4/android/XWords4/src/org/eehouse/android/xw4/GCMIntentService.java
	xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java
This commit is contained in:
Eric House 2013-05-22 07:01:08 -07:00
commit 31c443e4b1
7 changed files with 221 additions and 74 deletions

View file

@ -36,7 +36,7 @@ local_DEFINES += \
-DHASH_STREAM \ -DHASH_STREAM \
-DXWFEATURE_BASE64 \ -DXWFEATURE_BASE64 \
-DXWFEATURE_DEVID \ -DXWFEATURE_DEVID \
-DINITIAL_CLIENT_VERS=2 \ -DINITIAL_CLIENT_VERS=3 \
-DRELAY_ROOM_DEFAULT=\"\" \ -DRELAY_ROOM_DEFAULT=\"\" \
-D__LITTLE_ENDIAN \ -D__LITTLE_ENDIAN \

View file

@ -25,6 +25,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import com.google.android.gcm.GCMBaseIntentService; import com.google.android.gcm.GCMBaseIntentService;
import com.google.android.gcm.GCMRegistrar; import com.google.android.gcm.GCMRegistrar;
import org.json.JSONArray;
public class GCMIntentService extends GCMBaseIntentService { public class GCMIntentService extends GCMBaseIntentService {
@ -61,13 +62,31 @@ public class GCMIntentService extends GCMBaseIntentService {
if ( ignoreIt ) { if ( ignoreIt ) {
DbgUtils.logf( "received GCM but ignoring it" ); DbgUtils.logf( "received GCM but ignoring it" );
} else { } else {
value = intent.getStringExtra( "checkUpdates" );
if ( null != value && Boolean.parseBoolean( value ) ) {
UpdateCheckReceiver.checkVersions( context, true );
}
value = intent.getStringExtra( "getMoves" ); value = intent.getStringExtra( "getMoves" );
if ( null != value && Boolean.parseBoolean( value ) ) { if ( null != value && Boolean.parseBoolean( value ) ) {
RelayReceiver.RestartTimer( context, true ); RelayReceiver.RestartTimer( context, true );
} }
value = intent.getStringExtra( "checkUpdates" );
if ( null != value && Boolean.parseBoolean( value ) ) { value = intent.getStringExtra( "msgs64" );
UpdateCheckReceiver.checkVersions( context, true ); if ( null != value ) {
String connname = intent.getStringExtra( "connname" );
if ( null != connname ) {
try {
JSONArray msgs64 = new JSONArray( value );
String[] strs64 = new String[msgs64.length()];
for ( int ii = 0; ii < strs64.length; ++ii ) {
strs64[ii] = msgs64.optString(ii);
}
RelayService.processMsgs( context, connname, strs64 );
} catch (org.json.JSONException jse ) {
DbgUtils.loge( jse );
}
}
} }
value = intent.getStringExtra( "msg" ); value = intent.getStringExtra( "msg" );
@ -76,6 +95,7 @@ public class GCMIntentService extends GCMBaseIntentService {
if ( null != title ) { if ( null != title ) {
int code = value.hashCode() ^ title.hashCode(); int code = value.hashCode() ^ title.hashCode();
Utils.postNotification( context, null, title, value, code ); Utils.postNotification( context, null, title, value, code );
} }
} }
} }

View file

@ -23,7 +23,12 @@ package org.eehouse.android.xw4;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
<<<<<<< HEAD
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
=======
import android.os.AsyncTask;
import android.os.IBinder;
>>>>>>> android_branch
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
@ -43,16 +48,21 @@ import org.eehouse.android.xw4.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.GameSummary; import org.eehouse.android.xw4.jni.GameSummary;
import org.eehouse.android.xw4.jni.UtilCtxt; import org.eehouse.android.xw4.jni.UtilCtxt;
import org.eehouse.android.xw4.jni.XwJNI;
public class RelayService extends XWService { public class RelayService extends XWService {
private static final int MAX_SEND = 1024; private static final int MAX_SEND = 1024;
private static final int MAX_BUF = MAX_SEND - 2; private static final int MAX_BUF = MAX_SEND - 2;
private static final String CMD_STR = "CMD"; private static final String CMD_STR = "CMD";
private static final int UDP_CHANGED = 1;
private static final int SEND = 2; private static final int PROCESS_MSGS = 1;
private static final int RECEIVE = 3; private static final int UDP_CHANGED = 2;
private static final int SEND = 3;
private static final int RECEIVE = 4;
private static final String MSGS = "MSGS";
private static final String RELAY_ID = "RELAY_ID";
private static final String ROWID = "ROWID"; private static final String ROWID = "ROWID";
private static final String BINBUFFER = "BINBUFFER"; private static final String BINBUFFER = "BINBUFFER";
@ -103,9 +113,9 @@ public class RelayService extends XWService {
public static int sendPacket( Context context, long rowid, byte[] buf ) public static int sendPacket( Context context, long rowid, byte[] buf )
{ {
Intent intent = getIntentTo( context, SEND ); Intent intent = getIntentTo( context, SEND )
intent.putExtra( ROWID, rowid ); .putExtra( ROWID, rowid )
intent.putExtra( BINBUFFER, buf ); .putExtra( BINBUFFER, buf );
context.startService( intent ); context.startService( intent );
return buf.length; return buf.length;
} }
@ -115,9 +125,9 @@ public class RelayService extends XWService {
{ {
DbgUtils.logf( "RelayService::postData: packet of length %d for token %d", DbgUtils.logf( "RelayService::postData: packet of length %d for token %d",
msg.length, rowid ); msg.length, rowid );
Intent intent = getIntentTo( context, RECEIVE ); Intent intent = getIntentTo( context, RECEIVE )
intent.putExtra( ROWID, rowid ); .putExtra( ROWID, rowid )
intent.putExtra( BINBUFFER, msg ); .putExtra( BINBUFFER, msg );
context.startService( intent ); context.startService( intent );
} }
@ -126,6 +136,15 @@ public class RelayService extends XWService {
startService( context ); startService( context );
} }
public static void processMsgs( Context context, String relayId,
String[] msgs64 )
{
Intent intent = getIntentTo( context, PROCESS_MSGS )
.putExtra( MSGS, msgs64 )
.putExtra( RELAY_ID, relayId );
context.startService( intent );
}
private static Intent getIntentTo( Context context, int cmd ) private static Intent getIntentTo( Context context, int cmd )
{ {
Intent intent = new Intent( context, RelayService.class ); Intent intent = new Intent( context, RelayService.class );
@ -183,6 +202,31 @@ public class RelayService extends XWService {
return result; return result;
} }
@Override
public int onStartCommand( Intent intent, int flags, int startId )
{
int cmd = intent.getIntExtra( CMD_STR, -1 );
switch( cmd ) {
case PROCESS_MSGS:
String[] relayIDs = new String[1];
relayIDs[0] = intent.getStringExtra( RELAY_ID );
long[] rowIDs = DBUtils.getRowIDsFor( this, relayIDs[0] );
if ( 0 < rowIDs.length ) {
String[] msgs64 = intent.getStringArrayExtra( MSGS );
int count = msgs64.length;
byte[][][] msgs = new byte[1][count][];
for ( int ii = 0; ii < count; ++ii ) {
msgs[0][ii] = XwJNI.base64Decode( msgs64[ii] );
}
process( msgs, rowIDs, relayIDs );
}
break;
}
stopSelf( startId );
return Service.START_NOT_STICKY;
}
private void setupNotification( String[] relayIDs ) private void setupNotification( String[] relayIDs )
{ {
for ( String relayID : relayIDs ) { for ( String relayID : relayIDs ) {
@ -568,67 +612,80 @@ public class RelayService extends XWService {
long[][] rowIDss = new long[1][]; long[][] rowIDss = new long[1][];
String[] relayIDs = DBUtils.getRelayIDs( this, rowIDss ); String[] relayIDs = DBUtils.getRelayIDs( this, rowIDss );
if ( null != relayIDs && 0 < relayIDs.length ) { if ( null != relayIDs && 0 < relayIDs.length ) {
long[] rowIDs = rowIDss[0];
byte[][][] msgs = NetUtils.queryRelay( this, relayIDs ); byte[][][] msgs = NetUtils.queryRelay( this, relayIDs );
process( msgs, rowIDss[0], relayIDs );
if ( null != msgs ) {
RelayMsgSink sink = new RelayMsgSink();
int nameCount = relayIDs.length;
ArrayList<String> idsWMsgs =
new ArrayList<String>( nameCount );
for ( int ii = 0; ii < nameCount; ++ii ) {
byte[][] forOne = msgs[ii];
// if game has messages, open it and feed 'em
// to it.
if ( null == forOne ) {
// Nothing for this relayID
} else if ( BoardActivity.feedMessages( rowIDs[ii], forOne )
|| GameUtils.feedMessages( this, rowIDs[ii],
forOne, null,
sink ) ) {
idsWMsgs.add( relayIDs[ii] );
} else {
DbgUtils.logf( "dropping message for %s (rowid %d)",
relayIDs[ii], rowIDs[ii] );
}
}
if ( 0 < idsWMsgs.size() ) {
String[] tmp = new String[idsWMsgs.size()];
idsWMsgs.toArray( tmp );
setupNotification( tmp );
}
sink.send( this );
}
} }
} }
private static void sendToRelay( Context context, private void process( byte[][][] msgs, long[] rowIDs, String[] relayIDs )
HashMap<String,ArrayList<byte[]>> msgHash )
{ {
// format: total msg lenth: 2 if ( null != msgs ) {
// number-of-relayIDs: 2 RelayMsgSink sink = new RelayMsgSink();
// for-each-relayid: relayid + '\n': varies int nameCount = relayIDs.length;
// message count: 1 ArrayList<String> idsWMsgs = new ArrayList<String>( nameCount );
// for-each-message: length: 2
// message: varies
if ( null != msgHash ) { for ( int ii = 0; ii < nameCount; ++ii ) {
byte[][] forOne = msgs[ii];
// if game has messages, open it and feed 'em to it.
if ( null == forOne ) {
// Nothing for this relayID
} else if ( BoardActivity.feedMessages( rowIDs[ii], forOne )
|| GameUtils.feedMessages( this, rowIDs[ii],
forOne, null,
sink ) ) {
idsWMsgs.add( relayIDs[ii] );
} else {
DbgUtils.logf( "message for %s (rowid %d) not consumed",
relayIDs[ii], rowIDs[ii] );
}
}
if ( 0 < idsWMsgs.size() ) {
String[] tmp = new String[idsWMsgs.size()];
idsWMsgs.toArray( tmp );
setupNotification( tmp );
}
sink.send( this );
}
}
private static class AsyncSender extends AsyncTask<Void, Void, Void> {
private Context m_context;
private HashMap<String,ArrayList<byte[]>> m_msgHash;
public AsyncSender( Context context,
HashMap<String,ArrayList<byte[]>> msgHash )
{
m_context = context;
m_msgHash = msgHash;
}
@Override
protected Void doInBackground( Void... ignored )
{
// format: total msg lenth: 2
// number-of-relayIDs: 2
// for-each-relayid: relayid + '\n': varies
// message count: 1
// for-each-message: length: 2
// message: varies
// Build up a buffer containing everything but the total
// message length and number of relayIDs in the message.
try { try {
// Build up a buffer containing everything but the total
// message length and number of relayIDs in the message.
ByteArrayOutputStream store = ByteArrayOutputStream store =
new ByteArrayOutputStream( MAX_BUF ); // mem new ByteArrayOutputStream( MAX_BUF ); // mem
DataOutputStream outBuf = new DataOutputStream( store ); DataOutputStream outBuf = new DataOutputStream( store );
int msgLen = 4; // relayID count + protocol stuff int msgLen = 4; // relayID count + protocol stuff
int nRelayIDs = 0; int nRelayIDs = 0;
Iterator<String> iter = msgHash.keySet().iterator(); Iterator<String> iter = m_msgHash.keySet().iterator();
while ( iter.hasNext() ) { while ( iter.hasNext() ) {
String relayID = iter.next(); String relayID = iter.next();
int thisLen = 1 + relayID.length(); // string and '\n' int thisLen = 1 + relayID.length(); // string and '\n'
thisLen += 2; // message count thisLen += 2; // message count
ArrayList<byte[]> msgs = msgHash.get( relayID ); ArrayList<byte[]> msgs = m_msgHash.get( relayID );
for ( byte[] msg : msgs ) { for ( byte[] msg : msgs ) {
thisLen += 2 + msg.length; thisLen += 2 + msg.length;
} }
@ -649,10 +706,9 @@ public class RelayService extends XWService {
} }
msgLen += thisLen; msgLen += thisLen;
} }
// Now open a real socket, write size and proto, and // Now open a real socket, write size and proto, and
// copy in the formatted buffer // copy in the formatted buffer
Socket socket = NetUtils.makeProxySocket( context, 8000 ); Socket socket = NetUtils.makeProxySocket( m_context, 8000 );
if ( null != socket ) { if ( null != socket ) {
DataOutputStream outStream = DataOutputStream outStream =
new DataOutputStream( socket.getOutputStream() ); new DataOutputStream( socket.getOutputStream() );
@ -667,6 +723,15 @@ public class RelayService extends XWService {
} catch ( java.io.IOException ioe ) { } catch ( java.io.IOException ioe ) {
DbgUtils.loge( ioe ); DbgUtils.loge( ioe );
} }
return null;
} // doInBackground
}
private static void sendToRelay( Context context,
HashMap<String,ArrayList<byte[]>> msgHash )
{
if ( null != msgHash ) {
new AsyncSender( context, msgHash ).execute();
} else { } else {
DbgUtils.logf( "sendToRelay: null msgs" ); DbgUtils.logf( "sendToRelay: null msgs" );
} }

View file

@ -110,7 +110,7 @@ dict_getNextTileString( const DictionaryCtxt* dict, Tile tile,
} else { } else {
cur += XP_STRLEN( cur ) + 1; cur += XP_STRLEN( cur ) + 1;
XP_Bool isSpecial = dict_faceIsBitmap( dict, tile ); XP_Bool isSpecial = dict_faceIsBitmap( dict, tile );
if ( isSpecial ) { if ( isSpecial || tile == dict->blankTile ) {
const XP_UCHAR* facep = dict_getTileStringRaw( dict, tile ); const XP_UCHAR* facep = dict_getTileStringRaw( dict, tile );
if ( cur < dict->charEnds[(XP_U16)*facep] ) { if ( cur < dict->charEnds[(XP_U16)*facep] ) {
result = cur; result = cur;

View file

@ -2856,17 +2856,27 @@ server_formatDictCounts( ServerCtxt* server, XWStreamCtxt* stream,
nChars = dict_numTileFaces( dict ); nChars = dict_numTileFaces( dict );
for ( tile = 0, nPrinted = 0; ; ) { for ( tile = 0, nPrinted = 0; ; ) {
XP_UCHAR buf[24]; XP_UCHAR buf[128];
XP_U16 count, value; XP_U16 count, value;
count = dict_numTiles( dict, tile ); count = dict_numTiles( dict, tile );
if ( count > 0 ) { if ( count > 0 ) {
const XP_UCHAR* face = dict_getTileString( dict, tile ); const XP_UCHAR* face = NULL;
XP_UCHAR faces[48] = {0};
XP_U16 len = 0;
for ( ; ; ) {
face = dict_getNextTileString( dict, tile, face );
if ( !face ) {
break;
}
const XP_UCHAR* fmt = len == 0? "%s" : ",%s";
len += XP_SNPRINTF( faces + len, sizeof(faces) - len, fmt, face );
}
value = dict_getTileValue( dict, tile ); value = dict_getTileValue( dict, tile );
XP_SNPRINTF( buf, sizeof(buf), (XP_UCHAR*)"%s: %d/%d", XP_SNPRINTF( buf, sizeof(buf), (XP_UCHAR*)"%s: %d/%d",
face, count, value ); faces, count, value );
stream_catString( stream, buf ); stream_catString( stream, buf );
} }

View file

@ -19,6 +19,7 @@
XWLANG = PTBR4422 XWLANG = PTBR4422
LANGCODE = pt_PT LANGCODE = pt_PT
ENC = UTF-8 ENC = UTF-8
DICTNOTE = "Lista com as 4422 palavras mais frequentes nas legendas em português brasileiro do site www.opensubtitles.org."
TARGET_TYPE ?= WINCE TARGET_TYPE ?= WINCE

View file

@ -32,6 +32,7 @@ g_con = None
g_sent = None g_sent = None
g_debug = False g_debug = False
g_skipSend = False # for debugging g_skipSend = False # for debugging
g_columns = [ 'id', 'devid', 'connname', 'hid', 'msg64' ]
DEVTYPE_GCM = 3 # 3 == GCM DEVTYPE_GCM = 3 # 3 == GCM
LINE_LEN = 76 LINE_LEN = 76
@ -55,19 +56,51 @@ def init():
def getPendingMsgs( con, typ ): def getPendingMsgs( con, typ ):
cur = con.cursor() cur = con.cursor()
query = """SELECT id, devid FROM msgs query = """SELECT %s FROM msgs
WHERE devid IN (SELECT id FROM devices WHERE devtype=%d and NOT unreg) WHERE devid IN (SELECT id FROM devices WHERE devtype=%d and NOT unreg)
AND NOT connname IN (SELECT connname FROM games WHERE dead); """ AND NOT connname IN (SELECT connname FROM games WHERE dead); """
cur.execute(query % typ) cur.execute(query % (",".join( g_columns ), typ))
result = cur.fetchall()
result = []
for row in cur:
rowObj = {}
for ii in range( len( g_columns ) ):
rowObj[g_columns[ii]] = row[ii]
result.append( rowObj )
if g_debug: print "getPendingMsgs=>", result if g_debug: print "getPendingMsgs=>", result
return result return result
def addClntVers( con, rows ):
query = """select clntVers[%s] from games where connname = '%s';"""
cur = con.cursor()
for row in rows:
cur.execute( query % (row['hid'], row['connname']))
if cur.rowcount == 1: row['clntVers'] = cur.fetchone()[0]
else: print "bad row count: ", cur.rowcount
con.commit()
return rows
def deleteMsgs( con, msgIDs ):
if 0 < len( msgIDs ):
query = "DELETE from msgs where id in (%s);" % ",".join(msgIDs)
try:
cur = con.cursor()
cur.execute(query)
con.commit()
except psycopg2.DatabaseError, e:
print 'Error %s' % e
except Exception as inst:
print "failed to execute", query
print type(inst)
print inst.args
print inst
def unregister( gcmid ): def unregister( gcmid ):
global g_con global g_con
print "unregister(", gcmid, ")" print "unregister(", gcmid, ")"
query = "UPDATE devices SET unreg=TRUE WHERE devid = '%s' and devtype = 3" % gcmid query = "UPDATE devices SET unreg=TRUE WHERE devid = '%s' and devtype = 3" % gcmid
g_con.cursor().execute( query ) g_con.cursor().execute( query )
g_con.commit()
def asGCMIds(con, devids, typ): def asGCMIds(con, devids, typ):
cur = con.cursor() cur = con.cursor()
@ -76,13 +109,22 @@ def asGCMIds(con, devids, typ):
cur.execute( query ) cur.execute( query )
return [elem[0] for elem in cur.fetchall()] return [elem[0] for elem in cur.fetchall()]
def notifyGCM( devids, typ ): def notifyGCM( devids, typ, target ):
success = False
if typ == DEVTYPE_GCM: if typ == DEVTYPE_GCM:
if 3 <= target['clntVers']:
connname = "%s/%d" % (target['connname'], target['hid'])
data = { 'msgs64': [ target['msg64'] ],
'connname': connname,
}
else:
data = { 'getMoves': True, }
values = { values = {
'data' : { 'getMoves': True, }, 'data' : data,
'registration_ids': devids, 'registration_ids': devids,
} }
params = json.dumps( values ) params = json.dumps( values )
req = urllib2.Request("https://android.googleapis.com/gcm/send", params ) req = urllib2.Request("https://android.googleapis.com/gcm/send", params )
req.add_header( 'Content-Type' , 'application/x-www-form-urlencoded;charset=UTF-8' ) req.add_header( 'Content-Type' , 'application/x-www-form-urlencoded;charset=UTF-8' )
req.add_header( 'Authorization' , 'key=' + mykey.myKey ) req.add_header( 'Authorization' , 'key=' + mykey.myKey )
@ -92,11 +134,13 @@ def notifyGCM( devids, typ ):
if 'success' in asJson and 'failure' in asJson and len(devids) == asJson['success'] and 0 == asJson['failure']: if 'success' in asJson and 'failure' in asJson and len(devids) == asJson['success'] and 0 == asJson['failure']:
print "OK" print "OK"
success = True
else: else:
print "Errors: " print "Errors: "
print response print response
else: else:
print "not sending to", len(devids), "devices because typ ==", typ print "not sending to", len(devids), "devices because typ ==", typ
return success
def shouldSend(val): def shouldSend(val):
return val == 1 return val == 1
@ -113,14 +157,14 @@ def targetsAfterBackoff( msgs ):
global g_sent global g_sent
targets = {} targets = {}
for row in msgs: for row in msgs:
msgid = row[0] msgid = row['id']
devid = row[1] devid = row['devid']
if not msgid in g_sent: if not msgid in g_sent:
g_sent[msgid] = 0 g_sent[msgid] = 0
g_sent[msgid] += 1 g_sent[msgid] += 1
if shouldSend( g_sent[msgid] ): if shouldSend( g_sent[msgid] ):
targets[devid] = True targets[devid] = row
return targets.keys() return targets
# devids is an array of (msgid, devid) tuples # devids is an array of (msgid, devid) tuples
def pruneSent( devids ): def pruneSent( devids ):
@ -129,7 +173,7 @@ def pruneSent( devids ):
lenBefore = len(g_sent) lenBefore = len(g_sent)
msgids = [] msgids = []
for row in devids: for row in devids:
msgids.append(row[0]) msgids.append(row['id'])
for msgid in g_sent.keys(): for msgid in g_sent.keys():
if not msgid in msgids: if not msgid in msgids:
del g_sent[msgid] del g_sent[msgid]
@ -181,14 +225,21 @@ def main():
if g_debug: print if g_debug: print
devids = getPendingMsgs( g_con, typ ) devids = getPendingMsgs( g_con, typ )
if 0 < len(devids): if 0 < len(devids):
devids = addClntVers( g_con, devids )
targets = targetsAfterBackoff( devids ) targets = targetsAfterBackoff( devids )
if 0 < len(targets): if 0 < len(targets):
if 0 < emptyCount: print "" if 0 < emptyCount: print ""
emptyCount = 0 emptyCount = 0
print strftime("%Y-%m-%d %H:%M:%S", time.localtime()), print strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
print "devices needing notification:", targets, '=>', print "devices needing notification:", targets, '=>',
notifyGCM( asGCMIds( g_con, targets, typ ), typ ) toDelete = []
for devid in targets.keys():
target = targets[devid]
if notifyGCM( asGCMIds(g_con, [devid], typ), typ, target )\
and 3 <= target['clntVers']:
toDelete.append( str(target['id']) )
pruneSent( devids ) pruneSent( devids )
deleteMsgs( g_con, toDelete )
elif g_debug: print "no targets after backoff" elif g_debug: print "no targets after backoff"
else: else:
emptyCount += 1 emptyCount += 1