take
@@ -25,9 +25,12 @@
New with this release
- - Flip arrow to match when board flipped
- - Fix problems when undos arrive at unexpected times
- - Don't crash when game connects while app's in background
+ - Add new SMS-handling code. One feature isn't compatible with
+ the previous release so it won't be activated until there's been
+ time for everybody to upgrade.
+ - Include new Catalan translations
+ - Fix bug causing (rarely) unending notifications of wordlist
+ upgrade
(The full changelog
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictBrowseDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictBrowseDelegate.java
index a73490b3f..5a165bcf1 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictBrowseDelegate.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictBrowseDelegate.java
@@ -175,8 +175,7 @@ public class DictBrowseDelegate extends DelegateBase
String[] names = { name };
DictUtils.DictPairs pairs = DictUtils.openDicts( m_activity, names );
m_dictClosure = XwJNI.dict_iter_init( pairs.m_bytes[0],
- name, pairs.m_paths[0],
- JNIUtilsImpl.get(m_activity) );
+ name, pairs.m_paths[0] );
m_browseState = DBUtils.dictsGetOffset( m_activity, name, m_loc );
boolean newState = null == m_browseState;
@@ -280,7 +279,7 @@ public class DictBrowseDelegate extends DelegateBase
{
TextView text = (TextView)view;
// null text seems to have generated at least one google play report
- if ( null != text ) {
+ if ( null != text && null != m_browseState ) {
int newval = Integer.parseInt( text.getText().toString() );
switch ( parent.getId() ) {
case R.id.wordlen_min:
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictLangCache.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictLangCache.java
index fdab651b8..b614446b2 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictLangCache.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictLangCache.java
@@ -486,7 +486,6 @@ public class DictLangCache {
info = new DictInfo();
if ( XwJNI.dict_getInfo( pairs.m_bytes[0], dal.name,
pairs.m_paths[0],
- JNIUtilsImpl.get( context ),
DictLoc.DOWNLOAD == dal.loc,
info ) ) {
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictUtils.java
index 743d4b675..f354b1e29 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictUtils.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DictUtils.java
@@ -151,14 +151,16 @@ public class DictUtils {
// changes?
}
- private static void addLogDup( Map map, String path,
- DictLoc loc )
+ private static void addLogDupIf( Context context, Map map,
+ String path, File dir, DictLoc loc )
{
- String name = removeDictExtn( new File(path).getName() );
- if ( map.containsKey( name ) ) {
- Log.d( TAG, "replacing info for %s with from %s", name, loc );
+ if ( isDict( context, path, dir ) ) {
+ String name = removeDictExtn( new File(path).getName() );
+ if ( map.containsKey( name ) ) {
+ Log.d( TAG, "replacing info for %s with from %s", name, loc );
+ }
+ map.put( name, new DictAndLoc( name, loc ) );
}
- map.put( name, new DictAndLoc( name, loc ) );
}
private static void tryDir( Context context, File dir, boolean strict,
@@ -168,9 +170,7 @@ public class DictUtils {
String[] list = dir.list();
if ( null != list ) {
for ( String file : list ) {
- if ( isDict( context, file, strict? dir : null ) ) {
- addLogDup( map, file, loc );
- }
+ addLogDupIf( context, map, file, strict? dir : null, loc );
}
}
}
@@ -190,15 +190,11 @@ public class DictUtils {
Map map = new HashMap<>();
for ( String file : getAssets( context ) ) {
- if ( isDict( context, file, null ) ) {
- addLogDup( map, file, DictLoc.BUILT_IN );
- }
+ addLogDupIf( context, map, file, null, DictLoc.BUILT_IN );
}
for ( String file : context.fileList() ) {
- if ( isDict( context, file, null ) ) {
- addLogDup( map, file, DictLoc.INTERNAL );
- }
+ addLogDupIf( context, map, file, null, DictLoc.INTERNAL );
}
tryDir( context, getSDDir( context ), false, DictLoc.EXTERNAL, map );
@@ -578,7 +574,7 @@ public class DictUtils {
if ( ok && null != dir ) {
String fullPath = new File( dir, file ).getPath();
ok = XwJNI.dict_getInfo( null, removeDictExtn( file ), fullPath,
- JNIUtilsImpl.get(context), true, null );
+ true, null );
}
return ok;
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DwnldDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DwnldDelegate.java
index f7f6c1577..858787047 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DwnldDelegate.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/DwnldDelegate.java
@@ -172,7 +172,7 @@ public class DwnldDelegate extends ListDelegateBase {
callListener( m_uri, true );
} else if ( null != m_appFile ) {
// launch the installer
- Intent intent = Utils.makeInstallIntent( m_appFile );
+ Intent intent = Utils.makeInstallIntent( m_activity, m_appFile );
startActivity( intent );
} else {
// we failed at something....
@@ -473,5 +473,4 @@ public class DwnldDelegate extends ListDelegateBase {
intent.putExtra( APK_EXTRA, url );
return intent;
}
-
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java
index 97e6b8b70..56b71011b 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GameUtils.java
@@ -25,8 +25,8 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Handler;
import android.text.Html;
import android.text.TextUtils;
import android.view.Display;
@@ -131,8 +131,8 @@ public class GameUtils {
gamePtr = XwJNI.initNew( gi, dictNames, pairs.m_bytes, pairs.m_paths,
gi.langName( context ), (UtilCtxt)null,
- JNIUtilsImpl.get( context ), (DrawCtx)null,
- CommonPrefs.get( context ), (TransportProcs)null );
+ (DrawCtx)null, CommonPrefs.get( context ),
+ (TransportProcs)null );
if ( juggle ) {
gi.juggle();
@@ -372,15 +372,13 @@ public class GameUtils {
gamePtr = XwJNI.initFromStream( rowid, stream, gi, dictNames,
pairs.m_bytes, pairs.m_paths,
langName, util,
- JNIUtilsImpl.get( context ),
null,
CommonPrefs.get(context),
tp );
if ( null == gamePtr ) {
gamePtr = XwJNI.initNew( gi, dictNames,
pairs.m_bytes, pairs.m_paths,
- langName, (UtilCtxt)null,
- JNIUtilsImpl.get(context), null,
+ langName, (UtilCtxt)null, null,
CommonPrefs.get(context), null );
}
}
@@ -485,12 +483,12 @@ public class GameUtils {
}
if ( force ) {
- new ResendTask( context, filter, proc ).execute();
-
System.arraycopy( sendTimes, 0, /* src */
sendTimes, 1, /* dest */
sendTimes.length - 1 );
sendTimes[0] = now;
+
+ new Resender( context, filter, proc ).start();
}
}
@@ -914,6 +912,7 @@ public class GameUtils {
m_gotMsg = false;
m_gameOver = false;
}
+
@Override
public void showChat( String msg, int fromIndx, String fromName, int tsSeconds )
{
@@ -923,11 +922,14 @@ public class GameUtils {
m_chat = msg;
m_ts = tsSeconds;
}
+
+ @Override
public void turnChanged( int newTurn )
{
m_gotMsg = true;
}
+ @Override
public void notifyGameOver()
{
m_gameOver = true;
@@ -1024,8 +1026,7 @@ public class GameUtils {
XwJNI.initFromStream( rowid, stream, gi, dictNames,
pairs.m_bytes, pairs.m_paths,
gi.langName( context ), null,
- JNIUtilsImpl.get(context), null,
- CommonPrefs.get( context ), null );
+ null, CommonPrefs.get( context ), null );
// second time required as game_makeFromStream can overwrite
gi.replaceDicts( context, newDict );
@@ -1075,8 +1076,7 @@ public class GameUtils {
new CurGameInfo(context),
dictNames, pairs.m_bytes,
pairs.m_paths, langName,
- null, JNIUtilsImpl.get(context),
- null, cp, null );
+ null, null, cp, null );
madeGame = null != gamePtr;
}
@@ -1084,8 +1084,7 @@ public class GameUtils {
Assert.assertNull( gamePtr );
gamePtr = XwJNI.initNew( gi, dictNames, pairs.m_bytes,
pairs.m_paths, langName, util,
- JNIUtilsImpl.get(context), (DrawCtx)null,
- cp, sink );
+ (DrawCtx)null, cp, sink );
}
if ( null != car ) {
@@ -1255,23 +1254,27 @@ public class GameUtils {
return result;
}
- private static class ResendTask extends AsyncTask {
+ private static class Resender extends Thread {
private Context m_context;
private ResendDoneProc m_doneProc;
private CommsConnType m_filter;
- private int m_nSent = 0;
+ private Handler m_handler;
- public ResendTask( Context context, CommsConnType filter,
- ResendDoneProc proc )
+ public Resender( Context context, CommsConnType filter,
+ ResendDoneProc proc )
{
m_context = context;
m_filter = filter;
m_doneProc = proc;
+ if ( null != proc ) {
+ m_handler = new Handler();
+ }
}
@Override
- protected Void doInBackground( Void... unused )
+ public void run()
{
+ int nSentTotal = 0;
HashMap games
= DBUtils.getGamesWithSendsPending( m_context );
@@ -1296,11 +1299,11 @@ public class GameUtils {
int nSent = XwJNI.comms_resendAll( gamePtr, true,
m_filter, false );
gamePtr.release();
- Log.d( TAG, "ResendTask.doInBackground(): sent %d "
+ Log.d( TAG, "Resender.doInBackground(): sent %d "
+ "messages for rowid %d", nSent, rowid );
- m_nSent += sink.numSent();
+ nSentTotal += sink.numSent();
} else {
- Log.d( TAG, "ResendTask.doInBackground(): loadMakeGame()"
+ Log.d( TAG, "Resender.doInBackground(): loadMakeGame()"
+ " failed for rowid %d", rowid );
}
lock.unlock();
@@ -1311,19 +1314,21 @@ public class GameUtils {
false, false );
jniThread.release();
} else {
- Log.w( TAG, "ResendTask.doInBackground: unable to unlock %d",
+ Log.w( TAG, "Resender.doInBackground: unable to unlock %d",
rowid );
}
}
}
- return null;
- }
- @Override
- protected void onPostExecute( Void unused )
- {
if ( null != m_doneProc ) {
- m_doneProc.onResendDone( m_context, m_nSent );
+ final int fSentTotal = nSentTotal;
+ m_handler
+ .post( new Runnable() {
+ @Override
+ public void run() {
+ m_doneProc.onResendDone( m_context, fSentTotal );
+ }
+ });
}
}
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java
index 9296d70d6..7838c07e3 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/GamesListDelegate.java
@@ -1451,8 +1451,6 @@ public class GamesListDelegate extends ListDelegateBase
showItemsIf( ONEGAME_ITEMS, menu, 1 == nGamesSelected );
showItemsIf( ONEGROUP_ITEMS, menu, 1 == nGroupsSelected );
- // check for updates only serves release builds, so don't offer in
- // DEBUG case
boolean enable = showDbg && nothingSelected
&& UpdateCheckReceiver.haveToCheck( m_activity );
Utils.setItemVisible( menu, R.id.games_menu_checkupdates, enable );
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java
index b4ce7a02f..cadb8f6af 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/RelayService.java
@@ -34,7 +34,7 @@ import org.eehouse.android.xw4.MultiService.DictFetchOwner;
import org.eehouse.android.xw4.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
-import org.eehouse.android.xw4.jni.UtilCtxt.DevIDType;
+import org.eehouse.android.xw4.jni.DUtilCtxt.DevIDType;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java
index 30b35be4f..17cf0f21e 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/SMSService.java
@@ -1,6 +1,6 @@
-/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
+/* -*- compile-command: "find-and-gradle.sh inXw4Deb"; -*- */
/*
- * Copyright 2010 by Eric House (xwords@eehouse.org). All rights
+ * Copyright 2010 - 2018 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@@ -40,6 +40,7 @@ import org.eehouse.android.xw4.MultiService.DictFetchOwner;
import org.eehouse.android.xw4.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
+import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils;
import java.io.ByteArrayInputStream;
@@ -55,19 +56,11 @@ import java.util.Set;
public class SMSService extends XWService {
private static final String TAG = SMSService.class.getSimpleName();
- 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 int KITKAT = 19;
-
private static final String MSG_SENT = "MSG_SENT";
private static final String MSG_DELIVERED = "MSG_DELIVERED";
- private static final int SMS_PROTO_VERSION_ORIG = 0;
private static final int SMS_PROTO_VERSION_WITHPORT = 1;
private static final int SMS_PROTO_VERSION = SMS_PROTO_VERSION_WITHPORT;
- private static final int MAX_LEN_TEXT = 100;
- private static final int MAX_LEN_BINARY = 100;
- private static final int MAX_MSG_COUNT = 16; // 1.6K enough? Should be....
private enum SMSAction { _NONE,
INVITE,
SEND,
@@ -75,6 +68,7 @@ public class SMSService extends XWService {
ADDED_MISSING,
STOP_SELF,
HANDLEDATA,
+ RESEND,
};
private static final String CMD_STR = "CMD";
@@ -98,8 +92,6 @@ public class SMSService extends XWService {
private int m_nReceived = 0;
private static int s_nSent = 0;
- private static Map> s_partialMsgs
- = new HashMap>();
private static Set s_sentDied = new HashSet();
public static class SMSPhoneInfo {
@@ -366,6 +358,9 @@ public class SMSService extends XWService {
phone = intent.getStringExtra( PHONE );
sendDiedPacket( phone, gameID );
break;
+ case RESEND:
+ phone = intent.getStringExtra( PHONE );
+ resendFor( phone, null, false );
}
}
@@ -434,18 +429,18 @@ public class SMSService extends XWService {
dos.writeInt( gameID );
dos.write( bytes, 0, bytes.length );
dos.flush();
- if ( send( SMS_CMD.DATA, bas.toByteArray(), phone ) ) {
- nSent = bytes.length;
- }
+ send( SMS_CMD.DATA, bas.toByteArray(), phone );
+ nSent = bytes.length;
} catch ( java.io.IOException ioe ) {
Log.ex( TAG, ioe );
}
return nSent;
}
- private boolean send( SMS_CMD cmd, byte[] bytes, String phone )
+ private void send( SMS_CMD cmd, byte[] bytes, String phone )
throws java.io.IOException
{
+ Log.d( TAG, "send(%s, len=%d)", cmd, bytes.length );
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
DataOutputStream dos = new DataOutputStream( bas );
dos.writeByte( SMS_PROTO_VERSION );
@@ -457,42 +452,44 @@ public class SMSService extends XWService {
dos.flush();
byte[] data = bas.toByteArray();
- byte[][] msgs = breakAndEncode( data );
- boolean result = null != msgs && sendBuffers( msgs, phone );
- return result;
+
+ boolean newSMSEnabled = XWPrefs.getSMSProtoEnabled( this );
+ boolean forceNow = !newSMSEnabled; // || cmd == SMS_CMD.INVITE;
+ resendFor( phone, data, forceNow );
}
- private byte[][] breakAndEncode( byte msg[] ) throws java.io.IOException
+ private void resendFor( String phone, byte[] data, boolean forceNow )
{
- byte[][] result = null;
- int count = (msg.length + (MAX_LEN_BINARY-1)) / MAX_LEN_BINARY;
- if ( count < MAX_MSG_COUNT ) {
- result = new byte[count][];
- int msgID = ++s_nSent % 0x000000FF;
-
- int start = 0;
- int end = 0;
- for ( int ii = 0; ii < count; ++ii ) {
- int len = msg.length - end;
- if ( len > MAX_LEN_BINARY ) {
- len = MAX_LEN_BINARY;
- }
- end += len;
- byte[] part = new byte[4 + len];
- part[0] = (byte)SMS_PROTO_VERSION;
- part[1] = (byte)msgID;
- part[2] = (byte)ii;
- part[3] = (byte)count;
- System.arraycopy( msg, start, part, 4, len );
-
- result[ii] = part;
- start = end;
- }
- } else {
- Log.w( TAG, "breakAndEncode(): msg count %d too large; dropping",
- count );
+ int[] waitSecs = { 0 };
+ byte[][] msgs = XwJNI.smsproto_prepOutbound( data, phone, forceNow, waitSecs );
+ if ( null != msgs ) {
+ sendBuffers( msgs, phone );
}
- return result;
+ if ( waitSecs[0] > 0 ) {
+ Assert.assertFalse( forceNow );
+ postResend( phone, waitSecs[0] );
+ }
+ }
+
+ private void postResend( final String phone, final int waitSecs )
+ {
+ Log.d( TAG, "postResend" );
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep( waitSecs * 1000 );
+
+ Log.d( TAG, "postResend.run()" );
+ Intent intent = getIntentTo( SMSService.this,
+ SMSAction.RESEND );
+ intent.putExtra( PHONE, phone );
+ startService( intent );
+ } catch ( InterruptedException ie ) {
+ Log.e( TAG, ie.getMessage() );
+ }
+ }
+ } ).start();
}
private void receive( SMS_CMD cmd, byte[] data, String phone )
@@ -534,51 +531,18 @@ public class SMSService extends XWService {
private void receiveBuffer( byte[] buffer, String senderPhone )
{
- byte proto = buffer[0];
- int id = buffer[1];
- int index = buffer[2];
- int count = buffer[3];
- byte[] rest = new byte[buffer.length - 4];
- System.arraycopy( buffer, 4, rest, 0, rest.length );
- if ( tryAssemble( senderPhone, id, index, count, rest ) ) {
- postEvent( MultiEvent.SMS_RECEIVE_OK );
- } else {
- // Will see this when don't have SMS permission
- Log.w( TAG, "receiveBuffer(): bogus message from phone %s",
- senderPhone );
- }
- }
-
- private boolean tryAssemble( String senderPhone, int id, int index,
- int count, byte[] msg )
- {
- boolean success = true;
- if ( index == 0 && count == 1 ) { // most common case
- success = disAssemble( senderPhone, msg );
- } else if ( count > 0 && count < MAX_MSG_COUNT && index < count ) {
- // required? Should always be in main thread.
- synchronized( s_partialMsgs ) {
- HashMap perPhone =
- s_partialMsgs.get( senderPhone );
- if ( null == perPhone ) {
- perPhone = new HashMap ();
- s_partialMsgs.put( senderPhone, perPhone );
- }
- MsgStore store = perPhone.get( id );
- if ( null == store ) {
- store = new MsgStore( id, count, false );
- perPhone.put( id, store );
- }
-
- if ( store.add( index, msg ).isComplete() ) {
- success = disAssemble( senderPhone, store.messageData() );
- perPhone.remove( id );
+ byte[][] msgs = XwJNI.smsproto_prepInbound( buffer, senderPhone );
+ if ( null != msgs ) {
+ for ( byte[] msg : msgs ) {
+ if ( !disAssemble( senderPhone, msg ) ) {
+ Log.e( TAG, "failed on message from %s", senderPhone );
}
}
+ postEvent( MultiEvent.SMS_RECEIVE_OK );
} else {
- success = false;
+ Log.w( TAG, "receiveBuffer(): bogus or incomplete message from phone %s",
+ senderPhone );
}
- return success;
}
private boolean disAssemble( String senderPhone, byte[] fullMsg )
@@ -685,7 +649,7 @@ public class SMSService extends XWService {
Log.i( TAG, "dropping because SMS disabled" );
}
- if ( showToasts( this ) && success && (0 == (s_nSent % 5)) ) {
+ if ( showToasts( this ) && success && (0 == (++s_nSent % 5)) ) {
DbgUtils.showf( this, "Sent msg %d", s_nSent );
}
@@ -777,70 +741,4 @@ public class SMSService extends XWService {
return sendPacket( addr.sms_phone, gameID, buf );
}
}
-
- private class MsgStore {
- String[] m_msgsText;
- byte[][] m_msgsData;
- int m_msgID;
- int m_haveCount;
- int m_fullLength;
-
- public MsgStore( int id, int count, boolean usingStrings )
- {
- m_msgID = id;
- if ( usingStrings ) {
- m_msgsText = new String[count];
- } else {
- m_msgsData = new byte[count][];
- }
- m_fullLength = 0;
- }
-
- public MsgStore add( int index, String msg )
- {
- if ( null == m_msgsText[index] ) {
- ++m_haveCount;
- m_fullLength += msg.length();
- }
- m_msgsText[index] = msg;
- return this;
- }
-
- public MsgStore add( int index, byte[] msg )
- {
- if ( null == m_msgsData[index] ) {
- ++m_haveCount;
- m_fullLength += msg.length;
- }
- m_msgsData[index] = msg;
- return this;
- }
-
- public boolean isComplete()
- {
- int count = null != m_msgsText ? m_msgsText.length : m_msgsData.length;
- boolean complete = count == m_haveCount;
- return complete;
- }
-
- public String messageText()
- {
- StringBuffer sb = new StringBuffer(m_fullLength);
- for ( String msg : m_msgsText ) {
- sb.append( msg );
- }
- return sb.toString();
- }
-
- public byte[] messageData()
- {
- byte[] result = new byte[m_fullLength];
- int indx = 0;
- for ( byte[] msg : m_msgsData ) {
- System.arraycopy( msg, 0, result, indx, msg.length );
- indx += msg.length;
- }
- return result;
- }
- }
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/UpdateCheckReceiver.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/UpdateCheckReceiver.java
index 0a599d6e9..300a96ddf 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/UpdateCheckReceiver.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/UpdateCheckReceiver.java
@@ -256,7 +256,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
m_dals = dals;
}
- @Override protected String doInBackground( Void... unused )
+ @Override
+ protected String doInBackground( Void... unused )
{
HttpURLConnection conn
= NetUtils.makeHttpUpdateConn( m_context, "getUpdates" );
@@ -267,7 +268,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
return json;
}
- @Override protected void onPostExecute( String json )
+ @Override
+ protected void onPostExecute( String json )
{
if ( null != json ) {
makeNotificationsIf( json, m_params );
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java
index 4c073a829..c5a02bf0e 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/Utils.java
@@ -34,17 +34,16 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.AssetManager;
import android.content.res.Configuration;
-import android.text.ClipboardManager;
-
import android.database.Cursor;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.provider.ContactsContract.PhoneLookup;
import android.support.v4.app.NotificationCompat;
+import android.support.v4.content.FileProvider;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
-
+import android.text.ClipboardManager;
import android.util.Base64;
import android.view.Menu;
import android.view.MenuItem;
@@ -486,13 +485,16 @@ public class Utils {
return null == s_appVersion? 0 : s_appVersion;
}
- public static Intent makeInstallIntent( File file )
+ public static Intent makeInstallIntent( Context context, File file )
{
- String withScheme = "file://" + file.getPath();
- Uri uri = Uri.parse( withScheme );
+ Uri uri = FileProvider
+ .getUriForFile( context,
+ BuildConfig.APPLICATION_ID + ".provider",
+ file );
Intent intent = new Intent( Intent.ACTION_VIEW );
intent.setDataAndType( uri, XWConstants.APK_TYPE );
- intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
+ intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_GRANT_READ_URI_PERMISSION );
return intent;
}
@@ -501,7 +503,7 @@ public class Utils {
{
boolean result = false;
PackageManager pm = context.getPackageManager();
- Intent intent = makeInstallIntent( path );
+ Intent intent = makeInstallIntent( context, path );
List doers =
pm.queryIntentActivities( intent,
PackageManager.MATCH_DEFAULT_ONLY );
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java
index 5bad7482e..4de7cb416 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/XWPrefs.java
@@ -82,6 +82,11 @@ public class XWPrefs {
return getPrefsBoolean( context, R.string.key_enable_sms_toself, false );
}
+ public static boolean getSMSProtoEnabled( Context context )
+ {
+ return getPrefsBoolean( context, R.string.key_enable_smsproto, false );
+ }
+
public static boolean getHideNewgameButtons( Context context )
{
return getPrefsBoolean( context, R.string.key_hide_newgames,
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java
new file mode 100644
index 000000000..8192636f7
--- /dev/null
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/DUtilCtxt.java
@@ -0,0 +1,238 @@
+/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
+/*
+ * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All
+ * rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+package org.eehouse.android.xw4.jni;
+
+import android.content.Context;
+import android.telephony.PhoneNumberUtils;
+
+import junit.framework.Assert;
+
+import org.eehouse.android.xw4.XWApp;
+import org.eehouse.android.xw4.DBUtils;
+import org.eehouse.android.xw4.DevID;
+import org.eehouse.android.xw4.R;
+import org.eehouse.android.xw4.Log;
+import org.eehouse.android.xw4.loc.LocUtils;
+
+public class DUtilCtxt {
+ private static final String TAG = DUtilCtxt.class.getSimpleName();
+
+ private Context m_context;
+
+ public DUtilCtxt() {
+ m_context = XWApp.getContext();
+ }
+
+ // Possible values for typ[0], these must match enum in xwrelay.sh
+ public enum DevIDType { ID_TYPE_NONE
+ , ID_TYPE_RELAY
+ , ID_TYPE_LINUX
+ , ID_TYPE_ANDROID_GCM
+ , ID_TYPE_ANDROID_OTHER
+ , ID_TYPE_ANON
+ }
+
+ public String getDevID( /*out*/ byte[] typa )
+ {
+ DevIDType typ = DevIDType.ID_TYPE_NONE;
+ String result = DevID.getRelayDevID( m_context );
+ if ( null != result ) {
+ typ = DevIDType.ID_TYPE_RELAY;
+ } else {
+ result = DevID.getGCMDevID( m_context );
+ if ( result.equals("") ) {
+ result = null;
+ } else {
+ typ = DevIDType.ID_TYPE_ANDROID_GCM;
+ }
+ }
+ typa[0] = (byte)typ.ordinal();
+ return result;
+ }
+
+ public void deviceRegistered( DevIDType devIDType, String idRelay )
+ {
+ switch ( devIDType ) {
+ case ID_TYPE_RELAY:
+ DevID.setRelayDevID( m_context, idRelay );
+ break;
+ case ID_TYPE_NONE:
+ DevID.clearRelayDevID( m_context );
+ break;
+ default:
+ Assert.fail();
+ break;
+ }
+ }
+
+ static final int STRD_ROBOT_TRADED = 1;
+ static final int STR_ROBOT_MOVED = 2;
+ static final int STRS_VALUES_HEADER = 3;
+ static final int STRD_REMAINING_TILES_ADD = 4;
+ static final int STRD_UNUSED_TILES_SUB = 5;
+ static final int STRS_REMOTE_MOVED = 6;
+ static final int STRD_TIME_PENALTY_SUB = 7;
+ static final int STR_PASS = 8;
+ static final int STRS_MOVE_ACROSS = 9;
+ static final int STRS_MOVE_DOWN = 10;
+ static final int STRS_TRAY_AT_START = 11;
+ static final int STRSS_TRADED_FOR = 12;
+ static final int STR_PHONY_REJECTED = 13;
+ static final int STRD_CUMULATIVE_SCORE = 14;
+ static final int STRS_NEW_TILES = 15;
+ static final int STR_COMMIT_CONFIRM = 16;
+ static final int STR_BONUS_ALL = 17;
+ static final int STRD_TURN_SCORE = 18;
+ static final int STRD_REMAINS_HEADER = 19;
+ static final int STRD_REMAINS_EXPL = 20;
+ static final int STRSD_RESIGNED = 21;
+ static final int STRSD_WINNER = 22;
+ static final int STRDSD_PLACER = 23;
+
+
+ public String getUserString( int stringCode )
+ {
+ Log.d( TAG, "getUserString(%d)", stringCode );
+ int id = 0;
+ switch( stringCode ) {
+ case STR_ROBOT_MOVED:
+ id = R.string.str_robot_moved_fmt;
+ break;
+ case STRS_VALUES_HEADER:
+ id = R.string.strs_values_header_fmt;
+ break;
+ case STRD_REMAINING_TILES_ADD:
+ id = R.string.strd_remaining_tiles_add_fmt;
+ break;
+ case STRD_UNUSED_TILES_SUB:
+ id = R.string.strd_unused_tiles_sub_fmt;
+ break;
+ case STRS_REMOTE_MOVED:
+ id = R.string.str_remote_moved_fmt;
+ break;
+ case STRD_TIME_PENALTY_SUB:
+ id = R.string.strd_time_penalty_sub_fmt;
+ break;
+ case STR_PASS:
+ id = R.string.str_pass;
+ break;
+ case STRS_MOVE_ACROSS:
+ id = R.string.strs_move_across_fmt;
+ break;
+ case STRS_MOVE_DOWN:
+ id = R.string.strs_move_down_fmt;
+ break;
+ case STRS_TRAY_AT_START:
+ id = R.string.strs_tray_at_start_fmt;
+ break;
+ case STRSS_TRADED_FOR:
+ id = R.string.strss_traded_for_fmt;
+ break;
+ case STR_PHONY_REJECTED:
+ id = R.string.str_phony_rejected;
+ break;
+ case STRD_CUMULATIVE_SCORE:
+ id = R.string.strd_cumulative_score_fmt;
+ break;
+ case STRS_NEW_TILES:
+ id = R.string.strs_new_tiles_fmt;
+ break;
+ case STR_COMMIT_CONFIRM:
+ id = R.string.str_commit_confirm;
+ break;
+ case STR_BONUS_ALL:
+ id = R.string.str_bonus_all;
+ break;
+ case STRD_TURN_SCORE:
+ id = R.string.strd_turn_score_fmt;
+ break;
+ case STRSD_RESIGNED:
+ id = R.string.str_resigned_fmt;
+ break;
+ case STRSD_WINNER:
+ id = R.string.str_winner_fmt;
+ break;
+ case STRDSD_PLACER:
+ id = R.string.str_placer_fmt;
+ break;
+
+ default:
+ Log.w( TAG, "no such stringCode: %d", stringCode );
+ }
+
+ String result = (0 == id) ? "" : LocUtils.getString( m_context, id );
+ Log.d( TAG, "getUserString() => %s", result );
+ return result;
+ }
+
+ public String getUserQuantityString( int stringCode, int quantity )
+ {
+ int pluralsId = 0;
+ switch ( stringCode ) {
+ case STRD_ROBOT_TRADED:
+ pluralsId = R.plurals.strd_robot_traded_fmt;
+ break;
+ case STRD_REMAINS_HEADER:
+ pluralsId = R.plurals.strd_remains_header_fmt;
+ break;
+ case STRD_REMAINS_EXPL:
+ pluralsId = R.plurals.strd_remains_expl_fmt;
+ break;
+ }
+
+ String result = "";
+ if ( 0 != pluralsId ) {
+ result = LocUtils.getQuantityString( m_context, pluralsId, quantity );
+ }
+ return result;
+ }
+
+ public boolean phoneNumbersSame( String num1, String num2 )
+ {
+ boolean same = PhoneNumberUtils.compare( m_context, num1, num2 );
+ return same;
+ }
+
+ public void store( String key, byte[] data )
+ {
+ Log.d( TAG, "store(key=%s)", key );
+
+ if ( null == data ) {
+ } else {
+ DBUtils.setBytesFor( m_context, key, data );
+ }
+ }
+
+ public byte[] load( String key )
+ {
+ byte[] result = null;
+ int resultLen = 0;
+ Log.d( TAG, "load(key=%s)", key );
+
+ result = DBUtils.getBytesFor( m_context, key );
+ if ( result != null ) {
+ resultLen = result.length;
+ }
+
+ Log.d( TAG, "load(%s) returning %d bytes", key, resultLen );
+ return result;
+ }
+}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java
index 28bf81107..d6c9a6c37 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIThread.java
@@ -212,7 +212,6 @@ public class JNIThread extends Thread {
}
CommonPrefs cp = CommonPrefs.get( context );
- JNIUtils jniUtils = JNIUtilsImpl.get( context );
// Assert.assertNull( m_jniGamePtr ); // fired!!
if ( null != m_jniGamePtr ) {
@@ -224,15 +223,13 @@ public class JNIThread extends Thread {
dictNames, pairs.m_bytes,
pairs.m_paths,
m_gi.langName( m_context ),
- utils, jniUtils,
- null, cp, m_xport );
+ utils, null, cp, m_xport );
}
if ( null == m_jniGamePtr ) {
m_jniGamePtr = XwJNI.initNew( m_gi, dictNames, pairs.m_bytes,
pairs.m_paths,
m_gi.langName(m_context),
- utils, jniUtils, null, cp,
- m_xport );
+ utils, null, cp, m_xport );
}
Assert.assertNotNull( m_jniGamePtr );
m_lastSavedState = Arrays.hashCode( stream );
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIUtilsImpl.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIUtilsImpl.java
index 05506184c..79743be24 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIUtilsImpl.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/JNIUtilsImpl.java
@@ -25,6 +25,7 @@ import android.content.Context;
import junit.framework.Assert;
import org.eehouse.android.xw4.DBUtils;
+import org.eehouse.android.xw4.XWApp;
import org.eehouse.android.xw4.Log;
import org.eehouse.android.xw4.Utils;
@@ -41,14 +42,13 @@ public class JNIUtilsImpl implements JNIUtils {
private static JNIUtilsImpl s_impl = null;
private Context m_context;
- private JNIUtilsImpl(){}
+ private JNIUtilsImpl(Context context) { m_context = context; }
- public static JNIUtils get( Context context )
+ public static synchronized JNIUtils get()
{
if ( null == s_impl ) {
- s_impl = new JNIUtilsImpl();
+ s_impl = new JNIUtilsImpl( XWApp.getContext() );
}
- s_impl.m_context = context;
return s_impl;
}
@@ -133,24 +133,28 @@ public class JNIUtilsImpl implements JNIUtils {
public String getMD5SumFor( byte[] bytes )
{
- 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;
+ String result = null;
+ if ( bytes != null ) {
+ 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 ) {
+ Log.ex( TAG, nsae );
}
- digest = md.digest();
- } catch ( java.security.NoSuchAlgorithmException nsae ) {
- Log.ex( TAG, nsae );
+ result = Utils.digestToString( digest );
}
- return Utils.digestToString( digest );
+ return result;
}
public String getMD5SumFor( String dictName, byte[] bytes )
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java
index cc34891be..8812323e6 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxt.java
@@ -60,50 +60,10 @@ public interface UtilCtxt {
void remSelected();
void setIsServer( boolean isServer );
- // Possible values for typ[0], these must match enum in xwrelay.sh
- public enum DevIDType { ID_TYPE_NONE
- , ID_TYPE_RELAY
- , ID_TYPE_LINUX
- , ID_TYPE_ANDROID_GCM
- , ID_TYPE_ANDROID_OTHER
- , ID_TYPE_ANON
- }
-
- String getDevID( /*out*/ byte[] typ );
- void deviceRegistered( DevIDType devIDType, String idRelay );
-
void bonusSquareHeld( int bonus );
void playerScoreHeld( int player );
void cellSquareHeld( String words );
- static final int STRD_ROBOT_TRADED = 1;
- static final int STR_ROBOT_MOVED = 2;
- static final int STRS_VALUES_HEADER = 3;
- static final int STRD_REMAINING_TILES_ADD = 4;
- static final int STRD_UNUSED_TILES_SUB = 5;
- static final int STRS_REMOTE_MOVED = 6;
- static final int STRD_TIME_PENALTY_SUB = 7;
- static final int STR_PASS = 8;
- static final int STRS_MOVE_ACROSS = 9;
- static final int STRS_MOVE_DOWN = 10;
- static final int STRS_TRAY_AT_START = 11;
- static final int STRSS_TRADED_FOR = 12;
- static final int STR_PHONY_REJECTED = 13;
- static final int STRD_CUMULATIVE_SCORE = 14;
- static final int STRS_NEW_TILES = 15;
- static final int STR_COMMIT_CONFIRM = 16;
- static final int STR_BONUS_ALL = 17;
- static final int STRD_TURN_SCORE = 18;
- static final int STRD_REMAINS_HEADER = 19;
- static final int STRD_REMAINS_EXPL = 20;
- static final int STRSD_RESIGNED = 21;
- static final int STRSD_WINNER = 22;
- static final int STRDSD_PLACER = 23;
-
-
- String getUserString( int stringCode );
- String getUserQuantityString( int stringCode, int quantity );
-
void notifyMove( String query );
void notifyTrade( String[] tiles );
@@ -146,6 +106,4 @@ public interface UtilCtxt {
boolean turnLost );
void showChat( String msg, int fromIndx, String fromName, int tsSeconds );
-
- boolean phoneNumbersSame( String num1, String num2 );
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java
index d18821342..6b3883086 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/UtilCtxtImpl.java
@@ -21,16 +21,11 @@
package org.eehouse.android.xw4.jni;
import android.content.Context;
-import android.telephony.PhoneNumberUtils;
import junit.framework.Assert;
-import org.eehouse.android.xw4.DevID;
import org.eehouse.android.xw4.Log;
-import org.eehouse.android.xw4.R;
-import org.eehouse.android.xw4.XWApp;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnTypeSet;
-import org.eehouse.android.xw4.loc.LocUtils;
public class UtilCtxtImpl implements UtilCtxt {
private static final String TAG = UtilCtxtImpl.class.getSimpleName();
@@ -44,222 +39,114 @@ public class UtilCtxtImpl implements UtilCtxt {
m_context = context;
}
+ @Override
public void requestTime() {
subclassOverride( "requestTime" );
}
+ @Override
public void notifyPickTileBlank( int playerNum, int col, int row,
String[] texts )
{
subclassOverride( "userPickTileBlank" );
}
+ @Override
public void informNeedPickTiles( boolean isInitial, int playerNum, int nToPick,
String[] texts, int[] counts )
{
subclassOverride( "informNeedPickTiles" );
}
+ @Override
public void informNeedPassword( int player, String name )
{
subclassOverride( "informNeedPassword" );
}
+ @Override
public void turnChanged( int newTurn )
{
subclassOverride( "turnChanged" );
}
+ @Override
public boolean engineProgressCallback()
{
// subclassOverride( "engineProgressCallback" );
return true;
}
+ @Override
public void setTimer( int why, int when, int handle )
{
subclassOverride( "setTimer" );
}
+ @Override
public void clearTimer( int why )
{
subclassOverride( "clearTimer" );
}
+ @Override
public void remSelected()
{
subclassOverride( "remSelected" );
}
+ @Override
public void setIsServer( boolean isServer )
{
subclassOverride( "setIsServer" );
}
- public String getDevID( /*out*/ byte[] typa )
- {
- UtilCtxt.DevIDType typ = UtilCtxt.DevIDType.ID_TYPE_NONE;
- String result = DevID.getRelayDevID( m_context );
- if ( null != result ) {
- typ = UtilCtxt.DevIDType.ID_TYPE_RELAY;
- } else {
- result = DevID.getGCMDevID( m_context );
- if ( result.equals("") ) {
- result = null;
- } else {
- typ = UtilCtxt.DevIDType.ID_TYPE_ANDROID_GCM;
- }
- }
- typa[0] = (byte)typ.ordinal();
- return result;
- }
-
- public void deviceRegistered( UtilCtxt.DevIDType devIDType, String idRelay )
- {
- switch ( devIDType ) {
- case ID_TYPE_RELAY:
- DevID.setRelayDevID( m_context, idRelay );
- break;
- case ID_TYPE_NONE:
- DevID.clearRelayDevID( m_context );
- break;
- default:
- Assert.fail();
- break;
- }
- }
-
+ @Override
public void bonusSquareHeld( int bonus )
{
}
+ @Override
public void playerScoreHeld( int player )
{
}
+ @Override
public void cellSquareHeld( String words )
{
}
- public String getUserString( int stringCode )
- {
- int id = 0;
- switch( stringCode ) {
- case UtilCtxt.STR_ROBOT_MOVED:
- id = R.string.str_robot_moved_fmt;
- break;
- case UtilCtxt.STRS_VALUES_HEADER:
- id = R.string.strs_values_header_fmt;
- break;
- case UtilCtxt.STRD_REMAINING_TILES_ADD:
- id = R.string.strd_remaining_tiles_add_fmt;
- break;
- case UtilCtxt.STRD_UNUSED_TILES_SUB:
- id = R.string.strd_unused_tiles_sub_fmt;
- break;
- case UtilCtxt.STRS_REMOTE_MOVED:
- id = R.string.str_remote_moved_fmt;
- break;
- case UtilCtxt.STRD_TIME_PENALTY_SUB:
- id = R.string.strd_time_penalty_sub_fmt;
- break;
- case UtilCtxt.STR_PASS:
- id = R.string.str_pass;
- break;
- case UtilCtxt.STRS_MOVE_ACROSS:
- id = R.string.strs_move_across_fmt;
- break;
- case UtilCtxt.STRS_MOVE_DOWN:
- id = R.string.strs_move_down_fmt;
- break;
- case UtilCtxt.STRS_TRAY_AT_START:
- id = R.string.strs_tray_at_start_fmt;
- break;
- case UtilCtxt.STRSS_TRADED_FOR:
- id = R.string.strss_traded_for_fmt;
- break;
- case UtilCtxt.STR_PHONY_REJECTED:
- id = R.string.str_phony_rejected;
- break;
- case UtilCtxt.STRD_CUMULATIVE_SCORE:
- id = R.string.strd_cumulative_score_fmt;
- break;
- case UtilCtxt.STRS_NEW_TILES:
- id = R.string.strs_new_tiles_fmt;
- break;
- case UtilCtxt.STR_COMMIT_CONFIRM:
- id = R.string.str_commit_confirm;
- break;
- case UtilCtxt.STR_BONUS_ALL:
- id = R.string.str_bonus_all;
- break;
- case UtilCtxt.STRD_TURN_SCORE:
- id = R.string.strd_turn_score_fmt;
- break;
- case UtilCtxt.STRSD_RESIGNED:
- id = R.string.str_resigned_fmt;
- break;
- case UtilCtxt.STRSD_WINNER:
- id = R.string.str_winner_fmt;
- break;
- case UtilCtxt.STRDSD_PLACER:
- id = R.string.str_placer_fmt;
- break;
-
- default:
- Log.w( TAG, "no such stringCode: %d", stringCode );
- }
-
- String result = (0 == id) ? "" : LocUtils.getString( m_context, id );
- return result;
- }
-
- public String getUserQuantityString( int stringCode, int quantity )
- {
- int pluralsId = 0;
- switch ( stringCode ) {
- case UtilCtxt.STRD_ROBOT_TRADED:
- pluralsId = R.plurals.strd_robot_traded_fmt;
- break;
- case UtilCtxt.STRD_REMAINS_HEADER:
- pluralsId = R.plurals.strd_remains_header_fmt;
- break;
- case UtilCtxt.STRD_REMAINS_EXPL:
- pluralsId = R.plurals.strd_remains_expl_fmt;
- break;
- }
-
- String result = "";
- if ( 0 != pluralsId ) {
- result = LocUtils.getQuantityString( m_context, pluralsId, quantity );
- }
- return result;
- }
-
+ @Override
public void notifyMove( String query )
{
subclassOverride( "notifyMove" );
}
+
+ @Override
public void notifyTrade( String[] tiles )
{
subclassOverride( "notifyTrade" );
}
+ @Override
public void userError( int id )
{
subclassOverride( "userError" );
}
+ @Override
public void informMove( int turn, String expl, String words )
{
subclassOverride( "informMove" );
}
+ @Override
public void informUndo()
{
subclassOverride( "informUndo" );
}
+ @Override
public void informNetDict( int lang, String oldName,
String newName, String newSum,
CurGameInfo.XWPhoniesChoice phonies )
@@ -267,6 +154,7 @@ public class UtilCtxtImpl implements UtilCtxt {
subclassOverride( "informNetDict" );
}
+ @Override
public void informMissing( boolean isServer,
CommsConnTypeSet connTypes,
int nDevices, int nMissingPlayers )
@@ -276,11 +164,13 @@ public class UtilCtxtImpl implements UtilCtxt {
// Probably want to cache the fact that the game over notification
// showed up and then display it next time game's opened.
+ @Override
public void notifyGameOver()
{
subclassOverride( "notifyGameOver" );
}
+ @Override
public void notifyIllegalWords( String dict, String[] words, int turn,
boolean turnLost )
{
@@ -288,17 +178,12 @@ public class UtilCtxtImpl implements UtilCtxt {
}
// These need to go into some sort of chat DB, not dropped.
+ @Override
public void showChat( String msg, int fromIndx, String fromName, int tsSeconds )
{
subclassOverride( "showChat" );
}
- public boolean phoneNumbersSame( String num1, String num2 )
- {
- boolean same = PhoneNumberUtils.compare( m_context, num1, num2 );
- return same;
- }
-
private void subclassOverride( String name ) {
// DbgUtils.logf( "%s::%s() called", getClass().getName(), name );
}
diff --git a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java
index 3a7c4ba48..a2a701bcd 100644
--- a/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java
+++ b/xwords4/android/app/src/main/java/org/eehouse/android/xw4/jni/XwJNI.java
@@ -1,7 +1,7 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
/*
- * Copyright 2009-2010 by Eric House (xwords@eehouse.org). All
- * rights reserved.
+ * Copyright 2009 - 2018 by Eric House (xwords@eehouse.org). All rights
+ * reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -97,7 +97,7 @@ public class XwJNI {
private int m_ptr;
private XwJNI()
{
- m_ptr = initGlobals();
+ m_ptr = initGlobals( new DUtilCtxt(), JNIUtilsImpl.get() );
}
public static void cleanGlobals()
@@ -134,23 +134,30 @@ public class XwJNI {
public static native boolean timerFired( GamePtr gamePtr, int why,
int when, int handle );
- // Stateless methods
- public static native byte[] gi_to_stream( CurGameInfo gi );
- public static native void gi_from_stream( CurGameInfo gi, byte[] stream );
+ public static byte[] gi_to_stream( CurGameInfo gi )
+ {
+ return gi_to_stream( getJNI().m_ptr, gi );
+ }
+
+ public static void gi_from_stream( CurGameInfo gi, byte[] stream )
+ {
+ gi_from_stream( getJNI().m_ptr, gi, stream );
+ }
+
public static byte[] nliToStream( NetLaunchInfo nli )
{
nli.freezeAddrs();
- return nli_to_stream( nli );
+ return nli_to_stream( getJNI().m_ptr, nli );
}
- private static native byte[] nli_to_stream( NetLaunchInfo nli );
+
public static NetLaunchInfo nliFromStream( byte[] stream )
{
NetLaunchInfo nli = new NetLaunchInfo();
- nli_from_stream( nli, stream );
+ nli_from_stream( getJNI().m_ptr, nli, stream );
nli.unfreezeAddrs();
return nli;
}
- private static native void nli_from_stream( NetLaunchInfo nli, byte[] stream );
+
public static native void comms_getInitialAddr( CommsAddrRec addr,
String relayHost,
int relayPort );
@@ -160,8 +167,7 @@ public class XwJNI {
private static GamePtr initJNI( long rowid )
{
int seed = Utils.nextRandomInt();
- String tag = String.format( "%d", rowid );
- int ptr = initJNI( getJNI().m_ptr, seed, tag );
+ int ptr = initJNI( getJNI().m_ptr, seed );
GamePtr result = 0 == ptr ? null : new GamePtr( ptr, rowid );
return result;
}
@@ -170,13 +176,13 @@ public class XwJNI {
initFromStream( long rowid, byte[] stream, CurGameInfo gi,
String[] dictNames, byte[][] dictBytes,
String[] dictPaths, String langName,
- UtilCtxt util, JNIUtils jniu, DrawCtx draw,
+ UtilCtxt util, DrawCtx draw,
CommonPrefs cp, TransportProcs procs )
{
GamePtr gamePtr = initJNI( rowid );
if ( game_makeFromStream( gamePtr, stream, gi, dictNames, dictBytes,
- dictPaths, langName, util, jniu, draw,
+ dictPaths, langName, util, draw,
cp, procs ) ) {
gamePtr.retain();
} else {
@@ -189,12 +195,11 @@ public class XwJNI {
public static synchronized GamePtr
initNew( CurGameInfo gi, String[] dictNames, byte[][] dictBytes,
String[] dictPaths, String langName, UtilCtxt util,
- JNIUtils jniu, DrawCtx draw, CommonPrefs cp,
- TransportProcs procs )
+ DrawCtx draw, CommonPrefs cp, TransportProcs procs )
{
GamePtr gamePtr = initJNI( 0 );
game_makeNewGame( gamePtr, gi, dictNames, dictBytes, dictPaths,
- langName, util, jniu, draw, cp, procs );
+ langName, util, draw, cp, procs );
return gamePtr.retain();
}
@@ -211,7 +216,6 @@ public class XwJNI {
String[] dictPaths,
String langName,
UtilCtxt util,
- JNIUtils jniu,
DrawCtx draw, CommonPrefs cp,
TransportProcs procs );
@@ -223,7 +227,6 @@ public class XwJNI {
String[] dictPaths,
String langName,
UtilCtxt util,
- JNIUtils jniu,
DrawCtx draw,
CommonPrefs cp,
TransportProcs procs );
@@ -400,6 +403,19 @@ public class XwJNI {
public static native boolean comms_getAddrDisabled( GamePtr gamePtr, CommsConnType typ,
boolean send );
+ public static byte[][] smsproto_prepOutbound( byte[] buf, String phone, boolean forceNow,
+ /*out*/ int[] waitSecs )
+ {
+ int nowSeconds = (int)(System.currentTimeMillis() / 1000);
+ return smsproto_prepOutbound( getJNI().m_ptr, buf, phone, nowSeconds,
+ forceNow, waitSecs );
+ }
+
+ public static byte[][] smsproto_prepInbound( byte[] data, String fromPhone )
+ {
+ return smsproto_prepInbound( getJNI().m_ptr, data, fromPhone );
+ }
+
// Dicts
public static class DictWrapper {
private int m_dictPtr;
@@ -439,12 +455,10 @@ public class XwJNI {
public static native boolean dict_tilesAreSame( int dict1, int dict2 );
public static native String[] dict_getChars( int dict );
- public static boolean dict_getInfo( byte[] dict, String name,
- String path, JNIUtils jniu,
+ public static boolean dict_getInfo( byte[] dict, String name, String path,
boolean check, DictInfo info )
{
- return dict_getInfo( getJNI().m_ptr, dict, name, path, jniu,
- check, info );
+ return dict_getInfo( getJNI().m_ptr, dict, name, path, check, info );
}
public static native int dict_getTileValue( int dictPtr, int tile );
@@ -452,9 +466,9 @@ public class XwJNI {
// Dict iterator
public final static int MAX_COLS_DICT = 15; // from dictiter.h
public static int dict_iter_init( byte[] dict, String name,
- String path, JNIUtils jniu )
+ String path )
{
- return dict_iter_init( getJNI().m_ptr, dict, name, path, jniu );
+ return dict_iter_init( getJNI().m_ptr, dict, name, path );
}
public static native void dict_iter_setMinMax( int closure,
int min, int max );
@@ -469,19 +483,32 @@ public class XwJNI {
public static native String dict_iter_getDesc( int closure );
// Private methods -- called only here
- private static native int initGlobals();
- private static native void cleanGlobals( int globals );
- private static native int initJNI( int jniState, int seed, String tag );
+ private static native int initGlobals(DUtilCtxt dutil, JNIUtils jniu);
+ private static native void cleanGlobals( int jniState );
+ private static native byte[] gi_to_stream( int jniState, CurGameInfo gi );
+ private static native void gi_from_stream( int jniState, CurGameInfo gi,
+ byte[] stream );
+ private static native byte[] nli_to_stream( int jniState, NetLaunchInfo nli );
+ private static native void nli_from_stream( int jniState, NetLaunchInfo nli,
+ byte[] stream );
+ private static native int initJNI( int jniState, int seed );
private static native void envDone( int globals );
private static native void dict_ref( int dictPtr );
private static native void dict_unref( int dictPtr );
private static native boolean dict_getInfo( int jniState, byte[] dict,
String name, String path,
- JNIUtils jniu, boolean check,
+ boolean check,
DictInfo info );
private static native int dict_iter_init( int jniState, byte[] dict,
- String name, String path,
- JNIUtils jniu );
+ String name, String path );
+
+ private static native byte[][] smsproto_prepOutbound( int jniState, byte[] buf,
+ String phone, int nowSeconds,
+ boolean forceNow,
+ /*out*/int[] waitSecs );
+
+ private static native byte[][] smsproto_prepInbound( int jniState, byte[] data,
+ String fromPhone );
private static native boolean haveEnv( int jniState );
}
diff --git a/xwords4/android/app/src/main/res/values/common_rsrc.xml b/xwords4/android/app/src/main/res/values/common_rsrc.xml
index 984ff5cdc..b776b8be4 100644
--- a/xwords4/android/app/src/main/res/values/common_rsrc.xml
+++ b/xwords4/android/app/src/main/res/values/common_rsrc.xml
@@ -125,6 +125,7 @@
key_enable_dup_invite
key_enable_nfc_toself
key_enable_sms_toself
+ key_enable_smsproto
key_ignore_gcm
key_show_gcm
key_nag_intervals
diff --git a/xwords4/android/app/src/main/res/values/strings.xml b/xwords4/android/app/src/main/res/values/strings.xml
index 8a35abac2..7427f169a 100644
--- a/xwords4/android/app/src/main/res/values/strings.xml
+++ b/xwords4/android/app/src/main/res/values/strings.xml
@@ -2516,6 +2516,10 @@
Reminder intervals (minutes1,minutes2,...)
Enable NFC to self
Fake invitation to aid debugging
+
+ Use new/experimental SMS code
+ (Requires that opponent be using it too)
+
Short-circuit SMS to self
Skip radio when phone numbers same
diff --git a/xwords4/android/app/src/main/res/xml/provider_paths.xml b/xwords4/android/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 000000000..ed52e8162
--- /dev/null
+++ b/xwords4/android/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/xwords4/android/app/src/main/res/xml/xwprefs.xml b/xwords4/android/app/src/main/res/xml/xwprefs.xml
index 8ea8c8a6d..8d6204d6d 100644
--- a/xwords4/android/app/src/main/res/xml/xwprefs.xml
+++ b/xwords4/android/app/src/main/res/xml/xwprefs.xml
@@ -437,6 +437,11 @@
+
vtMgr,
globals, 0, NULL );
diff --git a/xwords4/android/jni/andutils.h b/xwords4/android/jni/andutils.h
index 22602e32f..1a156dee7 100644
--- a/xwords4/android/jni/andutils.h
+++ b/xwords4/android/jni/andutils.h
@@ -31,7 +31,7 @@
/* callback for streams */
void and_send_on_close( XWStreamCtxt* stream, void* closure );
-XWStreamCtxt* and_empty_stream( MPFORMAL AndGlobals* globals );
+XWStreamCtxt* and_empty_stream( MPFORMAL AndGameGlobals* globals );
typedef struct _SetInfo {
const char* name;
diff --git a/xwords4/android/jni/utilwrapper.c b/xwords4/android/jni/utilwrapper.c
index fb6aa18f5..4e948a906 100644
--- a/xwords4/android/jni/utilwrapper.c
+++ b/xwords4/android/jni/utilwrapper.c
@@ -30,6 +30,18 @@
#define MAX_QUANTITY_STRS 4
+typedef struct _AndDUtil {
+ XW_DUtilCtxt dutil;
+ EnvThreadInfo* ti;
+ JNIUtilCtxt* jniutil;
+ jobject jdutil; /* global ref to object implementing XW_DUtilCtxt */
+ XP_UCHAR* userStrings[N_AND_USER_STRINGS];
+ XP_U32 userStringsBits;
+#ifdef XWFEATURE_DEVID
+ XP_UCHAR* devIDStorage;
+#endif
+} AndDUtil;
+
typedef struct _TimerStorage {
XWTimerProc proc;
void* closure;
@@ -40,21 +52,8 @@ typedef struct _AndUtil {
EnvThreadInfo* ti;
jobject jutil; /* global ref to object implementing XW_UtilCtxt */
TimerStorage timerStorage[NUM_TIMERS_PLUS_ONE];
- XP_UCHAR* userStrings[N_AND_USER_STRINGS];
- XP_U32 userStringsBits;
-#ifdef XWFEATURE_DEVID
- XP_UCHAR* devIDStorage;
-#endif
} AndUtil;
-
-static VTableMgr*
-and_util_getVTManager( XW_UtilCtxt* uc )
-{
- AndGlobals* globals = (AndGlobals*)uc->closure;
- return globals->vtMgr;
-}
-
#ifndef XWFEATURE_STANDALONE_ONLY
static XWStreamCtxt*
and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo )
@@ -62,7 +61,7 @@ and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo )
#ifdef DEBUG
AndUtil* util = (AndUtil*)uc;
#endif
- AndGlobals* globals = (AndGlobals*)uc->closure;
+ AndGameGlobals* globals = (AndGameGlobals*)uc->closure;
XWStreamCtxt* stream = and_empty_stream( MPPARM(util->util.mpool)
globals );
stream_setAddress( stream, channelNo );
@@ -82,6 +81,14 @@ and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo )
XP_LOGF( "%s: skipping call into java because jutil==NULL", \
__func__ ); \
}
+
+#define DUTIL_CBK_HEADER(nam,sig) \
+ AndDUtil* dutil = (AndDUtil*)duc; \
+ JNIEnv* env = ENVFORME( dutil->ti ); \
+ if ( NULL != dutil->jdutil ) { \
+ jmethodID mid = getMethodID( env, dutil->jdutil, nam, sig )
+
+#define DUTIL_CBK_TAIL() UTIL_CBK_TAIL()
static XWBonusType and_util_getSquareBonus( XW_UtilCtxt* XP_UNUSED(uc),
XP_U16 boardSize,
@@ -349,26 +356,24 @@ and_util_altKeyDown( XW_UtilCtxt* uc )
return XP_FALSE;
}
-
XP_U32
-and_util_getCurSeconds( XW_UtilCtxt* uc )
+and_dutil_getCurSeconds( XW_DUtilCtxt* duc )
{
- AndUtil* andutil = (AndUtil*)uc;
- XP_U32 curSeconds = getCurSeconds( ENVFORME( andutil->ti ) );
+ AndDUtil* anddutil = (AndDUtil*)duc;
+ XP_U32 curSeconds = getCurSeconds( ENVFORME( anddutil->ti ) );
/* struct timeval tv; */
/* gettimeofday( &tv, NULL ); */
/* XP_LOGF( "%s: %d vs %d", __func__, (int)tv.tv_sec, (int)curSeconds ); */
return curSeconds;
}
-
static DictionaryCtxt*
and_util_makeEmptyDict( XW_UtilCtxt* uc )
{
#ifdef STUBBED_DICT
XP_ASSERT(0);
#else
- AndGlobals* globals = (AndGlobals*)uc->closure;
+ AndGameGlobals* globals = (AndGameGlobals*)uc->closure;
AndUtil* andutil = (AndUtil*)uc;
DictionaryCtxt* result =
and_dictionary_make_empty( MPPARM( ((AndUtil*)uc)->util.mpool )
@@ -378,51 +383,51 @@ and_util_makeEmptyDict( XW_UtilCtxt* uc )
}
static const XP_UCHAR*
-and_util_getUserString( XW_UtilCtxt* uc, XP_U16 stringCode )
+and_dutil_getUserString( XW_DUtilCtxt* duc, XP_U16 stringCode )
{
XP_UCHAR* result = "";
- UTIL_CBK_HEADER("getUserString", "(I)Ljava/lang/String;" );
+ DUTIL_CBK_HEADER("getUserString", "(I)Ljava/lang/String;" );
int index = stringCode - 1; /* see LocalizedStrIncludes.h */
- XP_ASSERT( index < VSIZE( util->userStrings ) );
+ XP_ASSERT( index < VSIZE( dutil->userStrings ) );
- XP_ASSERT( 0 == (util->userStringsBits & (1 << index)) );
+ XP_ASSERT( 0 == (dutil->userStringsBits & (1 << index)) );
- if ( ! util->userStrings[index] ) {
- jstring jresult = (*env)->CallObjectMethod( env, util->jutil, mid,
+ if ( ! dutil->userStrings[index] ) {
+ jstring jresult = (*env)->CallObjectMethod( env, dutil->jdutil, mid,
stringCode );
jsize len = (*env)->GetStringUTFLength( env, jresult );
- XP_UCHAR* buf = XP_MALLOC( util->util.mpool, len + 1 );
+ XP_UCHAR* buf = XP_MALLOC( dutil->dutil.mpool, len + 1 );
const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL );
XP_MEMCPY( buf, jchars, len );
buf[len] = '\0';
(*env)->ReleaseStringUTFChars( env, jresult, jchars );
deleteLocalRef( env, jresult );
- util->userStrings[index] = buf;
+ dutil->userStrings[index] = buf;
}
- result = util->userStrings[index];
- UTIL_CBK_TAIL();
+ result = dutil->userStrings[index];
+ DUTIL_CBK_TAIL();
return result;
}
static const XP_UCHAR*
-and_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 stringCode, XP_U16 quantity )
+and_dutil_getUserQuantityString( XW_DUtilCtxt* duc, XP_U16 stringCode, XP_U16 quantity )
{
XP_UCHAR* result = "";
- UTIL_CBK_HEADER("getUserQuantityString", "(II)Ljava/lang/String;" );
+ DUTIL_CBK_HEADER("getUserQuantityString", "(II)Ljava/lang/String;" );
int index = stringCode - 1; /* see LocalizedStrIncludes.h */
- XP_ASSERT( index < VSIZE( util->userStrings ) );
+ XP_ASSERT( index < VSIZE( dutil->userStrings ) );
XP_UCHAR** ptrs;
- util->userStringsBits |= 1 << index;
- ptrs = (XP_UCHAR**)util->userStrings[index];
+ dutil->userStringsBits |= 1 << index;
+ ptrs = (XP_UCHAR**)dutil->userStrings[index];
if ( !ptrs ) {
- ptrs = (XP_UCHAR**)XP_CALLOC( util->util.mpool, MAX_QUANTITY_STRS * sizeof(*ptrs) );
- util->userStrings[index] = (XP_UCHAR*)ptrs;
+ ptrs = (XP_UCHAR**)XP_CALLOC( dutil->dutil.mpool, MAX_QUANTITY_STRS * sizeof(*ptrs) );
+ dutil->userStrings[index] = (XP_UCHAR*)ptrs;
}
- jstring jresult = (*env)->CallObjectMethod( env, util->jutil, mid,
+ jstring jresult = (*env)->CallObjectMethod( env, dutil->jdutil, mid,
stringCode, quantity );
const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL );
int indx = 0;
@@ -439,7 +444,7 @@ and_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 stringCode, XP_U16 quant
if ( !ptrs[indx] ) {
XP_ASSERT( indx < MAX_QUANTITY_STRS );
jsize len = (*env)->GetStringUTFLength( env, jresult );
- XP_UCHAR* buf = XP_MALLOC( util->util.mpool, len + 1 );
+ XP_UCHAR* buf = XP_MALLOC( dutil->dutil.mpool, len + 1 );
XP_MEMCPY( buf, jchars, len );
buf[len] = '\0';
ptrs[indx] = buf;
@@ -449,10 +454,83 @@ and_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 stringCode, XP_U16 quant
deleteLocalRef( env, jresult );
result = ptrs[indx];
- UTIL_CBK_TAIL();
+ DUTIL_CBK_TAIL();
return result;
}
+static void
+and_dutil_storePtr( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ const void* data, XP_U16 len )
+{
+ DUTIL_CBK_HEADER( "store", "(Ljava/lang/String;[B)V" );
+
+ jbyteArray jdata = makeByteArray( env, len, data );
+ jstring jkey = (*env)->NewStringUTF( env, key );
+
+ (*env)->CallVoidMethod( env, dutil->jdutil, mid, jkey, jdata );
+
+ deleteLocalRefs( env, jdata, jkey, DELETE_NO_REF );
+
+ DUTIL_CBK_TAIL();
+}
+
+static void
+and_dutil_storeStream( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ XWStreamCtxt* stream )
+{
+ const void* ptr = stream_getPtr( stream );
+ XP_U16 len = stream_getSize( stream );
+
+ and_dutil_storePtr( duc, key, ptr, len );
+}
+
+static jbyteArray
+loadToByteArray( XW_DUtilCtxt* duc, const XP_UCHAR* key )
+{
+ jbyteArray result = NULL;
+ DUTIL_CBK_HEADER( "load", "(Ljava/lang/String;)[B");
+
+ jstring jkey = (*env)->NewStringUTF( env, key );
+ result = (*env)->CallObjectMethod( env, dutil->jdutil, mid, jkey );
+ deleteLocalRef( env, jkey );
+ DUTIL_CBK_TAIL();
+ return result;
+}
+
+static void
+and_dutil_loadPtr( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ void* data, XP_U16* lenp )
+{
+ AndDUtil* dutil = (AndDUtil*)duc;
+ JNIEnv* env = ENVFORME( dutil->ti );
+ jbyteArray jvalue = loadToByteArray( duc, key );
+ if ( jvalue != NULL ) {
+ jsize len = (*env)->GetArrayLength( env, jvalue );
+ if ( len <= *lenp ) {
+ jbyte* jelems = (*env)->GetByteArrayElements( env, jvalue, NULL );
+ XP_MEMCPY( data, jelems, len );
+ (*env)->ReleaseByteArrayElements( env, jvalue, jelems, 0 );
+ }
+ *lenp = len;
+ deleteLocalRef( env, jvalue );
+ }
+}
+
+static void
+and_dutil_loadStream( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* stream )
+{
+ AndDUtil* dutil = (AndDUtil*)duc;
+ JNIEnv* env = ENVFORME( dutil->ti );
+ jbyteArray jvalue = loadToByteArray( duc, key );
+ if ( jvalue != NULL ) {
+ jbyte* jelems = (*env)->GetByteArrayElements( env, jvalue, NULL );
+ jsize len = (*env)->GetArrayLength( env, jvalue );
+ stream_putBytes( stream, jelems, len );
+ (*env)->ReleaseByteArrayElements( env, jvalue, jelems, 0 );
+ deleteLocalRef( env, jvalue );
+ }
+}
+
static void
and_util_notifyIllegalWords( XW_UtilCtxt* uc, BadWordInfo* bwi,
XP_U16 turn, XP_Bool turnLost )
@@ -519,18 +597,18 @@ and_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player )
#ifdef XWFEATURE_SMS
static XP_Bool
-and_util_phoneNumbersSame( XW_UtilCtxt* uc, const XP_UCHAR* p1,
- const XP_UCHAR* p2 )
+and_dutil_phoneNumbersSame( XW_DUtilCtxt* duc, const XP_UCHAR* p1,
+ const XP_UCHAR* p2 )
{
XP_Bool same = 0 == strcmp( p1, p2 );
if ( !same ) {
- UTIL_CBK_HEADER( "phoneNumbersSame",
- "(Ljava/lang/String;Ljava/lang/String;)Z" );
+ DUTIL_CBK_HEADER( "phoneNumbersSame",
+ "(Ljava/lang/String;Ljava/lang/String;)Z" );
jstring js1 = (*env)->NewStringUTF( env, p1 );
jstring js2 = (*env)->NewStringUTF( env, p2 );
- same = (*env)->CallBooleanMethod( env, util->jutil, mid, js1, js2 );
+ same = (*env)->CallBooleanMethod( env, dutil->jdutil, mid, js1, js2 );
deleteLocalRefs( env, js1, js2, DELETE_NO_REF );
- UTIL_CBK_TAIL();
+ DUTIL_CBK_TAIL();
}
return same;
}
@@ -576,61 +654,61 @@ and_util_addrChange( XW_UtilCtxt* uc, const CommsAddrRec* oldAddr,
}
static void
-and_util_setIsServer(XW_UtilCtxt* uc, XP_Bool isServer )
+and_util_setIsServer( XW_UtilCtxt* uc, XP_Bool isServer )
{
/* Change both the C and Java structs, which need to stay in sync */
uc->gameInfo->serverRole = isServer? SERVER_ISSERVER : SERVER_ISCLIENT;
- UTIL_CBK_HEADER("setIsServer", "(Z)V" );
+ UTIL_CBK_HEADER( "setIsServer", "(Z)V" );
(*env)->CallVoidMethod( env, util->jutil, mid, isServer );
UTIL_CBK_TAIL();
}
#ifdef XWFEATURE_DEVID
static const XP_UCHAR*
-and_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ )
+and_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ )
{
const XP_UCHAR* result = NULL;
*typ = ID_TYPE_NONE;
- UTIL_CBK_HEADER( "getDevID", "([B)Ljava/lang/String;" );
+ DUTIL_CBK_HEADER( "getDevID", "([B)Ljava/lang/String;" );
jbyteArray jbarr = makeByteArray( env, 1, NULL );
- jstring jresult = (*env)->CallObjectMethod( env, util->jutil, mid, jbarr );
+ jstring jresult = (*env)->CallObjectMethod( env, dutil->jdutil, mid, jbarr );
if ( NULL != jresult ) {
const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL );
jsize len = (*env)->GetStringUTFLength( env, jresult );
- if ( NULL != util->devIDStorage
- && 0 == XP_MEMCMP( util->devIDStorage, jchars, len ) ) {
+ if ( NULL != dutil->devIDStorage
+ && 0 == XP_MEMCMP( dutil->devIDStorage, jchars, len ) ) {
XP_LOGF( "%s: already have matching devID", __func__ );
} else {
XP_LOGF( "%s: allocating storage for devID", __func__ );
- XP_FREEP( util->util.mpool, &util->devIDStorage );
- util->devIDStorage = XP_MALLOC( util->util.mpool, len + 1 );
- XP_MEMCPY( util->devIDStorage, jchars, len );
- util->devIDStorage[len] = '\0';
+ XP_FREEP( dutil->dutil.mpool, &dutil->devIDStorage );
+ dutil->devIDStorage = XP_MALLOC( dutil->dutil.mpool, len + 1 );
+ XP_MEMCPY( dutil->devIDStorage, jchars, len );
+ dutil->devIDStorage[len] = '\0';
}
(*env)->ReleaseStringUTFChars( env, jresult, jchars );
- result = (const XP_UCHAR*)util->devIDStorage;
+ result = (const XP_UCHAR*)dutil->devIDStorage;
jbyte* elems = (*env)->GetByteArrayElements( env, jbarr, NULL );
*typ = (DevIDType)elems[0];
(*env)->ReleaseByteArrayElements( env, jbarr, elems, 0 );
}
deleteLocalRef( env, jbarr );
- UTIL_CBK_TAIL();
+ DUTIL_CBK_TAIL();
return result;
}
static void
-and_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ,
- const XP_UCHAR* idRelay )
+and_dutil_deviceRegistered( XW_DUtilCtxt* duc, DevIDType typ,
+ const XP_UCHAR* idRelay )
{
- UTIL_CBK_HEADER( "deviceRegistered",
- "(L" PKG_PATH("jni/UtilCtxt$DevIDType") ";Ljava/lang/String;)V" );
+ DUTIL_CBK_HEADER( "deviceRegistered",
+ "(L" PKG_PATH("jni/UtilCtxt$DevIDType") ";Ljava/lang/String;)V" );
jstring jstr = (*env)->NewStringUTF( env, idRelay );
jobject jtyp = intToJEnum( env, typ,
PKG_PATH("jni/UtilCtxt$DevIDType") );
- (*env)->CallVoidMethod( env, util->jutil, mid, jtyp, jstr );
+ (*env)->CallVoidMethod( env, dutil->jdutil, mid, jtyp, jstr );
deleteLocalRefs( env, jstr, jtyp, DELETE_NO_REF );
- UTIL_CBK_TAIL();
+ DUTIL_CBK_TAIL();
}
#endif /* XWFEATURE_DEVID */
@@ -664,16 +742,23 @@ and_util_engineStopping( XW_UtilCtxt* uc )
}
#endif
+static XW_DUtilCtxt*
+and_util_getDevUtilCtxt( XW_UtilCtxt* uc )
+{
+ AndGameGlobals* globals = (AndGameGlobals*)uc->closure;
+ XP_ASSERT( !!globals->dutil );
+ return globals->dutil;
+}
+
#ifdef COMMS_CHECKSUM
static XP_UCHAR*
-and_util_md5sum( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len )
+and_dutil_md5sum( XW_DUtilCtxt* duc, const XP_U8* ptr, XP_U16 len )
{
- AndUtil* util = (AndUtil*)uc;
- JNIEnv* env = ENVFORME( util->ti );
- AndGlobals* globals = (AndGlobals*)uc->closure;
- struct JNIUtilCtxt* jniutil = globals->jniutil;
+ AndDUtil* dutil = (AndDUtil*)duc;
+ JNIEnv* env = ENVFORME( dutil->ti );
+ struct JNIUtilCtxt* jniutil = dutil->jniutil;
jstring jsum = and_util_getMD5SumForBytes( jniutil, ptr, len );
- XP_UCHAR* result = getStringCopy( MPPARM(uc->mpool) env, jsum );
+ XP_UCHAR* result = getStringCopy( MPPARM(duc->mpool) env, jsum );
deleteLocalRef( env, jsum );
return result;
}
@@ -681,8 +766,8 @@ and_util_md5sum( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len )
XW_UtilCtxt*
-makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
- AndGlobals* closure )
+makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
+ AndGameGlobals* closure )
{
AndUtil* util = (AndUtil*)XP_CALLOC( mpool, sizeof(*util) );
UtilVtable* vtable = (UtilVtable*)XP_CALLOC( mpool, sizeof(*vtable) );
@@ -697,7 +782,7 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
util->util.gameInfo = gi;
#define SET_PROC(nam) vtable->m_util_##nam = and_util_##nam
- SET_PROC(getVTManager);
+
#ifndef XWFEATURE_STANDALONE_ONLY
SET_PROC(makeStreamFromAddr);
#endif
@@ -725,10 +810,7 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
SET_PROC(clearTimer);
SET_PROC(requestTime);
SET_PROC(altKeyDown);
- SET_PROC(getCurSeconds);
SET_PROC(makeEmptyDict);
- SET_PROC(getUserString);
- SET_PROC(getUserQuantityString);
SET_PROC(notifyIllegalWords);
#ifdef XWFEATURE_CHAT
SET_PROC(showChat);
@@ -740,10 +822,6 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
SET_PROC(playerScoreHeld);
#endif
-#ifdef XWFEATURE_SMS
- SET_PROC(phoneNumbersSame);
-#endif
-
#ifdef XWFEATURE_BOARDWORDS
SET_PROC(cellSquareHeld);
#endif
@@ -752,10 +830,6 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
SET_PROC(informMissing);
SET_PROC(addrChange);
SET_PROC(setIsServer);
-# ifdef XWFEATURE_DEVID
- SET_PROC(getDevID);
- SET_PROC(deviceRegistered);
-# endif
#endif
#ifdef XWFEATURE_SEARCHLIMIT
SET_PROC(getTraySearchLimits);
@@ -764,9 +838,8 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
SET_PROC(engineStarting);
SET_PROC(engineStopping);
#endif
-#ifdef COMMS_CHECKSUM
- SET_PROC(md5sum);
-#endif
+
+ SET_PROC(getDevUtilCtxt);
#undef SET_PROC
return (XW_UtilCtxt*)util;
@@ -778,31 +851,81 @@ destroyUtil( XW_UtilCtxt** utilc )
AndUtil* util = (AndUtil*)*utilc;
JNIEnv* env = ENVFORME( util->ti );
- for ( int ii = 0; ii < VSIZE(util->userStrings); ++ii ) {
- XP_UCHAR* ptr = util->userStrings[ii];
+ if ( NULL != util->jutil ) {
+ (*env)->DeleteGlobalRef( env, util->jutil );
+ }
+ XP_FREE( util->util.mpool, util->util.vtable );
+ XP_FREE( util->util.mpool, util );
+ *utilc = NULL;
+}
+
+XW_DUtilCtxt*
+makeDUtil( MPFORMAL EnvThreadInfo* ti, jobject jdutil, VTableMgr* vtMgr,
+ JNIUtilCtxt* jniutil, void* closure )
+{
+ AndDUtil* dutil = (AndDUtil*)XP_CALLOC( mpool, sizeof(*dutil) );
+ dutil->ti = ti;
+ dutil->jniutil = jniutil;
+ dutil->dutil.closure = closure;
+ dutil->dutil.vtMgr = vtMgr;
+
+ if ( NULL != jdutil ) {
+ JNIEnv* env = ENVFORME( ti );
+ dutil->jdutil = (*env)->NewGlobalRef( env, jdutil );
+ }
+
+ MPASSIGN( dutil->dutil.mpool, mpool );
+
+ DUtilVtable* vtable = &dutil->dutil.vtable;
+#define SET_DPROC(nam) vtable->m_dutil_##nam = and_dutil_##nam
+ SET_DPROC(getCurSeconds);
+ SET_DPROC(getUserString);
+ SET_DPROC(getUserQuantityString);
+ SET_DPROC(storeStream);
+ SET_DPROC(loadStream);
+ SET_DPROC(storePtr);
+ SET_DPROC(loadPtr);
+# ifdef XWFEATURE_DEVID
+ SET_DPROC(getDevID);
+ SET_DPROC(deviceRegistered);
+# endif
+#ifdef XWFEATURE_SMS
+ SET_DPROC(phoneNumbersSame);
+#endif
+#ifdef COMMS_CHECKSUM
+ SET_DPROC(md5sum);
+#endif
+
+ return &dutil->dutil;
+}
+
+void
+destroyDUtil( XW_DUtilCtxt** dutilp )
+{
+ AndDUtil* dutil = (AndDUtil*)*dutilp;
+ JNIEnv* env = ENVFORME( dutil->ti );
+ if ( NULL != dutil->jdutil ) {
+ (*env)->DeleteGlobalRef( env, dutil->jdutil );
+ }
+
+ for ( int ii = 0; ii < VSIZE(dutil->userStrings); ++ii ) {
+ XP_UCHAR* ptr = dutil->userStrings[ii];
if ( NULL != ptr ) {
- if ( 0 == (util->userStringsBits & (1 << ii)) ) {
- XP_FREE( util->util.mpool, ptr );
+ if ( 0 == (dutil->userStringsBits & (1 << ii)) ) {
+ XP_FREE( dutil->dutil.mpool, ptr );
} else {
XP_UCHAR** ptrs = (XP_UCHAR**)ptr;
for ( int jj = 0; jj < MAX_QUANTITY_STRS; ++jj ) {
ptr = ptrs[jj];
if ( !!ptr ) {
- XP_FREE( util->util.mpool, ptr );
+ XP_FREE( dutil->dutil.mpool, ptr );
}
}
- XP_FREE( util->util.mpool, ptrs );
+ XP_FREE( dutil->dutil.mpool, ptrs );
}
}
}
-
- if ( NULL != util->jutil ) {
- (*env)->DeleteGlobalRef( env, util->jutil );
- }
#ifdef XWFEATURE_DEVID
- XP_FREEP( util->util.mpool, &util->devIDStorage );
+ XP_FREEP( dutil->dutil.mpool, &dutil->devIDStorage );
#endif
- XP_FREE( util->util.mpool, util->util.vtable );
- XP_FREE( util->util.mpool, util );
- *utilc = NULL;
}
diff --git a/xwords4/android/jni/utilwrapper.h b/xwords4/android/jni/utilwrapper.h
index 44a21acc3..b317d4ff5 100644
--- a/xwords4/android/jni/utilwrapper.h
+++ b/xwords4/android/jni/utilwrapper.h
@@ -25,10 +25,17 @@
#include "game.h"
#include "util.h"
+#include "dutil.h"
#include "andglobals.h"
+#include "jniutlswrapper.h"
+
+XW_DUtilCtxt* makeDUtil( MPFORMAL EnvThreadInfo* ti, jobject j_dutil,
+ VTableMgr* vtMgr, JNIUtilCtxt* jniutil,
+ void* closure );
+void destroyDUtil( XW_DUtilCtxt** dutilp );
XW_UtilCtxt* makeUtil( MPFORMAL EnvThreadInfo* ti, jobject j_util,
- CurGameInfo* gi, AndGlobals* globals );
+ CurGameInfo* gi, AndGameGlobals* globals );
void destroyUtil( XW_UtilCtxt** util );
bool utilTimerFired( XW_UtilCtxt* util, XWTimerReason why, int handle );
diff --git a/xwords4/android/jni/xwjni.c b/xwords4/android/jni/xwjni.c
index e5b867e5d..f3413a878 100644
--- a/xwords4/android/jni/xwjni.c
+++ b/xwords4/android/jni/xwjni.c
@@ -1,6 +1,7 @@
-/* -*- compile-command: "find-and-gradle.sh installXw4Debug"; -*- */
+
+/* -*- compile-command: "find-and-gradle.sh inXw4Deb"; -*- */
/*
- * Copyright © 2009 - 2014 by Eric House (xwords@eehouse.org). All rights
+ * Copyright © 2009 - 2018 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@@ -34,6 +35,7 @@
#include "dictiter.h"
#include "dictmgr.h"
#include "nli.h"
+#include "smsproto.h"
#include "utilwrapper.h"
#include "drawwrapper.h"
@@ -60,9 +62,33 @@ struct _EnvThreadInfo {
typedef struct _JNIGlobalState {
EnvThreadInfo ti;
DictMgrCtxt* dictMgr;
+ SMSProto* smsProto;
+ VTableMgr* vtMgr;
+ XW_DUtilCtxt* dutil;
+ JNIUtilCtxt* jniutil;
+ XP_Bool mpoolInUse;
MPSLOT
} JNIGlobalState;
+#ifdef MEM_DEBUG
+static MemPoolCtx*
+getMPool( JNIGlobalState* globalState )
+{
+ XP_ASSERT( !globalState->mpoolInUse );
+ globalState->mpoolInUse = XP_TRUE;
+ return globalState->mpool;
+}
+
+static void
+releaseMPool( JNIGlobalState* globalState )
+{
+ XP_ASSERT( globalState->mpoolInUse );
+ globalState->mpoolInUse = XP_FALSE;
+}
+#else
+# define releaseMPool(s)
+#endif
+
#define LOG_MAPPING
// #define LOG_MAPPING_ALL
@@ -157,7 +183,7 @@ static void
map_init( MPFORMAL EnvThreadInfo* ti, JNIEnv* env )
{
pthread_mutex_init( &ti->mtxThreads, NULL );
- MPASSIGN(ti->mpool, mpool);
+ MPASSIGN( ti->mpool, mpool );
map_thread( ti, env );
}
@@ -229,6 +255,7 @@ static void
tilesArrayToTileSet( JNIEnv* env, jintArray jtiles, TrayTileSet* tset )
{
if ( jtiles != NULL ) {
+ XP_ASSERT( !!jtiles );
jsize nTiles = (*env)->GetArrayLength( env, jtiles );
int tmp[MAX_TRAY_TILES];
getIntsFromArray( env, tmp, jtiles, nTiles, XP_FALSE );
@@ -255,33 +282,43 @@ getState( JNIEnv* env, GamePtrType gamePtr )
JNIEXPORT jint JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_initGlobals
-( JNIEnv* env, jclass C )
+( JNIEnv* env, jclass C, jobject jdutil, jobject jniu )
{
#ifdef MEM_DEBUG
MemPoolCtx* mpool = mpool_make( NULL );
#endif
- JNIGlobalState* state = (JNIGlobalState*)XP_CALLOC( mpool, sizeof(*state) );
- map_init( MPPARM(mpool) &state->ti, env );
- state->dictMgr = dmgr_make( MPPARM_NOCOMMA( mpool ) );
- MPASSIGN( state->mpool, mpool );
- // LOG_RETURNF( "%p", state );
- return (jint)state;
+ JNIGlobalState* globalState = (JNIGlobalState*)XP_CALLOC( mpool,
+ sizeof(*globalState) );
+ map_init( MPPARM(mpool) &globalState->ti, env );
+ globalState->jniutil = makeJNIUtil( MPPARM(mpool) env, &globalState->ti, jniu );
+ globalState->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
+ globalState->dutil = makeDUtil( MPPARM(mpool) &globalState->ti, jdutil,
+ globalState->vtMgr, globalState->jniutil, NULL );
+ globalState->dictMgr = dmgr_make( MPPARM_NOCOMMA( mpool ) );
+ globalState->smsProto = smsproto_init( MPPARM( mpool ) globalState->dutil );
+ MPASSIGN( globalState->mpool, mpool );
+ // LOG_RETURNF( "%p", globalState );
+ return (jint)globalState;
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_cleanGlobals
-( JNIEnv* env, jclass C, jint ptr )
+( JNIEnv* env, jclass C, jint jniGlobalPtr )
{
// LOG_FUNC();
- if ( 0 != ptr ) {
- JNIGlobalState* state = (JNIGlobalState*)ptr;
- XP_ASSERT( ENVFORME(&state->ti) == env );
- dmgr_destroy( state->dictMgr );
+ if ( 0 != jniGlobalPtr ) {
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG
- MemPoolCtx* mpool = state->mpool;
+ MemPoolCtx* mpool = getMPool( globalState );
#endif
- map_destroy( &state->ti );
- XP_FREE( mpool, state );
+ XP_ASSERT( ENVFORME(&globalState->ti) == env );
+ smsproto_free( globalState->smsProto );
+ vtmgr_destroy( MPPARM(mpool) globalState->vtMgr );
+ dmgr_destroy( globalState->dictMgr );
+ destroyDUtil( &globalState->dutil );
+ destroyJNIUtil( env, &globalState->jniutil );
+ map_destroy( &globalState->ti );
+ XP_FREE( mpool, globalState );
mpool_destroy( mpool );
}
}
@@ -511,6 +548,7 @@ loadCommonPrefs( JNIEnv* env, CommonPrefs* cp, jobject j_cp )
static XWStreamCtxt*
streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream )
{
+ XP_ASSERT( !!jstream );
int len = (*env)->GetArrayLength( env, jstream );
XWStreamCtxt* stream = mem_stream_make_sized( MPPARM(mpool) vtMgr,
len, NULL, 0, NULL );
@@ -525,15 +563,15 @@ streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream )
****************************************************/
JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_gi_1to_1stream
-(JNIEnv* env, jclass C, jobject jgi )
+( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject jgi )
{
jbyteArray result;
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG
- MemPoolCtx* mpool = mpool_make( NULL );
+ MemPoolCtx* mpool = getMPool( globalState );
#endif
CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi );
- VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
- XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) vtMgr,
+ XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr,
NULL, 0, NULL );
game_saveToStream( NULL, gi, stream, 0 );
@@ -541,28 +579,24 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1to_1stream
result = streamToBArray( env, stream );
stream_destroy( stream );
-
- vtmgr_destroy( MPPARM(mpool) vtMgr );
-#ifdef MEM_DEBUG
- mpool_destroy( mpool );
-#endif
+ releaseMPool( globalState );
return result;
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream
-( JNIEnv* env, jclass C, jobject jgi, jbyteArray jstream )
+( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject jgi, jbyteArray jstream )
{
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG
- MemPoolCtx* mpool = mpool_make( NULL );
+ MemPoolCtx* mpool = getMPool( globalState );
#endif
- VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
-
- XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, vtMgr, jstream );
+ XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env,
+ globalState->vtMgr, jstream );
CurGameInfo gi;
XP_MEMSET( &gi, 0, sizeof(gi) );
- if ( game_makeFromStream( MPPARM(mpool) stream, NULL,
+ if ( game_makeFromStream( MPPARM(mpool) stream, NULL,
&gi, NULL, NULL, NULL, NULL, NULL, NULL ) ) {
setJGI( env, jgi, &gi );
} else {
@@ -572,50 +606,45 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream
gi_disposePlayerInfo( MPPARM(mpool) &gi );
stream_destroy( stream );
- vtmgr_destroy( MPPARM(mpool) vtMgr );
-#ifdef MEM_DEBUG
- mpool_destroy( mpool );
-#endif
+ releaseMPool( globalState );
}
JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_nli_1to_1stream
-( JNIEnv* env, jclass C, jobject njli )
+( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject njli )
{
LOG_FUNC();
- jbyteArray result;
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG
- MemPoolCtx* mpool = mpool_make( NULL );
+ MemPoolCtx* mpool = getMPool( globalState );
#endif
+
+ jbyteArray result;
NetLaunchInfo nli = {0};
loadNLI( env, &nli, njli );
/* CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi ); */
- VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
- XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) vtMgr,
+ XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr,
NULL, 0, NULL );
nli_saveToStream( &nli, stream );
result = streamToBArray( env, stream );
stream_destroy( stream );
-
- vtmgr_destroy( MPPARM(mpool) vtMgr );
-#ifdef MEM_DEBUG
- mpool_destroy( mpool );
-#endif
+ releaseMPool( globalState );
return result;
}
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_nli_1from_1stream
-( JNIEnv* env, jclass C, jobject jnli, jbyteArray jstream )
+( JNIEnv* env, jclass C, jint jniGlobalPtr, jobject jnli, jbyteArray jstream )
{
LOG_FUNC();
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG
- MemPoolCtx* mpool = mpool_make( NULL );
+ MemPoolCtx* mpool = getMPool( globalState );
#endif
- VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
- XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, vtMgr, jstream );
+ XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env,
+ globalState->vtMgr, jstream );
NetLaunchInfo nli = {0};
if ( nli_makeFromStream( &nli, stream ) ) {
@@ -625,10 +654,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_nli_1from_1stream
}
stream_destroy( stream );
- vtmgr_destroy( MPPARM(mpool) vtMgr );
-#ifdef MEM_DEBUG
- mpool_destroy( mpool );
-#endif
+ releaseMPool( globalState );
}
JNIEXPORT void JNICALL
@@ -684,14 +710,18 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1unref
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo
( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jDictBytes,
- jstring jname, jstring jpath, jobject jniu, jboolean check, jobject jinfo )
+ jstring jname, jstring jpath, jboolean check, jobject jinfo )
{
jboolean result = false;
- JNIGlobalState* state = (JNIGlobalState*)jniGlobalPtr;
- map_thread( &state->ti, env );
- JNIUtilCtxt* jniutil = makeJNIUtil( MPPARM(state->mpool) env, &state->ti, jniu );
- DictionaryCtxt* dict = makeDict( MPPARM(state->mpool) env, state->dictMgr,
- jniutil, jname, jDictBytes, jpath,
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
+ map_thread( &globalState->ti, env );
+
+#ifdef MEM_DEBUG
+ MemPoolCtx* mpool = getMPool( globalState );
+#endif
+
+ DictionaryCtxt* dict = makeDict( MPPARM(mpool) env, globalState->dictMgr,
+ globalState->jniutil, jname, jDictBytes, jpath,
NULL, check );
if ( NULL != dict ) {
if ( NULL != jinfo ) {
@@ -704,8 +734,8 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo
dict_unref( dict );
result = true;
}
- destroyJNIUtil( env, &jniutil );
+ releaseMPool( globalState );
return result;
}
@@ -739,10 +769,88 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getTileValue
return dict_getTileValue( (DictionaryCtxt*)dictPtr, tile );
}
+static jobjectArray
+msgArrayToByteArrays( JNIEnv* env, const SMSMsgArray* arr )
+{
+ jclass clas = (*env)->FindClass( env, "[B" );
+ jobjectArray result = (*env)->NewObjectArray( env, arr->nMsgs, clas, NULL );
+ for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
+ SMSMsg* msg = &arr->msgs[ii];
+ jbyteArray arr = makeByteArray( env, msg->len, (const jbyte*)msg->data );
+ (*env)->SetObjectArrayElement( env, result, ii, arr );
+ deleteLocalRef( env, arr );
+ }
+ return result;
+}
+
+JNIEXPORT jobjectArray JNICALL
+Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepOutbound
+( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jData,
+ jstring jToPhone, jint jNow, jboolean jForce, jintArray jWaitSecsArr )
+{
+ jobjectArray result = NULL;
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
+ map_thread( &globalState->ti, env );
+
+ jbyte* data = NULL;
+ int len = 0;
+ if ( NULL != jData ) {
+ len = (*env)->GetArrayLength( env, jData );
+ data = (*env)->GetByteArrayElements( env, jData, NULL );
+ }
+ const char* toPhone = (*env)->GetStringUTFChars( env, jToPhone, NULL );
+
+ XP_U16 waitSecs;
+ SMSMsgArray* arr = smsproto_prepOutbound( globalState->smsProto, (const XP_U8*)data,
+ len, toPhone, jForce, &waitSecs );
+ if ( !!arr ) {
+ result = msgArrayToByteArrays( env, arr );
+ smsproto_freeMsgArray( globalState->smsProto, arr );
+ }
+
+ setIntInArray( env, jWaitSecsArr, 0, waitSecs );
+
+ (*env)->ReleaseStringUTFChars( env, jToPhone, toPhone );
+ if ( NULL != jData ) {
+ (*env)->ReleaseByteArrayElements( env, jData, data, 0 );
+ }
+
+ return result;
+}
+
+JNIEXPORT jobjectArray JNICALL
+Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepInbound
+( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jData,
+ jstring jFromPhone )
+{
+ jobjectArray result = NULL;
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
+ map_thread( &globalState->ti, env );
+
+ if ( !!jData ) {
+ int len = (*env)->GetArrayLength( env, jData );
+ jbyte* data = (*env)->GetByteArrayElements( env, jData, NULL );
+ const char* fromPhone = (*env)->GetStringUTFChars( env, jFromPhone, NULL );
+
+ SMSMsgArray* arr = smsproto_prepInbound( globalState->smsProto, fromPhone,
+ (XP_U8*)data, len );
+ if ( !!arr ) {
+ result = msgArrayToByteArrays( env, arr );
+ smsproto_freeMsgArray( globalState->smsProto, arr );
+ }
+
+ (*env)->ReleaseStringUTFChars( env, jFromPhone, fromPhone );
+ (*env)->ReleaseByteArrayElements( env, jData, data, 0 );
+ } else {
+ XP_LOGF( "%s() => null (null input)", __func__ );
+ }
+ return result;
+}
+
struct _JNIState {
XWGame game;
JNIGlobalState* globalJNI;
- AndGlobals globals;
+ AndGameGlobals globals;
// pthread_mutex_t msgMutex;
XP_U16 curSaveCount;
XP_U16 lastSavedSize;
@@ -761,27 +869,26 @@ struct _JNIState {
#define XWJNI_START_GLOBALS() \
XWJNI_START() \
- AndGlobals* globals = &state->globals; \
+ AndGameGlobals* globals = &state->globals; \
#define XWJNI_END() \
} \
JNIEXPORT jint JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_initJNI
-( JNIEnv* env, jclass C, int jniGlobalPtr, jint seed, jstring jtag )
+( JNIEnv* env, jclass C, int jniGlobalPtr, jint seed )
{
/* Why am I doing this twice? */
/* struct timeval tv; */
/* gettimeofday( &tv, NULL ); */
/* srandom( tv.tv_sec ); */
#ifdef MEM_DEBUG
- const char* tag = (*env)->GetStringUTFChars( env, jtag, NULL );
- MemPoolCtx* mpool = mpool_make( tag );
- (*env)->ReleaseStringUTFChars( env, jtag, tag );
+ MemPoolCtx* mpool = ((JNIGlobalState*)jniGlobalPtr)->mpool;
#endif
JNIState* state = (JNIState*)XP_CALLOC( mpool, sizeof(*state) );
state->globalJNI = (JNIGlobalState*)jniGlobalPtr;
- AndGlobals* globals = &state->globals;
+ AndGameGlobals* globals = &state->globals;
+ globals->dutil = state->globalJNI->dutil;
globals->state = (JNIState*)state;
MPASSIGN( state->mpool, mpool );
globals->vtMgr = make_vtablemgr(MPPARM_NOCOMMA(mpool));
@@ -807,8 +914,8 @@ JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame
( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject j_gi,
jobjectArray j_names, jobjectArray j_dicts, jobjectArray j_paths,
- jstring j_lang, jobject j_util, jobject jniu, jobject j_draw,
- jobject j_cp, jobject j_procs )
+ jstring j_lang, jobject j_util, jobject j_draw, jobject j_cp,
+ jobject j_procs )
{
XWJNI_START_GLOBALS();
EnvThreadInfo* ti = &state->globalJNI->ti;
@@ -816,7 +923,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame
globals->gi = gi;
globals->util = makeUtil( MPPARM(mpool) ti, j_util, gi,
globals );
- globals->jniutil = makeJNIUtil( MPPARM(mpool) env, ti, jniu );
+ globals->jniutil = state->globalJNI->jniutil;
DrawCtx* dctx = NULL;
if ( !!j_draw ) {
dctx = makeDraw( MPPARM(mpool) ti, j_draw );
@@ -858,7 +965,7 @@ JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1dispose
#ifdef MEM_DEBUG
MemPoolCtx* mpool = state->mpool;
#endif
- AndGlobals* globals = &state->globals;
+ AndGameGlobals* globals = &state->globals;
destroyGI( MPPARM(mpool) &globals->gi );
@@ -867,22 +974,19 @@ JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1dispose
destroyDraw( &globals->dctx );
destroyXportProcs( &globals->xportProcs );
destroyUtil( &globals->util );
- destroyJNIUtil( env, &globals->jniutil );
vtmgr_destroy( MPPARM(mpool) globals->vtMgr );
MAP_REMOVE( &state->globalJNI->ti, env );
/* pthread_mutex_destroy( &state->msgMutex ); */
XP_FREE( mpool, state );
- mpool_destroy( mpool );
} /* game_dispose */
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream
( JNIEnv* env, jclass C, GamePtrType gamePtr, jbyteArray jstream, jobject /*out*/jgi,
jobjectArray jdictNames, jobjectArray jdicts, jobjectArray jpaths,
- jstring jlang, jobject jutil, jobject jniu, jobject jdraw, jobject jcp,
- jobject jprocs )
+ jstring jlang, jobject jutil, jobject jdraw, jobject jcp, jobject jprocs )
{
jboolean result;
DictionaryCtxt* dict;
@@ -892,7 +996,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream
globals->gi = (CurGameInfo*)XP_CALLOC( mpool, sizeof(*globals->gi) );
globals->util = makeUtil( MPPARM(mpool) ti, jutil, globals->gi, globals);
- globals->jniutil = makeJNIUtil( MPPARM(mpool) env, ti, jniu );
+ globals->jniutil = state->globalJNI->jniutil;
makeDicts( MPPARM(state->globalJNI->mpool) env, state->globalJNI->dictMgr,
globals->jniutil, &dict, &dicts, jdictNames, jdicts, jpaths,
jlang );
@@ -923,7 +1027,6 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream
destroyDraw( &globals->dctx );
destroyXportProcs( &globals->xportProcs );
destroyUtil( &globals->util );
- destroyJNIUtil( env, &globals->jniutil );
destroyGI( MPPARM(mpool) &globals->gi );
}
@@ -1556,7 +1659,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_server_1writeFinalScores
void
and_send_on_close( XWStreamCtxt* stream, void* closure )
{
- AndGlobals* globals = (AndGlobals*)closure;
+ AndGameGlobals* globals = (AndGameGlobals*)closure;
JNIState* state = (JNIState*)globals->state;
XP_ASSERT( !!state->game.comms );
@@ -2049,8 +2152,8 @@ JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_haveEnv
( JNIEnv* env, jclass C, jint jniGlobalPtr )
{
- JNIGlobalState* state = (JNIGlobalState*)jniGlobalPtr;
- jboolean result = NULL != prvEnvForMe(&state->ti);
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
+ jboolean result = NULL != prvEnvForMe(&globalState->ti);
return result;
}
@@ -2102,29 +2205,25 @@ static void freeIndices( DictIterData* data );
JNIEXPORT jint JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1init
( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jDictBytes, jstring jname,
- jstring jpath, jobject jniu )
+ jstring jpath )
{
jint closure = 0;
- JNIGlobalState* state = (JNIGlobalState*)jniGlobalPtr;
+ JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
- JNIUtilCtxt* jniutil = makeJNIUtil( MPPARM(state->mpool) env,
- &state->ti, jniu );
- DictionaryCtxt* dict = makeDict( MPPARM(state->mpool) env, state->dictMgr,
- jniutil, jname, jDictBytes, jpath, NULL,
- false );
+ DictionaryCtxt* dict = makeDict( MPPARM(globalState->mpool) env,
+ globalState->dictMgr, globalState->jniutil,
+ jname, jDictBytes, jpath, NULL, false );
if ( !!dict ) {
- DictIterData* data = XP_CALLOC( state->mpool, sizeof(*data) );
+ DictIterData* data = XP_CALLOC( globalState->mpool, sizeof(*data) );
data->env = env;
- data->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(state->mpool) );
- data->jniutil = jniutil;
+ data->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(globalState->mpool) );
+ data->jniutil = globalState->jniutil;
data->dict = dict;
data->depth = 2;
#ifdef MEM_DEBUG
- data->mpool = state->mpool;
+ data->mpool = globalState->mpool;
#endif
closure = (int)data;
- } else {
- destroyJNIUtil( env, &jniutil );
}
return closure;
}
@@ -2152,7 +2251,6 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1destroy
#endif
dict_unref( data->dict );
- destroyJNIUtil( env, &data->jniutil );
freeIndices( data );
vtmgr_destroy( MPPARM(mpool) data->vtMgr );
XP_FREE( mpool, data );
diff --git a/xwords4/android/scripts/arelease-clone.sh b/xwords4/android/scripts/arelease-clone.sh
index fe244b2b1..05c7dda9e 100755
--- a/xwords4/android/scripts/arelease-clone.sh
+++ b/xwords4/android/scripts/arelease-clone.sh
@@ -53,7 +53,7 @@ git checkout ${TAG}${BRANCH}
cd ./xwords4/android/
./scripts/arelease.sh --apk-list $OUT_FILE
mkdir -p /tmp/releases
-cp app/build/outputs/apk/*.apk /tmp/releases
+cp app/build/outputs/apk/xw4/release/*.apk /tmp/releases
if [ -n "$XW_RELEASE_SCP_DEST" ]; then
cat $OUT_FILE | while read APK; do
diff --git a/xwords4/android/scripts/arelease.sh b/xwords4/android/scripts/arelease.sh
index d51f1d464..1493bb26a 100755
--- a/xwords4/android/scripts/arelease.sh
+++ b/xwords4/android/scripts/arelease.sh
@@ -54,7 +54,7 @@ fi
if [ -z "$FILES" ]; then
do_build
- for f in $(dirname $0)/../app/build/outputs/apk/*-unsigned-*.apk; do
+ for f in $(dirname $0)/../app/build/outputs/apk/xw4/release/*-unsigned-*.apk; do
$(dirname $0)/sign-align.sh --apk $f
done
fi
diff --git a/xwords4/common/board.c b/xwords4/common/board.c
index 774b1bf51..ad97837bf 100644
--- a/xwords4/common/board.c
+++ b/xwords4/common/board.c
@@ -166,6 +166,7 @@ board_make( MPFORMAL ModelCtxt* model, ServerCtxt* server, DrawCtx* draw,
result->draw = draw;
result->util = util;
+ result->dutil = util_getDevUtilCtxt( util );
result->gi = util->gameInfo;
XP_ASSERT( !!result->gi );
@@ -1090,12 +1091,11 @@ board_commitTurn( BoardCtxt* board, XP_Bool phoniesConfirmed,
XP_MEMSET( &bwl, 0, sizeof(bwl) );
if ( !legal ) {
- stream = mem_stream_make( MPPARM(board->mpool)
- util_getVTManager(board->util), NULL,
- CHANNEL_NONE, (MemStreamCloseCallback)NULL );
+ stream = mem_stream_make_raw( MPPARM(board->mpool)
+ dutil_getVTManager(board->dutil) );
- const XP_UCHAR* str = util_getUserString(board->util,
- STR_COMMIT_CONFIRM);
+ const XP_UCHAR* str = dutil_getUserString( board->dutil,
+ STR_COMMIT_CONFIRM );
stream_catString( stream, str );
XP_Bool warn = board->util->gameInfo->phoniesAction == PHONIES_WARN;
@@ -1319,10 +1319,8 @@ timerFiredForPen( BoardCtxt* board )
NULL, NULL, NULL );
if ( listWords ) {
XWStreamCtxt* stream =
- mem_stream_make( MPPARM(board->mpool)
- util_getVTManager(board->util), NULL,
- CHANNEL_NONE,
- (MemStreamCloseCallback)NULL );
+ mem_stream_make_raw( MPPARM(board->mpool)
+ dutil_getVTManager(board->dutil) );
model_listWordsThrough( board->model, modelCol, modelRow,
stream );
util_cellSquareHeld( board->util, stream );
@@ -1428,7 +1426,7 @@ board_pushTimerSave( BoardCtxt* board )
{
if ( board->gi->timerEnabled ) {
if ( board->timerSaveCount++ == 0 ) {
- board->timerStoppedTime = util_getCurSeconds( board->util );
+ board->timerStoppedTime = dutil_getCurSeconds( board->dutil );
#ifdef DEBUG
board->timerStoppedTurn = server_getCurrentTurn( board->server,
NULL );
@@ -1451,7 +1449,7 @@ board_popTimerSave( BoardCtxt* board )
XP_ASSERT( board->timerStoppedTurn == turn );
if ( --board->timerSaveCount == 0 && turn >= 0 ) {
- XP_U32 curTime = util_getCurSeconds( board->util );
+ XP_U32 curTime = dutil_getCurSeconds( board->dutil );
XP_U32 elapsed;
XP_ASSERT( board->timerStoppedTime != 0 );
diff --git a/xwords4/common/boarddrw.c b/xwords4/common/boarddrw.c
index 386b77841..f8f5bb495 100644
--- a/xwords4/common/boarddrw.c
+++ b/xwords4/common/boarddrw.c
@@ -371,7 +371,7 @@ static XP_Bool
drawCell( BoardCtxt* board, const XP_U16 col, const XP_U16 row, XP_Bool skipBlanks )
{
XP_Bool success = XP_TRUE;
- XP_Rect cellRect;
+ XP_Rect cellRect = {0};
Tile tile;
XP_Bool isBlank, isEmpty, recent, pending = XP_FALSE;
XWBonusType bonus;
diff --git a/xwords4/common/boardp.h b/xwords4/common/boardp.h
index f3d3db64f..b9e3e9462 100644
--- a/xwords4/common/boardp.h
+++ b/xwords4/common/boardp.h
@@ -139,6 +139,7 @@ struct BoardCtxt {
ModelCtxt* model;
ServerCtxt* server;
DrawCtx* draw;
+ XW_DUtilCtxt* dutil;
XW_UtilCtxt* util;
struct CurGameInfo* gi;
diff --git a/xwords4/common/comms.c b/xwords4/common/comms.c
index 5e2a73dae..af33b24e8 100644
--- a/xwords4/common/comms.c
+++ b/xwords4/common/comms.c
@@ -25,6 +25,7 @@
#include "comms.h"
#include "util.h"
+#include "dutil.h"
#include "game.h"
#include "xwstream.h"
#include "memstream.h"
@@ -100,6 +101,7 @@ typedef struct AddressRecord {
struct CommsCtxt {
XW_UtilCtxt* util;
+ XW_DUtilCtxt* dutil;
XP_U32 connID; /* set from gameID: 0 means ignore; otherwise
must match. Set by server. */
@@ -353,8 +355,7 @@ comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer,
#endif
)
{
- CommsCtxt* comms = (CommsCtxt*)XP_MALLOC( mpool, sizeof(*comms) );
- XP_MEMSET( comms, 0, sizeof(*comms) );
+ CommsCtxt* comms = (CommsCtxt*)XP_CALLOC( mpool, sizeof(*comms) );
#ifdef DEBUG
comms->tag = mpool_getTag(mpool);
XP_LOGF( TAGFMT(isServer=%d; forceChannel=%d), TAGPRMS, isServer, forceChannel );
@@ -372,7 +373,9 @@ comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer,
comms->xportFlags = comms->procs.flags;
#endif
}
+ comms->dutil = util_getDevUtilCtxt( util );
comms->util = util;
+ comms->dutil = util_getDevUtilCtxt( util );
#ifdef XWFEATURE_RELAY
init_relay( comms, nPlayersHere, nPlayersTotal );
@@ -722,7 +725,7 @@ comms_makeFromStream( MPFORMAL XWStreamCtxt* stream, XW_UtilCtxt* util,
msg->msg = (XP_U8*)XP_MALLOC( mpool, msg->len );
stream_getBytes( stream, msg->msg, msg->len );
#ifdef COMMS_CHECKSUM
- msg->checksum = util_md5sum( comms->util, msg->msg, msg->len );
+ msg->checksum = dutil_md5sum( comms->dutil, msg->msg, msg->len );
#endif
msg->next = (MsgQueueElem*)NULL;
*prevsQueueNext = comms->msgQueueTail = msg;
@@ -802,6 +805,11 @@ sendConnect( CommsCtxt* comms, XP_Bool breakExisting )
(void)send_via_bt_or_ip( comms, BTIPMSG_RESET, CHANNEL_NONE, typ, NULL, 0, NULL );
(void)comms_resendAll( comms, COMMS_CONN_NONE, XP_FALSE );
break;
+#endif
+#if defined XWFEATURE_SMS
+ case COMMS_CONN_SMS:
+ (void)comms_resendAll( comms, COMMS_CONN_NONE, XP_FALSE );
+ break;
#endif
default:
break;
@@ -973,7 +981,6 @@ comms_setAddr( CommsCtxt* comms, const CommsAddrRec* addr )
setDoHeartbeat( comms );
#endif
sendConnect( comms, XP_TRUE );
-
} /* comms_setAddr */
void
@@ -1136,10 +1143,8 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
newMsgElem->sendCount = 0;
#endif
- hdrStream = mem_stream_make( MPPARM(comms->mpool)
- util_getVTManager(comms->util),
- NULL, 0,
- (MemStreamCloseCallback)NULL );
+ hdrStream = mem_stream_make_raw( MPPARM(comms->mpool)
+ dutil_getVTManager(comms->dutil));
stream_open( hdrStream );
#if 0 < COMMS_VERSION
stream_putU16( hdrStream, HAS_VERSION_FLAG );
@@ -1168,8 +1173,8 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
}
#ifdef COMMS_CHECKSUM
- newMsgElem->checksum = util_md5sum( comms->util, newMsgElem->msg,
- newMsgElem->len );
+ newMsgElem->checksum = dutil_md5sum( comms->dutil, newMsgElem->msg,
+ newMsgElem->len );
#endif
return newMsgElem;
} /* makeElemWithID */
@@ -1508,7 +1513,7 @@ comms_resendAll( CommsCtxt* comms, CommsConnType filter, XP_Bool force )
XP_Bool success = XP_TRUE;
XP_ASSERT( !!comms );
- XP_U32 now = util_getCurSeconds( comms->util );
+ XP_U32 now = dutil_getCurSeconds( comms->dutil );
if ( !force && (now < comms->nextResend) ) {
XP_LOGF( "%s: aborting: %d seconds left in backoff", __func__,
comms->nextResend - now );
@@ -1664,7 +1669,7 @@ got_connect_cmd( CommsCtxt* comms, XWStreamCtxt* stream,
}
if ( ID_TYPE_NONE == typ /* error case */
|| '\0' != devID[0] ) /* new info case */ {
- util_deviceRegistered( comms->util, typ, devID );
+ dutil_deviceRegistered( comms->dutil, typ, devID );
}
#endif
@@ -1845,7 +1850,7 @@ relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID )
static void
noteHBReceived( CommsCtxt* comms/* , const CommsAddrRec* addr */ )
{
- comms->lastMsgRcvdTime = util_getCurSeconds( comms->util );
+ comms->lastMsgRcvdTime = dutil_getCurSeconds( comms->dutil );
setHeartbeatTimer( comms );
}
#else
@@ -1976,11 +1981,14 @@ getRecordFor( CommsCtxt* comms, const CommsAddrRec* addr,
break;
case COMMS_CONN_SMS:
#ifdef XWFEATURE_SMS
- if ( util_phoneNumbersSame( comms->util, addr->u.sms.phone,
- rec->addr.u.sms.phone )
- && addr->u.sms.port == rec->addr.u.sms.port ) {
- matched = XP_TRUE;
- XP_ASSERT( 0 );
+ {
+ XW_DUtilCtxt* duc = util_getDevUtilCtxt( comms->util );
+ if ( dutil_phoneNumbersSame( duc, addr->u.sms.phone,
+ rec->addr.u.sms.phone )
+ && addr->u.sms.port == rec->addr.u.sms.port ) {
+ matched = XP_TRUE;
+ XP_ASSERT( 0 );
+ }
}
#endif
break;
@@ -2227,7 +2235,7 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
XP_U16 len = stream_getSize( stream );
// stream_getPtr pts at base, but sum excludes relay header
const XP_U8* ptr = initialLen - len + stream_getPtr( stream );
- XP_UCHAR* sum = util_md5sum( comms->util, ptr, len );
+ XP_UCHAR* sum = dutil_md5sum( comms->dutil, ptr, len );
XP_LOGF( TAGFMT() "got message of len %d with sum %s",
TAGPRMS, len, sum );
XP_FREE( comms->mpool, sum );
@@ -2433,7 +2441,7 @@ heartbeat_checks( CommsCtxt* comms )
do {
if ( comms->lastMsgRcvdTime > 0 ) {
- XP_U32 now = util_getCurSeconds( comms->util );
+ XP_U32 now = dutil_getCurSeconds( comms->dutil );
XP_U32 tooLongAgo = now - (HB_INTERVAL * 2);
if ( comms->lastMsgRcvdTime < tooLongAgo ) {
XP_LOGF( "%s: calling reset proc; last was %ld secs too long "
@@ -2637,10 +2645,8 @@ logAddr( const CommsCtxt* comms, const CommsAddrRec* addr, const char* caller )
{
if ( !!addr ) {
char buf[128];
- XWStreamCtxt* stream = mem_stream_make( MPPARM(comms->mpool)
- util_getVTManager(comms->util),
- NULL, 0,
- (MemStreamCloseCallback)NULL );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(comms->mpool)
+ dutil_getVTManager(comms->dutil));
snprintf( buf, sizeof(buf), TAGFMT() "called on %p from %s:\n", TAGPRMS,
addr, caller );
stream_catString( stream, buf );
@@ -2942,10 +2948,8 @@ relay_msg_to_stream( CommsCtxt* comms, XWRELAY_Cmd cmd, XWHostID destID,
{
XP_LOGF( "%s(cmd=%s, destID=%x)", __func__, relayCmdToStr(cmd), destID );
XWStreamCtxt* stream;
- stream = mem_stream_make( MPPARM(comms->mpool)
- util_getVTManager(comms->util),
- NULL, 0,
- (MemStreamCloseCallback)NULL );
+ stream = mem_stream_make_raw( MPPARM(comms->mpool)
+ dutil_getVTManager(comms->dutil) );
if ( stream != NULL ) {
CommsAddrRec addr;
stream_open( stream );
@@ -3200,7 +3204,7 @@ putDevID( const CommsCtxt* comms, XWStreamCtxt* stream )
{
# if XWRELAY_PROTO_VERSION >= XWRELAY_PROTO_VERSION_CLIENTID
DevIDType typ;
- const XP_UCHAR* devID = util_getDevID( comms->util, &typ );
+ const XP_UCHAR* devID = dutil_getDevID( comms->dutil, &typ );
XP_ASSERT( ID_TYPE_NONE <= typ && typ < ID_TYPE_NTYPES );
stream_putU8( stream, typ );
if ( ID_TYPE_NONE != typ ) {
diff --git a/xwords4/common/comtypes.h b/xwords4/common/comtypes.h
index f7f4476e1..2e916c7c4 100644
--- a/xwords4/common/comtypes.h
+++ b/xwords4/common/comtypes.h
@@ -129,6 +129,22 @@ typedef enum {
OBJ_TRAY
} BoardObjectType;
+enum {
+ SERVER_STANDALONE,
+ SERVER_ISSERVER,
+ SERVER_ISCLIENT
+};
+typedef XP_U8 DeviceRole;
+
+enum {
+ PHONIES_IGNORE,
+ PHONIES_WARN,
+ PHONIES_DISALLOW
+};
+typedef XP_U8 XWPhoniesChoice;
+
+typedef XP_U8 XP_LangCode;
+
/* I'm going to try putting all forward "class" decls in the same file */
typedef struct BoardCtxt BoardCtxt;
typedef struct CommMgrCtxt CommMgrCtxt;
@@ -144,6 +160,7 @@ typedef struct XWStreamCtxt XWStreamCtxt;
typedef struct TrayContext TrayContext;
typedef struct PoolContext PoolContext;
typedef struct XW_UtilCtxt XW_UtilCtxt;
+typedef struct XW_DUtilCtxt XW_DUtilCtxt;
/* Low two bits treated as channel, third as short-term flag indicating
* sender's role; rest can be random to aid detection of duplicate packets. */
@@ -191,6 +208,8 @@ typedef enum {
BONUS_LAST
} XWBonusType;
+#define PERSIST_KEY(str) __FILE__ ":" str
+
/* I need a way to communiate prefs to common/ code. For now, though, I'll
* leave storage of these values up to the platforms. First, because I don't
* want to deal with versioning in the common code. Second, becuase they
diff --git a/xwords4/common/config.mk b/xwords4/common/config.mk
index 022fe1b2c..ac15f4e62 100644
--- a/xwords4/common/config.mk
+++ b/xwords4/common/config.mk
@@ -48,6 +48,7 @@ COMMONSRC = \
$(COMMONDIR)/vtabmgr.c \
$(COMMONDIR)/dictmgr.c \
$(COMMONDIR)/dbgutil.c \
+ $(COMMONDIR)/smsproto.c \
# PENDING: define this in terms of above!!!
@@ -85,5 +86,6 @@ COMMON5 = \
$(COMMONOBJDIR)/vtabmgr.o \
$(COMMONOBJDIR)/dictmgr.o \
$(COMMONOBJDIR)/dbgutil.o \
+ $(COMMONOBJDIR)/smsproto.o \
COMMONOBJ = $(COMMON1) $(COMMON2) $(COMMON3) $(COMMON4) $(COMMON5)
diff --git a/xwords4/common/dictnry.h b/xwords4/common/dictnry.h
index 759ef358a..fbe8f7499 100644
--- a/xwords4/common/dictnry.h
+++ b/xwords4/common/dictnry.h
@@ -44,8 +44,6 @@ extern "C" {
#define DICT_HEADER_MASK 0x08
#define DICT_SYNONYMS_MASK 0x10
-typedef XP_U8 XP_LangCode;
-
typedef enum {
INTRADE_MW_TEXT = BONUS_LAST
} XWMiniTextType;
diff --git a/xwords4/common/dutil.h b/xwords4/common/dutil.h
new file mode 100644
index 000000000..70aa8c70c
--- /dev/null
+++ b/xwords4/common/dutil.h
@@ -0,0 +1,104 @@
+/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
+/*
+ * Copyright 2018 by Eric House (xwords@eehouse.org). All rights
+ * reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _DEVUTIL_H_
+#define _DEVUTIL_H_
+
+#include "mempool.h"
+#include "comtypes.h"
+#include "xwrelay.h"
+#include "vtabmgr.h"
+
+typedef struct _DUtilVtable {
+ XP_U32 (*m_dutil_getCurSeconds)( XW_DUtilCtxt* duc );
+ const XP_UCHAR* (*m_dutil_getUserString)( XW_DUtilCtxt* duc,
+ XP_U16 stringCode );
+ const XP_UCHAR* (*m_dutil_getUserQuantityString)( XW_DUtilCtxt* duc,
+ XP_U16 stringCode,
+ XP_U16 quantity );
+ void (*m_dutil_storeStream)( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ XWStreamCtxt* data );
+ /* Pass in an empty stream, and it'll be returned full */
+ void (*m_dutil_loadStream)( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ XWStreamCtxt* inOut );
+ void (*m_dutil_storePtr)( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ const void* data, XP_U16 len );
+ void (*m_dutil_loadPtr)( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ void* data, XP_U16* lenp );
+#ifdef XWFEATURE_SMS
+ XP_Bool (*m_dutil_phoneNumbersSame)( XW_DUtilCtxt* uc, const XP_UCHAR* p1,
+ const XP_UCHAR* p2 );
+#endif
+
+#ifdef XWFEATURE_DEVID
+ const XP_UCHAR* (*m_dutil_getDevID)( XW_DUtilCtxt* duc, DevIDType* typ );
+ void (*m_dutil_deviceRegistered)( XW_DUtilCtxt* duc, DevIDType typ,
+ const XP_UCHAR* idRelay );
+#endif
+
+#ifdef COMMS_CHECKSUM
+ XP_UCHAR* (*m_dutil_md5sum)( XW_DUtilCtxt* duc, const XP_U8* ptr, XP_U16 len );
+#endif
+} DUtilVtable;
+
+struct XW_DUtilCtxt {
+ DUtilVtable vtable;
+
+ void* closure;
+ VTableMgr* vtMgr;
+ MPSLOT
+};
+
+/* This one cheats: direct access */
+#define dutil_getVTManager(duc) (duc)->vtMgr
+
+#define dutil_getCurSeconds(duc) \
+ (duc)->vtable.m_dutil_getCurSeconds((duc))
+#define dutil_getUserString( duc, c ) \
+ (duc)->vtable.m_dutil_getUserString((duc),(c))
+#define dutil_getUserQuantityString( duc, c, q ) \
+ (duc)->vtable.m_dutil_getUserQuantityString((duc),(c),(q))
+
+#define dutil_storeStream(duc, k, s) \
+ (duc)->vtable.m_dutil_storeStream((duc), (k), (s));
+#define dutil_storePtr(duc, k, p, l) \
+ (duc)->vtable.m_dutil_storePtr((duc), (k), (p), (l));
+#define dutil_loadStream(duc, k, s) \
+ (duc)->vtable.m_dutil_loadStream((duc), (k), (s));
+#define dutil_loadPtr(duc, k, p, l) \
+ (duc)->vtable.m_dutil_loadPtr((duc), (k), (p), (l));
+
+#ifdef XWFEATURE_SMS
+# define dutil_phoneNumbersSame(duc,p1,p2) \
+ (duc)->vtable.m_dutil_phoneNumbersSame( (duc), (p1), (p2) )
+#endif
+
+#ifdef XWFEATURE_DEVID
+# define dutil_getDevID( duc, t ) \
+ (duc)->vtable.m_dutil_getDevID((duc),(t))
+# define dutil_deviceRegistered( duc, typ, id ) \
+ (duc)->vtable.m_dutil_deviceRegistered( (duc), (typ), (id) )
+#endif
+
+#ifdef COMMS_CHECKSUM
+# define dutil_md5sum( duc, p, l ) (duc)->vtable.m_dutil_md5sum((duc), (p), (l))
+#endif
+
+#endif
diff --git a/xwords4/common/game.c b/xwords4/common/game.c
index 9bb55135f..cfb1ffe40 100644
--- a/xwords4/common/game.c
+++ b/xwords4/common/game.c
@@ -80,7 +80,7 @@ makeGameID( XW_UtilCtxt* util )
XP_U32 gameID = 0;
assertUtilOK( util );
while ( 0 == gameID ) {
- gameID = util_getCurSeconds( util );
+ gameID = dutil_getCurSeconds( util_getDevUtilCtxt( util ) );
}
return gameID;
}
diff --git a/xwords4/common/mempool.c b/xwords4/common/mempool.c
index 1e2ba20d3..cbc68fc0d 100644
--- a/xwords4/common/mempool.c
+++ b/xwords4/common/mempool.c
@@ -258,20 +258,27 @@ void*
mpool_realloc( MemPoolCtx* mpool, void* ptr, XP_U32 newsize, const char* file,
const char* func, XP_U32 lineNo )
{
- MemPoolEntry* entry = findEntryFor( mpool, ptr, (MemPoolEntry**)NULL );
-
- if ( !entry ) {
- XP_LOGF( "findEntryFor failed; called from %s, line %d",
- file, lineNo );
+ // XP_LOGF( "%s(func=%s, line=%d): newsize: %d", __func__, func, lineNo, newsize );
+ void* result;
+ if ( ptr == NULL ) {
+ result = mpool_alloc( mpool, newsize, file, func, lineNo );
} else {
- entry->ptr = XP_PLATREALLOC( entry->ptr, newsize );
- XP_ASSERT( !!entry->ptr );
- entry->fileName = file;
- entry->func = func;
- entry->lineNo = lineNo;
- entry->size = newsize;
+ MemPoolEntry* entry = findEntryFor( mpool, ptr, (MemPoolEntry**)NULL );
+
+ if ( !entry ) {
+ XP_LOGF( "findEntryFor failed; called from %s, line %d",
+ file, lineNo );
+ } else {
+ entry->ptr = XP_PLATREALLOC( entry->ptr, newsize );
+ XP_ASSERT( !!entry->ptr );
+ entry->fileName = file;
+ entry->func = func;
+ entry->lineNo = lineNo;
+ entry->size = newsize;
+ }
+ result = entry->ptr;
}
- return entry->ptr;
+ return result;
} /* mpool_realloc */
void
diff --git a/xwords4/common/memstream.c b/xwords4/common/memstream.c
index cfec0b4aa..f7cd5f53b 100644
--- a/xwords4/common/memstream.c
+++ b/xwords4/common/memstream.c
@@ -67,6 +67,12 @@ static StreamCtxVTable* make_vtable( MemStreamCtxt* stream );
* top of the file (first executable code).
*/
XWStreamCtxt*
+mem_stream_make_raw( MPFORMAL VTableMgr* vtmgr )
+{
+ return mem_stream_make( MPPARM(mpool) vtmgr, NULL, 0, NULL );
+}
+
+XWStreamCtxt*
mem_stream_make( MPFORMAL VTableMgr* vtmgr, void* closure,
XP_PlayerAddr channelNo, MemStreamCloseCallback onClose )
{
diff --git a/xwords4/common/memstream.h b/xwords4/common/memstream.h
index 5091088f2..9e7617559 100644
--- a/xwords4/common/memstream.h
+++ b/xwords4/common/memstream.h
@@ -32,6 +32,8 @@ extern "C" {
typedef void (*MemStreamCloseCallback)( XWStreamCtxt* stream,
void* closure );
+XWStreamCtxt* mem_stream_make_raw( MPFORMAL VTableMgr* vtmgr);
+
XWStreamCtxt* mem_stream_make( MPFORMAL VTableMgr* vtmgr,
void* closure,
XP_PlayerAddr addr, /* should be in a
diff --git a/xwords4/common/model.c b/xwords4/common/model.c
index 76f89a292..1c6788e10 100644
--- a/xwords4/common/model.c
+++ b/xwords4/common/model.c
@@ -108,6 +108,7 @@ model_make( MPFORMAL DictionaryCtxt* dict, const PlayerDicts* dicts,
MPASSIGN(result->vol.mpool, mpool);
result->vol.util = util;
+ result->vol.dutil = util_getDevUtilCtxt( util );
result->vol.wni.proc = recordWord;
result->vol.wni.closure = &result->vol.rwi;
@@ -295,7 +296,7 @@ model_setSize( ModelCtxt* model, XP_U16 nCols )
stack_init( model->vol.stack );
} else {
model->vol.stack = stack_make( MPPARM(model->vol.mpool)
- util_getVTManager(model->vol.util));
+ dutil_getVTManager(model->vol.dutil));
}
} /* model_setSize */
@@ -2090,13 +2091,13 @@ printMovePre( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), const StackEntry* entry
}
if ( isPass ) {
- format = util_getUserString( model->vol.util, STR_PASS );
+ format = dutil_getUserString( model->vol.dutil, STR_PASS );
XP_SNPRINTF( buf, VSIZE(buf), "%s", format );
} else {
if ( isHorizontal ) {
- format = util_getUserString( model->vol.util, STRS_MOVE_ACROSS );
+ format = dutil_getUserString( model->vol.dutil, STRS_MOVE_ACROSS );
} else {
- format = util_getUserString( model->vol.util, STRS_MOVE_DOWN );
+ format = dutil_getUserString( model->vol.dutil, STRS_MOVE_DOWN );
}
row = mi->commonCoord;
@@ -2114,7 +2115,7 @@ printMovePre( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), const StackEntry* entry
}
if ( !closure->keepHidden ) {
- format = util_getUserString( model->vol.util, STRS_TRAY_AT_START );
+ format = dutil_getUserString( model->vol.dutil, STRS_TRAY_AT_START );
formatTray( model_getPlayerTiles( model, entry->playerNum ),
closure->dict, (XP_UCHAR*)traybuf, sizeof(traybuf),
XP_FALSE );
@@ -2149,17 +2150,17 @@ printMovePost( ModelCtxt* model, XP_U16 XP_UNUSED(moveN),
formatTray( (const TrayTileSet*) &entry->u.trade.newTiles,
dict, traybuf2, sizeof(traybuf2), closure->keepHidden );
- format = util_getUserString( model->vol.util, STRSS_TRADED_FOR );
+ format = dutil_getUserString( model->vol.dutil, STRSS_TRADED_FOR );
XP_SNPRINTF( buf, sizeof(buf), format, traybuf1, traybuf2 );
printString( stream, buf );
printString( stream, (XP_UCHAR*)XP_CR );
break;
case PHONY_TYPE:
- format = util_getUserString( model->vol.util, STR_PHONY_REJECTED );
+ format = dutil_getUserString( model->vol.dutil, STR_PHONY_REJECTED );
printString( stream, format );
case MOVE_TYPE:
- format = util_getUserString( model->vol.util, STRD_CUMULATIVE_SCORE );
+ format = dutil_getUserString( model->vol.dutil, STRD_CUMULATIVE_SCORE );
XP_SNPRINTF( buf, sizeof(buf), format, totalScore );
printString( stream, buf );
@@ -2174,7 +2175,7 @@ printMovePost( ModelCtxt* model, XP_U16 XP_UNUSED(moveN),
if ( entry->moveType == PHONY_TYPE ) {
/* printString( stream, (XP_UCHAR*)"phony rejected " ); */
} else if ( !closure->keepHidden ) {
- format = util_getUserString(model->vol.util, STRS_NEW_TILES);
+ format = dutil_getUserString( model->vol.dutil, STRS_NEW_TILES );
XP_SNPRINTF( buf, sizeof(buf), format,
formatTray( &entry->u.move.newTiles, dict,
traybuf1, sizeof(traybuf1),
@@ -2195,9 +2196,9 @@ static void
copyStack( const ModelCtxt* model, StackCtxt* destStack,
const StackCtxt* srcStack )
{
- XWStreamCtxt* stream = mem_stream_make( MPPARM(model->vol.mpool)
- util_getVTManager(model->vol.util),
- NULL, 0, NULL );
+ XWStreamCtxt* stream =
+ mem_stream_make_raw( MPPARM(model->vol.mpool)
+ dutil_getVTManager(model->vol.dutil) );
stack_writeToStream( (StackCtxt*)srcStack, stream );
stack_loadFromStream( destStack, stream );
diff --git a/xwords4/common/modelp.h b/xwords4/common/modelp.h
index 47cca9080..e560e87b5 100644
--- a/xwords4/common/modelp.h
+++ b/xwords4/common/modelp.h
@@ -51,6 +51,7 @@ typedef struct _RecordWordsInfo {
} RecordWordsInfo;
typedef struct ModelVolatiles {
+ XW_DUtilCtxt* dutil;
XW_UtilCtxt* util;
struct CurGameInfo* gi;
DictionaryCtxt* dict;
diff --git a/xwords4/common/movestak.c b/xwords4/common/movestak.c
index a76450fb5..9f9f2130a 100644
--- a/xwords4/common/movestak.c
+++ b/xwords4/common/movestak.c
@@ -120,9 +120,7 @@ stack_loadFromStream( StackCtxt* stack, XWStreamCtxt* stream )
stack->highWaterMark = stream_getU16( stream );
stack->nEntries = stream_getU16( stream );
stack->top = stream_getU32( stream );
- stack->data = mem_stream_make( MPPARM(stack->mpool) stack->vtmgr,
- NULL, 0,
- (MemStreamCloseCallback)NULL );
+ stack->data = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr );
stream_getFromStream( stack->data, stream, nBytes );
} else {
@@ -164,8 +162,8 @@ StackCtxt*
stack_copy( const StackCtxt* stack )
{
StackCtxt* newStack = NULL;
- XWStreamCtxt* stream = mem_stream_make( MPPARM(stack->mpool)
- stack->vtmgr, NULL, 0, NULL );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(stack->mpool)
+ stack->vtmgr );
stack_writeToStream( stack, stream );
newStack = stack_make( MPPARM(stack->mpool) stack->vtmgr );
@@ -184,8 +182,7 @@ pushEntryImpl( StackCtxt* stack, const StackEntry* entry )
XWStreamCtxt* stream = stack->data;
if ( !stream ) {
- stream = mem_stream_make( MPPARM(stack->mpool) stack->vtmgr, NULL, 0,
- (MemStreamCloseCallback)NULL );
+ stream = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr );
stack->data = stream;
}
diff --git a/xwords4/common/mscore.c b/xwords4/common/mscore.c
index 3024474f5..5132eb9d6 100644
--- a/xwords4/common/mscore.c
+++ b/xwords4/common/mscore.c
@@ -513,8 +513,8 @@ figureMoveScore( const ModelCtxt* model, XP_U16 turn, MoveInfo* moveInfo,
score += EMPTIED_TRAY_BONUS;
if ( !!stream ) {
- const XP_UCHAR* bstr = util_getUserString( model->vol.util,
- STR_BONUS_ALL );
+ const XP_UCHAR* bstr = dutil_getUserString( model->vol.dutil,
+ STR_BONUS_ALL );
stream_catString( stream, bstr );
}
}
@@ -844,9 +844,9 @@ static void
formatSummary( XWStreamCtxt* stream, const ModelCtxt* model, XP_U16 score )
{
XP_UCHAR buf[60];
- XP_SNPRINTF(buf, sizeof(buf),
- util_getUserString(model->vol.util, STRD_TURN_SCORE),
- score);
+ XP_SNPRINTF( buf, sizeof(buf),
+ dutil_getUserString(model->vol.dutil, STRD_TURN_SCORE),
+ score );
XP_ASSERT( XP_STRLEN(buf) < sizeof(buf) );
stream_catString( stream, buf );
} /* formatSummary */
diff --git a/xwords4/common/nwgamest.c b/xwords4/common/nwgamest.c
index 08c54e236..f1296886f 100644
--- a/xwords4/common/nwgamest.c
+++ b/xwords4/common/nwgamest.c
@@ -531,7 +531,7 @@ setRoleStrings( NewGameCtx* ngc )
strID = STR_TOTALPLAYERS;
}
- value.ng_cp = util_getUserString( ngc->util, strID );
+ value.ng_cp = dutil_getUserString( util_getDevUtilCtxt(ngc->util), strID );
(*ngc->setAttrProc)( closure, NG_ATTR_NPLAYHEADER, value );
} /* setRoleStrings */
diff --git a/xwords4/common/server.c b/xwords4/common/server.c
index 00bcd2e30..3f6652409 100644
--- a/xwords4/common/server.c
+++ b/xwords4/common/server.c
@@ -72,6 +72,7 @@ typedef struct ServerVolatiles {
ModelCtxt* model;
CommsCtxt* comms;
XW_UtilCtxt* util;
+ XW_DUtilCtxt* dutil;
CurGameInfo* gi;
TurnChangeListener turnChangeListener;
void* turnChangeData;
@@ -271,6 +272,7 @@ server_make( MPFORMAL ModelCtxt* model, CommsCtxt* comms, XW_UtilCtxt* util )
result->vol.model = model;
result->vol.comms = comms;
result->vol.util = util;
+ result->vol.dutil = util_getDevUtilCtxt( util );
result->vol.gi = util->gameInfo;
initServer( result );
@@ -672,7 +674,7 @@ sendChatToClientsExcept( ServerCtxt* server, XP_U16 skip, const XP_UCHAR* msg,
void
server_sendChat( ServerCtxt* server, const XP_UCHAR* msg, XP_S16 from )
{
- XP_U32 timestamp = util_getCurSeconds( server->vol.util );
+ XP_U32 timestamp = dutil_getCurSeconds( server->vol.dutil );
if ( server->vol.gi->serverRole == SERVER_ISCLIENT ) {
sendChatTo( server, SERVER_DEVICE, msg, from, timestamp );
} else {
@@ -828,10 +830,8 @@ static XWStreamCtxt*
mkServerStream( ServerCtxt* server )
{
XWStreamCtxt* stream;
- stream = mem_stream_make( MPPARM(server->mpool)
- util_getVTManager(server->vol.util),
- NULL, CHANNEL_NONE,
- (MemStreamCloseCallback)NULL );
+ stream = mem_stream_make_raw( MPPARM(server->mpool)
+ dutil_getVTManager(server->vol.dutil) );
XP_ASSERT( !!stream );
return stream;
} /* mkServerStream */
@@ -849,11 +849,11 @@ makeRobotMove( ServerCtxt* server )
XP_Bool timerEnabled = gi->timerEnabled;
XP_Bool canMove;
XP_U32 time = 0L; /* stupid compiler.... */
- XW_UtilCtxt* util = server->vol.util;
+ XW_DUtilCtxt* dutil = server->vol.dutil;
XP_Bool forceTrade = XP_FALSE;
if ( timerEnabled ) {
- time = util_getCurSeconds( util );
+ time = dutil_getCurSeconds( dutil );
}
#ifdef XWFEATURE_SLOW_ROBOT
@@ -917,8 +917,8 @@ makeRobotMove( ServerCtxt* server )
if ( !!stream ) {
XP_UCHAR buf[64];
- str = util_getUserQuantityString( util, STRD_ROBOT_TRADED,
- MAX_TRAY_TILES );
+ str = dutil_getUserQuantityString( dutil, STRD_ROBOT_TRADED,
+ MAX_TRAY_TILES );
XP_SNPRINTF( buf, sizeof(buf), str, MAX_TRAY_TILES );
stream_catString( stream, buf );
@@ -951,7 +951,7 @@ makeRobotMove( ServerCtxt* server )
if ( timerEnabled ) {
gi->players[turn].secondsUsed +=
- (XP_U16)(util_getCurSeconds( util ) - time);
+ (XP_U16)(dutil_getCurSeconds( dutil ) - time);
} else {
XP_ASSERT( gi->players[turn].secondsUsed == 0 );
}
@@ -1013,6 +1013,7 @@ showPrevScore( ServerCtxt* server )
{
if ( server->nv.showRobotScores ) { /* this can be changed between turns */
XW_UtilCtxt* util = server->vol.util;
+ XW_DUtilCtxt* dutil = server->vol.dutil;
XWStreamCtxt* stream;
const XP_UCHAR* str;
XP_UCHAR buf[128];
@@ -1025,9 +1026,9 @@ showPrevScore( ServerCtxt* server )
lp = &gi->players[prevTurn];
if ( LP_IS_LOCAL(lp) ) {
- str = util_getUserString( util, STR_ROBOT_MOVED );
+ str = dutil_getUserString( dutil, STR_ROBOT_MOVED );
} else {
- str = util_getUserString( util, STRS_REMOTE_MOVED );
+ str = dutil_getUserString( dutil, STRS_REMOTE_MOVED );
}
XP_SNPRINTF( buf, sizeof(buf), str, lp->name );
str = buf;
@@ -2243,8 +2244,8 @@ makeTradeReportIf( ServerCtxt* server, const TrayTileSet* tradedTiles )
if ( server->nv.showRobotScores ) {
XP_UCHAR tradeBuf[64];
const XP_UCHAR* tradeStr =
- util_getUserQuantityString( server->vol.util, STRD_ROBOT_TRADED,
- tradedTiles->nTiles );
+ dutil_getUserQuantityString( server->vol.dutil, STRD_ROBOT_TRADED,
+ tradedTiles->nTiles );
XP_SNPRINTF( tradeBuf, sizeof(tradeBuf), tradeStr,
tradedTiles->nTiles );
stream = mkServerStream( server );
@@ -2503,6 +2504,9 @@ server_commitMove( ServerCtxt* server, TrayTileSet* newTilesP )
} else {
nextTurn( server, PICK_NEXT );
}
+
+ XP_LOGF( "%s(): player %d now has %d tiles", __func__, turn,
+ model_getNumTilesInTray( model, turn ) );
return XP_TRUE;
} /* server_commitMove */
@@ -2706,7 +2710,7 @@ setTurn( ServerCtxt* server, XP_S16 turn )
|| (!amServer(server) || (0 == server->nv.pendingRegistrations)));
if ( server->nv.currentTurn != turn || 1 == server->vol.gi->nPlayers ) {
server->nv.currentTurn = turn;
- server->nv.lastMoveTime = util_getCurSeconds( server->vol.util );
+ server->nv.lastMoveTime = dutil_getCurSeconds( server->vol.dutil );
callTurnChangeListener( server );
}
}
@@ -3025,8 +3029,8 @@ server_formatDictCounts( ServerCtxt* server, XWStreamCtxt* stream,
Tile tile;
XP_U16 nChars, nPrinted;
XP_UCHAR buf[48];
- const XP_UCHAR* fmt = util_getUserString( server->vol.util,
- STRS_VALUES_HEADER );
+ const XP_UCHAR* fmt = dutil_getUserString( server->vol.dutil,
+ STRS_VALUES_HEADER );
const XP_UCHAR* langName;
XP_ASSERT( !!server->vol.model );
@@ -3096,9 +3100,9 @@ server_formatRemainingTiles( ServerCtxt* server, XWStreamCtxt* stream,
XP_ASSERT( !!server->vol.model );
- const XP_UCHAR* fmt = util_getUserQuantityString( server->vol.util,
- STRD_REMAINS_HEADER,
- nLeft );
+ const XP_UCHAR* fmt = dutil_getUserQuantityString( server->vol.dutil,
+ STRD_REMAINS_HEADER,
+ nLeft );
XP_SNPRINTF( buf, sizeof(buf), fmt, nLeft );
stream_catString( stream, buf );
stream_catString( stream, "\n\n" );
@@ -3136,8 +3140,8 @@ server_formatRemainingTiles( ServerCtxt* server, XWStreamCtxt* stream,
XP_ASSERT( offset < sizeof(cntsBuf) );
}
- fmt = util_getUserQuantityString( server->vol.util, STRD_REMAINS_EXPL,
- nLeft );
+ fmt = dutil_getUserQuantityString( server->vol.dutil, STRD_REMAINS_EXPL,
+ nLeft );
XP_SNPRINTF( buf, sizeof(buf), fmt, nLeft );
stream_catString( stream, buf );
@@ -3226,10 +3230,10 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
XP_S16 quitter = server->nv.quitter;
XP_Bool quitterDone = XP_FALSE;
ModelCtxt* model = server->vol.model;
- const XP_UCHAR* addString = util_getUserString( server->vol.util,
- STRD_REMAINING_TILES_ADD );
- const XP_UCHAR* subString = util_getUserString( server->vol.util,
- STRD_UNUSED_TILES_SUB );
+ const XP_UCHAR* addString = dutil_getUserString( server->vol.dutil,
+ STRD_REMAINING_TILES_ADD );
+ const XP_UCHAR* subString = dutil_getUserString( server->vol.dutil,
+ STRD_UNUSED_TILES_SUB );
XP_UCHAR* timeStr;
CurGameInfo* gi = server->vol.gi;
const XP_U16 nPlayers = gi->nPlayers;
@@ -3279,9 +3283,8 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
XP_U16 penalty = player_timePenalty( gi, thisIndex );
if ( penalty > 0 ) {
XP_SNPRINTF( timeBuf, sizeof(timeBuf),
- util_getUserString(
- server->vol.util,
- STRD_TIME_PENALTY_SUB ),
+ dutil_getUserString( server->vol.dutil,
+ STRD_TIME_PENALTY_SUB ),
penalty ); /* positive for formatting */
timeStr = timeBuf;
}
@@ -3296,12 +3299,12 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
const XP_UCHAR* name = emptyStringIfNull(gi->players[thisIndex].name);
if ( 0 == placeKey ) {
- const XP_UCHAR* fmt = util_getUserString( server->vol.util,
+ const XP_UCHAR* fmt = dutil_getUserString( server->vol.dutil,
STRDSD_PLACER );
XP_SNPRINTF( buf, sizeof(buf), fmt, place,
name, scores.arr[thisIndex] );
} else {
- const XP_UCHAR* fmt = util_getUserString( server->vol.util,
+ const XP_UCHAR* fmt = dutil_getUserString( server->vol.dutil,
placeKey );
XP_SNPRINTF( buf, sizeof(buf), fmt, name,
scores.arr[thisIndex] );
diff --git a/xwords4/common/server.h b/xwords4/common/server.h
index 0ca3a6a0f..5813a7f89 100644
--- a/xwords4/common/server.h
+++ b/xwords4/common/server.h
@@ -30,20 +30,6 @@
extern "C" {
#endif
-enum {
- PHONIES_IGNORE,
- PHONIES_WARN,
- PHONIES_DISALLOW
-};
-typedef XP_U8 XWPhoniesChoice;
-
-enum {
- SERVER_STANDALONE,
- SERVER_ISSERVER,
- SERVER_ISCLIENT
-};
-typedef XP_U8 DeviceRole;
-
/* typedef struct ServerCtxt ServerCtxt; */
/* typedef struct ServerVtable { */
diff --git a/xwords4/common/smsproto.c b/xwords4/common/smsproto.c
new file mode 100644
index 000000000..19958772a
--- /dev/null
+++ b/xwords4/common/smsproto.c
@@ -0,0 +1,870 @@
+/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */
+/*
+ * Copyright 2018 by Eric House (xwords@eehouse.org). All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include
+#include
+
+#include "util.h"
+#include "smsproto.h"
+#include "comtypes.h"
+#include "strutils.h"
+
+# define MAX_WAIT 3
+// # define MAX_MSG_LEN 50 /* for testing */
+# define MAX_LEN_BINARY 115
+/* PENDING: Might want to make SEND_NOW_SIZE smaller; might as well send now
+ if even the smallest new message is likely to put us over. */
+# define SEND_NOW_SIZE MAX_LEN_BINARY
+# define SMS_PROTO_VERSION 1
+# define SMS_PROTO_VERSION_COMBO 2
+
+# define PARTIALS_FORMAT 0
+
+typedef struct _MsgRec {
+ XP_U32 createSeconds;
+ SMSMsg msg;
+} MsgRec;
+
+typedef struct _ToPhoneRec {
+ XP_UCHAR phone[32];
+ XP_U32 createSeconds;
+ XP_U16 nMsgs;
+ XP_U16 totalSize;
+ MsgRec** msgs;
+} ToPhoneRec;
+
+typedef struct _MsgIDRec {
+ int msgID;
+ int count;
+ struct {
+ XP_U16 len;
+ XP_U8* data;
+ }* parts;
+} MsgIDRec;
+
+typedef struct _FromPhoneRec {
+ XP_UCHAR phone[32];
+ int nMsgIDs;
+ MsgIDRec* msgIDRecs;
+} FromPhoneRec;
+
+struct SMSProto {
+ XW_DUtilCtxt* dutil;
+ pthread_t creator;
+ XP_U16 nNextID;
+ int lastStoredSize;
+ XP_U16 nToPhones;
+ ToPhoneRec* toPhoneRecs;
+
+ int nFromPhones;
+ FromPhoneRec* fromPhoneRecs;
+
+ MPSLOT;
+};
+
+#define KEY_PARTIALS PERSIST_KEY("partials")
+#define KEY_NEXTID PERSIST_KEY("nextID")
+
+static int nextMsgID( SMSProto* state );
+static SMSMsgArray* toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld );
+static ToPhoneRec* getForPhone( SMSProto* state, const XP_UCHAR* phone,
+ XP_Bool create );
+static void addToRec( SMSProto* state, ToPhoneRec* rec, const XP_U8* buf,
+ XP_U16 buflen, XP_U32 nowSeconds );
+static void addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID,
+ int indx, int count, const XP_U8* data, XP_U16 len );
+static SMSMsgArray* completeMsgs( SMSProto* state, SMSMsgArray* arr,
+ const XP_UCHAR* fromPhone, int msgID );
+static void savePartials( SMSProto* state );
+static void restorePartials( SMSProto* state );
+static void rmFromPhoneRec( SMSProto* state, int fromPhoneIndex );
+static void freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex,
+ int msgIDIndex );
+static void freeForPhone( SMSProto* state, const XP_UCHAR* phone );
+static void freeMsg( SMSProto* state, MsgRec** msg );
+static void freeRec( SMSProto* state, ToPhoneRec* rec );
+#ifdef DEBUG
+static void checkThread( SMSProto* state );
+#else
+# define checkThread(p)
+#endif
+
+SMSProto*
+smsproto_init( MPFORMAL XW_DUtilCtxt* dutil )
+{
+ SMSProto* state = (SMSProto*)XP_CALLOC( mpool, sizeof(*state) );
+ state->dutil = dutil;
+ // checkThread( state ); <-- Android's calling this on background thread now
+ MPASSIGN( state->mpool, mpool );
+
+ XP_U16 siz = sizeof(state->nNextID);
+ dutil_loadPtr( state->dutil, KEY_NEXTID, &state->nNextID, &siz );
+ XP_LOGF( "%s(): loaded nextMsgID: %d", __func__, state->nNextID );
+
+ restorePartials( state );
+
+ return state;
+}
+
+void
+smsproto_free( SMSProto* state )
+{
+ if ( NULL != state ) {
+ // checkThread( state ); <-- risky (see above)
+ XP_ASSERT( state->creator == 0 || state->creator == pthread_self() );
+
+ for ( XP_U16 ii = 0; ii < state->nToPhones; ++ii ) {
+ freeRec( state, &state->toPhoneRecs[ii] );
+ }
+ XP_FREEP( state->mpool, &state->toPhoneRecs );
+
+ if ( 0 < state->nFromPhones ) {
+ XP_LOGF( "%s(): freeing undelivered partial messages", __func__ );
+ }
+ while (0 < state->nFromPhones) {
+ FromPhoneRec* ffr = &state->fromPhoneRecs[0];
+ while ( 0 < ffr->nMsgIDs ) {
+ freeMsgIDRec( state, &ffr->msgIDRecs[0], 0, 0 );
+ }
+ }
+ XP_ASSERT( !state->fromPhoneRecs ); /* above nulls this once empty */
+
+ XP_FREEP( state->mpool, &state );
+ }
+}
+
+/* Maintain a list of pending messages per phone number. When called and it's
+ * been at least some amount of time since we last added something, or at
+ * least some longer time since the oldest message was added, return an array
+ * of messages ready to send via the device's raw SMS (i.e. respecting its
+ * size limits.)
+
+ * Pass in the current time, as that's easier than keeping an instance of
+ * UtilCtxt around.
+ */
+SMSMsgArray*
+smsproto_prepOutbound( SMSProto* state, const XP_U8* buf,
+ XP_U16 buflen, const XP_UCHAR* toPhone,
+ XP_Bool forceOld, XP_U16* waitSecsP )
+{
+ SMSMsgArray* result = NULL;
+
+#ifdef DEBUG
+ XP_UCHAR* checksum = dutil_md5sum( state->dutil, buf, buflen );
+ XP_LOGF( "%s(): len=%d, sum=%s, toPhone=%s", __func__, buflen,
+ checksum, toPhone );
+ XP_FREEP( state->mpool, &checksum );
+#endif
+
+ checkThread( state );
+ ToPhoneRec* rec = getForPhone( state, toPhone, !!buf );
+
+ /* First, add the new message (if present) to the array */
+ XP_U32 nowSeconds = dutil_getCurSeconds( state->dutil );
+ if ( !!buf ) {
+ addToRec( state, rec, buf, buflen, nowSeconds );
+ }
+
+ /* rec will be non-null if there's something in it */
+ XP_Bool doSend = XP_FALSE;
+ if ( rec != NULL ) {
+ doSend = forceOld
+ || rec->totalSize > SEND_NOW_SIZE
+ || MAX_WAIT <= nowSeconds - rec->createSeconds;
+ /* other criteria? */
+ }
+
+ if ( doSend ) {
+ result = toMsgs( state, rec, forceOld );
+ freeForPhone( state, toPhone );
+ }
+
+ XP_U16 waitSecs = 0;
+ if ( !result && !!rec && (rec->nMsgs > 0) ) {
+ waitSecs = MAX_WAIT - (nowSeconds - rec->createSeconds);
+ }
+ *waitSecsP = waitSecs;
+
+ XP_LOGF( "%s() => %p (len=%d, *waitSecs=%d)", __func__, result,
+ !!result ? result->nMsgs : 0, *waitSecsP );
+ return result;
+}
+
+static SMSMsgArray*
+appendMsg( SMSProto* state, SMSMsgArray* arr, SMSMsg* msg )
+{
+ if ( NULL == arr ) {
+ arr = XP_CALLOC( state->mpool, sizeof(*arr) );
+ }
+
+ arr->msgs = XP_REALLOC( state->mpool, arr->msgs,
+ (arr->nMsgs + 1) * sizeof(*arr->msgs) );
+ arr->msgs[arr->nMsgs++] = *msg;
+ return arr;
+}
+
+SMSMsgArray*
+smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
+ const XP_U8* data, XP_U16 len )
+{
+ XP_LOGF( "%s(): len=%d, fromPhone=%s", __func__, len, fromPhone );
+ checkThread( state );
+
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool)
+ dutil_getVTManager(state->dutil) );
+ stream_putBytes( stream, data, len );
+
+ SMSMsgArray* result = NULL;
+ XP_U8 proto;
+ if ( stream_gotU8( stream, &proto ) ) {
+ switch ( proto ) {
+ case SMS_PROTO_VERSION: {
+ XP_U8 msgID, indx, count;
+ if ( stream_gotU8( stream, &msgID )
+ && stream_gotU8( stream, &indx )
+ && stream_gotU8( stream, &count )
+ && indx < count ) {
+ XP_U16 len = stream_getSize( stream );
+ XP_U8 buf[len];
+ stream_getBytes( stream, buf, len );
+ addMessage( state, fromPhone, msgID, indx, count, buf, len );
+ result = completeMsgs( state, result, fromPhone, msgID );
+ savePartials( state );
+ }
+ }
+ break;
+ case SMS_PROTO_VERSION_COMBO: {
+ XP_U8 oneLen, msgID;
+ while ( stream_gotU8( stream, &oneLen )
+ && stream_gotU8( stream, &msgID ) ) {
+ XP_U8 buf[oneLen];
+ if ( stream_gotBytes( stream, buf, oneLen ) ) {
+ SMSMsg msg = { .len = oneLen,
+ .msgID = msgID,
+ .data = XP_MALLOC( state->mpool, oneLen ),
+ };
+ XP_MEMCPY( msg.data, buf, oneLen );
+ result = appendMsg( state, result, &msg );
+ }
+ }
+ }
+ break;
+ default:
+ XP_LOGF( "%s(): unexpected proto %d", __func__, proto );
+ break;
+ }
+ }
+
+ stream_destroy( stream );
+
+ XP_LOGF( "%s() => %p (len=%d)", __func__, result, (!!result) ? result->nMsgs : 0 );
+ return result;
+}
+
+void
+smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr )
+{
+ checkThread( state );
+
+ for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
+ XP_FREEP( state->mpool, &arr->msgs[ii].data );
+ }
+
+ XP_FREEP( state->mpool, &arr->msgs );
+ XP_FREEP( state->mpool, &arr );
+}
+
+static void
+freeMsg( SMSProto* state, MsgRec** msgp )
+{
+ XP_FREEP( state->mpool, &(*msgp)->msg.data );
+ XP_FREEP( state->mpool, msgp );
+}
+
+static void
+freeRec( SMSProto* state, ToPhoneRec* rec )
+{
+ for ( XP_U16 jj = 0; jj < rec->nMsgs; ++jj ) {
+ freeMsg( state, &rec->msgs[jj] );
+ }
+ XP_FREEP( state->mpool, &rec->msgs );
+}
+
+static ToPhoneRec*
+getForPhone( SMSProto* state, const XP_UCHAR* phone, XP_Bool create )
+{
+ ToPhoneRec* rec = NULL;
+ for ( XP_U16 ii = 0; !rec && ii < state->nToPhones; ++ii ) {
+ if ( 0 == XP_STRCMP( state->toPhoneRecs[ii].phone, phone ) ) {
+ rec = &state->toPhoneRecs[ii];
+ }
+ }
+
+ if ( !rec && create ) {
+ state->toPhoneRecs = XP_REALLOC( state->mpool, state->toPhoneRecs,
+ (1 + state->nToPhones) * sizeof(*state->toPhoneRecs) );
+ rec = &state->toPhoneRecs[state->nToPhones++];
+ XP_MEMSET( rec, 0, sizeof(*rec) );
+ XP_STRCAT( rec->phone, phone );
+ }
+
+ return rec;
+}
+
+static void
+freeForPhone( SMSProto* state, const XP_UCHAR* phone )
+{
+ for ( XP_U16 ii = 0; ii < state->nToPhones; ++ii ) {
+ if ( 0 == XP_STRCMP( state->toPhoneRecs[ii].phone, phone ) ) {
+ freeRec( state, &state->toPhoneRecs[ii] );
+
+ XP_U16 nAbove = state->nToPhones - ii - 1;
+ XP_ASSERT( nAbove >= 0 );
+ if ( nAbove > 0 ) {
+ XP_MEMMOVE( &state->toPhoneRecs[ii], &state->toPhoneRecs[ii+1],
+ nAbove * sizeof(*state->toPhoneRecs) );
+ }
+ --state->nToPhones;
+ if ( 0 == state->nToPhones ) {
+ XP_FREEP( state->mpool, &state->toPhoneRecs );
+ } else {
+ state->toPhoneRecs = XP_REALLOC( state->mpool, state->toPhoneRecs,
+ state->nToPhones * sizeof(*state->toPhoneRecs) );
+ }
+ break;
+ }
+ }
+}
+
+static void
+addToRec( SMSProto* state, ToPhoneRec* rec, const XP_U8* buf, XP_U16 buflen,
+ XP_U32 nowSeconds )
+{
+ MsgRec* mRec = XP_CALLOC( state->mpool, sizeof(*rec) );
+ mRec->msg.len = buflen;
+ mRec->msg.data = XP_MALLOC( state->mpool, buflen );
+ XP_MEMCPY( mRec->msg.data, buf, buflen );
+ mRec->createSeconds = nowSeconds;
+
+ rec->msgs = XP_REALLOC( state->mpool, rec->msgs, (1 + rec->nMsgs) * sizeof(*rec->msgs) );
+ rec->msgs[rec->nMsgs++] = mRec;
+ rec->totalSize += buflen;
+ XP_LOGF( "%s(): added msg to %s of len %d; total now %d", __func__, rec->phone,
+ buflen, rec->totalSize );
+
+ if ( rec->nMsgs == 1 ) {
+ rec->createSeconds = nowSeconds;
+ }
+}
+
+static MsgIDRec*
+getMsgIDRec( SMSProto* state, const XP_UCHAR* fromPhone, int msgID,
+ XP_Bool addMissing, int* fromPhoneIndex, int* msgIDIndex )
+{
+ MsgIDRec* result = NULL;
+
+ FromPhoneRec* fromPhoneRec = NULL;
+ for ( int ii = 0; ii < state->nFromPhones; ++ii ) {
+ if ( 0 == XP_STRCMP( state->fromPhoneRecs[ii].phone, fromPhone ) ) {
+ fromPhoneRec = &state->fromPhoneRecs[ii];
+ *fromPhoneIndex = ii;
+ break;
+ }
+ }
+
+ // create and add if not found
+ if ( NULL == fromPhoneRec && addMissing ) {
+ state->fromPhoneRecs =
+ XP_REALLOC( state->mpool, state->fromPhoneRecs,
+ (state->nFromPhones + 1) * sizeof(*state->fromPhoneRecs) );
+ *fromPhoneIndex = state->nFromPhones;
+ fromPhoneRec = &state->fromPhoneRecs[state->nFromPhones++];
+ XP_MEMSET( fromPhoneRec, 0, sizeof(*fromPhoneRec) );
+ XP_STRCAT( fromPhoneRec->phone, fromPhone );
+ }
+
+ // Now find msgID record
+ if ( NULL != fromPhoneRec ) {
+ for ( int ii = 0; ii < fromPhoneRec->nMsgIDs; ++ii ) {
+ if ( fromPhoneRec->msgIDRecs[ii].msgID == msgID ) {
+ result = &fromPhoneRec->msgIDRecs[ii];
+ *msgIDIndex = ii;
+ break;
+ }
+ }
+
+ // create and add if not found
+ if ( NULL == result && addMissing ) {
+ fromPhoneRec->msgIDRecs = XP_REALLOC( state->mpool, fromPhoneRec->msgIDRecs,
+ (fromPhoneRec->nMsgIDs + 1)
+ * sizeof(*fromPhoneRec->msgIDRecs) );
+ MsgIDRec newRec = { .msgID = msgID };
+ *msgIDIndex = fromPhoneRec->nMsgIDs;
+ result = &fromPhoneRec->msgIDRecs[fromPhoneRec->nMsgIDs];
+ fromPhoneRec->msgIDRecs[fromPhoneRec->nMsgIDs++] = newRec;
+ }
+ }
+
+ return result;
+}
+
+/* Messages that are split gather here until complete
+ */
+static void
+addMessage( SMSProto* state, const XP_UCHAR* fromPhone, int msgID, int indx,
+ int count, const XP_U8* data, XP_U16 len )
+{
+ XP_LOGF( "phone=%s, msgID=%d, %d/%d", fromPhone, msgID, indx, count );
+ XP_ASSERT( 0 < len );
+ MsgIDRec* msgIDRec;
+ for ( ; ; ) {
+ int fromPhoneIndex;
+ int msgIDIndex;
+ msgIDRec = getMsgIDRec( state, fromPhone, msgID, XP_TRUE,
+ &fromPhoneIndex, &msgIDIndex );
+
+ /* sanity check... */
+ if ( msgIDRec->count == 0 || msgIDRec->count == count ) {
+ break;
+ }
+ freeMsgIDRec( state, msgIDRec, fromPhoneIndex, msgIDIndex );
+ }
+
+ /* if it's new, fill in missing fields */
+ if ( msgIDRec->count == 0 ) {
+ msgIDRec->count = count; /* in case it's new */
+ msgIDRec->parts = XP_CALLOC( state->mpool, count * sizeof(*msgIDRec->parts));
+ }
+
+ XP_ASSERT( msgIDRec->parts[indx].len == 0
+ || msgIDRec->parts[indx].len == len ); /* replace with same ok */
+ msgIDRec->parts[indx].len = len;
+ XP_FREEP( state->mpool, &msgIDRec->parts[indx].data ); /* in case non-null (replacement) */
+ msgIDRec->parts[indx].data = XP_MALLOC( state->mpool, len );
+ XP_MEMCPY( msgIDRec->parts[indx].data, data, len );
+}
+
+static void
+rmFromPhoneRec( SMSProto* state, int fromPhoneIndex )
+{
+ FromPhoneRec* fromPhoneRec = &state->fromPhoneRecs[fromPhoneIndex];
+ XP_ASSERT( fromPhoneRec->nMsgIDs == 0 );
+ XP_FREEP( state->mpool, &fromPhoneRec->msgIDRecs );
+
+ if ( --state->nFromPhones == 0 ) {
+ XP_FREEP( state->mpool, &state->fromPhoneRecs );
+ } else {
+ XP_U16 nAbove = state->nFromPhones - fromPhoneIndex;
+ XP_ASSERT( nAbove >= 0 );
+ if ( nAbove > 0 ) {
+ XP_MEMMOVE( &state->fromPhoneRecs[fromPhoneIndex], &state->fromPhoneRecs[fromPhoneIndex+1],
+ nAbove * sizeof(*state->fromPhoneRecs) );
+ }
+ state->fromPhoneRecs = XP_REALLOC( state->mpool, state->fromPhoneRecs,
+ state->nFromPhones * sizeof(*state->fromPhoneRecs));
+ }
+}
+
+static void
+freeMsgIDRec( SMSProto* state, MsgIDRec* rec, int fromPhoneIndex, int msgIDIndex )
+{
+ FromPhoneRec* fromPhoneRec = &state->fromPhoneRecs[fromPhoneIndex];
+ MsgIDRec* msgIDRec = &fromPhoneRec->msgIDRecs[msgIDIndex];
+ XP_ASSERT( msgIDRec == rec );
+
+ for ( int ii = 0; ii < msgIDRec->count; ++ii ) {
+ XP_FREEP( state->mpool, &msgIDRec->parts[ii].data );
+ }
+ XP_FREEP( state->mpool, &msgIDRec->parts );
+
+ if ( --fromPhoneRec->nMsgIDs > 0 ) {
+ XP_U16 nAbove = fromPhoneRec->nMsgIDs - msgIDIndex;
+ XP_ASSERT( nAbove >= 0 );
+ if ( nAbove > 0 ) {
+ XP_MEMMOVE( &fromPhoneRec->msgIDRecs[msgIDIndex], &fromPhoneRec->msgIDRecs[msgIDIndex+1],
+ nAbove * sizeof(*fromPhoneRec->msgIDRecs) );
+ }
+ fromPhoneRec->msgIDRecs = XP_REALLOC( state->mpool, fromPhoneRec->msgIDRecs,
+ fromPhoneRec->nMsgIDs
+ * sizeof(*fromPhoneRec->msgIDRecs));
+ } else {
+ rmFromPhoneRec( state, fromPhoneIndex );
+ }
+}
+
+static void
+savePartials( SMSProto* state )
+{
+ checkThread( state );
+
+ XWStreamCtxt* stream
+ = mem_stream_make_raw( MPPARM(state->mpool)
+ dutil_getVTManager(state->dutil) );
+ stream_putU8( stream, PARTIALS_FORMAT );
+
+ stream_putU8( stream, state->nFromPhones );
+ for ( int ii = 0; ii < state->nFromPhones; ++ii ) {
+ const FromPhoneRec* rec = &state->fromPhoneRecs[ii];
+ stringToStream( stream, rec->phone );
+ stream_putU8( stream, rec->nMsgIDs );
+ for ( int jj = 0; jj < rec->nMsgIDs; ++jj ) {
+ MsgIDRec* mir = &rec->msgIDRecs[jj];
+ stream_putU16( stream, mir->msgID );
+ stream_putU8( stream, mir->count );
+
+ /* There's an array here. It may be sparse. Save a len of 0 */
+ for ( int kk = 0; kk < mir->count; ++kk ) {
+ int len = mir->parts[kk].len;
+ stream_putU8( stream, len );
+ stream_putBytes( stream, mir->parts[kk].data, len );
+ }
+ }
+ }
+
+ XP_U16 newSize = stream_getSize( stream );
+ if ( state->lastStoredSize == 2 && newSize == 2 ) {
+ XP_LOGF( "%s(): not storing empty again", __func__ );
+ } else {
+ dutil_storeStream( state->dutil, KEY_PARTIALS, stream );
+ state->lastStoredSize = newSize;
+ }
+
+ stream_destroy( stream );
+
+ LOG_RETURN_VOID();
+} /* savePartials */
+
+static void
+restorePartials( SMSProto* state )
+{
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(state->mpool)
+ dutil_getVTManager(state->dutil) );
+ dutil_loadStream( state->dutil, KEY_PARTIALS, stream );
+ if ( stream_getSize( stream ) >= 1
+ && PARTIALS_FORMAT == stream_getU8( stream ) ) {
+ int nFromPhones = stream_getU8( stream );
+ for ( int ii = 0; ii < nFromPhones; ++ii ) {
+ XP_UCHAR phone[32];
+ (void)stringFromStreamHere( stream, phone, VSIZE(phone) );
+ int nMsgIDs = stream_getU8( stream );
+ XP_LOGF( "%s(): got %d message records for phone %s", __func__,
+ nMsgIDs, phone );
+ for ( int jj = 0; jj < nMsgIDs; ++jj ) {
+ XP_U16 msgID = stream_getU16( stream );
+ int count = stream_getU8( stream );
+ XP_LOGF( "%s(): got %d records for msgID %d", __func__, count, msgID );
+ for ( int kk = 0; kk < count; ++kk ) {
+ int len = stream_getU8( stream );
+ if ( 0 < len ) {
+ XP_U8 buf[len];
+ stream_getBytes( stream, buf, len );
+ addMessage( state, phone, msgID, kk, count, buf, len );
+ }
+ }
+ }
+ }
+ }
+ stream_destroy( stream );
+}
+
+static SMSMsgArray*
+completeMsgs( SMSProto* state, SMSMsgArray* arr, const XP_UCHAR* fromPhone,
+ int msgID )
+{
+ int fromPhoneIndex, msgIDIndex;
+ MsgIDRec* rec = getMsgIDRec( state, fromPhone, msgID, XP_FALSE,
+ &fromPhoneIndex, &msgIDIndex);
+ if ( !rec ) {
+ XP_LOGF( "%s(): no rec for phone %s, msgID %d", __func__, fromPhone, msgID );
+ XP_ASSERT( 0 );
+ }
+
+ int len = 0;
+ XP_Bool haveAll = XP_TRUE;
+ for ( int ii = 0; ii < rec->count; ++ii ) {
+ if ( rec->parts[ii].len == 0 ) {
+ haveAll = XP_FALSE;
+ break;
+ } else {
+ len += rec->parts[ii].len;
+ }
+ }
+
+ if ( haveAll ) {
+ SMSMsg msg = { .len = len,
+ .msgID = msgID,
+ .data = XP_MALLOC( state->mpool, len ),
+ };
+ XP_U8* ptr = msg.data;
+ for ( int ii = 0; ii < rec->count; ++ii ) {
+ XP_MEMCPY( ptr, rec->parts[ii].data, rec->parts[ii].len );
+ ptr += rec->parts[ii].len;
+ }
+ arr = appendMsg( state, arr, &msg );
+
+ freeMsgIDRec( state, rec, fromPhoneIndex, msgIDIndex );
+ }
+
+ return arr;
+}
+
+static SMSMsgArray*
+toMsgs( SMSProto* state, ToPhoneRec* rec, XP_Bool forceOld )
+{
+ SMSMsgArray* result = NULL;
+
+ for ( XP_U16 ii = 0; ii < rec->nMsgs; ) {
+ // XP_LOGF( "%s(): looking at msg %d of %d", __func__, ii, rec->nMsgs );
+ XP_U16 count = (rec->msgs[ii]->msg.len + (MAX_LEN_BINARY-1)) / MAX_LEN_BINARY;
+
+ /* First, see if this message and some number of its neighbors can be
+ combined */
+ int last = ii;
+ int sum = 0;
+ if ( count == 1 && !forceOld ) {
+ for ( ; last < rec->nMsgs; ++last ) {
+ int nextLen = rec->msgs[last]->msg.len;
+ if ( sum + nextLen > MAX_LEN_BINARY ) {
+ break;
+ }
+ sum += nextLen;
+ }
+ }
+
+ if ( last > ii ) {
+ int nMsgs = last - ii;
+ if ( nMsgs > 1 ) {
+ XP_LOGF( "%s(): combining %d through %d (%d msgs)", __func__, ii,
+ last - 1, nMsgs );
+ }
+ int len = 1 + sum + (nMsgs * 2); /* 1: len & msgID */
+ SMSMsg newMsg = { .len = len,
+ .data = XP_MALLOC( state->mpool, len )
+ };
+ int indx = 0;
+ newMsg.data[indx++] = SMS_PROTO_VERSION_COMBO;
+ for ( int jj = ii; jj < last; ++jj ) {
+ const SMSMsg* msg = &rec->msgs[jj]->msg;
+ newMsg.data[indx++] = msg->len;
+ newMsg.data[indx++] = nextMsgID( state );
+ XP_MEMCPY( &newMsg.data[indx], msg->data, msg->len ); /* bad! */
+ indx += msg->len;
+ }
+ result = appendMsg( state, result, &newMsg );
+ ii = last;
+ } else {
+ int msgID = nextMsgID( state );
+ const SMSMsg* msg = &rec->msgs[ii]->msg;
+ XP_U8* nextStart = msg->data;
+ XP_U16 lenLeft = msg->len;
+ for ( XP_U16 indx = 0; indx < count; ++indx ) {
+ XP_ASSERT( lenLeft > 0 );
+ XP_U16 useLen = lenLeft;
+ if ( useLen >= MAX_LEN_BINARY ) {
+ useLen = MAX_LEN_BINARY;
+ }
+ lenLeft -= useLen;
+
+ SMSMsg newMsg = { .len = useLen + 4,
+ .data = XP_MALLOC( state->mpool, useLen + 4 )
+ };
+ newMsg.data[0] = SMS_PROTO_VERSION;
+ newMsg.data[1] = msgID;
+ newMsg.data[2] = indx;
+ newMsg.data[3] = count;
+ XP_MEMCPY( newMsg.data + 4, nextStart, useLen );
+ nextStart += useLen;
+
+ result = appendMsg( state, result, &newMsg );
+ }
+ ++ii;
+ }
+ }
+
+ return result;
+} /* toMsgs */
+
+static int
+nextMsgID( SMSProto* state )
+{
+ int result = ++state->nNextID % 0x000000FF;
+ dutil_storePtr( state->dutil, KEY_NEXTID, &state->nNextID,
+ sizeof(state->nNextID) );
+ LOG_RETURNF( "%d", result );
+ return result;
+}
+
+#ifdef DEBUG
+static void
+checkThread( SMSProto* state )
+{
+ pthread_t curThread = pthread_self();
+ if ( 0 == state->creator ) {
+ state->creator = curThread;
+ } else {
+ /* If this is firing will need to use a mutex to make SMSProto access
+ thread-safe */
+ XP_ASSERT( state->creator == curThread );
+ }
+}
+
+void
+smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil )
+{
+ LOG_FUNC();
+ SMSProto* state = smsproto_init( mpool, dutil );
+
+ const int smallSiz = 20;
+ const char* phones[] = {"1234", "3456", "5467", "9877"};
+ const char* buf = "asoidfaisdfoausdf aiousdfoiu asodfu oiuasdofi oiuaosiduf oaisudf oiasd f"
+ ";oiaisdjfljiojaklj asdlkjalskdjf laksjd flkjasdlfkj aldsjkf lsakdjf lkjsad flkjsd fl;kj"
+ "asdifaoaosidfoiauosidufoaus doifuoaiusdoifu aoisudfoaisd foia sdoifuasodfu aosiud foiuas odfiu asd"
+ "aosdoiaosdoiisidfoiosi isoidufoisu doifuoisud oiuoi98a90iu-asjdfoiasdfij"
+ ;
+ const XP_Bool forceOld = XP_TRUE;
+
+ SMSMsgArray* arrs[VSIZE(phones)];
+ for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
+ arrs[ii] = NULL;
+ }
+
+ /* Loop until all the messages are ready. */
+ for ( XP_Bool firstTime = XP_TRUE; ; firstTime = XP_FALSE) {
+ XP_Bool allDone = XP_TRUE;
+ for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
+ XP_U16 waitSecs;
+ if ( firstTime ) {
+ XP_U16 len = (ii + 1) * 30;
+ arrs[ii] = smsproto_prepOutbound( state, (XP_U8*)buf, len, phones[ii],
+ forceOld, &waitSecs );
+ } else if ( NULL == arrs[ii]) {
+ arrs[ii] = smsproto_prepOutbound( state, NULL, 0, phones[ii],
+ forceOld, &waitSecs );
+ } else {
+ continue;
+ }
+ allDone = allDone & (waitSecs == 0 && !!arrs[ii]);
+ }
+ if ( allDone ) {
+ break;
+ } else {
+ (void)sleep( 2 );
+ }
+ }
+
+ for ( int indx = 0; ; ++indx ) {
+ XP_Bool haveOne = XP_FALSE;
+ for ( int ii = 0; ii < VSIZE(arrs); ++ii ) {
+ if (!!arrs[ii] && indx < arrs[ii]->nMsgs) {
+ haveOne = XP_TRUE;
+ SMSMsgArray* outArr = smsproto_prepInbound( state, phones[ii],
+ arrs[ii]->msgs[indx].data,
+ arrs[ii]->msgs[indx].len );
+ if ( !!outArr ) {
+ SMSMsg* msg = &outArr->msgs[0];
+ XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID );
+ XP_ASSERT( outArr->nMsgs == 1 );
+ XP_ASSERT( 0 == memcmp(buf, msg->data, (ii + 1) * 30) );
+ smsproto_freeMsgArray( state, outArr );
+
+ smsproto_freeMsgArray( state, arrs[ii] );
+ arrs[ii] = NULL;
+ }
+ }
+ }
+ if (!haveOne) {
+ break;
+ }
+ }
+
+ /* Now let's send a bunch of small messages that should get combined */
+ for ( int nUsed = 0; ; ++nUsed ) {
+ XP_U16 waitSecs;
+ SMSMsgArray* sendArr = smsproto_prepOutbound( state, (XP_U8*)&buf[nUsed],
+ smallSiz, phones[0],
+ XP_FALSE, &waitSecs );
+ if ( sendArr == NULL ) {
+ XP_LOGF( "%s(): msg[%d] of len %d sent; still not ready", __func__, nUsed, smallSiz );
+ continue;
+ }
+
+ XP_ASSERT( waitSecs == 0 );
+ int totalBack = 0;
+ for ( int jj = 0; jj < sendArr->nMsgs; ++jj ) {
+ SMSMsgArray* recvArr = smsproto_prepInbound( state, phones[0],
+ sendArr->msgs[jj].data,
+ sendArr->msgs[jj].len );
+
+ if ( !!recvArr ) {
+ XP_LOGF( "%s(): got %d msgs (from %d)", __func__, recvArr->nMsgs, nUsed + 1 );
+ for ( int kk = 0; kk < recvArr->nMsgs; ++kk ) {
+ SMSMsg* msg = &recvArr->msgs[kk];
+ XP_LOGF( "%s(): got msgID %d", __func__, msg->msgID );
+ XP_ASSERT( msg->len == smallSiz );
+ XP_ASSERT( 0 == memcmp( msg->data, &buf[totalBack], smallSiz ) );
+ ++totalBack;
+ }
+
+ smsproto_freeMsgArray( state, recvArr );
+ }
+ }
+ XP_ASSERT( forceOld || totalBack == nUsed + 1 );
+ XP_LOGF( "%s(): %d messages checked out", __func__, totalBack );
+ smsproto_freeMsgArray( state, sendArr );
+ break;
+ }
+
+ /* Now let's add a too-long message and unpack only the first part. Make
+ sure it's cleaned up correctly */
+ XP_U16 waitSecs;
+ SMSMsgArray* arr = smsproto_prepOutbound( state, (XP_U8*)buf, 200, "33333", XP_TRUE, &waitSecs );
+ XP_ASSERT( !!arr && arr->nMsgs > 1 );
+ /* add only part 1 */
+ SMSMsgArray* out = smsproto_prepInbound( state, "33333", arr->msgs[0].data, arr->msgs[0].len );
+ XP_ASSERT( !out );
+ smsproto_freeMsgArray( state, arr );
+
+
+ /* now a message that's unpacked across multiple sessions to test store/load */
+ XP_LOGF( "%s(): testing store/restore", __func__ );
+ arr = smsproto_prepOutbound( state, (XP_U8*)buf, 200, "33333", XP_TRUE, &waitSecs );
+ for ( int ii = 0; ii < arr->nMsgs; ++ii ) {
+ SMSMsgArray* out = smsproto_prepInbound( state, "33333", arr->msgs[ii].data,
+ arr->msgs[ii].len );
+ if ( !!out ) {
+ XP_ASSERT( out->nMsgs == 1);
+ XP_LOGF( "%s(): got the message on the %dth loop", __func__, ii );
+ XP_ASSERT( out->msgs[0].len == 200 );
+ XP_ASSERT( 0 == memcmp( out->msgs[0].data, buf, 200 ) );
+ smsproto_freeMsgArray( state, out );
+ break;
+ }
+ smsproto_free( state ); /* give it a chance to store state */
+ state = smsproto_init( mpool, dutil );
+ }
+
+ /* Really bad to pass a different state than was created with, but now
+ since only mpool is used and it's the same for all states, let it
+ go. */
+ smsproto_freeMsgArray( state, arr ); /* give it a chance to store state */
+
+ smsproto_free( state );
+ LOG_RETURN_VOID();
+}
+#endif
diff --git a/xwords4/common/smsproto.h b/xwords4/common/smsproto.h
new file mode 100644
index 000000000..76ad7c88f
--- /dev/null
+++ b/xwords4/common/smsproto.h
@@ -0,0 +1,85 @@
+/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */
+/*
+ * Copyright 2018 by Eric House (xwords@eehouse.org). All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __SMSPROTO_H__
+#define __SMSPROTO_H__
+
+// The sms protocol is getting more complicated as I add message combining. So
+// let's try to move it into C where it can be tested in linux.
+//
+// How the protocol works. Clients want to send data packets to phones and
+// receive from phones. They use a send(byte[] data, String phone) method, and
+// provide a callback that's called when packets arrive. Internally this
+// module buffers messages on a per-number basis (new post-java feature) and
+// periodically, based on a timer that's set when buffered data is waiting to
+// be sent, gathers messages and transmits them. Probably it'll wait a few
+// seconds after the last message was enqueued, the idea being that SMS is not
+// expected to be fast and that sending lots of small messages is bad.
+//
+// Because there's a max size to SMS messages any [combined] message that's
+// too big is broken up. To keep things simple the two processes, combining
+// and breaking, are independent; there's no attempt to avoid combining
+// messages even when doing so creates something that will have to be broken
+// up.
+//
+// Received messages (buffers) are recombined (if the result of breaking up)
+// then split (if the result of combining), and the constituent data packets
+// are returned to the app as an array of byte[].
+
+#include "xptypes.h"
+#include "mempool.h" /* debug only */
+
+typedef struct SMSProto SMSProto;
+
+typedef struct _SMSMsg {
+ XP_U16 len;
+ XP_U16 msgID;
+ XP_U8* data;
+} SMSMsg;
+
+typedef struct _SMSMsgArray {
+ XP_U16 nMsgs;
+ SMSMsg* msgs;
+} SMSMsgArray;
+
+struct SMSProto* smsproto_init( MPFORMAL XW_DUtilCtxt* dutil );
+void smsproto_free( SMSProto* state );
+
+/* Return ptr to structure if one's ready to be sent, otherwise null. Caller *
+ * should interpret null as meaning it's meant to call again. To support that,
+ * null buf is legit.
+ *
+ * When send() returns a non-null value, that value must be passed to
+ * freeMsgArray() or there will be leakage.
+*/
+SMSMsgArray* smsproto_prepOutbound( SMSProto* state, const XP_U8* buf,
+ XP_U16 buflen, const XP_UCHAR* toPhone,
+ XP_Bool forceOld, XP_U16* waitSecs );
+
+/* When a message is received, pass it in for reassambly. Non-null return
+ means one or more messages is ready for consumption. */
+SMSMsgArray* smsproto_prepInbound( SMSProto* state, const XP_UCHAR* fromPhone,
+ const XP_U8* data, XP_U16 len );
+
+void smsproto_freeMsgArray( SMSProto* state, SMSMsgArray* arr );
+
+# ifdef DEBUG
+void smsproto_runTests( MPFORMAL XW_DUtilCtxt* dutil );
+# endif
+#endif
diff --git a/xwords4/common/strutils.c b/xwords4/common/strutils.c
index 9ba8a6da3..7ad645b8d 100644
--- a/xwords4/common/strutils.c
+++ b/xwords4/common/strutils.c
@@ -179,6 +179,26 @@ stringToStream( XWStreamCtxt* stream, const XP_UCHAR* str )
stream_putBytes( stream, str, len );
} /* putStringToStream */
+XP_Bool
+stream_gotU8( XWStreamCtxt* stream, XP_U8* ptr )
+{
+ XP_Bool success = sizeof(*ptr) <= stream_getSize( stream );
+ if ( success ) {
+ *ptr = stream_getU8( stream );
+ }
+ return success;
+}
+
+XP_Bool
+stream_gotBytes( XWStreamCtxt* stream, void* ptr, XP_U16 len )
+{
+ XP_Bool success = len <= stream_getSize( stream );
+ if ( success ) {
+ stream_getBytes( stream, ptr, len );
+ }
+ return success;
+}
+
/*****************************************************************************
*
****************************************************************************/
diff --git a/xwords4/common/strutils.h b/xwords4/common/strutils.h
index 1451e6dc7..f35a92630 100644
--- a/xwords4/common/strutils.h
+++ b/xwords4/common/strutils.h
@@ -56,6 +56,9 @@ XP_UCHAR* p_stringFromStream( MPFORMAL XWStreamCtxt* stream
XP_U16 stringFromStreamHere( XWStreamCtxt* stream, XP_UCHAR* buf, XP_U16 len );
void stringToStream( XWStreamCtxt* stream, const XP_UCHAR* str );
+XP_Bool stream_gotU8( XWStreamCtxt* stream, XP_U8* ptr );
+XP_Bool stream_gotBytes( XWStreamCtxt* stream, void* ptr, XP_U16 len );
+
XP_UCHAR* p_copyString( MPFORMAL const XP_UCHAR* instr
#ifdef MEM_DEBUG
, const char* file, const char* func, XP_U32 lineNo
diff --git a/xwords4/common/util.h b/xwords4/common/util.h
index 85a726df8..a3b67a96b 100644
--- a/xwords4/common/util.h
+++ b/xwords4/common/util.h
@@ -1,6 +1,6 @@
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
/*
- * Copyright 1997 - 2010 by Eric House (xwords@eehouse.org). All rights
+ * Copyright 1997 - 2018 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@@ -26,9 +26,8 @@
#include "dawg.h"
#include "model.h"
#include "board.h"
-#include "mempool.h"
-#include "vtabmgr.h"
#include "comms.h"
+#include "dutil.h"
#include "xwrelay.h"
@@ -87,8 +86,6 @@ typedef XP_Bool (*XWTimerProc)( void* closure, XWTimerReason why );
*/
typedef struct UtilVtable {
- VTableMgr* (*m_util_getVTManager)(XW_UtilCtxt* uc);
-
#ifndef XWFEATURE_STANDALONE_ONLY
XWStreamCtxt* (*m_util_makeStreamFromAddr )(XW_UtilCtxt* uc,
XP_PlayerAddr channelNo );
@@ -144,21 +141,8 @@ typedef struct UtilVtable {
void (*m_util_requestTime)( XW_UtilCtxt* uc );
XP_Bool (*m_util_altKeyDown)( XW_UtilCtxt* uc );
-
- XP_U32 (*m_util_getCurSeconds)( XW_UtilCtxt* uc );
-#ifdef XWFEATURE_DEVID
- const XP_UCHAR* (*m_util_getDevID)( XW_UtilCtxt* uc, DevIDType* typ );
- void (*m_util_deviceRegistered)( XW_UtilCtxt* uc, DevIDType typ,
- const XP_UCHAR* idRelay );
-#endif
DictionaryCtxt* (*m_util_makeEmptyDict)( XW_UtilCtxt* uc );
- const XP_UCHAR* (*m_util_getUserString)( XW_UtilCtxt* uc,
- XP_U16 stringCode );
- const XP_UCHAR* (*m_util_getUserQuantityString)( XW_UtilCtxt* uc,
- XP_U16 stringCode,
- XP_U16 quantity );
-
void (*m_util_notifyIllegalWords)( XW_UtilCtxt* uc, BadWordInfo* bwi,
XP_U16 turn, XP_Bool turnLost );
@@ -172,11 +156,6 @@ typedef struct UtilVtable {
void (*m_util_cellSquareHeld)( XW_UtilCtxt* uc, XWStreamCtxt* words );
#endif
-#ifdef XWFEATURE_SMS
- XP_Bool (*m_util_phoneNumbersSame)( XW_UtilCtxt* uc, const XP_UCHAR* p1,
- const XP_UCHAR* p2 );
-#endif
-
#ifndef XWFEATURE_STANDALONE_ONLY
void (*m_util_informMissing)(XW_UtilCtxt* uc, XP_Bool isServer,
const CommsAddrRec* addr, XP_U16 nDevs,
@@ -201,9 +180,7 @@ typedef struct UtilVtable {
void (*m_util_engineStopping)( XW_UtilCtxt* uc );
#endif
-#ifdef COMMS_CHECKSUM
- XP_UCHAR* (*m_util_md5sum)( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len );
-#endif
+ XW_DUtilCtxt* (*m_util_getDevUtilCtxt)( XW_UtilCtxt* uc );
} UtilVtable;
@@ -217,9 +194,6 @@ struct XW_UtilCtxt {
MPSLOT
};
-#define util_getVTManager(uc) \
- (uc)->vtable->m_util_getVTManager((uc))
-
#define util_makeStreamFromAddr(uc,a) \
(uc)->vtable->m_util_makeStreamFromAddr((uc),(a))
@@ -286,24 +260,9 @@ struct XW_UtilCtxt {
#define util_altKeyDown( uc ) \
(uc)->vtable->m_util_altKeyDown((uc))
-#define util_getCurSeconds(uc) \
- (uc)->vtable->m_util_getCurSeconds((uc))
-
-#ifdef XWFEATURE_DEVID
-# define util_getDevID( uc, t ) \
- (uc)->vtable->m_util_getDevID((uc),(t))
-# define util_deviceRegistered( uc, typ, id ) \
- (uc)->vtable->m_util_deviceRegistered( (uc), (typ), (id) )
-#endif
-
#define util_makeEmptyDict( uc ) \
(uc)->vtable->m_util_makeEmptyDict((uc))
-#define util_getUserString( uc, c ) \
- (uc)->vtable->m_util_getUserString((uc),(c))
-#define util_getUserQuantityString( uc, c, q ) \
- (uc)->vtable->m_util_getUserQuantityString((uc),(c),(q))
-
#define util_notifyIllegalWords( uc, w, p, b ) \
(uc)->vtable->m_util_notifyIllegalWords((uc),(w),(p),(b))
@@ -320,10 +279,6 @@ struct XW_UtilCtxt {
#define util_cellSquareHeld(uc, s) \
(uc)->vtable->m_util_cellSquareHeld( (uc), (s) )
#endif
-#ifdef XWFEATURE_SMS
-#define util_phoneNumbersSame(uc,p1,p2) \
- (uc)->vtable->m_util_phoneNumbersSame( (uc), (p1), (p2) )
-#endif
#ifndef XWFEATURE_STANDALONE_ONLY
# define util_informMissing( uc, is, ct, nd, nm ) \
@@ -355,8 +310,7 @@ struct XW_UtilCtxt {
# define util_engineStopping( uc )
# endif
-#ifdef COMMS_CHECKSUM
-# define util_md5sum( uc, p, l ) (uc)->vtable->m_util_md5sum((uc), (p), (l))
-#endif
+# define util_getDevUtilCtxt(uc) \
+ (uc)->vtable->m_util_getDevUtilCtxt( (uc) )
#endif
diff --git a/xwords4/linux/.gitignore b/xwords4/linux/.gitignore
index 3f284af16..053b23d2b 100644
--- a/xwords4/linux/.gitignore
+++ b/xwords4/linux/.gitignore
@@ -8,3 +8,4 @@ discon_ok2.sh_logs
test_backsend.sh_*
*.db
dawg2dict
+discon_ok2.py_logs
\ No newline at end of file
diff --git a/xwords4/linux/Makefile b/xwords4/linux/Makefile
index 801a2e8e2..c721d4bdc 100644
--- a/xwords4/linux/Makefile
+++ b/xwords4/linux/Makefile
@@ -225,6 +225,7 @@ OBJ = \
$(BUILD_PLAT_DIR)/linuxutl.o \
$(BUILD_PLAT_DIR)/gamesdb.o \
$(BUILD_PLAT_DIR)/relaycon.o \
+ $(BUILD_PLAT_DIR)/lindutil.o \
$(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS)
LIBS = -lm -lpthread -luuid -lcurl -ljson-c $(GPROFFLAG)
diff --git a/xwords4/linux/cursesmain.c b/xwords4/linux/cursesmain.c
index d7ebc15db..823b815ba 100644
--- a/xwords4/linux/cursesmain.c
+++ b/xwords4/linux/cursesmain.c
@@ -64,6 +64,7 @@
#include "linuxudp.h"
#include "gamesdb.h"
#include "relaycon.h"
+#include "smsproto.h"
#ifdef CURSES_SMALL_SCREEN
# define MENU_WINDOW_HEIGHT 1
@@ -346,9 +347,8 @@ cursesShowFinalScores( CursesAppGlobals* globals )
XWStreamCtxt* stream;
XP_UCHAR* text;
- stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool)
- globals->cGlobals.params->vtMgr,
- globals, CHANNEL_NONE, NULL );
+ stream = mem_stream_make_raw( MPPARM(globals->cGlobals.util->mpool)
+ globals->cGlobals.params->vtMgr );
server_writeFinalScores( globals->cGlobals.game.server, stream );
text = strFromStream( stream );
@@ -1363,13 +1363,6 @@ initClientSocket( CursesAppGlobals* globals, char* serverName )
} /* initClientSocket */
#endif
-static VTableMgr*
-curses_util_getVTManager(XW_UtilCtxt* uc)
-{
- CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure;
- return globals->cGlobals.params->vtMgr;
-} /* linux_util_getVTManager */
-
static void
curses_util_informNeedPassword( XW_UtilCtxt* XP_UNUSED(uc),
XP_U16 XP_UNUSED_DBG(playerNum),
@@ -1413,9 +1406,8 @@ curses_util_remSelected( XW_UtilCtxt* uc )
XWStreamCtxt* stream;
XP_UCHAR* text;
- stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool)
- globals->cGlobals.params->vtMgr,
- globals, CHANNEL_NONE, NULL );
+ stream = mem_stream_make_raw( MPPARM(globals->cGlobals.util->mpool)
+ globals->cGlobals.params->vtMgr );
board_formatRemainingTiles( globals->cGlobals.game.board, stream );
text = strFromStream( stream );
@@ -1464,7 +1456,6 @@ setupCursesUtilCallbacks( CursesAppGlobals* globals, XW_UtilCtxt* util )
{
util->vtable->m_util_userError = curses_util_userError;
- util->vtable->m_util_getVTManager = curses_util_getVTManager;
util->vtable->m_util_informNeedPassword = curses_util_informNeedPassword;
util->vtable->m_util_yOffsetChange = curses_util_yOffsetChange;
#ifdef XWFEATURE_TURNCHANGENOTIFY
@@ -1740,7 +1731,8 @@ curses_requestMsgs( gpointer data )
{
CursesAppGlobals* globals = (CursesAppGlobals*)data;
XP_UCHAR devIDBuf[64] = {0};
- db_fetch( globals->cGlobals.pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) );
+ db_fetch_safe( globals->cGlobals.params->pDb, KEY_RDEVID, devIDBuf,
+ sizeof(devIDBuf) );
if ( '\0' != devIDBuf[0] ) {
relaycon_requestMsgs( globals->cGlobals.params, devIDBuf );
} else {
@@ -1772,13 +1764,13 @@ cursesDevIDReceived( void* closure, const XP_UCHAR* devID,
{
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
CommonGlobals* cGlobals = &globals->cGlobals;
- sqlite3* pDb = cGlobals->pDb;
+ sqlite3* pDb = cGlobals->params->pDb;
if ( !!devID ) {
XP_LOGF( "%s(devID=%s)", __func__, devID );
/* If we already have one, make sure it's the same! Else store. */
gchar buf[64];
- XP_Bool have = db_fetch( pDb, KEY_RDEVID, buf, sizeof(buf) )
+ XP_Bool have = db_fetch_safe( pDb, KEY_RDEVID, buf, sizeof(buf) )
&& 0 == strcmp( buf, devID );
if ( !have ) {
db_store( pDb, KEY_RDEVID, devID );
@@ -1998,9 +1990,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
XP_Bool idIsNew = XP_TRUE;
if ( !!params->dbName ) {
- sqlite3* pDb = openGamesDB( params->dbName );
- /* Gross that both need to be set, but they do. */
- params->pDb = g_globals.cGlobals.pDb = pDb;
+ params->pDb = openGamesDB( params->dbName );
/* Check if we have a local ID already. If we do and it's
changed, we care. */
@@ -2026,18 +2016,18 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
#ifdef XWFEATURE_SMS
gchar buf[32];
- const gchar* myPhone = params->connInfo.sms.phone;
+ const gchar* myPhone = params->connInfo.sms.myPhone;
if ( !!myPhone ) {
db_store( params->pDb, KEY_SMSPHONE, myPhone );
- } else if ( !myPhone && db_fetch( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) {
- params->connInfo.sms.phone = myPhone = buf;
+ } else if ( !myPhone && db_fetch_safe( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) {
+ params->connInfo.sms.myPhone = myPhone = buf;
}
XP_U16 myPort = params->connInfo.sms.port;
gchar portbuf[8];
if ( 0 < myPort ) {
sprintf( portbuf, "%d", myPort );
db_store( params->pDb, KEY_SMSPORT, portbuf );
- } else if ( db_fetch( params->pDb, KEY_SMSPORT, portbuf, VSIZE(portbuf) ) ) {
+ } else if ( db_fetch_safe( params->pDb, KEY_SMSPORT, portbuf, VSIZE(portbuf) ) ) {
params->connInfo.sms.port = myPort = atoi( portbuf );
}
@@ -2049,6 +2039,11 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
};
linux_sms_init( params, myPhone, myPort, &smsProcs, &g_globals.cGlobals );
}
+
+ if ( params->runSMSTest ) {
+ smsproto_runTests(g_globals.cGlobals.util->mpool,
+ g_globals.cGlobals.params->dutil );
+ }
#endif
XWStreamCtxt* stream = NULL;
@@ -2056,9 +2051,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
GSList* games = listGames( params->pDb );
if ( !!games ) {
XP_ASSERT( 1 == g_slist_length(games) ); /* for now */
- stream = mem_stream_make( MEMPOOL params->vtMgr,
- &g_globals.cGlobals, CHANNEL_NONE,
- NULL );
+ stream = mem_stream_make_raw( MEMPOOL params->vtMgr);
sqlite3_int64 selRow = *(sqlite3_int64*)games->data;
/* XP_UCHAR buf[32]; */
/* XP_SNPRINTF( buf, sizeof(buf), "%lld", selRow ); */
@@ -2074,14 +2067,13 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
} else if ( !!params->fileName && file_exists( params->fileName ) ) {
mpool_setTag( MEMPOOL "file" );
- stream = streamFromFile( &g_globals.cGlobals, params->fileName,
- &g_globals );
+ stream = streamFromFile( &g_globals.cGlobals, params->fileName );
#ifdef USE_SQLITE
} else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) {
XP_UCHAR buf[32];
XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID );
mpool_setTag( MEMPOOL buf );
- stream = streamFromDB( &g_globals.cGlobals, &g_globals );
+ stream = streamFromDB( &g_globals.cGlobals );
#endif
}
@@ -2141,7 +2133,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
# ifdef XWFEATURE_SMS
case COMMS_CONN_SMS:
addr_addType( &addr, COMMS_CONN_SMS );
- XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.phone,
+ XP_STRNCPY( addr.u.sms.phone, params->connInfo.sms.myPhone,
sizeof(addr.u.sms.phone) - 1 );
addr.u.sms.port = params->connInfo.sms.port;
break;
@@ -2238,7 +2230,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
endwin();
if ( !!params->dbName ) {
- closeGamesDB( g_globals.cGlobals.pDb );
+ closeGamesDB( params->pDb );
}
relaycon_cleanup( params );
diff --git a/xwords4/linux/gamesdb.c b/xwords4/linux/gamesdb.c
index 778417c52..a2b53e204 100644
--- a/xwords4/linux/gamesdb.c
+++ b/xwords4/linux/gamesdb.c
@@ -27,8 +27,9 @@
#define SNAP_WIDTH 150
#define SNAP_HEIGHT 150
-static void getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf,
- int len );
+static XP_Bool getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf,
+ int* len );
+
#ifdef DEBUG
static char* sqliteErr2str( int err );
#endif
@@ -160,13 +161,16 @@ writeToDB( XWStreamCtxt* stream, void* closure )
{
CommonGlobals* cGlobals = (CommonGlobals*)closure;
sqlite3_int64 selRow = cGlobals->selRow;
- sqlite3* pDb = cGlobals->pDb;
+ sqlite3* pDb = cGlobals->params->pDb;
XP_Bool newGame = -1 == selRow;
selRow = writeBlobColumnStream( stream, pDb, selRow, "game" );
if ( newGame ) { /* new row; need to insert blob first */
cGlobals->selRow = selRow;
+ XP_LOGF( "%s(): new game at row %lld", __func__, selRow );
+ } else {
+ assert( selRow == cGlobals->selRow );
}
(*cGlobals->onSave)( cGlobals->onSaveClosure, selRow, newGame );
@@ -184,11 +188,12 @@ addSnapshot( CommonGlobals* cGlobals )
addSurface( dctx, SNAP_WIDTH, SNAP_HEIGHT );
board_drawSnapshot( board, (DrawCtx*)dctx, SNAP_WIDTH, SNAP_HEIGHT );
- XWStreamCtxt* stream = make_simple_stream( cGlobals );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ cGlobals->params->vtMgr );
getImage( dctx, stream );
removeSurface( dctx );
- writeBlobColumnStream( stream, cGlobals->pDb, cGlobals->selRow, "snap" );
- // XP_ASSERT( cGlobals->selRow == newRow );
+ cGlobals->selRow = writeBlobColumnStream( stream, cGlobals->params->pDb,
+ cGlobals->selRow, "snap" );
stream_destroy( stream );
}
@@ -264,8 +269,8 @@ summarize( CommonGlobals* cGlobals )
cGlobals->selRow );
XP_LOGF( "query: %s", buf );
sqlite3_stmt* stmt = NULL;
- int result = sqlite3_prepare_v2( cGlobals->pDb, buf, -1, &stmt, NULL );
- assertPrintResult( cGlobals->pDb, result, SQLITE_OK );
+ int result = sqlite3_prepare_v2( cGlobals->params->pDb, buf, -1, &stmt, NULL );
+ assertPrintResult( cGlobals->params->pDb, result, SQLITE_OK );
result = sqlite3_step( stmt );
if ( SQLITE_DONE != result ) {
XP_LOGF( "sqlite3_step=>%s", sqliteErr2str( result ) );
@@ -337,7 +342,8 @@ getRelayIDsToRowsMap( sqlite3* pDb )
case SQLITE_ROW: /* have data */
{
XP_UCHAR relayID[32];
- getColumnText( ppStmt, 0, relayID, VSIZE(relayID) );
+ int len = VSIZE(relayID);
+ getColumnText( ppStmt, 0, relayID, &len );
gpointer key = g_strdup( relayID );
sqlite3_int64* value = g_malloc( sizeof( value ) );
*value = sqlite3_column_int64( ppStmt, 1 );
@@ -375,7 +381,8 @@ getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
if ( SQLITE_ROW == result ) {
success = XP_TRUE;
int col = 0;
- getColumnText( ppStmt, col++, gib->room, sizeof(gib->room) );
+ int len = sizeof(gib->room);
+ getColumnText( ppStmt, col++, gib->room, &len );
gib->gameOver = 1 == sqlite3_column_int( ppStmt, col++ );
gib->turn = sqlite3_column_int( ppStmt, col++ );
gib->turnLocal = 1 == sqlite3_column_int( ppStmt, col++ );
@@ -383,10 +390,12 @@ getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
gib->nTotal = sqlite3_column_int( ppStmt, col++ );
gib->nMissing = sqlite3_column_int( ppStmt, col++ );
gib->seed = sqlite3_column_int( ppStmt, col++ );
- getColumnText( ppStmt, col++, gib->conn, sizeof(gib->conn) );
+ len = sizeof(gib->conn);
+ getColumnText( ppStmt, col++, gib->conn, &len );
gib->gameID = sqlite3_column_int( ppStmt, col++ );
gib->lastMoveTime = sqlite3_column_int( ppStmt, col++ );
- getColumnText( ppStmt, col++, gib->relayID, sizeof(gib->relayID) );
+ len = sizeof(gib->relayID);
+ getColumnText( ppStmt, col++, gib->relayID, &len );
snprintf( gib->name, sizeof(gib->name), "Game %lld", rowid );
#ifdef PLATFORM_GTK
@@ -488,6 +497,7 @@ loadInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid )
void
deleteGame( sqlite3* pDb, sqlite3_int64 rowid )
{
+ XP_ASSERT( !!pDb );
char query[256];
snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid );
sqlite3_stmt* ppStmt;
@@ -502,10 +512,10 @@ deleteGame( sqlite3* pDb, sqlite3_int64 rowid )
void
db_store( sqlite3* pDb, const gchar* key, const gchar* value )
{
- char buf[256];
- snprintf( buf, sizeof(buf),
- "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')",
- key, value );
+ XP_ASSERT( !!pDb );
+ gchar* buf =
+ g_strdup_printf( "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')",
+ key, value );
sqlite3_stmt *ppStmt;
int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL );
assertPrintResult( pDb, result, SQLITE_OK );
@@ -513,33 +523,51 @@ db_store( sqlite3* pDb, const gchar* key, const gchar* value )
assertPrintResult( pDb, result, SQLITE_DONE );
XP_USE( result );
sqlite3_finalize( ppStmt );
+ g_free( buf );
}
-XP_Bool
-db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen )
+FetchResult
+db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen )
{
+ XP_ASSERT( !!pDb );
+ FetchResult result = NOT_THERE;
char query[256];
snprintf( query, sizeof(query),
"SELECT value from pairs where key = '%s'", key );
sqlite3_stmt *ppStmt;
- int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL );
- XP_Bool found = SQLITE_OK == result;
+ int sqlResult = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL );
+ XP_Bool found = SQLITE_OK == sqlResult;
if ( found ) {
result = sqlite3_step( ppStmt );
found = SQLITE_ROW == result;
if ( found ) {
- getColumnText( ppStmt, 0, buf, buflen );
- } else {
+ if ( getColumnText( ppStmt, 0, buf, buflen ) ) {
+ result = SUCCESS;
+ } else {
+ result = BUFFER_TOO_SMALL;
+ }
+ } else if ( !!buf ) {
buf[0] = '\0';
}
}
sqlite3_finalize( ppStmt );
- return found;
+ return result;
+}
+
+XP_Bool
+db_fetch_safe( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen )
+{
+ XP_ASSERT( !!pDb );
+ int tmp = buflen;
+ FetchResult result = db_fetch( pDb, key, buf, &tmp );
+ XP_ASSERT( result != BUFFER_TOO_SMALL );
+ return SUCCESS == result;
}
void
db_remove( sqlite3* pDb, const gchar* key )
{
+ XP_ASSERT( !!pDb );
char query[256];
snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key );
sqlite3_stmt *ppStmt;
@@ -551,15 +579,19 @@ db_remove( sqlite3* pDb, const gchar* key )
sqlite3_finalize( ppStmt );
}
-static void
-getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf,
- int XP_UNUSED_DBG(len) )
+static XP_Bool
+getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int *len )
{
- const unsigned char* txt = sqlite3_column_text( ppStmt, iCol );
- int needLen = sqlite3_column_bytes( ppStmt, iCol );
- XP_ASSERT( needLen < len );
- XP_MEMCPY( buf, txt, needLen );
- buf[needLen] = '\0';
+ int colLen = sqlite3_column_bytes( ppStmt, iCol );
+
+ XP_Bool success = colLen < *len;
+ *len = colLen + 1; /* sqlite does not store the null byte */
+ if ( success ) {
+ const unsigned char* colTxt = sqlite3_column_text( ppStmt, iCol );
+ XP_MEMCPY( buf, colTxt, colLen );
+ buf[colLen] = '\0';
+ }
+ return success;
}
#ifdef DEBUG
diff --git a/xwords4/linux/gamesdb.h b/xwords4/linux/gamesdb.h
index e9803b88f..7ba93d5a3 100644
--- a/xwords4/linux/gamesdb.h
+++ b/xwords4/linux/gamesdb.h
@@ -47,7 +47,7 @@ typedef struct _GameInfo {
} GameInfo;
sqlite3* openGamesDB( const char* dbName );
-void closeGamesDB( sqlite3* dbp );
+void closeGamesDB( sqlite3* pDb );
void writeToDB( XWStreamCtxt* stream, void* closure );
sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb );
@@ -55,15 +55,15 @@ sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb );
void summarize( CommonGlobals* cGlobals );
/* Return GSList whose data is (ptrs to) rowids */
-GSList* listGames( sqlite3* dbp );
+GSList* listGames( sqlite3* pDb );
/* free list and data allocated by above */
void freeGamesList( GSList* games );
/* Mapping of relayID -> rowid */
GHashTable* getRelayIDsToRowsMap( sqlite3* pDb );
-XP_Bool getGameInfo( sqlite3* dbp, sqlite3_int64 rowid, GameInfo* gib );
-void getRowsForGameID( sqlite3* dbp, XP_U32 gameID, sqlite3_int64* rowids,
+XP_Bool getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib );
+void getRowsForGameID( sqlite3* pDb, XP_U32 gameID, sqlite3_int64* rowids,
int* nRowIDs );
XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid );
void saveInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb,
@@ -78,8 +78,12 @@ void deleteGame( sqlite3* pDb, sqlite3_int64 rowid );
#define KEY_SMSPORT "SMSPORT"
#define KEY_WIN_LOC "WIN_LOC"
-void db_store( sqlite3* dbp, const gchar* key, const gchar* value );
-XP_Bool db_fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen );
-void db_remove( sqlite3* dbp, const gchar* key );
+void db_store( sqlite3* pDb, const gchar* key, const gchar* value );
+void db_remove( sqlite3* pDb, const gchar* key );
+
+typedef enum { NOT_THERE, BUFFER_TOO_SMALL, SUCCESS } FetchResult;
+FetchResult db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen );
+XP_Bool db_fetch_safe( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen );
+
#endif
diff --git a/xwords4/linux/gtkboard.c b/xwords4/linux/gtkboard.c
index 1e506edea..1849cdbc7 100644
--- a/xwords4/linux/gtkboard.c
+++ b/xwords4/linux/gtkboard.c
@@ -86,7 +86,7 @@ static void gtkShowFinalScores( const GtkGameGlobals* globals,
XP_Bool ignoreTimeout );
static void send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
XP_U32 devID, const XP_UCHAR* relayID,
- const XP_UCHAR* phone );
+ const CommsAddrRec* addrs );
#define GTK_TRAY_HT_ROWS 3
@@ -589,19 +589,18 @@ createOrLoadObjects( GtkGameGlobals* globals )
setTransportProcs( &procs, globals );
if ( !!params->fileName && file_exists( params->fileName ) ) {
- stream = streamFromFile( cGlobals, params->fileName, globals );
+ stream = streamFromFile( cGlobals, params->fileName );
#ifdef USE_SQLITE
} else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) {
XP_UCHAR buf[32];
XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID );
mpool_setTag( MEMPOOL buf );
- stream = streamFromDB( cGlobals, globals );
+ stream = streamFromDB( cGlobals );
#endif
- } else if ( !!cGlobals->pDb && 0 <= cGlobals->selRow ) {
- stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- params->vtMgr,
- cGlobals, CHANNEL_NONE, NULL );
- if ( !loadGame( stream, cGlobals->pDb, cGlobals->selRow ) ) {
+ } else if ( !!params->pDb && 0 <= cGlobals->selRow ) {
+ stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ params->vtMgr );
+ if ( !loadGame( stream, params->pDb, cGlobals->selRow ) ) {
stream_destroy( stream );
stream = NULL;
}
@@ -874,10 +873,9 @@ on_board_window_shown( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
CommsCtxt* comms = cGlobals->game.comms;
if ( !!comms /*&& COMMS_CONN_NONE == comms_getConTypes( comms )*/ ) {
/* If it has pending invite info, send the invitation! */
- XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- cGlobals->params->vtMgr,
- cGlobals, CHANNEL_NONE, NULL );
- if ( loadInviteAddrs( stream, cGlobals->pDb, cGlobals->selRow ) ) {
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ cGlobals->params->vtMgr );
+ if ( loadInviteAddrs( stream, cGlobals->params->pDb, cGlobals->selRow ) ) {
CommsAddrRec addr = {0};
addrFromStream( &addr, stream );
comms_setAddr( cGlobals->game.comms, &addr );
@@ -1622,21 +1620,21 @@ handle_invite_button( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
XP_LOGF( "%s: inviteDlg => %d", __func__, confirmed );
if ( confirmed ) {
- send_invites( cGlobals, nPlayers, devID, NULL, NULL );
+ send_invites( cGlobals, nPlayers, devID, NULL, &inviteAddr );
}
} /* handle_invite_button */
static void
send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
XP_U32 devID, const XP_UCHAR* relayID,
- const XP_UCHAR* phone )
+ const CommsAddrRec* addrs )
{
CommsAddrRec addr = {0};
CommsCtxt* comms = cGlobals->game.comms;
XP_ASSERT( comms );
comms_getAddr( comms, &addr );
- gint forceChannel = 0; /* PENDING */
+ gint forceChannel = 1; /* 1 is what Android does. Limits to two-device games */
NetLaunchInfo nli = {0};
nli_init( &nli, cGlobals->gi, &addr, nPlayers, forceChannel );
@@ -1647,9 +1645,8 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
#ifdef DEBUG
{
- XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- cGlobals->params->vtMgr,
- NULL, CHANNEL_NONE, NULL );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ cGlobals->params->vtMgr );
nli_saveToStream( &nli, stream );
NetLaunchInfo tmp;
nli_makeFromStream( &tmp, stream );
@@ -1658,11 +1655,13 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
}
#endif
- if ( !!phone ) {
- XP_ASSERT( 0 ); /* not implemented */
- /* linux_sms_invite( cGlobals->params, gi, &addr, gameName, */
- /* nPlayers, forceChannel, */
- /* inviteAddr.u.sms.phone, inviteAddr.u.sms.port ); */
+ if ( !!addrs->u.sms.phone ) {
+ gchar gameName[64];
+ snprintf( gameName, VSIZE(gameName), "Game %d", cGlobals->gi->gameID );
+
+ linux_sms_invite( cGlobals->params, cGlobals->gi, &addr, gameName,
+ nPlayers, forceChannel,
+ addrs->u.sms.phone, addrs->u.sms.port );
}
if ( 0 != devID || !!relayID ) {
XP_ASSERT( 0 != devID || (!!relayID && !!relayID[0]) );
@@ -1722,14 +1721,6 @@ gtkUserError( GtkGameGlobals* globals, const char* format, ... )
va_end(ap);
} /* gtkUserError */
-static VTableMgr*
-gtk_util_getVTManager(XW_UtilCtxt* uc)
-{
- GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
- return globals->cGlobals.params->vtMgr;
-} /* linux_util_getVTManager */
-
-
static gint
ask_blank( gpointer data )
{
@@ -1934,9 +1925,8 @@ gtkShowFinalScores( const GtkGameGlobals* globals, XP_Bool ignoreTimeout )
XP_UCHAR* text;
const CommonGlobals* cGlobals = &globals->cGlobals;
- stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- cGlobals->params->vtMgr,
- NULL, CHANNEL_NONE, NULL );
+ stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ cGlobals->params->vtMgr );
server_writeFinalScores( cGlobals->game.server, stream );
text = strFromStream( stream );
@@ -2272,9 +2262,8 @@ gtk_util_remSelected( XW_UtilCtxt* uc )
XWStreamCtxt* stream;
XP_UCHAR* text;
- stream = mem_stream_make( MEMPOOL
- globals->cGlobals.params->vtMgr,
- globals, CHANNEL_NONE, NULL );
+ stream = mem_stream_make_raw( MEMPOOL
+ globals->cGlobals.params->vtMgr );
board_formatRemainingTiles( globals->cGlobals.game.board, stream );
text = strFromStream( stream );
stream_destroy( stream );
@@ -2570,7 +2559,6 @@ setupGtkUtilCallbacks( GtkGameGlobals* globals, XW_UtilCtxt* util )
util->vtable->m_util_userError = gtk_util_userError;
util->vtable->m_util_notifyMove = gtk_util_notifyMove;
util->vtable->m_util_notifyTrade = gtk_util_notifyTrade;
- util->vtable->m_util_getVTManager = gtk_util_getVTManager;
util->vtable->m_util_notifyPickTileBlank = gtk_util_notifyPickTileBlank;
util->vtable->m_util_informNeedPickTiles = gtk_util_informNeedPickTiles;
util->vtable->m_util_informNeedPassword = gtk_util_informNeedPassword;
@@ -2935,11 +2923,9 @@ loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params,
CommonGlobals* cGlobals = &globals->cGlobals;
cGlobals->selRow = rowid;
- cGlobals->pDb = pDb;
- XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- params->vtMgr, cGlobals,
- CHANNEL_NONE, NULL );
- XP_Bool loaded = loadGame( stream, cGlobals->pDb, rowid );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ params->vtMgr );
+ XP_Bool loaded = loadGame( stream, pDb, rowid );
if ( loaded ) {
if ( NULL == cGlobals->dict ) {
cGlobals->dict = makeDictForStream( cGlobals, stream );
diff --git a/xwords4/linux/gtkconnsdlg.c b/xwords4/linux/gtkconnsdlg.c
index 311182799..cd0bd74e6 100644
--- a/xwords4/linux/gtkconnsdlg.c
+++ b/xwords4/linux/gtkconnsdlg.c
@@ -305,7 +305,7 @@ makeSMSPage( GtkConnsState* state, PageData* data )
GtkWidget* vbox = boxWithUseCheck( state, data );
XP_Bool hasSMS = addr_hasType( state->addr, data->pageType );
const gchar* phone = hasSMS ?
- state->addr->u.sms.phone : state->globals->cGlobals.params->connInfo.sms.phone;
+ state->addr->u.sms.phone : state->globals->cGlobals.params->connInfo.sms.myPhone;
GtkWidget* hbox = makeLabeledField( "My phone", &state->smsphone, phone );
gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 );
gtk_widget_set_sensitive( state->smsphone, !state->readOnly );
diff --git a/xwords4/linux/gtkinvit.c b/xwords4/linux/gtkinvit.c
index 01809e04f..d2ca40fc1 100644
--- a/xwords4/linux/gtkinvit.c
+++ b/xwords4/linux/gtkinvit.c
@@ -237,7 +237,7 @@ makeSMSPage( GtkInviteState* state, PageData* data )
GtkWidget* vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
XP_Bool hasSMS = addr_hasType( state->addr, data->pageType );
const gchar* phone = hasSMS ?
- state->addr->u.sms.phone : state->globals->cGlobals.params->connInfo.sms.phone;
+ state->addr->u.sms.phone : state->globals->cGlobals.params->connInfo.sms.myPhone;
GtkWidget* hbox = makeLabeledField( "Invitee phone", &state->smsphone, phone );
gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 );
diff --git a/xwords4/linux/gtkmain.c b/xwords4/linux/gtkmain.c
index e7b83f83f..453931ba2 100644
--- a/xwords4/linux/gtkmain.c
+++ b/xwords4/linux/gtkmain.c
@@ -21,11 +21,13 @@
#ifdef PLATFORM_GTK
#include "strutils.h"
+#include "smsproto.h"
#include "main.h"
#include "gtkmain.h"
#include "gamesdb.h"
#include "gtkboard.h"
#include "linuxmain.h"
+#include "linuxutl.h"
#include "relaycon.h"
#include "linuxsms.h"
#include "gtkask.h"
@@ -275,7 +277,6 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure )
freeGlobals( globals );
} else {
GtkWidget* gameWindow = globals->window;
- globals->cGlobals.pDb = apg->params->pDb;
globals->cGlobals.selRow = -1;
recordOpened( apg, globals );
gtk_widget_show( gameWindow );
@@ -293,7 +294,6 @@ open_row( GtkAppGlobals* apg, sqlite3_int64 row, XP_Bool isNew )
apg->params->needsNewGame = XP_FALSE;
GtkGameGlobals* globals = malloc( sizeof(*globals) );
initGlobals( globals, apg->params, NULL );
- globals->cGlobals.pDb = apg->params->pDb;
globals->cGlobals.selRow = row;
recordOpened( apg, globals );
gtk_widget_show( globals->window );
@@ -315,10 +315,10 @@ handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure )
void
make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
{
- // LaunchParams* params = apg->params;
- XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- cGlobals->params->vtMgr,
- NULL, CHANNEL_NONE, NULL );
+ LaunchParams* params = apg->params;
+ XP_ASSERT( params == cGlobals->params );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ params->vtMgr );
/* Create new game. But has no addressing info, so need to set that
aside for later. */
@@ -333,16 +333,15 @@ make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
game_saveNewGame( MPPARM(cGlobals->util->mpool) &gi,
cGlobals->util, &cGlobals->cp, stream );
- sqlite3_int64 rowID = writeNewGameToDB( stream, cGlobals->pDb );
+ sqlite3_int64 rowID = writeNewGameToDB( stream, params->pDb );
stream_destroy( stream );
gi_disposePlayerInfo( MPPARM(cGlobals->util->mpool) &gi );
/* If it's a multi-device game, save enough information with it than when
opened it can invite the other device[s] join the rematch. */
if ( !!comms ) {
- XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- cGlobals->params->vtMgr,
- NULL, CHANNEL_NONE, NULL );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ params->vtMgr );
CommsAddrRec addr;
comms_getAddr( comms, &addr );
addrToStream( stream, &addr );
@@ -364,7 +363,7 @@ make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
}
addrToStream( stream, &addrs[ii] );
}
- saveInviteAddrs( stream, cGlobals->pDb, rowID );
+ saveInviteAddrs( stream, params->pDb, rowID );
stream_destroy( stream );
}
@@ -406,7 +405,7 @@ handle_delete_button( GtkWidget* XP_UNUSED(widget), void* closure )
deleteGame( params->pDb, rowid );
XP_UCHAR devIDBuf[64] = {0};
- db_fetch( params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) );
+ db_fetch_safe( params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) );
if ( '\0' != devIDBuf[0] ) {
relaycon_deleted( params, devIDBuf, clientToken );
} else {
@@ -482,7 +481,7 @@ setWindowTitle( GtkAppGlobals* apg )
#ifdef XWFEATURE_SMS
int len = strlen( title );
snprintf( &title[len], VSIZE(title) - len, " (phone: %s, port: %d)",
- params->connInfo.sms.phone, params->connInfo.sms.port );
+ params->connInfo.sms.myPhone, params->connInfo.sms.port );
#endif
#ifdef XWFEATURE_RELAY
XP_U32 relayID = linux_getDevIDRelay( params );
@@ -501,7 +500,7 @@ trySetWinConfig( GtkAppGlobals* apg )
int height = 400;
gchar buf[64];
- if ( db_fetch( apg->params->pDb, KEY_WIN_LOC, buf, sizeof(buf)) ) {
+ if ( db_fetch_safe( apg->params->pDb, KEY_WIN_LOC, buf, sizeof(buf)) ) {
sscanf( buf, "%d:%d:%d:%d", &xx, &yy, &width, &height );
}
@@ -684,7 +683,6 @@ relayInviteReceived( void* closure, NetLaunchInfo* invite )
// globals->cGlobals.addr = *returnAddr;
GtkWidget* gameWindow = globals->window;
- globals->cGlobals.pDb = apg->params->pDb;
globals->cGlobals.selRow = -1;
recordOpened( apg, globals );
gtk_widget_show( gameWindow );
@@ -730,7 +728,7 @@ requestMsgs( gpointer data )
{
GtkAppGlobals* apg = (GtkAppGlobals*)data;
XP_UCHAR devIDBuf[64] = {0};
- db_fetch( apg->params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) );
+ db_fetch_safe( apg->params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) );
if ( '\0' != devIDBuf[0] ) {
relaycon_requestMsgs( apg->params, devIDBuf );
} else {
@@ -755,8 +753,9 @@ smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName),
{
GtkAppGlobals* apg = (GtkAppGlobals*)closure;
LaunchParams* params = apg->params;
- XP_LOGF( "%s(gameName=%s, gameID=%d, dictName=%s, nPlayers=%d, nHere=%d)",
- __func__, gameName, gameID, dictName, nPlayers, nHere );
+ XP_LOGF( "%s(gameName=%s, gameID=%d, dictName=%s, nPlayers=%d, "
+ "nHere=%d, forceChannel=%d)", __func__, gameName, gameID, dictName,
+ nPlayers, nHere, forceChannel );
CurGameInfo gi = {0};
gi_copy( MPPARM(params->mpool) &gi, ¶ms->pgi );
@@ -765,6 +764,7 @@ smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName),
gi.gameID = gameID;
gi.dictLang = dictLang;
gi.forceChannel = forceChannel;
+ gi.serverRole = SERVER_ISCLIENT; /* recipient of invitation is client */
replaceStringIfDifferent( params->mpool, &gi.dictName, dictName );
GtkGameGlobals* globals = malloc( sizeof(*globals) );
@@ -773,7 +773,6 @@ smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName),
globals->cGlobals.addr = *returnAddr;
GtkWidget* gameWindow = globals->window;
- globals->cGlobals.pDb = apg->params->pDb;
globals->cGlobals.selRow = -1;
recordOpened( apg, globals );
gtk_widget_show( gameWindow );
@@ -896,18 +895,18 @@ gtkmain( LaunchParams* params )
#ifdef XWFEATURE_SMS
gchar buf[32];
- const gchar* myPhone = params->connInfo.sms.phone;
+ const gchar* myPhone = params->connInfo.sms.myPhone;
if ( !!myPhone ) {
db_store( params->pDb, KEY_SMSPHONE, myPhone );
- } else if ( !myPhone && db_fetch( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) {
- params->connInfo.sms.phone = myPhone = buf;
+ } else if ( !myPhone && db_fetch_safe( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) {
+ params->connInfo.sms.myPhone = myPhone = buf;
}
XP_U16 myPort = params->connInfo.sms.port;
gchar portbuf[8];
if ( 0 < myPort ) {
sprintf( portbuf, "%d", myPort );
db_store( params->pDb, KEY_SMSPORT, portbuf );
- } else if ( db_fetch( params->pDb, KEY_SMSPORT, portbuf, VSIZE(portbuf) ) ) {
+ } else if ( db_fetch_safe( params->pDb, KEY_SMSPORT, portbuf, VSIZE(portbuf) ) ) {
params->connInfo.sms.port = myPort = atoi( portbuf );
}
if ( !!myPhone && 0 < myPort ) {
@@ -921,7 +920,13 @@ gtkmain( LaunchParams* params )
XP_LOGF( "not activating SMS: I don't have a phone" );
}
-
+ if ( params->runSMSTest ) {
+ CommonGlobals cGlobals = {.params = params };
+ setupUtil( &cGlobals );
+ smsproto_runTests( params->mpool, cGlobals.params->dutil );
+ linux_util_vt_destroy( cGlobals.util );
+ free( cGlobals.util );
+ }
#endif
makeGamesWindow( &apg );
} else if ( !!params->dbFileName ) {
diff --git a/xwords4/linux/lindutil.c b/xwords4/linux/lindutil.c
new file mode 100644
index 000000000..931b907b2
--- /dev/null
+++ b/xwords4/linux/lindutil.c
@@ -0,0 +1,315 @@
+/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
+/*
+ * Copyright 2018 by Eric House (xwords@eehouse.org). All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "dutil.h"
+#include "mempool.h"
+#include "lindutil.h"
+#include "linuxutl.h"
+#include "linuxmain.h"
+#include "gamesdb.h"
+#include "LocalizedStrIncludes.h"
+
+static XP_U32 linux_dutil_getCurSeconds( XW_DUtilCtxt* duc );
+static const XP_UCHAR* linux_dutil_getUserString( XW_DUtilCtxt* duc, XP_U16 code );
+static const XP_UCHAR* linux_dutil_getUserQuantityString( XW_DUtilCtxt* duc, XP_U16 code,
+ XP_U16 quantity );
+
+static void linux_dutil_storeStream( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ XWStreamCtxt* data );
+static void linux_dutil_loadStream( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ XWStreamCtxt* inOut );
+static void linux_dutil_storePtr( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ const void* data, XP_U16 len );
+static void linux_dutil_loadPtr( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ void* data, XP_U16* lenp );
+
+
+#ifdef XWFEATURE_SMS
+static XP_Bool linux_dutil_phoneNumbersSame( XW_DUtilCtxt* duc,
+ const XP_UCHAR* p1,
+ const XP_UCHAR* p2 );
+#endif
+
+#ifdef XWFEATURE_DEVID
+static const XP_UCHAR* linux_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ );
+static void linux_dutil_deviceRegistered( XW_DUtilCtxt* duc, DevIDType typ,
+ const XP_UCHAR* idRelay );
+#endif
+
+#ifdef COMMS_CHECKSUM
+static XP_UCHAR* linux_dutil_md5sum( XW_DUtilCtxt* duc, const XP_U8* ptr,
+ XP_U16 len );
+#endif
+
+XW_DUtilCtxt*
+dutils_init( MPFORMAL VTableMgr* vtMgr, void* closure )
+{
+ XW_DUtilCtxt* result = XP_CALLOC( mpool, sizeof(*result) );
+ result->vtMgr = vtMgr;
+ result->closure = closure;
+
+# define SET_PROC(nam) \
+ result->vtable.m_dutil_ ## nam = linux_dutil_ ## nam;
+
+ SET_PROC(getCurSeconds);
+ SET_PROC(getUserString);
+ SET_PROC(getUserQuantityString);
+ SET_PROC(storeStream);
+ SET_PROC(loadStream);
+ SET_PROC(storePtr);
+ SET_PROC(loadPtr);
+
+#ifdef XWFEATURE_SMS
+ SET_PROC(phoneNumbersSame);
+#endif
+
+#ifdef XWFEATURE_DEVID
+ SET_PROC(getDevID);
+ SET_PROC(deviceRegistered);
+#endif
+
+#ifdef COMMS_CHECKSUM
+ SET_PROC(md5sum);
+#endif
+
+# undef SET_PROC
+
+ MPASSIGN( result->mpool, mpool );
+ return result;
+}
+
+void dutils_free( XW_DUtilCtxt** ducp )
+{
+# ifdef MEM_DEBUG
+ XP_FREEP( (*ducp)->mpool, ducp );
+# endif
+}
+
+static XP_U32
+linux_dutil_getCurSeconds( XW_DUtilCtxt* XP_UNUSED(duc) )
+{
+ return linux_getCurSeconds();
+}
+
+static const XP_UCHAR*
+linux_dutil_getUserString( XW_DUtilCtxt* XP_UNUSED(uc), XP_U16 code )
+{
+ switch( code ) {
+ case STRD_REMAINING_TILES_ADD:
+ return (XP_UCHAR*)"+ %d [all remaining tiles]";
+ case STRD_UNUSED_TILES_SUB:
+ return (XP_UCHAR*)"- %d [unused tiles]";
+ case STR_COMMIT_CONFIRM:
+ return (XP_UCHAR*)"Are you sure you want to commit the current move?\n";
+ case STRD_TURN_SCORE:
+ return (XP_UCHAR*)"Score for turn: %d\n";
+ case STR_BONUS_ALL:
+ return (XP_UCHAR*)"Bonus for using all tiles: 50\n";
+ case STR_LOCAL_NAME:
+ return (XP_UCHAR*)"%s";
+ case STR_NONLOCAL_NAME:
+ return (XP_UCHAR*)"%s (remote)";
+ case STRD_TIME_PENALTY_SUB:
+ return (XP_UCHAR*)" - %d [time]";
+ /* added.... */
+ case STRD_CUMULATIVE_SCORE:
+ return (XP_UCHAR*)"Cumulative score: %d\n";
+ case STRS_TRAY_AT_START:
+ return (XP_UCHAR*)"Tray at start: %s\n";
+ case STRS_MOVE_DOWN:
+ return (XP_UCHAR*)"move (from %s down)\n";
+ case STRS_MOVE_ACROSS:
+ return (XP_UCHAR*)"move (from %s across)\n";
+ case STRS_NEW_TILES:
+ return (XP_UCHAR*)"New tiles: %s\n";
+ case STRSS_TRADED_FOR:
+ return (XP_UCHAR*)"Traded %s for %s.";
+ case STR_PASS:
+ return (XP_UCHAR*)"pass\n";
+ case STR_PHONY_REJECTED:
+ return (XP_UCHAR*)"Illegal word in move; turn lost!\n";
+
+ case STRD_ROBOT_TRADED:
+ return (XP_UCHAR*)"%d tiles traded this turn.";
+ case STR_ROBOT_MOVED:
+ return (XP_UCHAR*)"The robot \"%s\" moved:\n";
+ case STRS_REMOTE_MOVED:
+ return (XP_UCHAR*)"Remote player \"%s\" moved:\n";
+
+#ifndef XWFEATURE_STANDALONE_ONLY
+ case STR_LOCALPLAYERS:
+ return (XP_UCHAR*)"Local players";
+ case STR_REMOTE:
+ return (XP_UCHAR*)"Remote";
+#endif
+ case STR_TOTALPLAYERS:
+ return (XP_UCHAR*)"Total players";
+
+ case STRS_VALUES_HEADER:
+ return (XP_UCHAR*)"%s counts/values:\n";
+
+ case STRD_REMAINS_HEADER:
+ return (XP_UCHAR*)"%d tiles left in pool.";
+ case STRD_REMAINS_EXPL:
+ return (XP_UCHAR*)"%d tiles left in pool and hidden trays:\n";
+
+ case STRSD_RESIGNED:
+ return "[Resigned] %s: %d";
+ case STRSD_WINNER:
+ return "[Winner] %s: %d";
+ case STRDSD_PLACER:
+ return "[#%d] %s: %d";
+
+ default:
+ return (XP_UCHAR*)"unknown code to linux_util_getUserString";
+ }
+} /* linux_dutil_getUserString */
+
+static const XP_UCHAR*
+linux_dutil_getUserQuantityString( XW_DUtilCtxt* duc, XP_U16 code,
+ XP_U16 XP_UNUSED(quantity) )
+{
+ return linux_dutil_getUserString( duc, code );
+}
+
+static void
+linux_dutil_storeStream( XW_DUtilCtxt* duc, const XP_UCHAR* key, XWStreamCtxt* stream )
+{
+ const void* ptr = stream_getPtr( stream );
+ XP_U16 len = stream_getSize( stream );
+ linux_dutil_storePtr( duc, key, ptr, len );
+}
+
+static void
+linux_dutil_loadStream( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ XWStreamCtxt* stream )
+{
+ XP_U16 len = 0;
+ linux_dutil_loadPtr( duc, key, NULL, &len );
+ XP_U8 buf[len];
+ linux_dutil_loadPtr( duc, key, buf, &len );
+
+ gsize out_len;
+ guchar* txt = g_base64_decode( (const gchar*)buf, &out_len );
+ stream_putBytes( stream, txt, out_len );
+ g_free( txt );
+
+ XP_LOGF( "%s(key=%s) => len: %d", __func__, key, stream_getSize(stream) );
+}
+
+static void
+linux_dutil_storePtr( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ const void* data, XP_U16 len )
+{
+ LaunchParams* params = (LaunchParams*)duc->closure;
+ sqlite3* pDb = params->pDb;
+
+ gchar* b64 = g_base64_encode( data, len);
+ db_store( pDb, key, b64 );
+ g_free( b64 );
+}
+
+static void
+linux_dutil_loadPtr( XW_DUtilCtxt* duc, const XP_UCHAR* key,
+ void* data, XP_U16* lenp )
+{
+ LaunchParams* params = (LaunchParams*)duc->closure;
+ sqlite3* pDb = params->pDb;
+
+ gint buflen = 0;
+ FetchResult res = db_fetch( pDb, key, NULL, &buflen );
+ if ( res == BUFFER_TOO_SMALL ) { /* expected: I passed 0 */
+ void* tmp = XP_MALLOC( duc->mpool, buflen );
+ res = db_fetch( pDb, key, tmp, &buflen );
+ XP_ASSERT( res == SUCCESS );
+
+ gsize out_len;
+ guchar* txt = g_base64_decode( (const gchar*)tmp, &out_len );
+ if ( out_len <= *lenp ) {
+ XP_MEMCPY( data, txt, out_len );
+ *lenp = out_len;
+ }
+ XP_FREEP( duc->mpool, &tmp );
+ g_free( txt );
+ } else {
+ *lenp = 0; /* doesn't exist */
+ }
+
+ XP_LOGF( "%s(key=%s) => len: %d", __func__, key, *lenp );
+}
+
+#ifdef XWFEATURE_SMS
+static XP_Bool
+linux_dutil_phoneNumbersSame( XW_DUtilCtxt* duc, const XP_UCHAR* p1,
+ const XP_UCHAR* p2 )
+{
+ LOG_FUNC();
+ XP_USE( duc );
+ XP_Bool result = 0 == strcmp( p1, p2 );
+ XP_LOGF( "%s(%s, %s) => %d", __func__, p1, p2, result );
+ return result;
+}
+#endif
+
+#ifdef XWFEATURE_DEVID
+static const XP_UCHAR*
+linux_dutil_getDevID( XW_DUtilCtxt* duc, DevIDType* typ )
+{
+ LaunchParams* params = (LaunchParams*)duc->closure;
+ return linux_getDevID( params, typ );
+}
+
+static void
+linux_dutil_deviceRegistered( XW_DUtilCtxt* duc, DevIDType typ,
+ const XP_UCHAR* idRelay )
+{
+ /* Script discon_ok2.sh is grepping for these strings in logs, so don't
+ change them! */
+ LaunchParams* params = (LaunchParams*)duc->closure;
+ switch( typ ) {
+ case ID_TYPE_NONE: /* error case */
+ XP_LOGF( "%s: id rejected", __func__ );
+ params->lDevID = NULL;
+ break;
+ case ID_TYPE_RELAY:
+ if ( !!params->pDb && 0 < strlen( idRelay ) ) {
+ XP_LOGF( "%s: new id: %s", __func__, idRelay );
+ db_store( params->pDb, KEY_RDEVID, idRelay );
+ }
+ break;
+ default:
+ XP_ASSERT(0);
+ break;
+ }
+}
+#endif
+
+#ifdef COMMS_CHECKSUM
+static XP_UCHAR*
+linux_dutil_md5sum( XW_DUtilCtxt* duc, const XP_U8* ptr, XP_U16 len )
+{
+ gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, ptr, len );
+ XP_U16 sumlen = 1 + strlen( sum );
+ XP_UCHAR* result = XP_MALLOC( duc->mpool, sumlen );
+ XP_MEMCPY( result, sum, sumlen );
+ g_free( sum );
+ return result;
+}
+#endif
+
diff --git a/xwords4/linux/lindutil.h b/xwords4/linux/lindutil.h
new file mode 100644
index 000000000..c28d30e89
--- /dev/null
+++ b/xwords4/linux/lindutil.h
@@ -0,0 +1,28 @@
+/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
+/*
+ * Copyright 2018 by Eric House (xwords@eehouse.org). All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _LINDUTIL_H_
+#define _LINDUTIL_H_
+
+#include "dutil.h"
+
+XW_DUtilCtxt* dutils_init( MPFORMAL VTableMgr* vtMgr, void* closure );
+void dutils_free( XW_DUtilCtxt** ducp );
+
+#endif
diff --git a/xwords4/linux/linuxmain.c b/xwords4/linux/linuxmain.c
index 53ab3ea08..845e6b41d 100644
--- a/xwords4/linux/linuxmain.c
+++ b/xwords4/linux/linuxmain.c
@@ -57,7 +57,9 @@
#include "main.h"
#include "gamesdb.h"
#include "linuxdict.h"
+#include "lindutil.h"
#include "relaycon.h"
+#include "smsproto.h"
#ifdef PLATFORM_NCURSES
# include "cursesmain.h"
#endif
@@ -95,7 +97,7 @@ file_exists( const char* fileName )
} /* file_exists */
XWStreamCtxt*
-streamFromFile( CommonGlobals* cGlobals, char* name, void* closure )
+streamFromFile( CommonGlobals* cGlobals, char* name )
{
XP_U8* buf;
struct stat statBuf;
@@ -110,9 +112,8 @@ streamFromFile( CommonGlobals* cGlobals, char* name, void* closure )
}
fclose( f );
- stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- cGlobals->params->vtMgr,
- closure, CHANNEL_NONE, NULL );
+ stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ cGlobals->params->vtMgr );
stream_putBytes( stream, buf, statBuf.st_size );
free( buf );
@@ -121,7 +122,7 @@ streamFromFile( CommonGlobals* cGlobals, char* name, void* closure )
#ifdef USE_SQLITE
XWStreamCtxt*
-streamFromDB( CommonGlobals* cGlobals, void* closure )
+streamFromDB( CommonGlobals* cGlobals )
{
LOG_FUNC();
XWStreamCtxt* stream = NULL;
@@ -139,9 +140,8 @@ streamFromDB( CommonGlobals* cGlobals, void* closure )
XP_U8 buf[size];
res = sqlite3_blob_read( ppBlob, buf, size, 0 );
if ( SQLITE_OK == res ) {
- stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- params->vtMgr,
- closure, CHANNEL_NONE, NULL );
+ stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ params->vtMgr );
stream_putBytes( stream, buf, size );
}
}
@@ -236,7 +236,7 @@ requestMsgsIdle( gpointer data )
{
CommonGlobals* cGlobals = (CommonGlobals*)data;
XP_UCHAR devIDBuf[64] = {0};
- db_fetch( cGlobals->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) );
+ db_fetch_safe( cGlobals->params->pDb, KEY_RDEVID, devIDBuf, sizeof(devIDBuf) );
if ( '\0' != devIDBuf[0] ) {
relaycon_requestMsgs( cGlobals->params, devIDBuf );
} else {
@@ -352,8 +352,9 @@ void
saveGame( CommonGlobals* cGlobals )
{
LOG_FUNC();
+ sqlite3* pDb = cGlobals->params->pDb;
if ( !!cGlobals->game.model &&
- (!!cGlobals->params->fileName || !!cGlobals->pDb) ) {
+ (!!cGlobals->params->fileName || !!pDb) ) {
XP_Bool doSave = XP_TRUE;
XP_Bool newGame = !file_exists( cGlobals->params->fileName )
|| -1 == cGlobals->selRow;
@@ -364,13 +365,12 @@ saveGame( CommonGlobals* cGlobals )
}
if ( doSave ) {
- if ( !!cGlobals->pDb ) {
+ if ( !!pDb ) {
summarize( cGlobals );
}
XWStreamCtxt* outStream;
- MemStreamCloseCallback onClose = !!cGlobals->pDb?
- writeToDB : writeToFile;
+ MemStreamCloseCallback onClose = !!pDb? writeToDB : writeToFile;
outStream =
mem_stream_make_sized( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr,
@@ -398,8 +398,7 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs,
{
LOG_FUNC();
LaunchParams* params = cGlobals->params;
- XWStreamCtxt* stream =
- streamFromFile( cGlobals, params->fileName, cGlobals );
+ XWStreamCtxt* stream = streamFromFile( cGlobals, params->fileName );
#ifdef DEBUG
XP_Bool opened =
@@ -431,9 +430,8 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs,
XP_LOGF( "%s: 2: unexpected nRead: %zd", __func__, nRead );
break;
}
- stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- params->vtMgr, cGlobals, CHANNEL_NONE,
- NULL );
+ stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ params->vtMgr );
stream_putBytes( stream, buf, len );
(void)processMessage( cGlobals, stream, NULL, XP_TRUE );
stream_destroy( stream );
@@ -447,8 +445,7 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs )
{
LOG_FUNC();
LaunchParams* params = cGlobals->params;
- XWStreamCtxt* stream =
- streamFromFile( cGlobals, params->fileName, cGlobals );
+ XWStreamCtxt* stream = streamFromFile( cGlobals, params->fileName );
#ifdef DEBUG
XP_Bool opened =
@@ -483,9 +480,8 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs )
XP_LOGF( "%s: 2: unexpected nRead: %zd", __func__, nRead );
break;
}
- stream = mem_stream_make( MPPARM(cGlobals->util->mpool)
- params->vtMgr, cGlobals, CHANNEL_NONE,
- NULL );
+ stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ params->vtMgr );
stream_putBytes( stream, buf, len );
(void)processMessage( cGlobals, stream, NULL, XP_TRUE );
stream_destroy( stream );
@@ -643,6 +639,7 @@ typedef enum {
,CMD_DROPSENDRELAY
,CMD_DROPRCVRELAY
,CMD_DROPSENDSMS
+ ,CMD_SMSFAILPCT
,CMD_DROPRCVSMS
,CMD_FORCECHANNEL
@@ -656,6 +653,7 @@ typedef enum {
#endif
#ifdef XWFEATURE_SMS
,CMD_SMSNUMBER /* SMS phone number */
+ ,CMD_SERVER_SMSNUMBER
,CMD_SMSPORT
#endif
#ifdef XWFEATURE_RELAY
@@ -684,6 +682,7 @@ typedef enum {
,CMD_NHIDDENROWS
#endif
,CMD_ASKTIME
+ ,CMD_SMSTEST
,N_CMDS
} XwLinuxCmd;
@@ -764,6 +763,7 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_DROPSENDRELAY, false, "drop-send-relay", "start new games with relay send disabled" }
,{ CMD_DROPRCVRELAY, false, "drop-receive-relay", "start new games with relay receive disabled" }
,{ CMD_DROPSENDSMS, false, "drop-send-sms", "start new games with sms send disabled" }
+ ,{ CMD_SMSFAILPCT, true, "sms-fail-pct", "percent of sms sends, randomly chosen, never arrive" }
,{ CMD_DROPRCVSMS, false, "drop-receive-sms", "start new games with sms receive disabled" }
,{ CMD_FORCECHANNEL, true, "force-channel", "force (clients) to use this hostid/channel" }
@@ -779,6 +779,7 @@ static CmdInfoRec CmdInfoRecs[] = {
#endif
#ifdef XWFEATURE_SMS
,{ CMD_SMSNUMBER, true, "sms-number", "this devices's sms phone number" }
+ ,{ CMD_SERVER_SMSNUMBER, true, "server-sms-number", "number this device should connect to" }
,{ CMD_SMSPORT, true, "sms-port", "this devices's sms port" }
#endif
#ifdef XWFEATURE_RELAY
@@ -811,6 +812,7 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_ASKTIME, true, "ask-timeout",
"Wait this many ms before cancelling dialog (default 500 ms; 0 means forever)" }
#endif
+ ,{ CMD_SMSTEST, false, "run-sms-test", "Run smsproto_runTests() on startup"}
};
static struct option*
@@ -910,7 +912,7 @@ linux_getDevIDRelay( LaunchParams* params )
{
XP_U32 result = 0;
gchar buf[32];
- if ( db_fetch( params->pDb, KEY_RDEVID, buf, sizeof(buf) ) ) {
+ if ( db_fetch_safe( params->pDb, KEY_RDEVID, buf, sizeof(buf) ) ) {
sscanf( buf, "%X", &result );
XP_LOGF( "%s(): %s => %x", __func__, buf, result );
}
@@ -918,6 +920,12 @@ linux_getDevIDRelay( LaunchParams* params )
return result;
}
+XP_U32
+linux_getCurSeconds()
+{
+ return (XP_U32)time(NULL);//tv.tv_sec;
+}
+
const XP_UCHAR*
linux_getDevID( LaunchParams* params, DevIDType* typ )
{
@@ -928,12 +936,12 @@ linux_getDevID( LaunchParams* params, DevIDType* typ )
if ( !!params->lDevID ) {
result = params->lDevID;
*typ = ID_TYPE_LINUX;
- } else if ( db_fetch( params->pDb, KEY_RDEVID, params->devIDStore,
- sizeof(params->devIDStore) ) ) {
+ } else if ( db_fetch_safe( params->pDb, KEY_RDEVID, params->devIDStore,
+ sizeof(params->devIDStore) ) ) {
result = params->devIDStore;
*typ = '\0' == result[0] ? ID_TYPE_ANON : ID_TYPE_RELAY;
- } else if ( db_fetch( params->pDb, KEY_LDEVID, params->devIDStore,
- sizeof(params->devIDStore) ) ) {
+ } else if ( db_fetch_safe( params->pDb, KEY_LDEVID, params->devIDStore,
+ sizeof(params->devIDStore) ) ) {
result = params->devIDStore;
*typ = '\0' == result[0] ? ID_TYPE_ANON : ID_TYPE_LINUX;
} else if ( !params->noAnonDevid ) {
@@ -947,8 +955,8 @@ void
linux_doInitialReg( LaunchParams* params, XP_Bool idIsNew )
{
gchar rDevIDBuf[64];
- if ( !db_fetch( params->pDb, KEY_RDEVID, rDevIDBuf,
- sizeof(rDevIDBuf) ) ) {
+ if ( !db_fetch_safe( params->pDb, KEY_RDEVID, rDevIDBuf,
+ sizeof(rDevIDBuf) ) ) {
rDevIDBuf[0] = '\0';
}
DevIDType typ = ID_TYPE_NONE;
@@ -964,7 +972,7 @@ linux_setupDevidParams( LaunchParams* params )
{
XP_Bool idIsNew = XP_TRUE;
gchar oldLDevID[256];
- if ( db_fetch( params->pDb, KEY_LDEVID, oldLDevID, sizeof(oldLDevID) )
+ if ( db_fetch_safe( params->pDb, KEY_LDEVID, oldLDevID, sizeof(oldLDevID) )
&& (!params->lDevID || 0 == strcmp( oldLDevID, params->lDevID )) ) {
idIsNew = XP_FALSE;
} else {
@@ -1299,9 +1307,15 @@ linux_send( const XP_U8* buf, XP_U16 buflen, const XP_UCHAR* XP_UNUSED_DBG(msgNo
comms_getAddr( cGlobals->game.comms, &addr );
addrRec = &addr;
}
+
+ // use serverphone if I'm a client, else hope one's provided (this is
+ // a reply)
+ const XP_UCHAR* phone = cGlobals->params->connInfo.sms.serverPhone;
+ if ( !phone ) {
+ phone = addrRec->u.sms.phone;
+ }
nSent = linux_sms_send( cGlobals->params, buf, buflen,
- addrRec->u.sms.phone, addrRec->u.sms.port,
- gameID );
+ phone, addrRec->u.sms.port, gameID );
}
break;
#endif
@@ -1434,9 +1448,8 @@ stream_from_msgbuf( CommonGlobals* globals, const unsigned char* bufPtr,
XP_U16 nBytes )
{
XWStreamCtxt* result;
- result = mem_stream_make( MPPARM(globals->util->mpool)
- globals->params->vtMgr,
- globals, CHANNEL_NONE, NULL );
+ result = mem_stream_make_raw( MPPARM(globals->util->mpool)
+ globals->params->vtMgr );
stream_putBytes( result, bufPtr, nBytes );
return result;
@@ -1950,7 +1963,7 @@ initFromParams( CommonGlobals* cGlobals, LaunchParams* params )
#ifdef XWFEATURE_SMS
case COMMS_CONN_SMS:
addr_addType( addr, COMMS_CONN_SMS );
- XP_STRNCPY( addr->u.sms.phone, params->connInfo.sms.phone,
+ XP_STRNCPY( addr->u.sms.phone, params->connInfo.sms.myPhone,
sizeof(addr->u.sms.phone) - 1 );
addr->u.sms.port = params->connInfo.sms.port;
break;
@@ -2001,12 +2014,15 @@ initParams( LaunchParams* params )
/* params->util->vtable->m_util_addrChange = linux_util_addrChange; */
/* params->util->vtable->m_util_setIsServer = linux_util_setIsServer; */
#endif
+
+ params->dutil = dutils_init( MPPARM(params->mpool) params->vtMgr, params );
}
static void
freeParams( LaunchParams* params )
{
vtmgr_destroy( MPPARM(params->mpool) params->vtMgr );
+ dutils_free( ¶ms->dutil );
dmgr_destroy( params->dictMgr );
gi_disposePlayerInfo( MPPARM(params->mpool) ¶ms->pgi );
@@ -2272,7 +2288,11 @@ main( int argc, char** argv )
break;
#ifdef XWFEATURE_SMS
case CMD_SMSNUMBER: /* SMS phone number */
- mainParams.connInfo.sms.phone = optarg;
+ mainParams.connInfo.sms.myPhone = optarg;
+ addr_addType( &mainParams.addr, COMMS_CONN_SMS );
+ break;
+ case CMD_SERVER_SMSNUMBER:
+ mainParams.connInfo.sms.serverPhone = optarg;
addr_addType( &mainParams.addr, COMMS_CONN_SMS );
break;
case CMD_SMSPORT:
@@ -2427,6 +2447,10 @@ main( int argc, char** argv )
case CMD_DROPSENDSMS:
mainParams.commsDisableds[COMMS_CONN_SMS][1] = XP_TRUE;
break;
+ case CMD_SMSFAILPCT:
+ mainParams.smsSendFailPct = atoi(optarg);
+ XP_ASSERT( mainParams.smsSendFailPct >= 0 && mainParams.smsSendFailPct <= 100 );
+ break;
case CMD_DROPRCVSMS:
mainParams.commsDisableds[COMMS_CONN_SMS][0] = XP_TRUE;
break;
@@ -2488,6 +2512,10 @@ main( int argc, char** argv )
mainParams.askTimeout = atoi(optarg);
break;
#endif
+ case CMD_SMSTEST:
+ mainParams.runSMSTest = XP_TRUE;
+ break;
+
default:
done = true;
break;
diff --git a/xwords4/linux/linuxmain.h b/xwords4/linux/linuxmain.h
index a3622257e..e03fed979 100644
--- a/xwords4/linux/linuxmain.h
+++ b/xwords4/linux/linuxmain.h
@@ -64,9 +64,8 @@ void sendOnClose( XWStreamCtxt* stream, void* closure );
void catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter );
XP_Bool file_exists( const char* fileName );
-XWStreamCtxt* streamFromFile( CommonGlobals* cGlobals, char* name,
- void* closure );
-XWStreamCtxt* streamFromDB( CommonGlobals* cGlobals, void* closure );
+XWStreamCtxt* streamFromFile( CommonGlobals* cGlobals, char* name );
+XWStreamCtxt* streamFromDB( CommonGlobals* cGlobals );
void writeToFile( XWStreamCtxt* stream, void* closure );
XP_Bool getDictPath( const LaunchParams *params, const char* name,
char* result, int resultLen );
diff --git a/xwords4/linux/linuxsms.c b/xwords4/linux/linuxsms.c
index 3958a36c9..8d0693129 100644
--- a/xwords4/linux/linuxsms.c
+++ b/xwords4/linux/linuxsms.c
@@ -1,6 +1,6 @@
/* -*- compile-command: "make -j MEMDEBUG=TRUE";-*- */
/*
- * Copyright 2006-2009 by Eric House (xwords@eehouse.org). All rights
+ * Copyright 2006 - 2018 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@@ -20,15 +20,18 @@
#ifdef XWFEATURE_SMS
+#include
#include
#include
#include
#include
#include
-#include
#include "linuxsms.h"
+#include "linuxutl.h"
#include "strutils.h"
+#include "smsproto.h"
+#include "linuxmain.h"
#define SMS_DIR "/tmp/xw_sms"
#define LOCK_FILE ".lock"
@@ -54,26 +57,34 @@
#define ADDR_FMT "from: %s %d\n"
-static void
-makeQueuePath( const XP_UCHAR* phone, XP_U16 port,
- XP_UCHAR* path, XP_U16 pathlen )
-{
- XP_ASSERT( 0 != port );
- snprintf( path, pathlen, "%s/%s_%d", SMS_DIR, phone, port );
-}
-
typedef struct _LinSMSData {
- int fd, wd;
XP_UCHAR myQueue[256];
XP_U16 myPort;
FILE* lock;
- XP_U16 count;
const gchar* myPhone;
const SMSProcs* procs;
void* procClosure;
+ SMSProto* protoState;
} LinSMSData;
+static void doSend( LaunchParams* params, const XP_U8* buf,
+ XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port,
+ XP_U32 gameID );
+static gboolean retrySend( gpointer data );
+static void sendOrRetry( LaunchParams* params, SMSMsgArray* arr, XP_U16 waitSecs,
+ const XP_UCHAR* phone, XP_U16 port, XP_U32 gameID );
+static gint check_for_files( gpointer data );
+static gint check_for_files_once( gpointer data );
+
+static void
+formatQueuePath( const XP_UCHAR* phone, XP_U16 port, XP_UCHAR* path,
+ XP_U16 pathlen )
+{
+ XP_ASSERT( 0 != port );
+ snprintf( path, pathlen, "%s/%s_%d", SMS_DIR, phone, port );
+}
+
typedef enum { NONE, INVITE, DATA, DEATH, ACK, } SMS_CMD;
#define SMS_PROTO_VERSION 0
@@ -103,58 +114,74 @@ unlock_queue( LinSMSData* storage )
}
static XP_S16
-send_sms( LinSMSData* storage, XWStreamCtxt* stream,
- const XP_UCHAR* phone, XP_U16 port )
+write_fake_sms( LaunchParams* params, XWStreamCtxt* stream,
+ const XP_UCHAR* phone, XP_U16 port )
{
- const XP_U8* buf = stream_getPtr( stream );
- XP_U16 buflen = stream_getSize( stream );
+ XP_S16 nSent;
+ XP_U16 pct = XP_RANDOM() % 100;
+ XP_Bool skipWrite = pct < params->smsSendFailPct;
- XP_S16 nSent = -1;
- XP_ASSERT( !!storage );
- char path[256];
+ if ( skipWrite ) {
+ nSent = stream_getSize( stream );
+ XP_LOGF( "%s(): dropping sms msg of len %d to phone %s", __func__,
+ nSent, phone );
+ } else {
+ LinSMSData* storage = getStorage( params );
+ const XP_U8* buf = stream_getPtr( stream );
+ XP_U16 buflen = stream_getSize( stream );
+ XP_LOGF( "%s(phone=%s, port=%d, len=%d)", __func__, phone,
+ port, buflen );
- lock_queue( storage );
+ XP_ASSERT( !!storage );
+ char path[256];
+
+ lock_queue( storage );
#ifdef DEBUG
- gchar* str64 = g_base64_encode( buf, buflen );
+ gchar* str64 = g_base64_encode( buf, buflen );
#endif
- XP_U16 count = ++storage->count;
- makeQueuePath( phone, port, path, sizeof(path) );
- XP_LOGF( "%s: writing msg %d to %s", __func__, count, path );
- g_mkdir_with_parents( path, 0777 ); /* just in case */
- int len = strlen( path );
- snprintf( &path[len], sizeof(path)-len, "/%d", count );
+ formatQueuePath( phone, port, path, sizeof(path) );
- XP_UCHAR sms[buflen*2]; /* more like (buflen*4/3) */
- XP_U16 smslen = sizeof(sms);
- binToSms( sms, &smslen, buf, buflen );
- XP_ASSERT( smslen == strlen(sms) );
+ /* Random-number-based name is fine, as we pick based on age. */
+ int rint = makeRandomInt();
+ g_mkdir_with_parents( path, 0777 ); /* just in case */
+ int len = strlen( path );
+ snprintf( &path[len], sizeof(path)-len, "/%u", rint );
+
+ XP_UCHAR sms[buflen*2]; /* more like (buflen*4/3) */
+ XP_U16 smslen = sizeof(sms);
+ binToSms( sms, &smslen, buf, buflen );
+ XP_ASSERT( smslen == strlen(sms) );
+ XP_LOGF( "%s: writing msg to %s", __func__, path );
#ifdef DEBUG
- XP_ASSERT( !strcmp( str64, sms ) );
- g_free( str64 );
+ XP_ASSERT( !strcmp( str64, sms ) );
+ g_free( str64 );
- XP_U8 testout[buflen];
- XP_U16 lenout = sizeof( testout );
- XP_ASSERT( smsToBin( testout, &lenout, sms, smslen ) );
- XP_ASSERT( lenout == buflen );
- XP_ASSERT( XP_MEMCMP( testout, buf, smslen ) );
+ XP_U8 testout[buflen];
+ XP_U16 lenout = sizeof( testout );
+ XP_ASSERT( smsToBin( testout, &lenout, sms, smslen ) );
+ XP_ASSERT( lenout == buflen );
+ // valgrind doesn't like this; punting on figuring out
+ // XP_ASSERT( XP_MEMCMP( testout, buf, smslen ) );
#endif
- FILE* fp = fopen( path, "w" );
- XP_ASSERT( !!fp );
- (void)fprintf( fp, ADDR_FMT, storage->myPhone, storage->myPort );
- (void)fprintf( fp, "%s\n", sms );
- fclose( fp );
- sync();
+ FILE* fp = fopen( path, "w" );
+ XP_ASSERT( !!fp );
+ (void)fprintf( fp, ADDR_FMT, storage->myPhone, storage->myPort );
+ (void)fprintf( fp, "%s\n", sms );
+ fclose( fp );
+ sync();
- unlock_queue( storage );
+ unlock_queue( storage );
- nSent = buflen;
+ nSent = buflen;
+ LOG_RETURNF( "%d", nSent );
+ }
return nSent;
-} /* linux_sms_send */
+} /* write_fake_sms */
static XP_S16
decodeAndDelete( LinSMSData* storage, const gchar* name,
@@ -185,6 +212,7 @@ decodeAndDelete( LinSMSData* storage, const gchar* name,
*strstr(eol, "\n" ) = '\0';
XP_U16 inlen = strlen(eol); /* skip \n */
+ XP_LOGF( "%s(): decoding message from file %s", __func__, name );
XP_U8 out[inlen];
XP_U16 outlen = sizeof(out);
XP_Bool valid = smsToBin( out, &outlen, eol, inlen );
@@ -194,7 +222,8 @@ decodeAndDelete( LinSMSData* storage, const gchar* name,
nRead = outlen;
addr_setType( addr, COMMS_CONN_SMS );
XP_STRNCPY( addr->u.sms.phone, phone, sizeof(addr->u.sms.phone) );
- XP_LOGF( "%s: message came from phone: %s, port: %d", __func__, phone, port );
+ XP_LOGF( "%s: message came from phone: %s, port: %d", __func__,
+ phone, port );
addr->u.sms.port = port;
}
} else {
@@ -226,7 +255,7 @@ dispatch_invite( LinSMSData* storage, XP_U16 XP_UNUSED(proto),
addrFromStream( addr, stream );
(*storage->procs->inviteReceived)( storage->procClosure, gameName,
- gameID, dictLang, dictName, nPlayers,
+ gameID, dictLang, dictName, nPlayers,
nMissing, forceChannel, addr );
}
@@ -234,13 +263,24 @@ static void
dispatch_data( LinSMSData* storage, XP_U16 XP_UNUSED(proto),
XWStreamCtxt* stream, const CommsAddrRec* addr )
{
+ LOG_FUNC();
+
XP_U32 gameID = stream_getU32( stream );
XP_U16 len = stream_getSize( stream );
XP_U8 data[len];
stream_getBytes( stream, data, len );
-
- (*storage->procs->msgReceived)( storage->procClosure, addr, gameID,
- data, len );
+
+ const XP_UCHAR* fromPhone = addr->u.sms.phone;
+ SMSMsgArray* arr = smsproto_prepInbound( storage->protoState, fromPhone,
+ data, len );
+ if ( NULL != arr ) {
+ for ( XP_U16 ii = 0; ii < arr->nMsgs; ++ii ) {
+ SMSMsg* msg = &arr->msgs[ii];
+ (*storage->procs->msgReceived)( storage->procClosure, addr, gameID,
+ msg->data, msg->len );
+ }
+ smsproto_freeMsgArray( storage->protoState, arr );
+ }
}
static void
@@ -248,9 +288,8 @@ parseAndDispatch( LaunchParams* params, uint8_t* buf, int len,
CommsAddrRec* addr )
{
LinSMSData* storage = getStorage( params );
- XWStreamCtxt* stream = mem_stream_make( MPPARM(params->mpool)
- params->vtMgr,
- NULL, CHANNEL_NONE, NULL );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
+ params->vtMgr );
stream_setVersion( stream, CUR_STREAM_VERS );
stream_putBytes( stream, buf, len );
@@ -274,66 +313,6 @@ parseAndDispatch( LaunchParams* params, uint8_t* buf, int len,
stream_destroy( stream );
}
-static gboolean
-sms_receive( GIOChannel *source, GIOCondition XP_UNUSED_DBG(condition), gpointer data )
-{
- LOG_FUNC();
- XP_ASSERT( 0 != (G_IO_IN & condition) );
- LaunchParams* params = (LaunchParams*)data;
- XP_ASSERT( !!params->smsStorage );
- LinSMSData* storage = getStorage( params );
-
- int socket = g_io_channel_unix_get_fd( source );
- XP_ASSERT( socket == storage->fd );
-
- lock_queue( storage );
-
- /* read required or we'll just get the event again. But we don't care
- about the result or the buffer contents. */
- XP_U8 buffer[sizeof(struct inotify_event) + 16];
- if ( 0 > read( socket, buffer, sizeof(buffer) ) ) {
- XP_LOGF( "%s: discarding inotify buffer", __func__ );
- }
- for ( ; ; ) {
- XP_S16 nRead = -1;
- char shortest[256] = { '\0' };
- GDir* dir = g_dir_open( storage->myQueue, 0, NULL );
- XP_LOGF( "%s: opening queue %s", __func__, storage->myQueue );
- for ( ; ; ) {
- const gchar* name = g_dir_read_name( dir );
- if ( NULL == name ) {
- break;
- } else if ( 0 == strcmp( name, LOCK_FILE ) ) {
- continue;
- }
- if ( !shortest[0] || 0 < strcmp( shortest, name ) ) {
- snprintf( shortest, sizeof(shortest), "%s", name );
- }
- }
- g_dir_close( dir );
-
- uint8_t buf[256];
- CommsAddrRec fromAddr = {0};
- if ( !!shortest[0] ) {
- XP_LOGF( "%s: decoding message %s", __func__, shortest );
- nRead = decodeAndDelete( storage, shortest, buf,
- sizeof(buf), &fromAddr );
- } else {
- XP_LOGF( "%s: never found shortest", __func__ );
- }
-
- unlock_queue( storage );
-
- if ( 0 < nRead ) {
- parseAndDispatch( params, buf, nRead, &fromAddr );
- lock_queue( storage );
- } else {
- break;
- }
- }
- return TRUE;
-} /* sms_receive */
-
void
linux_sms_init( LaunchParams* params, const gchar* myPhone, XP_U16 myPort,
const SMSProcs* procs, void* procClosure )
@@ -346,18 +325,19 @@ linux_sms_init( LaunchParams* params, const gchar* myPhone, XP_U16 myPort,
storage->myPort = myPort;
storage->procs = procs;
storage->procClosure = procClosure;
+ storage->protoState = smsproto_init( MPPARM(params->mpool) params->dutil );
- makeQueuePath( myPhone, myPort, storage->myQueue, sizeof(storage->myQueue) );
+ formatQueuePath( myPhone, myPort, storage->myQueue, sizeof(storage->myQueue) );
XP_LOGF( "%s: my queue: %s", __func__, storage->myQueue );
storage->myPort = params->connInfo.sms.port;
(void)g_mkdir_with_parents( storage->myQueue, 0777 );
- int fd = inotify_init();
- storage->fd = fd;
- storage->wd = inotify_add_watch( fd, storage->myQueue, IN_MODIFY );
-
- (*procs->socketAdded)( params, fd, sms_receive );
+ /* Look for preexisting or new files every half second. Easier than
+ inotify, especially when you add the need to handle files written while
+ not running. */
+ (void)g_idle_add( check_for_files_once, params );
+ (void)g_timeout_add( 500, check_for_files, params );
} /* linux_sms_init */
void
@@ -368,8 +348,7 @@ linux_sms_invite( LaunchParams* params, const CurGameInfo* gi,
{
LOG_FUNC();
XWStreamCtxt* stream;
- stream = mem_stream_make( MPPARM(params->mpool) params->vtMgr,
- NULL, CHANNEL_NONE, NULL );
+ stream = mem_stream_make_raw( MPPARM(params->mpool) params->vtMgr );
writeHeader( stream, INVITE );
stream_putU32( stream, gi->gameID );
stringToStream( stream, gameName );
@@ -381,8 +360,7 @@ linux_sms_invite( LaunchParams* params, const CurGameInfo* gi,
addrToStream( stream, addr );
- LinSMSData* storage = getStorage( params );
- send_sms( storage, stream, toPhone, toPort );
+ write_fake_sms( params, stream, toPhone, toPort );
stream_destroy( stream );
}
@@ -391,26 +369,161 @@ XP_S16
linux_sms_send( LaunchParams* params, const XP_U8* buf,
XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port,
XP_U32 gameID )
+{
+ LinSMSData* storage = getStorage( params );
+ XP_U16 waitSecs;
+ SMSMsgArray* arr = smsproto_prepOutbound( storage->protoState, buf, buflen,
+ phone, XP_TRUE, &waitSecs );
+ sendOrRetry( params, arr, waitSecs, phone, port, gameID );
+ return buflen;
+}
+
+static void
+doSend( LaunchParams* params, const XP_U8* buf,
+ XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port,
+ XP_U32 gameID )
{
XP_LOGF( "%s(len=%d)", __func__, buflen );
- XWStreamCtxt* stream = mem_stream_make( MPPARM(params->mpool) params->vtMgr,
- NULL, CHANNEL_NONE, NULL );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
+ params->vtMgr );
writeHeader( stream, DATA );
stream_putU32( stream, gameID );
stream_putBytes( stream, buf, buflen );
- LinSMSData* storage = getStorage( params );
- if ( 0 >= send_sms( storage, stream, phone, port ) ) {
- buflen = -1;
- }
+ (void)write_fake_sms( params, stream, phone, port );
stream_destroy( stream );
-
- return buflen;
}
+typedef struct _RetryClosure {
+ LaunchParams* params;
+ XP_U16 port;
+ XP_U32 gameID;
+ XP_UCHAR phone[32];
+} RetryClosure;
+
+static void
+sendOrRetry( LaunchParams* params, SMSMsgArray* arr, XP_U16 waitSecs,
+ const XP_UCHAR* phone, XP_U16 port, XP_U32 gameID )
+{
+ if ( !!arr ) {
+ for ( XP_U16 ii = 0; ii < arr->nMsgs; ++ii ) {
+ SMSMsg* msg = &arr->msgs[ii];
+ doSend( params, msg->data, msg->len, phone, port, gameID );
+ }
+
+ LinSMSData* storage = getStorage( params );
+ smsproto_freeMsgArray( storage->protoState, arr );
+ } else if ( waitSecs > 0 ) {
+ RetryClosure* closure = (RetryClosure*)XP_CALLOC( params->mpool,
+ sizeof(*closure) );
+ closure->params = params;
+ XP_STRCAT( closure->phone, phone );
+ closure->port = port;
+ closure->gameID = gameID;
+ g_timeout_add_seconds( 5, retrySend, closure );
+ }
+}
+
+static gboolean
+retrySend( gpointer data )
+{
+ RetryClosure* closure = (RetryClosure*)data;
+ LinSMSData* storage = getStorage( closure->params );
+ XP_U16 waitSecs;
+ SMSMsgArray* arr = smsproto_prepOutbound( storage->protoState, NULL, 0,
+ closure->phone, XP_TRUE, &waitSecs );
+ sendOrRetry( closure->params, arr, waitSecs, closure->phone,
+ closure->port, closure->gameID );
+ XP_FREEP( closure->params->mpool, &closure );
+ return FALSE;
+}
+
+static gint
+check_for_files( gpointer data )
+{
+ check_for_files_once( data );
+ return TRUE;
+}
+
+static gint
+check_for_files_once( gpointer data )
+{
+ LOG_FUNC();
+ LaunchParams* params = (LaunchParams*)data;
+ LinSMSData* storage = getStorage( params );
+
+ for ( ; ; ) {
+ lock_queue( storage );
+
+ char oldestFile[256] = { '\0' };
+ struct timespec oldestModTime;
+
+ GDir* dir = g_dir_open( storage->myQueue, 0, NULL );
+ XP_LOGF( "%s: opening queue %s", __func__, storage->myQueue );
+ for ( ; ; ) {
+ const gchar* name = g_dir_read_name( dir );
+ if ( NULL == name ) {
+ break;
+ } else if ( 0 == strcmp( name, LOCK_FILE ) ) {
+ continue;
+ }
+
+ /* We want the oldest file first. Timestamp comes from stat(). */
+ struct stat statbuf;
+ char fullPath[500];
+ snprintf( fullPath, sizeof(fullPath), "%s/%s", storage->myQueue, name );
+ int err = stat( fullPath, &statbuf );
+ if ( err != 0 ) {
+ XP_LOGF( "%s(); %d from stat (error: %s)", __func__,
+ err, strerror(errno) );
+ XP_ASSERT( 0 );
+ } else {
+ XP_Bool replace = !oldestFile[0]; /* always replace empty/unset :-) */
+ if ( !replace ) {
+ if (statbuf.st_mtim.tv_sec == oldestModTime.tv_sec ) {
+ replace = statbuf.st_mtim.tv_nsec < oldestModTime.tv_nsec;
+ } else {
+ replace = statbuf.st_mtim.tv_sec < oldestModTime.tv_sec;
+ }
+ }
+
+ if ( replace ) {
+ oldestModTime = statbuf.st_mtim;
+ if ( !!oldestFile[0] ) {
+ XP_LOGF( "%s(): replacing %s with older %s", __func__, oldestFile, name );
+ }
+ snprintf( oldestFile, sizeof(oldestFile), "%s", name );
+ }
+ }
+ }
+ g_dir_close( dir );
+
+ uint8_t buf[256];
+ CommsAddrRec fromAddr = {0};
+ XP_S16 nRead = -1;
+ if ( !!oldestFile[0] ) {
+ nRead = decodeAndDelete( storage, oldestFile, buf,
+ sizeof(buf), &fromAddr );
+ } else {
+ XP_LOGF( "%s: no file found", __func__ );
+ }
+
+ unlock_queue( storage );
+
+ if ( 0 >= nRead ) {
+ break;
+ }
+
+ parseAndDispatch( params, buf, nRead, &fromAddr );
+ }
+ return FALSE;
+} /* check_for_files_once */
+
void
linux_sms_cleanup( LaunchParams* params )
{
+ LinSMSData* storage = getStorage( params );
+ smsproto_free( storage->protoState );
XP_FREEP( params->mpool, ¶ms->smsStorage );
}
diff --git a/xwords4/linux/linuxutl.c b/xwords4/linux/linuxutl.c
index b596f9cdf..44504b614 100644
--- a/xwords4/linux/linuxutl.c
+++ b/xwords4/linux/linuxutl.c
@@ -266,151 +266,13 @@ linux_util_getSquareBonus( XW_UtilCtxt* uc, XP_U16 nCols,
return result;
} /* linux_util_getSquareBonus */
-static XP_U32
-linux_util_getCurSeconds( XW_UtilCtxt* XP_UNUSED(uc) )
-{
- return (XP_U32)time(NULL);//tv.tv_sec;
-} /* gtk_util_getCurSeconds */
-
-static const XP_UCHAR*
-linux_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 code )
-{
- switch( code ) {
- case STRD_REMAINING_TILES_ADD:
- return (XP_UCHAR*)"+ %d [all remaining tiles]";
- case STRD_UNUSED_TILES_SUB:
- return (XP_UCHAR*)"- %d [unused tiles]";
- case STR_COMMIT_CONFIRM:
- return (XP_UCHAR*)"Are you sure you want to commit the current move?\n";
- case STRD_TURN_SCORE:
- return (XP_UCHAR*)"Score for turn: %d\n";
- case STR_BONUS_ALL:
- return (XP_UCHAR*)"Bonus for using all tiles: 50\n";
- case STR_LOCAL_NAME:
- return (XP_UCHAR*)"%s";
- case STR_NONLOCAL_NAME:
- return (XP_UCHAR*)"%s (remote)";
- case STRD_TIME_PENALTY_SUB:
- return (XP_UCHAR*)" - %d [time]";
- /* added.... */
- case STRD_CUMULATIVE_SCORE:
- return (XP_UCHAR*)"Cumulative score: %d\n";
- case STRS_TRAY_AT_START:
- return (XP_UCHAR*)"Tray at start: %s\n";
- case STRS_MOVE_DOWN:
- return (XP_UCHAR*)"move (from %s down)\n";
- case STRS_MOVE_ACROSS:
- return (XP_UCHAR*)"move (from %s across)\n";
- case STRS_NEW_TILES:
- return (XP_UCHAR*)"New tiles: %s\n";
- case STRSS_TRADED_FOR:
- return (XP_UCHAR*)"Traded %s for %s.";
- case STR_PASS:
- return (XP_UCHAR*)"pass\n";
- case STR_PHONY_REJECTED:
- return (XP_UCHAR*)"Illegal word in move; turn lost!\n";
-
- case STRD_ROBOT_TRADED:
- return (XP_UCHAR*)"%d tiles traded this turn.";
- case STR_ROBOT_MOVED:
- return (XP_UCHAR*)"The robot \"%s\" moved:\n";
- case STRS_REMOTE_MOVED:
- return (XP_UCHAR*)"Remote player \"%s\" moved:\n";
-
-#ifndef XWFEATURE_STANDALONE_ONLY
- case STR_LOCALPLAYERS:
- return (XP_UCHAR*)"Local players";
- case STR_REMOTE:
- return (XP_UCHAR*)"Remote";
-#endif
- case STR_TOTALPLAYERS:
- return (XP_UCHAR*)"Total players";
-
- case STRS_VALUES_HEADER:
- return (XP_UCHAR*)"%s counts/values:\n";
-
- case STRD_REMAINS_HEADER:
- return (XP_UCHAR*)"%d tiles left in pool.";
- case STRD_REMAINS_EXPL:
- return (XP_UCHAR*)"%d tiles left in pool and hidden trays:\n";
-
- case STRSD_RESIGNED:
- return "[Resigned] %s: %d";
- case STRSD_WINNER:
- return "[Winner] %s: %d";
- case STRDSD_PLACER:
- return "[#%d] %s: %d";
-
- default:
- return (XP_UCHAR*)"unknown code to linux_util_getUserString";
- }
-} /* linux_util_getUserString */
-
-static const XP_UCHAR*
-linux_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 code,
- XP_U16 XP_UNUSED(quantity) )
-{
- return linux_util_getUserString( uc, code );
-}
-
-#ifdef XWFEATURE_DEVID
-static const XP_UCHAR*
-linux_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ )
+static XW_DUtilCtxt*
+linux_util_getDevUtilCtxt( XW_UtilCtxt* uc )
{
CommonGlobals* cGlobals = (CommonGlobals*)uc->closure;
- return linux_getDevID( cGlobals->params, typ );
+ return cGlobals->params->dutil;
}
-static void
-linux_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ,
- const XP_UCHAR* idRelay )
-{
- /* Script discon_ok2.sh is grepping for these strings in logs, so don't
- change them! */
- CommonGlobals* cGlobals = (CommonGlobals*)uc->closure;
- switch( typ ) {
- case ID_TYPE_NONE: /* error case */
- XP_LOGF( "%s: id rejected", __func__ );
- cGlobals->params->lDevID = NULL;
- break;
- case ID_TYPE_RELAY:
- if ( !!cGlobals->pDb && 0 < strlen( idRelay ) ) {
- XP_LOGF( "%s: new id: %s", __func__, idRelay );
- db_store( cGlobals->pDb, KEY_RDEVID, idRelay );
- }
- break;
- default:
- XP_ASSERT(0);
- break;
- }
-}
-#endif
-
-#ifdef COMMS_CHECKSUM
-static XP_UCHAR*
-linux_util_md5sum( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len )
-{
- gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, ptr, len );
- XP_U16 sumlen = 1 + strlen( sum );
- XP_UCHAR* result = XP_MALLOC( uc->mpool, sumlen );
- XP_MEMCPY( result, sum, sumlen );
- g_free( sum );
- return result;
-}
-#endif
-
-#ifdef XWFEATURE_SMS
-static XP_Bool
-linux_util_phoneNumbersSame( XW_UtilCtxt* uc, const XP_UCHAR* p1,
- const XP_UCHAR* p2 )
-{
- XP_USE( uc );
- XP_Bool result = 0 == strcmp( p1, p2 );
- XP_LOGF( "%s(%s, %s) => %d", __func__, p1, p2, result );
- return result;
-}
-#endif
-
void
linux_util_vt_init( MPFORMAL XW_UtilCtxt* util )
{
@@ -421,19 +283,8 @@ linux_util_vt_init( MPFORMAL XW_UtilCtxt* util )
util->vtable->m_util_makeEmptyDict = linux_util_makeEmptyDict;
util->vtable->m_util_getSquareBonus = linux_util_getSquareBonus;
- util->vtable->m_util_getCurSeconds = linux_util_getCurSeconds;
- util->vtable->m_util_getUserString = linux_util_getUserString;
- util->vtable->m_util_getUserQuantityString = linux_util_getUserQuantityString;
-#ifdef XWFEATURE_DEVID
- util->vtable->m_util_getDevID = linux_util_getDevID;
- util->vtable->m_util_deviceRegistered = linux_util_deviceRegistered;
-#endif
-#ifdef COMMS_CHECKSUM
- util->vtable->m_util_md5sum = linux_util_md5sum;
-#endif
-#ifdef XWFEATURE_SMS
- util->vtable->m_util_phoneNumbersSame = linux_util_phoneNumbersSame;
-#endif
+
+ util->vtable->m_util_getDevUtilCtxt = linux_util_getDevUtilCtxt;
}
void
@@ -666,16 +517,6 @@ storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 msglen,
return inUse;
}
-XWStreamCtxt*
-make_simple_stream( CommonGlobals* cGlobals )
-{
- XWStreamCtxt* stream =
- mem_stream_make( MPPARM(cGlobals->util->mpool)
- cGlobals->params->vtMgr,
- cGlobals, CHANNEL_NONE, NULL );
- return stream;
-}
-
void
writeNoConnMsgs( CommonGlobals* cGlobals, int fd )
{
@@ -688,7 +529,8 @@ writeNoConnMsgs( CommonGlobals* cGlobals, int fd )
guint nMsgs = g_slist_length( list );
XP_ASSERT( 0 < nMsgs );
- XWStreamCtxt* stream = make_simple_stream( cGlobals );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
+ cGlobals->params->vtMgr );
stream_putU16( stream, 1 ); /* number of relayIDs */
stream_catString( stream, relayID );
stream_putU8( stream, '\n' );
diff --git a/xwords4/linux/linuxutl.h b/xwords4/linux/linuxutl.h
index 28d961672..afbaab254 100644
--- a/xwords4/linux/linuxutl.h
+++ b/xwords4/linux/linuxutl.h
@@ -48,7 +48,6 @@ void initNoConnStorage( CommonGlobals* cGlobals );
XP_Bool storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 len,
const XP_UCHAR* relayID );
void writeNoConnMsgs( CommonGlobals* cGlobals, int fd );
-XWStreamCtxt* make_simple_stream( CommonGlobals* cGlobals );
#ifdef STREAM_VERS_BIGBOARD
void setSquareBonuses( const CommonGlobals* cGlobals );
@@ -56,4 +55,6 @@ void setSquareBonuses( const CommonGlobals* cGlobals );
# define setSquareBonuses( cg )
#endif
+XP_U32 linux_getCurSeconds();
+
#endif
diff --git a/xwords4/linux/main.h b/xwords4/linux/main.h
index 5f90e6999..0ed10b6cf 100644
--- a/xwords4/linux/main.h
+++ b/xwords4/linux/main.h
@@ -32,6 +32,7 @@
#include "game.h"
#include "vtabmgr.h"
#include "dictmgr.h"
+#include "dutil.h"
typedef struct ServerInfo {
XP_U16 nRemotePlayers;
@@ -58,6 +59,7 @@ typedef struct LaunchParams {
char* dbName;
sqlite3* pDb; /* null unless opened */
XP_U16 saveFailPct;
+ XP_U16 smsSendFailPct;
const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS];
#ifdef USE_SQLITE
char* dbFileName;
@@ -77,6 +79,7 @@ typedef struct LaunchParams {
#endif
VTableMgr* vtMgr;
DictMgrCtxt* dictMgr;
+ XW_DUtilCtxt* dutil;
XP_U16 nLocalPlayers;
XP_U16 nHidden;
XP_U16 gameSeed;
@@ -106,6 +109,7 @@ typedef struct LaunchParams {
XP_Bool useCurses;
XP_Bool useUdp;
XP_Bool useHTTP;
+ XP_Bool runSMSTest;
XP_Bool noHTTPAuto;
XP_U16 splitPackets;
XP_U16 chatsInterval; /* 0 means disabled */
@@ -150,7 +154,8 @@ typedef struct LaunchParams {
#endif
#ifdef XWFEATURE_SMS
struct {
- const char* phone;
+ const char* myPhone;
+ const char* serverPhone;
int port;
} sms;
#endif
@@ -197,7 +202,6 @@ struct CommonGlobals {
XP_U16 lastStreamSize;
XP_U16 nMissing;
XP_Bool manualFinal; /* use asked for final scores */
- sqlite3* pDb;
sqlite3_int64 selRow;
SocketAddedFunc socketAdded;
diff --git a/xwords4/linux/relaycon.c b/xwords4/linux/relaycon.c
index b156b268a..a1cdfb179 100644
--- a/xwords4/linux/relaycon.c
+++ b/xwords4/linux/relaycon.c
@@ -151,7 +151,7 @@ write_callback(void *contents, size_t size, size_t nmemb, void* data)
ws->curSize = 1L;
}
- XP_LOGF( "%s(size=%ld, nmemb=%ld)", __func__, size, nmemb );
+ XP_LOGF( "%s(size=%zd, nmemb=%zd)", __func__, size, nmemb );
size_t oldLen = ws->curSize;
const size_t newLength = size * nmemb;
XP_ASSERT( (oldLen + newLength) > 0 );
@@ -332,9 +332,8 @@ relaycon_invite( LaunchParams* params, XP_U32 destDevID,
indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, destDevID );
}
- XWStreamCtxt* stream = mem_stream_make( MPPARM(params->mpool)
- params->vtMgr, params,
- CHANNEL_NONE, NULL );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
+ params->vtMgr );
nli_saveToStream( invit, stream );
XP_U16 len = stream_getSize( stream );
indx += writeShort( &tmpbuf[indx], sizeof(tmpbuf) - indx, len );
@@ -679,9 +678,8 @@ process( RelayConStorage* storage, XP_U8* buf, ssize_t nRead )
#endif
getNetLong( &ptr );
XP_U16 len = getNetShort( &ptr );
- XWStreamCtxt* stream = mem_stream_make( MPPARM(storage->params->mpool)
- storage->params->vtMgr, storage,
- CHANNEL_NONE, NULL );
+ XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(storage->params->mpool)
+ storage->params->vtMgr );
stream_putBytes( stream, ptr, len );
NetLaunchInfo invit;
XP_Bool success = nli_makeFromStream( &invit, stream );
diff --git a/xwords4/linux/scripts/discon_ok2.py b/xwords4/linux/scripts/discon_ok2.py
index 5acc8a5fe..92ef14af7 100755
--- a/xwords4/linux/scripts/discon_ok2.py
+++ b/xwords4/linux/scripts/discon_ok2.py
@@ -2,6 +2,7 @@
import re, os, sys, getopt, shutil, threading, requests, json, glob
import argparse, datetime, random, signal, subprocess, time
+from shutil import rmtree
# LOGDIR=./$(basename $0)_logs
# APP_NEW=""
@@ -155,14 +156,14 @@ def player_params(args, NLOCALS, NPLAYERS, NAME_INDX):
def logReaderStub(dev): dev.logReaderMain()
class Device():
- sConnnameMap = {}
sHasLDevIDMap = {}
sConnNamePat = re.compile('.*got_connect_cmd: connName: "([^"]+)".*$')
sGameOverPat = re.compile('.*\[unused tiles\].*')
- sTilesLeftPat = re.compile('.*pool_removeTiles: (\d+) tiles left in pool')
+ sTilesLeftPoolPat = re.compile('.*pool_removeTiles: (\d+) tiles left in pool')
+ sTilesLeftTrayPat = re.compile('.*player \d+ now has (\d+) tiles')
sRelayIDPat = re.compile('.*UPDATE games.*seed=(\d+),.*relayid=\'([^\']+)\'.*')
- def __init__(self, args, game, indx, app, params, room, db, log, nInGame):
+ def __init__(self, args, game, indx, app, params, room, peers, db, log, nInGame):
self.game = game
self.indx = indx
self.args = args
@@ -176,13 +177,15 @@ class Device():
self.nInGame = nInGame
# runtime stuff; init now
self.proc = None
- self.connname = None
+ self.peers = peers
self.devID = ''
self.launchCount = 0
self.allDone = False # when true, can be killed
- self.nTilesLeft = None
+ self.nTilesLeftPool = None
+ self.nTilesLeftTray = None
self.relayID = None
self.relaySeed = 0
+ self.locked = False
with open(self.logPath, "w") as log:
log.write('New cmdline: ' + self.app + ' ' + (' '.join([str(p) for p in self.params])))
@@ -198,23 +201,20 @@ class Device():
nLines += 1
log.write(line + os.linesep)
- # check for connname
- if not self.connname:
- match = Device.sConnNamePat.match(line)
- if match:
- self.connname = match.group(1)
- if not self.connname in Device.sConnnameMap:
- Device.sConnnameMap[self.connname] = set()
- Device.sConnnameMap[self.connname].add(self)
+ self.locked = True
# check for game over
if not self.gameOver:
match = Device.sGameOverPat.match(line)
if match: self.gameOver = True
- # Check every line for tiles left
- match = Device.sTilesLeftPat.match(line)
- if match: self.nTilesLeft = int(match.group(1))
+ # Check every line for tiles left in pool
+ match = Device.sTilesLeftPoolPat.match(line)
+ if match: self.nTilesLeftPool = int(match.group(1))
+
+ # Check every line for tiles left in tray
+ match = Device.sTilesLeftTrayPat.match(line)
+ if match: self.nTilesLeftTray = int(match.group(1))
if not self.relayID:
match = Device.sRelayIDPat.match(line)
@@ -222,6 +222,8 @@ class Device():
self.relaySeed = int(match.group(1))
self.relayID = match.group(2)
+ self.locked = False
+
# print('logReaderMain done, wrote lines:', nLines, 'to', self.logPath);
def launch(self):
@@ -233,7 +235,6 @@ class Device():
args += [self.app] + [str(p) for p in self.params]
if self.devID: args.extend( ' '.split(self.devID))
self.launchCount += 1
- # self.logStream = open(self.logPath, flag)
self.proc = subprocess.Popen(args, stdout = subprocess.DEVNULL,
stderr = subprocess.PIPE, universal_newlines = True)
self.pid = self.proc.pid
@@ -278,17 +279,22 @@ class Device():
shutil.move(self.db, self.args.LOGDIR + '/done')
def send_dead(self):
- JSON = json.dumps([{'relayID': self.relayID, 'seed': self.relaySeed}])
- url = 'http://%s/xw4/relay.py/kill' % (self.args.HOST)
- params = {'params' : JSON}
- try:
- req = requests.get(url, params = params) # failing
- except requests.exceptions.ConnectionError:
- print('got exception sending to', url, params, '; is relay.py running as apache module?')
+ if self.args.ADD_RELAY:
+ JSON = json.dumps([{'relayID': self.relayID, 'seed': self.relaySeed}])
+ url = 'http://%s/xw4/relay.py/kill' % (self.args.HOST)
+ params = {'params' : JSON}
+ try:
+ req = requests.get(url, params = params) # failing
+ except requests.exceptions.ConnectionError:
+ print('got exception sending to', url, params, '; is relay.py running as apache module?')
def getTilesCount(self):
- return {'index': self.indx, 'nTilesLeft': self.nTilesLeft,
- 'launchCount': self.launchCount, 'game': self.game,
+ assert not self.locked
+ return {'index': self.indx,
+ 'nTilesLeftPool': self.nTilesLeftPool,
+ 'nTilesLeftTray': self.nTilesLeftTray,
+ 'launchCount': self.launchCount,
+ 'game': self.game,
}
def update_ldevid(self):
@@ -313,19 +319,17 @@ class Device():
def check_game(self):
if self.gameOver and not self.allDone:
- allDone = False
- if len(Device.sConnnameMap[self.connname]) == self.nInGame:
- allDone = True
- for dev in Device.sConnnameMap[self.connname]:
- if dev == self: continue
- if not dev.gameOver:
- allDone = False
- break
+ allDone = True
+ for dev in self.peers:
+ if dev == self: continue
+ if not dev.gameOver:
+ allDone = False
+ break
- if allDone:
- for dev in Device.sConnnameMap[self.connname]:
- assert self.game == dev.game
- dev.allDone = True
+ if allDone:
+ for dev in self.peers:
+ assert self.game == dev.game
+ dev.allDone = True
# print('Closing', self.connname, datetime.datetime.now())
# for dev in Device.sConnnameMap[self.connname]:
@@ -360,8 +364,10 @@ def build_cmds(args):
if not args.USE_GTK:
PLAT_PARMS += ['--curses', '--close-stdin']
- for GAME in range(1, args.NGAMES + 1):
+ for GAME in range(1, args.NGAMES + 1):
+ peers = set()
ROOM = 'ROOM_%.3d' % (GAME % args.NROOMS)
+ PHONE_BASE = '%.4d' % (GAME % args.NROOMS)
NDEVS = pick_ndevs(args)
LOCALS = figure_locals(args, NDEVS) # as array
NPLAYERS = sum(LOCALS)
@@ -376,27 +382,27 @@ def build_cmds(args):
DB = '{}/{:02d}_{:02d}_DB.sql3'.format(args.LOGDIR, GAME, DEV)
LOG = '{}/{:02d}_{:02d}_LOG.txt'.format(args.LOGDIR, GAME, DEV)
- BOARD_SIZE = ['--board-size', '15']
- # if [ 0 -lt ${#APPS_OLD[@]} ]; then
- # # 50% chance of starting out with old app
- # NAPPS=$((1+${#APPS_OLD[*]}))
- # if [ 0 -lt $((RANDOM%$NAPPS)) ]; then
- # APPS[$COUNTER]=${APPS_OLD[$((RANDOM%${#APPS_OLD[*]}))]}
- # BOARD_SIZE="--board-size ${BOARD_SIZES_OLD[$((RANDOM%${#BOARD_SIZES_OLD[*]}))]}"
- # NEW_ARGS[$COUNTER]=""
- # fi
- # fi
-
PARAMS = player_params(args, NLOCALS, NPLAYERS, DEV)
PARAMS += PLAT_PARMS
- PARAMS += BOARD_SIZE + ['--room', ROOM, '--trade-pct', args.TRADE_PCT, '--sort-tiles']
+ PARAMS += ['--board-size', '15', '--trade-pct', args.TRADE_PCT, '--sort-tiles']
+
+ # We SHOULD support having both SMS and relay working...
+ if args.ADD_RELAY:
+ PARAMS += [ '--relay-port', args.PORT, '--room', ROOM, '--host', args.HOST]
+ if random.randint(0,100) % 100 < g_UDP_PCT_START:
+ PARAMS += ['--use-udp']
+ if args.ADD_SMS:
+ PARAMS += [ '--sms-number', PHONE_BASE + str(DEV - 1) ]
+ if args.SMS_FAIL_PCT > 0:
+ PARAMS += [ '--sms-fail-pct', args.SMS_FAIL_PCT ]
+ if DEV > 1:
+ PARAMS += [ '--server-sms-number', PHONE_BASE + '0' ]
+
if args.UNDO_PCT > 0:
PARAMS += ['--undo-pct', args.UNDO_PCT]
- PARAMS += [ '--game-dict', DICT, '--relay-port', args.PORT, '--host', args.HOST]
+ PARAMS += [ '--game-dict', DICT]
PARAMS += ['--slow-robot', '1:3', '--skip-confirm']
PARAMS += ['--db', DB]
- if random.randint(0,100) % 100 < g_UDP_PCT_START:
- PARAMS += ['--use-udp']
PARAMS += ['--drop-nth-packet', g_DROP_N]
if random.randint(0, 100) < args.HTTP_PCT:
@@ -417,231 +423,21 @@ def build_cmds(args):
# it isn't a priority.
# PARAMS += ['--seed', args.SEED]
PARAMS += PUBLIC
- if DEV > 1:
+ if DEV > 1:
PARAMS += ['--force-channel', DEV - 1]
else:
PARAMS += ['--server']
# print('PARAMS:', PARAMS)
- dev = Device(args, GAME, COUNTER, args.APP_NEW, PARAMS, ROOM, DB, LOG, len(LOCALS))
+ dev = Device(args, GAME, COUNTER, args.APP_NEW, PARAMS, ROOM, peers, DB, LOG, len(LOCALS))
+ peers.add(dev)
dev.update_ldevid()
devs.append(dev)
COUNTER += 1
return devs
-# read_resume_cmds() {
-# COUNTER=0
-# for LOG in $(ls $LOGDIR/*.txt); do
-# echo "need to parse cmd and deal with changes"
-# exit 1
-# CMD=$(head -n 1 $LOG)
-
-# ARGS[$COUNTER]=$CMD
-# LOGS[$COUNTER]=$LOG
-# PIDS[$COUNTER]=0
-
-# set $CMD
-# while [ $# -gt 0 ]; do
-# case $1 in
-# --file)
-# FILES[$COUNTER]=$2
-# shift
-# ;;
-# --room)
-# ROOMS[$COUNTER]=$2
-# shift
-# ;;
-# esac
-# shift
-# done
-# COUNTER=$((COUNTER+1))
-# done
-# ROOM_PIDS[$ROOM]=0
-# }
-
-# launch() {
-# KEY=$1
-# LOG=${LOGS[$KEY]}
-# APP="${APPS[$KEY]}"
-# if [ -z "$APP" ]; then
-# echo "error: no app set"
-# exit 1
-# fi
-# PARAMS="${NEW_ARGS[$KEY]} ${ARGS[$KEY]} ${ARGS_DEVID[$KEY]}"
-# exec $APP $PARAMS >/dev/null 2>>$LOG
-# }
-
-# # launch_via_rq() {
-# # KEY=$1
-# # RELAYID=$2
-# # PIPE=${PIPES[$KEY]}
-# # ../relay/rq -f $RELAYID -o $PIPE &
-# # CMD="${CMDS[$KEY]}"
-# # exec $CMD >/dev/null 2>>$LOG
-# # }
-
-# send_dead() {
-# ID=$1
-# DB=${FILES[$ID]}
-# while :; do
-# [ -f $DB ] || break # it's gone
-# RES=$(echo 'select relayid, seed from games limit 1;' | sqlite3 -separator ' ' $DB || /bin/true)
-# [ -n "$RES" ] && break
-# sleep 0.2
-# done
-# RELAYID=$(echo $RES | awk '{print $1}')
-# SEED=$(echo $RES | awk '{print $2}')
-# JSON="[{\"relayID\":\"$RELAYID\", \"seed\":$SEED}]"
-# curl -G --data-urlencode params="$JSON" http://$HOST/xw4/relay.py/kill >/dev/null 2>&1
-# }
-
-# close_device() {
-# ID=$1
-# MVTO=$2
-# REASON="$3"
-# PID=${PIDS[$ID]}
-# if [ $PID -ne 0 ]; then
-# kill ${PIDS[$ID]} 2>/dev/null
-# wait ${PIDS[$ID]}
-# ROOM=${ROOMS[$ID]}
-# [ ${ROOM_PIDS[$ROOM]} -eq $PID ] && ROOM_PIDS[$ROOM]=0
-# fi
-# unset PIDS[$ID]
-# unset ARGS[$ID]
-# echo "closing game: $REASON" >> ${LOGS[$ID]}
-# if [ -n "$MVTO" ]; then
-# [ -f "${FILES[$ID]}" ] && mv ${FILES[$ID]} $MVTO
-# mv ${LOGS[$ID]} $MVTO
-# else
-# rm -f ${FILES[$ID]}
-# rm -f ${LOGS[$ID]}
-# fi
-# unset FILES[$ID]
-# unset LOGS[$ID]
-# unset ROOMS[$ID]
-# unset APPS[$ID]
-# unset ARGS_DEVID[$ID]
-
-# COUNT=${#ARGS[*]}
-# echo "$COUNT devices left playing..."
-# }
-
-# OBITS=""
-
-# kill_from_log() {
-# LOG=$1
-# RELAYID=$(./scripts/relayID.sh --long $LOG)
-# if [ -n "$RELAYID" ]; then
-# OBITS="$OBITS -d $RELAYID"
-# if [ 0 -eq $(($RANDOM%2)) ]; then
-# ../relay/rq -a $HOST $OBITS 2>/dev/null || /bin/true
-# OBITS=""
-# fi
-# return 0 # success
-# fi
-# echo "unable to send kill command for $LOG"
-# return 1
-# }
-
-# maybe_resign() {
-# if [ "$RESIGN_PCT" -gt 0 ]; then
-# KEY=$1
-# LOG=${LOGS[$KEY]}
-# if grep -aq XWRELAY_ALLHERE $LOG; then
-# if [ $((${RANDOM}%100)) -lt $RESIGN_PCT ]; then
-# echo "making $LOG $(connName $LOG) resign..."
-# kill_from_log $LOG && close_device $KEY $DEADDIR "resignation forced" || /bin/true
-# fi
-# fi
-# fi
-# }
-
-# try_upgrade() {
-# KEY=$1
-# if [ 0 -lt ${#APPS_OLD[@]} ]; then
-# if [ $APP_NEW != "${APPS[$KEY]}" ]; then
-# # one in five chance of upgrading
-# if [ 0 -eq $((RANDOM % UPGRADE_ODDS)) ]; then
-# APPS[$KEY]=$APP_NEW
-# NEW_ARGS[$KEY]="$APP_NEW_PARAMS"
-# print_cmdline $KEY
-# fi
-# fi
-# fi
-# }
-
-# try_upgrade_upd() {
-# KEY=$1
-# CMD=${ARGS[$KEY]}
-# if [ "${CMD/--use-udp/}" = "${CMD}" ]; then
-# if [ $((RANDOM % 100)) -lt $UDP_PCT_INCR ]; then
-# ARGS[$KEY]="$CMD --use-udp"
-# echo -n "$(date +%r): "
-# echo "upgrading key $KEY to use UDP"
-# fi
-# fi
-# }
-
-# check_game() {
-# KEY=$1
-# LOG=${LOGS[$KEY]}
-# CONNNAME="$(connName $LOG)"
-# OTHERS=""
-# if [ -n "$CONNNAME" ]; then
-# if grep -aq '\[unused tiles\]' $LOG ; then
-# for INDX in ${!LOGS[*]}; do
-# [ $INDX -eq $KEY ] && continue
-# ALOG=${LOGS[$INDX]}
-# CONNNAME2="$(connName $ALOG)"
-# if [ "$CONNNAME2" = "$CONNNAME" ]; then
-# if ! grep -aq '\[unused tiles\]' $ALOG; then
-# OTHERS=""
-# break
-# fi
-# OTHERS="$OTHERS $INDX"
-# fi
-# done
-# fi
-# fi
-
-# if [ -n "$OTHERS" ]; then
-# echo -n "Closing $CONNNAME [$(date)]: "
-# # kill_from_logs $OTHERS $KEY
-# for ID in $OTHERS $KEY; do
-# echo -n "${ID}:${LOGS[$ID]}, "
-# kill_from_log ${LOGS[$ID]} || /bin/true
-# send_dead $ID
-# close_device $ID $DONEDIR "game over"
-# done
-# echo ""
-# # XWRELAY_ERROR_DELETED may be old
-# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DELETED)' $LOG; then
-# echo "deleting $LOG $(connName $LOG) b/c another resigned"
-# kill_from_log $LOG || /bin/true
-# close_device $KEY $DEADDIR "other resigned"
-# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DEADGAME)' $LOG; then
-# echo "deleting $LOG $(connName $LOG) b/c another resigned"
-# kill_from_log $LOG || /bin/true
-# close_device $KEY $DEADDIR "other resigned"
-# else
-# maybe_resign $KEY
-# fi
-# }
-
-# increment_drop() {
-# KEY=$1
-# CMD=${ARGS[$KEY]}
-# if [ "$CMD" != "${CMD/drop-nth-packet//}" ]; then
-# DROP_N=$(echo $CMD | sed 's,^.*drop-nth-packet \(-*[0-9]*\) .*$,\1,')
-# if [ $DROP_N -gt 0 ]; then
-# NEXT_N=$((DROP_N+1))
-# ARGS[$KEY]=$(echo $CMD | sed "s,^\(.*drop-nth-packet \)$DROP_N\(.*\)$,\1$NEXT_N\2,")
-# fi
-# fi
-# }
-
def summarizeTileCounts(devs, endTime, state):
global gDeadLaunches
shouldGoOn = True
@@ -675,13 +471,22 @@ def summarizeTileCounts(devs, endTime, state):
nLaunches += launchCount
fmtData[1]['data'].append('{:{width}d}'.format(launchCount, width=colWidth))
- nTiles = datum['nTilesLeft']
- fmtData[2]['data'].append(nTiles is None and ('-' * colWidth) or '{:{width}d}'.format(nTiles, width=colWidth))
- if not nTiles is None: totalTiles += int(nTiles)
-
+ # Format tiles left. It's the number in the bag/pool until
+ # that drops to 0, then the number in the tray preceeded by
+ # '+'. Only the pool number is included in the totalTiles sum.
+ nTilesPool = datum['nTilesLeftPool']
+ nTilesTray = datum['nTilesLeftTray']
+ if nTilesPool is None and nTilesTray is None:
+ txt = ('-' * colWidth)
+ elif int(nTilesPool) == 0 and not nTilesTray is None:
+ txt = '{:+{width}d}'.format(nTilesTray, width=colWidth-1)
+ else:
+ txt = '{:{width}d}'.format(nTilesPool, width=colWidth)
+ totalTiles += int(nTilesPool)
+ fmtData[2]['data'].append(txt)
print('')
- print('devs left: {}; tiles left: {}; total launches: {}; {}/{}'
+ print('devs left: {}; bag tiles left: {}; total launches: {}; {}/{}'
.format(nDevs, totalTiles, nLaunches, datetime.datetime.now(), endTime ))
fmt = '{head:>%d} {data}' % headWidth
for datum in fmtData: datum['data'] = ' '.join(datum['data'])
@@ -708,19 +513,21 @@ gDone = False
def run_cmds(args, devs):
nCores = countCores()
endTime = datetime.datetime.now() + datetime.timedelta(minutes = args.TIMEOUT_MINS)
- LOOPCOUNT = 0
printState = {}
+ lastPrint = datetime.datetime.now()
while len(devs) > 0 and not gDone:
if countCores() > nCores:
print('core file count increased; exiting')
break
- if datetime.datetime.now() > endTime:
+ now = datetime.datetime.now()
+ if now > endTime:
print('outta time; outta here')
break
- LOOPCOUNT += 1
- if 0 == LOOPCOUNT % 20:
+ # print stats every 5 seconds
+ if now - lastPrint > datetime.timedelta(seconds = 5):
+ lastPrint = now
if not summarizeTileCounts(devs, endTime, printState):
print('no change in too long; exiting')
break
@@ -742,20 +549,16 @@ def run_cmds(args, devs):
# PIDS[$KEY]=$PID
# ROOM_PIDS[$ROOM]=$PID
# MINEND[$KEY]=$(($NOW + $MINRUN))
- elif not dev.minTimeExpired():
- # print('sleeping...')
- time.sleep(1.0)
- else:
+ elif dev.minTimeExpired():
dev.kill()
if dev.handleAllDone():
devs.remove(dev)
- # if g_DROP_N >= 0: dev.increment_drop()
- # update_ldevid $KEY
-
+ else:
+ time.sleep(1.0)
# if we get here via a break, kill any remaining games
if devs:
- print('stopping %d remaining games' % (len(devs)))
+ print('stopping {} remaining games'.format(len(devs)))
for dev in devs:
if dev.running(): dev.kill()
@@ -828,6 +631,7 @@ def mkParser():
help = 'No game will have fewer devices than this')
parser.add_argument('--max-devs', dest = 'MAXDEVS', type = int, default = 4,
help = 'No game will have more devices than this')
+
parser.add_argument('--min-run', dest = 'MINRUN', type = int, default = 2,
help = 'Keep each run alive at least this many seconds')
# # echo " [--new-app &2
@@ -852,6 +656,10 @@ def mkParser():
parser.add_argument('--undo-pct', dest = 'UNDO_PCT', default = 0, type = int)
parser.add_argument('--trade-pct', dest = 'TRADE_PCT', default = 0, type = int)
+ parser.add_argument('--add-sms', dest = 'ADD_SMS', default = False, action = 'store_true')
+ parser.add_argument('--sms-fail-pct', dest = 'SMS_FAIL_PCT', default = 0, type = int)
+ parser.add_argument('--remove-relay', dest = 'ADD_RELAY', default = True, action = 'store_false')
+
parser.add_argument('--with-valgrind', dest = 'VALGRIND', default = False,
action = 'store_true')
@@ -1058,6 +866,10 @@ def main():
signal.signal(signal.SIGINT, termHandler)
args = parseArgs()
+ # Hack: old files confuse things. Remove is simple fix good for now
+ if args.ADD_SMS:
+ try: rmtree('/tmp/xw_sms')
+ except: None
devs = build_cmds(args)
nDevs = len(devs)
run_cmds(args, devs)
diff --git a/xwords4/relay/dbmgr.cpp b/xwords4/relay/dbmgr.cpp
index 99492a87a..aecdb29ec 100644
--- a/xwords4/relay/dbmgr.cpp
+++ b/xwords4/relay/dbmgr.cpp
@@ -1135,7 +1135,7 @@ DBMgr::StoreMessage( const char* const connName, int destHid,
void
DBMgr::decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx,
- int byteaIndex, uint8_t* buf, size_t* buflen )
+ int byteaIndex, vector& buf )
{
const char* from = NULL;
if ( useB64 ) {
@@ -1146,22 +1146,19 @@ DBMgr::decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx,
from = PQgetvalue( result, rowIndx, byteaIndex );
}
- size_t to_length;
if ( useB64 ) {
gsize out_len;
guchar* txt = g_base64_decode( (const gchar*)from, &out_len );
- to_length = out_len;
- assert( to_length <= *buflen );
- memcpy( buf, txt, to_length );
+ buf.insert( buf.end(), txt, txt + out_len );
+ assert( buf.size() == out_len );
g_free( txt );
} else {
- uint8_t* bytes = PQunescapeBytea( (const uint8_t*)from,
- &to_length );
- assert( to_length <= *buflen );
- memcpy( buf, bytes, to_length );
+ size_t to_length;
+ uint8_t* bytes = PQunescapeBytea( (const uint8_t*)from, &to_length );
+ buf.insert( buf.end(), bytes, bytes + to_length );
+ assert( buf.size() == to_length );
PQfreemem( bytes );
}
- *buflen = to_length;
}
void
@@ -1193,12 +1190,9 @@ DBMgr::storedMessagesImpl( string test, vector& msgs,
bool hasConnname = connname != NULL && '\0' != connname[0];
MsgInfo msg( id, token, hasConnname );
- uint8_t buf[1024];
- size_t buflen = sizeof(buf);
- decodeMessage( result, m_useB64, ii, 1, 2, buf, &buflen );
+ decodeMessage( result, m_useB64, ii, 1, 2, msg.msg );
size_t msglen = atoi( PQgetvalue( result, ii, 3 ) );
- assert( 0 == msglen || buflen == msglen );
- msg.msg.insert( msg.msg.end(), buf, &buf[buflen] );
+ assert( 0 == msglen || msg.msg.size() == msglen );
msgs.push_back( msg );
}
PQclear( result );
diff --git a/xwords4/relay/dbmgr.h b/xwords4/relay/dbmgr.h
index d23622c7e..026d06851 100644
--- a/xwords4/relay/dbmgr.h
+++ b/xwords4/relay/dbmgr.h
@@ -166,7 +166,8 @@ class DBMgr {
int getCountWhere( const char* table, string& test );
void RemoveStoredMessages( string& msgIDs );
void decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx,
- int byteaIndex, uint8_t* buf, size_t* buflen );
+ int byteaIndex, vector& buf );
+
void storedMessagesImpl( string query, vector& msgs,
bool nullConnnameOK );
int CountStoredMessages( const char* const connName, int hid );
diff --git a/xwords4/relay/devmgr.cpp b/xwords4/relay/devmgr.cpp
index 86e682423..ef3163999 100644
--- a/xwords4/relay/devmgr.cpp
+++ b/xwords4/relay/devmgr.cpp
@@ -57,8 +57,7 @@ DevMgr::rememberDevice( DevIDRelay devid, const AddrInfo::AddrUnion* saddr )
pair