Merge branch 'android_branch' into android_translate

This commit is contained in:
Eric House 2018-07-18 22:30:58 -07:00
commit a3fb82ebc4
84 changed files with 3187 additions and 1672 deletions

View file

@ -10,7 +10,11 @@ android:
- tools - tools
- platform-tools - platform-tools
- build-tools-27.0.3 - build-tools-27.0.3
- android-23 - android-26
licenses:
- android-sdk-preview-license-.+
- android-sdk-license-.+
- google-gdk-license-.+
before_script: before_script:
- export TERM=dumb - export TERM=dumb
- curl -L http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin -O - curl -L http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin -O
@ -23,18 +27,18 @@ before_script:
- export PATH=$PATH:${ANDROID_NDK_HOME} - export PATH=$PATH:${ANDROID_NDK_HOME}
- cd xwords4/android/ - cd xwords4/android/
before_install: before_install:
- yes | sdkmanager "platforms;android-27"
- openssl aes-256-cbc -K $encrypted_8436f2891714_key -iv $encrypted_8436f2891714_iv - openssl aes-256-cbc -K $encrypted_8436f2891714_key -iv $encrypted_8436f2891714_iv
-in id_rsa_uploader.enc -out /tmp/id_rsa_uploader -d -in id_rsa_uploader.enc -out /tmp/id_rsa_uploader -d
- chmod 600 \/tmp\/id_rsa_uploader - chmod 600 \/tmp\/id_rsa_uploader
- sudo apt-get -qq update - sudo apt-get -qq update
- sudo apt-get install -y python-lxml imagemagick - sudo apt-get install -y python-lxml imagemagick
script: script:
- "./gradlew -PuseCrashlytics assXw4dDeb" - "./gradlew -PuseCrashlytics asXw4dDeb"
- scp -o "StrictHostKeyChecking no" -i /tmp/id_rsa_uploader -d app/build/outputs/apk/xw4d/debug/*.apk - scp -o "StrictHostKeyChecking no" -i /tmp/id_rsa_uploader -d app/build/outputs/apk/xw4d/debug/*.apk
uploader@eehouse.org:XW4D_UPLOAD uploader@eehouse.org:XW4D_UPLOAD
notifications: notifications:
email: email:
recipients: recipients:
- xwords@eehouse.org - xwords@eehouse.org
on_success: always
on_failure: always on_failure: always

View file

@ -1,6 +1,6 @@
def INITIAL_CLIENT_VERS = 8 def INITIAL_CLIENT_VERS = 8
def VERSION_CODE_BASE = 132 def VERSION_CODE_BASE = 133
def VERSION_NAME = '4.4.136' def VERSION_NAME = '4.4.137'
def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY") def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY")
def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID") def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID")
def BUILD_INFO_NAME = "build-info.txt" def BUILD_INFO_NAME = "build-info.txt"
@ -24,6 +24,7 @@ if ( FABRIC_API_KEY && hasProperty('useCrashlytics') ) {
} }
repositories { repositories {
maven { url 'https://maven.fabric.io/public' } maven { url 'https://maven.fabric.io/public' }
google()
} }
android { android {
@ -32,7 +33,7 @@ android {
buildToolsVersion '27.0.3' buildToolsVersion '27.0.3'
defaultConfig { defaultConfig {
minSdkVersion 8 minSdkVersion 8
targetSdkVersion 23 targetSdkVersion 26
versionCode VERSION_CODE_BASE versionCode VERSION_CODE_BASE
versionName VERSION_NAME versionName VERSION_NAME
} }

View file

@ -43,6 +43,16 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
> >
<provider android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true"
>
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"
/>
</provider>
<activity android:name="MainActivity" <activity android:name="MainActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="standard" android:launchMode="standard"

View file

@ -13,9 +13,9 @@
</style> </style>
</head> </head>
<body> <body>
<h2>CrossWords 4.4.136 release</h2> <h2>CrossWords 4.4.137 release</h2>
<p>This release fixes a couple of rare bugs.</p> <p>This release paves the way for play-via-SMS improvements.</p>
<div id="survey"> <div id="survey">
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take <p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
@ -25,9 +25,12 @@
<h3>New with this release</h3> <h3>New with this release</h3>
<ul> <ul>
<li>Flip arrow to match when board flipped</li> <li>Add new SMS-handling code. One feature isn't compatible with
<li>Fix problems when undos arrive at unexpected times</li> the previous release so it won't be activated until there's been
<li>Don't crash when game connects while app's in background</li> time for everybody to upgrade.</li>
<li>Include new Catalan translations</li>
<li>Fix bug causing (rarely) unending notifications of wordlist
upgrade</li>
</ul> </ul>
<p>(The full changelog <p>(The full changelog

View file

@ -175,8 +175,7 @@ public class DictBrowseDelegate extends DelegateBase
String[] names = { name }; String[] names = { name };
DictUtils.DictPairs pairs = DictUtils.openDicts( m_activity, names ); DictUtils.DictPairs pairs = DictUtils.openDicts( m_activity, names );
m_dictClosure = XwJNI.dict_iter_init( pairs.m_bytes[0], m_dictClosure = XwJNI.dict_iter_init( pairs.m_bytes[0],
name, pairs.m_paths[0], name, pairs.m_paths[0] );
JNIUtilsImpl.get(m_activity) );
m_browseState = DBUtils.dictsGetOffset( m_activity, name, m_loc ); m_browseState = DBUtils.dictsGetOffset( m_activity, name, m_loc );
boolean newState = null == m_browseState; boolean newState = null == m_browseState;
@ -280,7 +279,7 @@ public class DictBrowseDelegate extends DelegateBase
{ {
TextView text = (TextView)view; TextView text = (TextView)view;
// null text seems to have generated at least one google play report // 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() ); int newval = Integer.parseInt( text.getText().toString() );
switch ( parent.getId() ) { switch ( parent.getId() ) {
case R.id.wordlen_min: case R.id.wordlen_min:

View file

@ -486,7 +486,6 @@ public class DictLangCache {
info = new DictInfo(); info = new DictInfo();
if ( XwJNI.dict_getInfo( pairs.m_bytes[0], dal.name, if ( XwJNI.dict_getInfo( pairs.m_bytes[0], dal.name,
pairs.m_paths[0], pairs.m_paths[0],
JNIUtilsImpl.get( context ),
DictLoc.DOWNLOAD == dal.loc, DictLoc.DOWNLOAD == dal.loc,
info ) ) { info ) ) {

View file

@ -151,15 +151,17 @@ public class DictUtils {
// changes? // changes?
} }
private static void addLogDup( Map<String, DictAndLoc> map, String path, private static void addLogDupIf( Context context, Map<String, DictAndLoc> map,
DictLoc loc ) String path, File dir, DictLoc loc )
{ {
if ( isDict( context, path, dir ) ) {
String name = removeDictExtn( new File(path).getName() ); String name = removeDictExtn( new File(path).getName() );
if ( map.containsKey( name ) ) { if ( map.containsKey( name ) ) {
Log.d( TAG, "replacing info for %s with from %s", name, loc ); 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, private static void tryDir( Context context, File dir, boolean strict,
DictLoc loc, Map<String, DictAndLoc> map ) DictLoc loc, Map<String, DictAndLoc> map )
@ -168,9 +170,7 @@ public class DictUtils {
String[] list = dir.list(); String[] list = dir.list();
if ( null != list ) { if ( null != list ) {
for ( String file : list ) { for ( String file : list ) {
if ( isDict( context, file, strict? dir : null ) ) { addLogDupIf( context, map, file, strict? dir : null, loc );
addLogDup( map, file, loc );
}
} }
} }
} }
@ -190,15 +190,11 @@ public class DictUtils {
Map<String, DictAndLoc> map = new HashMap<>(); Map<String, DictAndLoc> map = new HashMap<>();
for ( String file : getAssets( context ) ) { for ( String file : getAssets( context ) ) {
if ( isDict( context, file, null ) ) { addLogDupIf( context, map, file, null, DictLoc.BUILT_IN );
addLogDup( map, file, DictLoc.BUILT_IN );
}
} }
for ( String file : context.fileList() ) { for ( String file : context.fileList() ) {
if ( isDict( context, file, null ) ) { addLogDupIf( context, map, file, null, DictLoc.INTERNAL );
addLogDup( map, file, DictLoc.INTERNAL );
}
} }
tryDir( context, getSDDir( context ), false, DictLoc.EXTERNAL, map ); tryDir( context, getSDDir( context ), false, DictLoc.EXTERNAL, map );
@ -578,7 +574,7 @@ public class DictUtils {
if ( ok && null != dir ) { if ( ok && null != dir ) {
String fullPath = new File( dir, file ).getPath(); String fullPath = new File( dir, file ).getPath();
ok = XwJNI.dict_getInfo( null, removeDictExtn( file ), fullPath, ok = XwJNI.dict_getInfo( null, removeDictExtn( file ), fullPath,
JNIUtilsImpl.get(context), true, null ); true, null );
} }
return ok; return ok;
} }

View file

@ -172,7 +172,7 @@ public class DwnldDelegate extends ListDelegateBase {
callListener( m_uri, true ); callListener( m_uri, true );
} else if ( null != m_appFile ) { } else if ( null != m_appFile ) {
// launch the installer // launch the installer
Intent intent = Utils.makeInstallIntent( m_appFile ); Intent intent = Utils.makeInstallIntent( m_activity, m_appFile );
startActivity( intent ); startActivity( intent );
} else { } else {
// we failed at something.... // we failed at something....
@ -473,5 +473,4 @@ public class DwnldDelegate extends ListDelegateBase {
intent.putExtra( APK_EXTRA, url ); intent.putExtra( APK_EXTRA, url );
return intent; return intent;
} }
} }

View file

@ -25,8 +25,8 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.text.Html; import android.text.Html;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Display; import android.view.Display;
@ -131,8 +131,8 @@ public class GameUtils {
gamePtr = XwJNI.initNew( gi, dictNames, pairs.m_bytes, pairs.m_paths, gamePtr = XwJNI.initNew( gi, dictNames, pairs.m_bytes, pairs.m_paths,
gi.langName( context ), (UtilCtxt)null, gi.langName( context ), (UtilCtxt)null,
JNIUtilsImpl.get( context ), (DrawCtx)null, (DrawCtx)null, CommonPrefs.get( context ),
CommonPrefs.get( context ), (TransportProcs)null ); (TransportProcs)null );
if ( juggle ) { if ( juggle ) {
gi.juggle(); gi.juggle();
@ -372,15 +372,13 @@ public class GameUtils {
gamePtr = XwJNI.initFromStream( rowid, stream, gi, dictNames, gamePtr = XwJNI.initFromStream( rowid, stream, gi, dictNames,
pairs.m_bytes, pairs.m_paths, pairs.m_bytes, pairs.m_paths,
langName, util, langName, util,
JNIUtilsImpl.get( context ),
null, null,
CommonPrefs.get(context), CommonPrefs.get(context),
tp ); tp );
if ( null == gamePtr ) { if ( null == gamePtr ) {
gamePtr = XwJNI.initNew( gi, dictNames, gamePtr = XwJNI.initNew( gi, dictNames,
pairs.m_bytes, pairs.m_paths, pairs.m_bytes, pairs.m_paths,
langName, (UtilCtxt)null, langName, (UtilCtxt)null, null,
JNIUtilsImpl.get(context), null,
CommonPrefs.get(context), null ); CommonPrefs.get(context), null );
} }
} }
@ -485,12 +483,12 @@ public class GameUtils {
} }
if ( force ) { if ( force ) {
new ResendTask( context, filter, proc ).execute();
System.arraycopy( sendTimes, 0, /* src */ System.arraycopy( sendTimes, 0, /* src */
sendTimes, 1, /* dest */ sendTimes, 1, /* dest */
sendTimes.length - 1 ); sendTimes.length - 1 );
sendTimes[0] = now; sendTimes[0] = now;
new Resender( context, filter, proc ).start();
} }
} }
@ -914,6 +912,7 @@ public class GameUtils {
m_gotMsg = false; m_gotMsg = false;
m_gameOver = false; m_gameOver = false;
} }
@Override @Override
public void showChat( String msg, int fromIndx, String fromName, int tsSeconds ) public void showChat( String msg, int fromIndx, String fromName, int tsSeconds )
{ {
@ -923,11 +922,14 @@ public class GameUtils {
m_chat = msg; m_chat = msg;
m_ts = tsSeconds; m_ts = tsSeconds;
} }
@Override
public void turnChanged( int newTurn ) public void turnChanged( int newTurn )
{ {
m_gotMsg = true; m_gotMsg = true;
} }
@Override
public void notifyGameOver() public void notifyGameOver()
{ {
m_gameOver = true; m_gameOver = true;
@ -1024,8 +1026,7 @@ public class GameUtils {
XwJNI.initFromStream( rowid, stream, gi, dictNames, XwJNI.initFromStream( rowid, stream, gi, dictNames,
pairs.m_bytes, pairs.m_paths, pairs.m_bytes, pairs.m_paths,
gi.langName( context ), null, gi.langName( context ), null,
JNIUtilsImpl.get(context), null, null, CommonPrefs.get( context ), null );
CommonPrefs.get( context ), null );
// second time required as game_makeFromStream can overwrite // second time required as game_makeFromStream can overwrite
gi.replaceDicts( context, newDict ); gi.replaceDicts( context, newDict );
@ -1075,8 +1076,7 @@ public class GameUtils {
new CurGameInfo(context), new CurGameInfo(context),
dictNames, pairs.m_bytes, dictNames, pairs.m_bytes,
pairs.m_paths, langName, pairs.m_paths, langName,
null, JNIUtilsImpl.get(context), null, null, cp, null );
null, cp, null );
madeGame = null != gamePtr; madeGame = null != gamePtr;
} }
@ -1084,8 +1084,7 @@ public class GameUtils {
Assert.assertNull( gamePtr ); Assert.assertNull( gamePtr );
gamePtr = XwJNI.initNew( gi, dictNames, pairs.m_bytes, gamePtr = XwJNI.initNew( gi, dictNames, pairs.m_bytes,
pairs.m_paths, langName, util, pairs.m_paths, langName, util,
JNIUtilsImpl.get(context), (DrawCtx)null, (DrawCtx)null, cp, sink );
cp, sink );
} }
if ( null != car ) { if ( null != car ) {
@ -1255,23 +1254,27 @@ public class GameUtils {
return result; return result;
} }
private static class ResendTask extends AsyncTask<Void, Void, Void> { private static class Resender extends Thread {
private Context m_context; private Context m_context;
private ResendDoneProc m_doneProc; private ResendDoneProc m_doneProc;
private CommsConnType m_filter; private CommsConnType m_filter;
private int m_nSent = 0; private Handler m_handler;
public ResendTask( Context context, CommsConnType filter, public Resender( Context context, CommsConnType filter,
ResendDoneProc proc ) ResendDoneProc proc )
{ {
m_context = context; m_context = context;
m_filter = filter; m_filter = filter;
m_doneProc = proc; m_doneProc = proc;
if ( null != proc ) {
m_handler = new Handler();
}
} }
@Override @Override
protected Void doInBackground( Void... unused ) public void run()
{ {
int nSentTotal = 0;
HashMap<Long,CommsConnTypeSet> games HashMap<Long,CommsConnTypeSet> games
= DBUtils.getGamesWithSendsPending( m_context ); = DBUtils.getGamesWithSendsPending( m_context );
@ -1296,11 +1299,11 @@ public class GameUtils {
int nSent = XwJNI.comms_resendAll( gamePtr, true, int nSent = XwJNI.comms_resendAll( gamePtr, true,
m_filter, false ); m_filter, false );
gamePtr.release(); gamePtr.release();
Log.d( TAG, "ResendTask.doInBackground(): sent %d " Log.d( TAG, "Resender.doInBackground(): sent %d "
+ "messages for rowid %d", nSent, rowid ); + "messages for rowid %d", nSent, rowid );
m_nSent += sink.numSent(); nSentTotal += sink.numSent();
} else { } else {
Log.d( TAG, "ResendTask.doInBackground(): loadMakeGame()" Log.d( TAG, "Resender.doInBackground(): loadMakeGame()"
+ " failed for rowid %d", rowid ); + " failed for rowid %d", rowid );
} }
lock.unlock(); lock.unlock();
@ -1311,19 +1314,21 @@ public class GameUtils {
false, false ); false, false );
jniThread.release(); jniThread.release();
} else { } else {
Log.w( TAG, "ResendTask.doInBackground: unable to unlock %d", Log.w( TAG, "Resender.doInBackground: unable to unlock %d",
rowid ); rowid );
} }
} }
} }
return null;
}
@Override
protected void onPostExecute( Void unused )
{
if ( null != m_doneProc ) { 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 );
}
});
} }
} }
} }

View file

@ -1451,8 +1451,6 @@ public class GamesListDelegate extends ListDelegateBase
showItemsIf( ONEGAME_ITEMS, menu, 1 == nGamesSelected ); showItemsIf( ONEGAME_ITEMS, menu, 1 == nGamesSelected );
showItemsIf( ONEGROUP_ITEMS, menu, 1 == nGroupsSelected ); showItemsIf( ONEGROUP_ITEMS, menu, 1 == nGroupsSelected );
// check for updates only serves release builds, so don't offer in
// DEBUG case
boolean enable = showDbg && nothingSelected boolean enable = showDbg && nothingSelected
&& UpdateCheckReceiver.haveToCheck( m_activity ); && UpdateCheckReceiver.haveToCheck( m_activity );
Utils.setItemVisible( menu, R.id.games_menu_checkupdates, enable ); Utils.setItemVisible( menu, R.id.games_menu_checkupdates, enable );

View file

@ -34,7 +34,7 @@ import org.eehouse.android.xw4.MultiService.DictFetchOwner;
import org.eehouse.android.xw4.MultiService.MultiEvent; import org.eehouse.android.xw4.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; 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.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;

View file

@ -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. * reserved.
* *
* This program is free software; you can redistribute it and/or * 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.MultiService.MultiEvent;
import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.CommsAddrRec;
import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType;
import org.eehouse.android.xw4.jni.XwJNI;
import org.eehouse.android.xw4.loc.LocUtils; import org.eehouse.android.xw4.loc.LocUtils;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -55,19 +56,11 @@ import java.util.Set;
public class SMSService extends XWService { public class SMSService extends XWService {
private static final String TAG = SMSService.class.getSimpleName(); 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_SENT = "MSG_SENT";
private static final String MSG_DELIVERED = "MSG_DELIVERED"; 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_WITHPORT = 1;
private static final int SMS_PROTO_VERSION = SMS_PROTO_VERSION_WITHPORT; 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, private enum SMSAction { _NONE,
INVITE, INVITE,
SEND, SEND,
@ -75,6 +68,7 @@ public class SMSService extends XWService {
ADDED_MISSING, ADDED_MISSING,
STOP_SELF, STOP_SELF,
HANDLEDATA, HANDLEDATA,
RESEND,
}; };
private static final String CMD_STR = "CMD"; private static final String CMD_STR = "CMD";
@ -98,8 +92,6 @@ public class SMSService extends XWService {
private int m_nReceived = 0; private int m_nReceived = 0;
private static int s_nSent = 0; private static int s_nSent = 0;
private static Map<String, HashMap <Integer, MsgStore>> s_partialMsgs
= new HashMap<String, HashMap <Integer, MsgStore>>();
private static Set<Integer> s_sentDied = new HashSet<Integer>(); private static Set<Integer> s_sentDied = new HashSet<Integer>();
public static class SMSPhoneInfo { public static class SMSPhoneInfo {
@ -366,6 +358,9 @@ public class SMSService extends XWService {
phone = intent.getStringExtra( PHONE ); phone = intent.getStringExtra( PHONE );
sendDiedPacket( phone, gameID ); sendDiedPacket( phone, gameID );
break; break;
case RESEND:
phone = intent.getStringExtra( PHONE );
resendFor( phone, null, false );
} }
} }
@ -434,18 +429,18 @@ public class SMSService extends XWService {
dos.writeInt( gameID ); dos.writeInt( gameID );
dos.write( bytes, 0, bytes.length ); dos.write( bytes, 0, bytes.length );
dos.flush(); dos.flush();
if ( send( SMS_CMD.DATA, bas.toByteArray(), phone ) ) { send( SMS_CMD.DATA, bas.toByteArray(), phone );
nSent = bytes.length; nSent = bytes.length;
}
} catch ( java.io.IOException ioe ) { } catch ( java.io.IOException ioe ) {
Log.ex( TAG, ioe ); Log.ex( TAG, ioe );
} }
return nSent; 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 throws java.io.IOException
{ {
Log.d( TAG, "send(%s, len=%d)", cmd, bytes.length );
ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 ); ByteArrayOutputStream bas = new ByteArrayOutputStream( 128 );
DataOutputStream dos = new DataOutputStream( bas ); DataOutputStream dos = new DataOutputStream( bas );
dos.writeByte( SMS_PROTO_VERSION ); dos.writeByte( SMS_PROTO_VERSION );
@ -457,42 +452,44 @@ public class SMSService extends XWService {
dos.flush(); dos.flush();
byte[] data = bas.toByteArray(); byte[] data = bas.toByteArray();
byte[][] msgs = breakAndEncode( data );
boolean result = null != msgs && sendBuffers( msgs, phone ); boolean newSMSEnabled = XWPrefs.getSMSProtoEnabled( this );
return result; 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[] waitSecs = { 0 };
int count = (msg.length + (MAX_LEN_BINARY-1)) / MAX_LEN_BINARY; byte[][] msgs = XwJNI.smsproto_prepOutbound( data, phone, forceNow, waitSecs );
if ( count < MAX_MSG_COUNT ) { if ( null != msgs ) {
result = new byte[count][]; sendBuffers( msgs, phone );
int msgID = ++s_nSent % 0x000000FF; }
if ( waitSecs[0] > 0 ) {
Assert.assertFalse( forceNow );
postResend( phone, waitSecs[0] );
}
}
int start = 0; private void postResend( final String phone, final int waitSecs )
int end = 0; {
for ( int ii = 0; ii < count; ++ii ) { Log.d( TAG, "postResend" );
int len = msg.length - end; new Thread(new Runnable() {
if ( len > MAX_LEN_BINARY ) { @Override
len = MAX_LEN_BINARY; public void run() {
} try {
end += len; Thread.sleep( waitSecs * 1000 );
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; Log.d( TAG, "postResend.run()" );
start = end; Intent intent = getIntentTo( SMSService.this,
SMSAction.RESEND );
intent.putExtra( PHONE, phone );
startService( intent );
} catch ( InterruptedException ie ) {
Log.e( TAG, ie.getMessage() );
} }
} else {
Log.w( TAG, "breakAndEncode(): msg count %d too large; dropping",
count );
} }
return result; } ).start();
} }
private void receive( SMS_CMD cmd, byte[] data, String phone ) private void receive( SMS_CMD cmd, byte[] data, String phone )
@ -534,53 +531,20 @@ public class SMSService extends XWService {
private void receiveBuffer( byte[] buffer, String senderPhone ) private void receiveBuffer( byte[] buffer, String senderPhone )
{ {
byte proto = buffer[0]; byte[][] msgs = XwJNI.smsproto_prepInbound( buffer, senderPhone );
int id = buffer[1]; if ( null != msgs ) {
int index = buffer[2]; for ( byte[] msg : msgs ) {
int count = buffer[3]; if ( !disAssemble( senderPhone, msg ) ) {
byte[] rest = new byte[buffer.length - 4]; Log.e( TAG, "failed on message from %s", senderPhone );
System.arraycopy( buffer, 4, rest, 0, rest.length ); }
if ( tryAssemble( senderPhone, id, index, count, rest ) ) { }
postEvent( MultiEvent.SMS_RECEIVE_OK ); postEvent( MultiEvent.SMS_RECEIVE_OK );
} else { } else {
// Will see this when don't have SMS permission Log.w( TAG, "receiveBuffer(): bogus or incomplete message from phone %s",
Log.w( TAG, "receiveBuffer(): bogus message from phone %s",
senderPhone ); 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<Integer, MsgStore> perPhone =
s_partialMsgs.get( senderPhone );
if ( null == perPhone ) {
perPhone = new HashMap <Integer, MsgStore>();
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 );
}
}
} else {
success = false;
}
return success;
}
private boolean disAssemble( String senderPhone, byte[] fullMsg ) private boolean disAssemble( String senderPhone, byte[] fullMsg )
{ {
boolean success = false; boolean success = false;
@ -685,7 +649,7 @@ public class SMSService extends XWService {
Log.i( TAG, "dropping because SMS disabled" ); 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 ); DbgUtils.showf( this, "Sent msg %d", s_nSent );
} }
@ -777,70 +741,4 @@ public class SMSService extends XWService {
return sendPacket( addr.sms_phone, gameID, buf ); 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;
}
}
} }

View file

@ -256,7 +256,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
m_dals = dals; m_dals = dals;
} }
@Override protected String doInBackground( Void... unused ) @Override
protected String doInBackground( Void... unused )
{ {
HttpURLConnection conn HttpURLConnection conn
= NetUtils.makeHttpUpdateConn( m_context, "getUpdates" ); = NetUtils.makeHttpUpdateConn( m_context, "getUpdates" );
@ -267,7 +268,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
return json; return json;
} }
@Override protected void onPostExecute( String json ) @Override
protected void onPostExecute( String json )
{ {
if ( null != json ) { if ( null != json ) {
makeNotificationsIf( json, m_params ); makeNotificationsIf( json, m_params );

View file

@ -34,17 +34,16 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.text.ClipboardManager;
import android.database.Cursor; import android.database.Cursor;
import android.media.Ringtone; import android.media.Ringtone;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.PhoneLookup;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.FileProvider;
import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager; import android.telephony.TelephonyManager;
import android.text.ClipboardManager;
import android.util.Base64; import android.util.Base64;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -486,13 +485,16 @@ public class Utils {
return null == s_appVersion? 0 : s_appVersion; 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 = FileProvider
Uri uri = Uri.parse( withScheme ); .getUriForFile( context,
BuildConfig.APPLICATION_ID + ".provider",
file );
Intent intent = new Intent( Intent.ACTION_VIEW ); Intent intent = new Intent( Intent.ACTION_VIEW );
intent.setDataAndType( uri, XWConstants.APK_TYPE ); 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; return intent;
} }
@ -501,7 +503,7 @@ public class Utils {
{ {
boolean result = false; boolean result = false;
PackageManager pm = context.getPackageManager(); PackageManager pm = context.getPackageManager();
Intent intent = makeInstallIntent( path ); Intent intent = makeInstallIntent( context, path );
List<ResolveInfo> doers = List<ResolveInfo> doers =
pm.queryIntentActivities( intent, pm.queryIntentActivities( intent,
PackageManager.MATCH_DEFAULT_ONLY ); PackageManager.MATCH_DEFAULT_ONLY );

View file

@ -82,6 +82,11 @@ public class XWPrefs {
return getPrefsBoolean( context, R.string.key_enable_sms_toself, false ); 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 ) public static boolean getHideNewgameButtons( Context context )
{ {
return getPrefsBoolean( context, R.string.key_hide_newgames, return getPrefsBoolean( context, R.string.key_hide_newgames,

View file

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

View file

@ -212,7 +212,6 @@ public class JNIThread extends Thread {
} }
CommonPrefs cp = CommonPrefs.get( context ); CommonPrefs cp = CommonPrefs.get( context );
JNIUtils jniUtils = JNIUtilsImpl.get( context );
// Assert.assertNull( m_jniGamePtr ); // fired!! // Assert.assertNull( m_jniGamePtr ); // fired!!
if ( null != m_jniGamePtr ) { if ( null != m_jniGamePtr ) {
@ -224,15 +223,13 @@ public class JNIThread extends Thread {
dictNames, pairs.m_bytes, dictNames, pairs.m_bytes,
pairs.m_paths, pairs.m_paths,
m_gi.langName( m_context ), m_gi.langName( m_context ),
utils, jniUtils, utils, null, cp, m_xport );
null, cp, m_xport );
} }
if ( null == m_jniGamePtr ) { if ( null == m_jniGamePtr ) {
m_jniGamePtr = XwJNI.initNew( m_gi, dictNames, pairs.m_bytes, m_jniGamePtr = XwJNI.initNew( m_gi, dictNames, pairs.m_bytes,
pairs.m_paths, pairs.m_paths,
m_gi.langName(m_context), m_gi.langName(m_context),
utils, jniUtils, null, cp, utils, null, cp, m_xport );
m_xport );
} }
Assert.assertNotNull( m_jniGamePtr ); Assert.assertNotNull( m_jniGamePtr );
m_lastSavedState = Arrays.hashCode( stream ); m_lastSavedState = Arrays.hashCode( stream );

View file

@ -25,6 +25,7 @@ import android.content.Context;
import junit.framework.Assert; import junit.framework.Assert;
import org.eehouse.android.xw4.DBUtils; import org.eehouse.android.xw4.DBUtils;
import org.eehouse.android.xw4.XWApp;
import org.eehouse.android.xw4.Log; import org.eehouse.android.xw4.Log;
import org.eehouse.android.xw4.Utils; import org.eehouse.android.xw4.Utils;
@ -41,14 +42,13 @@ public class JNIUtilsImpl implements JNIUtils {
private static JNIUtilsImpl s_impl = null; private static JNIUtilsImpl s_impl = null;
private Context m_context; 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 ) { if ( null == s_impl ) {
s_impl = new JNIUtilsImpl(); s_impl = new JNIUtilsImpl( XWApp.getContext() );
} }
s_impl.m_context = context;
return s_impl; return s_impl;
} }
@ -133,6 +133,8 @@ public class JNIUtilsImpl implements JNIUtils {
public String getMD5SumFor( byte[] bytes ) public String getMD5SumFor( byte[] bytes )
{ {
String result = null;
if ( bytes != null ) {
byte[] digest = null; byte[] digest = null;
try { try {
MessageDigest md = MessageDigest.getInstance("MD5"); MessageDigest md = MessageDigest.getInstance("MD5");
@ -150,7 +152,9 @@ public class JNIUtilsImpl implements JNIUtils {
} catch ( java.security.NoSuchAlgorithmException nsae ) { } catch ( java.security.NoSuchAlgorithmException nsae ) {
Log.ex( TAG, nsae ); Log.ex( TAG, nsae );
} }
return Utils.digestToString( digest ); result = Utils.digestToString( digest );
}
return result;
} }
public String getMD5SumFor( String dictName, byte[] bytes ) public String getMD5SumFor( String dictName, byte[] bytes )

View file

@ -60,50 +60,10 @@ public interface UtilCtxt {
void remSelected(); void remSelected();
void setIsServer( boolean isServer ); 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 bonusSquareHeld( int bonus );
void playerScoreHeld( int player ); void playerScoreHeld( int player );
void cellSquareHeld( String words ); 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 notifyMove( String query );
void notifyTrade( String[] tiles ); void notifyTrade( String[] tiles );
@ -146,6 +106,4 @@ public interface UtilCtxt {
boolean turnLost ); boolean turnLost );
void showChat( String msg, int fromIndx, String fromName, int tsSeconds ); void showChat( String msg, int fromIndx, String fromName, int tsSeconds );
boolean phoneNumbersSame( String num1, String num2 );
} }

View file

@ -21,16 +21,11 @@
package org.eehouse.android.xw4.jni; package org.eehouse.android.xw4.jni;
import android.content.Context; import android.content.Context;
import android.telephony.PhoneNumberUtils;
import junit.framework.Assert; import junit.framework.Assert;
import org.eehouse.android.xw4.DevID;
import org.eehouse.android.xw4.Log; 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.jni.CommsAddrRec.CommsConnTypeSet;
import org.eehouse.android.xw4.loc.LocUtils;
public class UtilCtxtImpl implements UtilCtxt { public class UtilCtxtImpl implements UtilCtxt {
private static final String TAG = UtilCtxtImpl.class.getSimpleName(); private static final String TAG = UtilCtxtImpl.class.getSimpleName();
@ -44,222 +39,114 @@ public class UtilCtxtImpl implements UtilCtxt {
m_context = context; m_context = context;
} }
@Override
public void requestTime() { public void requestTime() {
subclassOverride( "requestTime" ); subclassOverride( "requestTime" );
} }
@Override
public void notifyPickTileBlank( int playerNum, int col, int row, public void notifyPickTileBlank( int playerNum, int col, int row,
String[] texts ) String[] texts )
{ {
subclassOverride( "userPickTileBlank" ); subclassOverride( "userPickTileBlank" );
} }
@Override
public void informNeedPickTiles( boolean isInitial, int playerNum, int nToPick, public void informNeedPickTiles( boolean isInitial, int playerNum, int nToPick,
String[] texts, int[] counts ) String[] texts, int[] counts )
{ {
subclassOverride( "informNeedPickTiles" ); subclassOverride( "informNeedPickTiles" );
} }
@Override
public void informNeedPassword( int player, String name ) public void informNeedPassword( int player, String name )
{ {
subclassOverride( "informNeedPassword" ); subclassOverride( "informNeedPassword" );
} }
@Override
public void turnChanged( int newTurn ) public void turnChanged( int newTurn )
{ {
subclassOverride( "turnChanged" ); subclassOverride( "turnChanged" );
} }
@Override
public boolean engineProgressCallback() public boolean engineProgressCallback()
{ {
// subclassOverride( "engineProgressCallback" ); // subclassOverride( "engineProgressCallback" );
return true; return true;
} }
@Override
public void setTimer( int why, int when, int handle ) public void setTimer( int why, int when, int handle )
{ {
subclassOverride( "setTimer" ); subclassOverride( "setTimer" );
} }
@Override
public void clearTimer( int why ) public void clearTimer( int why )
{ {
subclassOverride( "clearTimer" ); subclassOverride( "clearTimer" );
} }
@Override
public void remSelected() public void remSelected()
{ {
subclassOverride( "remSelected" ); subclassOverride( "remSelected" );
} }
@Override
public void setIsServer( boolean isServer ) public void setIsServer( boolean isServer )
{ {
subclassOverride( "setIsServer" ); subclassOverride( "setIsServer" );
} }
public String getDevID( /*out*/ byte[] typa ) @Override
{
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;
}
}
public void bonusSquareHeld( int bonus ) public void bonusSquareHeld( int bonus )
{ {
} }
@Override
public void playerScoreHeld( int player ) public void playerScoreHeld( int player )
{ {
} }
@Override
public void cellSquareHeld( String words ) public void cellSquareHeld( String words )
{ {
} }
public String getUserString( int stringCode ) @Override
{
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;
}
public void notifyMove( String query ) public void notifyMove( String query )
{ {
subclassOverride( "notifyMove" ); subclassOverride( "notifyMove" );
} }
@Override
public void notifyTrade( String[] tiles ) public void notifyTrade( String[] tiles )
{ {
subclassOverride( "notifyTrade" ); subclassOverride( "notifyTrade" );
} }
@Override
public void userError( int id ) public void userError( int id )
{ {
subclassOverride( "userError" ); subclassOverride( "userError" );
} }
@Override
public void informMove( int turn, String expl, String words ) public void informMove( int turn, String expl, String words )
{ {
subclassOverride( "informMove" ); subclassOverride( "informMove" );
} }
@Override
public void informUndo() public void informUndo()
{ {
subclassOverride( "informUndo" ); subclassOverride( "informUndo" );
} }
@Override
public void informNetDict( int lang, String oldName, public void informNetDict( int lang, String oldName,
String newName, String newSum, String newName, String newSum,
CurGameInfo.XWPhoniesChoice phonies ) CurGameInfo.XWPhoniesChoice phonies )
@ -267,6 +154,7 @@ public class UtilCtxtImpl implements UtilCtxt {
subclassOverride( "informNetDict" ); subclassOverride( "informNetDict" );
} }
@Override
public void informMissing( boolean isServer, public void informMissing( boolean isServer,
CommsConnTypeSet connTypes, CommsConnTypeSet connTypes,
int nDevices, int nMissingPlayers ) int nDevices, int nMissingPlayers )
@ -276,11 +164,13 @@ public class UtilCtxtImpl implements UtilCtxt {
// Probably want to cache the fact that the game over notification // Probably want to cache the fact that the game over notification
// showed up and then display it next time game's opened. // showed up and then display it next time game's opened.
@Override
public void notifyGameOver() public void notifyGameOver()
{ {
subclassOverride( "notifyGameOver" ); subclassOverride( "notifyGameOver" );
} }
@Override
public void notifyIllegalWords( String dict, String[] words, int turn, public void notifyIllegalWords( String dict, String[] words, int turn,
boolean turnLost ) boolean turnLost )
{ {
@ -288,17 +178,12 @@ public class UtilCtxtImpl implements UtilCtxt {
} }
// These need to go into some sort of chat DB, not dropped. // These need to go into some sort of chat DB, not dropped.
@Override
public void showChat( String msg, int fromIndx, String fromName, int tsSeconds ) public void showChat( String msg, int fromIndx, String fromName, int tsSeconds )
{ {
subclassOverride( "showChat" ); subclassOverride( "showChat" );
} }
public boolean phoneNumbersSame( String num1, String num2 )
{
boolean same = PhoneNumberUtils.compare( m_context, num1, num2 );
return same;
}
private void subclassOverride( String name ) { private void subclassOverride( String name ) {
// DbgUtils.logf( "%s::%s() called", getClass().getName(), name ); // DbgUtils.logf( "%s::%s() called", getClass().getName(), name );
} }

View file

@ -1,7 +1,7 @@
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */ /* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
/* /*
* Copyright 2009-2010 by Eric House (xwords@eehouse.org). All * Copyright 2009 - 2018 by Eric House (xwords@eehouse.org). All rights
* rights reserved. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
@ -97,7 +97,7 @@ public class XwJNI {
private int m_ptr; private int m_ptr;
private XwJNI() private XwJNI()
{ {
m_ptr = initGlobals(); m_ptr = initGlobals( new DUtilCtxt(), JNIUtilsImpl.get() );
} }
public static void cleanGlobals() public static void cleanGlobals()
@ -134,23 +134,30 @@ public class XwJNI {
public static native boolean timerFired( GamePtr gamePtr, int why, public static native boolean timerFired( GamePtr gamePtr, int why,
int when, int handle ); int when, int handle );
// Stateless methods public static byte[] gi_to_stream( CurGameInfo gi )
public static native byte[] gi_to_stream( CurGameInfo gi ); {
public static native void gi_from_stream( CurGameInfo gi, byte[] stream ); 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 ) public static byte[] nliToStream( NetLaunchInfo nli )
{ {
nli.freezeAddrs(); 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 ) public static NetLaunchInfo nliFromStream( byte[] stream )
{ {
NetLaunchInfo nli = new NetLaunchInfo(); NetLaunchInfo nli = new NetLaunchInfo();
nli_from_stream( nli, stream ); nli_from_stream( getJNI().m_ptr, nli, stream );
nli.unfreezeAddrs(); nli.unfreezeAddrs();
return nli; return nli;
} }
private static native void nli_from_stream( NetLaunchInfo nli, byte[] stream );
public static native void comms_getInitialAddr( CommsAddrRec addr, public static native void comms_getInitialAddr( CommsAddrRec addr,
String relayHost, String relayHost,
int relayPort ); int relayPort );
@ -160,8 +167,7 @@ public class XwJNI {
private static GamePtr initJNI( long rowid ) private static GamePtr initJNI( long rowid )
{ {
int seed = Utils.nextRandomInt(); int seed = Utils.nextRandomInt();
String tag = String.format( "%d", rowid ); int ptr = initJNI( getJNI().m_ptr, seed );
int ptr = initJNI( getJNI().m_ptr, seed, tag );
GamePtr result = 0 == ptr ? null : new GamePtr( ptr, rowid ); GamePtr result = 0 == ptr ? null : new GamePtr( ptr, rowid );
return result; return result;
} }
@ -170,13 +176,13 @@ public class XwJNI {
initFromStream( long rowid, byte[] stream, CurGameInfo gi, initFromStream( long rowid, byte[] stream, CurGameInfo gi,
String[] dictNames, byte[][] dictBytes, String[] dictNames, byte[][] dictBytes,
String[] dictPaths, String langName, String[] dictPaths, String langName,
UtilCtxt util, JNIUtils jniu, DrawCtx draw, UtilCtxt util, DrawCtx draw,
CommonPrefs cp, TransportProcs procs ) CommonPrefs cp, TransportProcs procs )
{ {
GamePtr gamePtr = initJNI( rowid ); GamePtr gamePtr = initJNI( rowid );
if ( game_makeFromStream( gamePtr, stream, gi, dictNames, dictBytes, if ( game_makeFromStream( gamePtr, stream, gi, dictNames, dictBytes,
dictPaths, langName, util, jniu, draw, dictPaths, langName, util, draw,
cp, procs ) ) { cp, procs ) ) {
gamePtr.retain(); gamePtr.retain();
} else { } else {
@ -189,12 +195,11 @@ public class XwJNI {
public static synchronized GamePtr public static synchronized GamePtr
initNew( CurGameInfo gi, String[] dictNames, byte[][] dictBytes, initNew( CurGameInfo gi, String[] dictNames, byte[][] dictBytes,
String[] dictPaths, String langName, UtilCtxt util, String[] dictPaths, String langName, UtilCtxt util,
JNIUtils jniu, DrawCtx draw, CommonPrefs cp, DrawCtx draw, CommonPrefs cp, TransportProcs procs )
TransportProcs procs )
{ {
GamePtr gamePtr = initJNI( 0 ); GamePtr gamePtr = initJNI( 0 );
game_makeNewGame( gamePtr, gi, dictNames, dictBytes, dictPaths, game_makeNewGame( gamePtr, gi, dictNames, dictBytes, dictPaths,
langName, util, jniu, draw, cp, procs ); langName, util, draw, cp, procs );
return gamePtr.retain(); return gamePtr.retain();
} }
@ -211,7 +216,6 @@ public class XwJNI {
String[] dictPaths, String[] dictPaths,
String langName, String langName,
UtilCtxt util, UtilCtxt util,
JNIUtils jniu,
DrawCtx draw, CommonPrefs cp, DrawCtx draw, CommonPrefs cp,
TransportProcs procs ); TransportProcs procs );
@ -223,7 +227,6 @@ public class XwJNI {
String[] dictPaths, String[] dictPaths,
String langName, String langName,
UtilCtxt util, UtilCtxt util,
JNIUtils jniu,
DrawCtx draw, DrawCtx draw,
CommonPrefs cp, CommonPrefs cp,
TransportProcs procs ); TransportProcs procs );
@ -400,6 +403,19 @@ public class XwJNI {
public static native boolean comms_getAddrDisabled( GamePtr gamePtr, CommsConnType typ, public static native boolean comms_getAddrDisabled( GamePtr gamePtr, CommsConnType typ,
boolean send ); 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 // Dicts
public static class DictWrapper { public static class DictWrapper {
private int m_dictPtr; private int m_dictPtr;
@ -439,12 +455,10 @@ public class XwJNI {
public static native boolean dict_tilesAreSame( int dict1, int dict2 ); public static native boolean dict_tilesAreSame( int dict1, int dict2 );
public static native String[] dict_getChars( int dict ); public static native String[] dict_getChars( int dict );
public static boolean dict_getInfo( byte[] dict, String name, public static boolean dict_getInfo( byte[] dict, String name, String path,
String path, JNIUtils jniu,
boolean check, DictInfo info ) boolean check, DictInfo info )
{ {
return dict_getInfo( getJNI().m_ptr, dict, name, path, jniu, return dict_getInfo( getJNI().m_ptr, dict, name, path, check, info );
check, info );
} }
public static native int dict_getTileValue( int dictPtr, int tile ); public static native int dict_getTileValue( int dictPtr, int tile );
@ -452,9 +466,9 @@ public class XwJNI {
// Dict iterator // Dict iterator
public final static int MAX_COLS_DICT = 15; // from dictiter.h public final static int MAX_COLS_DICT = 15; // from dictiter.h
public static int dict_iter_init( byte[] dict, String name, 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, public static native void dict_iter_setMinMax( int closure,
int min, int max ); int min, int max );
@ -469,19 +483,32 @@ public class XwJNI {
public static native String dict_iter_getDesc( int closure ); public static native String dict_iter_getDesc( int closure );
// Private methods -- called only here // Private methods -- called only here
private static native int initGlobals(); private static native int initGlobals(DUtilCtxt dutil, JNIUtils jniu);
private static native void cleanGlobals( int globals ); private static native void cleanGlobals( int jniState );
private static native int initJNI( int jniState, int seed, String tag ); 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 envDone( int globals );
private static native void dict_ref( int dictPtr ); private static native void dict_ref( int dictPtr );
private static native void dict_unref( int dictPtr ); private static native void dict_unref( int dictPtr );
private static native boolean dict_getInfo( int jniState, byte[] dict, private static native boolean dict_getInfo( int jniState, byte[] dict,
String name, String path, String name, String path,
JNIUtils jniu, boolean check, boolean check,
DictInfo info ); DictInfo info );
private static native int dict_iter_init( int jniState, byte[] dict, private static native int dict_iter_init( int jniState, byte[] dict,
String name, String path, String name, String path );
JNIUtils jniu );
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 ); private static native boolean haveEnv( int jniState );
} }

View file

@ -125,6 +125,7 @@
<string name="key_enable_dup_invite">key_enable_dup_invite</string> <string name="key_enable_dup_invite">key_enable_dup_invite</string>
<string name="key_enable_nfc_toself">key_enable_nfc_toself</string> <string name="key_enable_nfc_toself">key_enable_nfc_toself</string>
<string name="key_enable_sms_toself">key_enable_sms_toself</string> <string name="key_enable_sms_toself">key_enable_sms_toself</string>
<string name="key_enable_smsproto">key_enable_smsproto</string>
<string name="key_ignore_gcm">key_ignore_gcm</string> <string name="key_ignore_gcm">key_ignore_gcm</string>
<string name="key_show_gcm">key_show_gcm</string> <string name="key_show_gcm">key_show_gcm</string>
<string name="key_nag_intervals">key_nag_intervals</string> <string name="key_nag_intervals">key_nag_intervals</string>

View file

@ -2516,6 +2516,10 @@
<string name="nag_intervals">Reminder intervals (minutes1,minutes2,...)</string> <string name="nag_intervals">Reminder intervals (minutes1,minutes2,...)</string>
<string name="enable_nfc_toself_title">Enable NFC to self</string> <string name="enable_nfc_toself_title">Enable NFC to self</string>
<string name="enable_nfc_toself_summary">Fake invitation to aid debugging</string> <string name="enable_nfc_toself_summary">Fake invitation to aid debugging</string>
<string name="enable_smsproto_title">Use new/experimental SMS code</string>
<string name="enable_smsproto_summary">(Requires that opponent be using it too)</string>
<string name="enable_sms_toself_title">Short-circuit SMS to self</string> <string name="enable_sms_toself_title">Short-circuit SMS to self</string>
<string name="enable_sms_toself_summary">Skip radio when phone numbers same</string> <string name="enable_sms_toself_summary">Skip radio when phone numbers same</string>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>

View file

@ -437,6 +437,11 @@
<PreferenceScreen android:title="@string/pref_group_sms_title" <PreferenceScreen android:title="@string/pref_group_sms_title"
android:summary="@string/pref_group_sms_summary" android:summary="@string/pref_group_sms_summary"
> >
<CheckBoxPreference android:key="@string/key_enable_smsproto"
android:title="@string/enable_smsproto_title"
android:summary="@string/enable_smsproto_summary"
android:defaultValue="false"
/>
<CheckBoxPreference android:key="@string/key_enable_sms_toself" <CheckBoxPreference android:key="@string/key_enable_sms_toself"
android:title="@string/enable_sms_toself_title" android:title="@string/enable_sms_toself_title"
android:summary="@string/enable_sms_toself_summary" android:summary="@string/enable_sms_toself_summary"

View file

@ -84,6 +84,7 @@ COMMON_SRC_FILES += \
$(COMMON_PATH)/movestak.c \ $(COMMON_PATH)/movestak.c \
$(COMMON_PATH)/dbgutil.c \ $(COMMON_PATH)/dbgutil.c \
$(COMMON_PATH)/nli.c \ $(COMMON_PATH)/nli.c \
$(COMMON_PATH)/smsproto.c \
LOCAL_CFLAGS+=$(LOCAL_C_INCLUDES) $(LOCAL_DEFINES) -Wall -std=c99 LOCAL_CFLAGS+=$(LOCAL_C_INCLUDES) $(LOCAL_DEFINES) -Wall -std=c99
LOCAL_SRC_FILES := $(linux_SRC_FILES) $(LOCAL_SRC_FILES) $(COMMON_SRC_FILES) LOCAL_SRC_FILES := $(linux_SRC_FILES) $(LOCAL_SRC_FILES) $(COMMON_SRC_FILES)

View file

@ -26,15 +26,16 @@
typedef struct _JNIState JNIState; typedef struct _JNIState JNIState;
typedef struct _AndGlobals { typedef struct _AndGameGlobals {
VTableMgr* vtMgr; VTableMgr* vtMgr;
CurGameInfo* gi; CurGameInfo* gi;
DrawCtx* dctx; DrawCtx* dctx;
XW_UtilCtxt* util; XW_UtilCtxt* util;
struct JNIUtilCtxt* jniutil; struct JNIUtilCtxt* jniutil;
TransportProcs* xportProcs; TransportProcs* xportProcs;
XW_DUtilCtxt* dutil;
JNIState* state; JNIState* state;
} AndGlobals; } AndGameGlobals;
typedef struct _EnvThreadInfo EnvThreadInfo; typedef struct _EnvThreadInfo EnvThreadInfo;

View file

@ -689,7 +689,7 @@ jEnumToInt( JNIEnv* env, jobject jenum )
} }
XWStreamCtxt* XWStreamCtxt*
and_empty_stream( MPFORMAL AndGlobals* globals ) and_empty_stream( MPFORMAL AndGameGlobals* globals )
{ {
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globals->vtMgr, XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globals->vtMgr,
globals, 0, NULL ); globals, 0, NULL );

View file

@ -31,7 +31,7 @@
/* callback for streams */ /* callback for streams */
void and_send_on_close( XWStreamCtxt* stream, void* closure ); 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 { typedef struct _SetInfo {
const char* name; const char* name;

View file

@ -30,6 +30,18 @@
#define MAX_QUANTITY_STRS 4 #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 { typedef struct _TimerStorage {
XWTimerProc proc; XWTimerProc proc;
void* closure; void* closure;
@ -40,21 +52,8 @@ typedef struct _AndUtil {
EnvThreadInfo* ti; EnvThreadInfo* ti;
jobject jutil; /* global ref to object implementing XW_UtilCtxt */ jobject jutil; /* global ref to object implementing XW_UtilCtxt */
TimerStorage timerStorage[NUM_TIMERS_PLUS_ONE]; TimerStorage timerStorage[NUM_TIMERS_PLUS_ONE];
XP_UCHAR* userStrings[N_AND_USER_STRINGS];
XP_U32 userStringsBits;
#ifdef XWFEATURE_DEVID
XP_UCHAR* devIDStorage;
#endif
} AndUtil; } AndUtil;
static VTableMgr*
and_util_getVTManager( XW_UtilCtxt* uc )
{
AndGlobals* globals = (AndGlobals*)uc->closure;
return globals->vtMgr;
}
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
static XWStreamCtxt* static XWStreamCtxt*
and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo ) and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo )
@ -62,7 +61,7 @@ and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo )
#ifdef DEBUG #ifdef DEBUG
AndUtil* util = (AndUtil*)uc; AndUtil* util = (AndUtil*)uc;
#endif #endif
AndGlobals* globals = (AndGlobals*)uc->closure; AndGameGlobals* globals = (AndGameGlobals*)uc->closure;
XWStreamCtxt* stream = and_empty_stream( MPPARM(util->util.mpool) XWStreamCtxt* stream = and_empty_stream( MPPARM(util->util.mpool)
globals ); globals );
stream_setAddress( stream, channelNo ); stream_setAddress( stream, channelNo );
@ -83,6 +82,14 @@ and_util_makeStreamFromAddr( XW_UtilCtxt* uc, XP_PlayerAddr channelNo )
__func__ ); \ __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), static XWBonusType and_util_getSquareBonus( XW_UtilCtxt* XP_UNUSED(uc),
XP_U16 boardSize, XP_U16 boardSize,
XP_U16 col, XP_U16 row ) XP_U16 col, XP_U16 row )
@ -349,26 +356,24 @@ and_util_altKeyDown( XW_UtilCtxt* uc )
return XP_FALSE; return XP_FALSE;
} }
XP_U32 XP_U32
and_util_getCurSeconds( XW_UtilCtxt* uc ) and_dutil_getCurSeconds( XW_DUtilCtxt* duc )
{ {
AndUtil* andutil = (AndUtil*)uc; AndDUtil* anddutil = (AndDUtil*)duc;
XP_U32 curSeconds = getCurSeconds( ENVFORME( andutil->ti ) ); XP_U32 curSeconds = getCurSeconds( ENVFORME( anddutil->ti ) );
/* struct timeval tv; */ /* struct timeval tv; */
/* gettimeofday( &tv, NULL ); */ /* gettimeofday( &tv, NULL ); */
/* XP_LOGF( "%s: %d vs %d", __func__, (int)tv.tv_sec, (int)curSeconds ); */ /* XP_LOGF( "%s: %d vs %d", __func__, (int)tv.tv_sec, (int)curSeconds ); */
return curSeconds; return curSeconds;
} }
static DictionaryCtxt* static DictionaryCtxt*
and_util_makeEmptyDict( XW_UtilCtxt* uc ) and_util_makeEmptyDict( XW_UtilCtxt* uc )
{ {
#ifdef STUBBED_DICT #ifdef STUBBED_DICT
XP_ASSERT(0); XP_ASSERT(0);
#else #else
AndGlobals* globals = (AndGlobals*)uc->closure; AndGameGlobals* globals = (AndGameGlobals*)uc->closure;
AndUtil* andutil = (AndUtil*)uc; AndUtil* andutil = (AndUtil*)uc;
DictionaryCtxt* result = DictionaryCtxt* result =
and_dictionary_make_empty( MPPARM( ((AndUtil*)uc)->util.mpool ) and_dictionary_make_empty( MPPARM( ((AndUtil*)uc)->util.mpool )
@ -378,51 +383,51 @@ and_util_makeEmptyDict( XW_UtilCtxt* uc )
} }
static const XP_UCHAR* 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 = ""; 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 */ 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] ) { if ( ! dutil->userStrings[index] ) {
jstring jresult = (*env)->CallObjectMethod( env, util->jutil, mid, jstring jresult = (*env)->CallObjectMethod( env, dutil->jdutil, mid,
stringCode ); stringCode );
jsize len = (*env)->GetStringUTFLength( env, jresult ); 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 ); const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL );
XP_MEMCPY( buf, jchars, len ); XP_MEMCPY( buf, jchars, len );
buf[len] = '\0'; buf[len] = '\0';
(*env)->ReleaseStringUTFChars( env, jresult, jchars ); (*env)->ReleaseStringUTFChars( env, jresult, jchars );
deleteLocalRef( env, jresult ); deleteLocalRef( env, jresult );
util->userStrings[index] = buf; dutil->userStrings[index] = buf;
} }
result = util->userStrings[index]; result = dutil->userStrings[index];
UTIL_CBK_TAIL(); DUTIL_CBK_TAIL();
return result; return result;
} }
static const XP_UCHAR* 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 = ""; 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 */ int index = stringCode - 1; /* see LocalizedStrIncludes.h */
XP_ASSERT( index < VSIZE( util->userStrings ) ); XP_ASSERT( index < VSIZE( dutil->userStrings ) );
XP_UCHAR** ptrs; XP_UCHAR** ptrs;
util->userStringsBits |= 1 << index; dutil->userStringsBits |= 1 << index;
ptrs = (XP_UCHAR**)util->userStrings[index]; ptrs = (XP_UCHAR**)dutil->userStrings[index];
if ( !ptrs ) { if ( !ptrs ) {
ptrs = (XP_UCHAR**)XP_CALLOC( util->util.mpool, MAX_QUANTITY_STRS * sizeof(*ptrs) ); ptrs = (XP_UCHAR**)XP_CALLOC( dutil->dutil.mpool, MAX_QUANTITY_STRS * sizeof(*ptrs) );
util->userStrings[index] = (XP_UCHAR*)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 ); stringCode, quantity );
const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL ); const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL );
int indx = 0; int indx = 0;
@ -439,7 +444,7 @@ and_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 stringCode, XP_U16 quant
if ( !ptrs[indx] ) { if ( !ptrs[indx] ) {
XP_ASSERT( indx < MAX_QUANTITY_STRS ); XP_ASSERT( indx < MAX_QUANTITY_STRS );
jsize len = (*env)->GetStringUTFLength( env, jresult ); 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 ); XP_MEMCPY( buf, jchars, len );
buf[len] = '\0'; buf[len] = '\0';
ptrs[indx] = buf; ptrs[indx] = buf;
@ -449,10 +454,83 @@ and_util_getUserQuantityString( XW_UtilCtxt* uc, XP_U16 stringCode, XP_U16 quant
deleteLocalRef( env, jresult ); deleteLocalRef( env, jresult );
result = ptrs[indx]; result = ptrs[indx];
UTIL_CBK_TAIL(); DUTIL_CBK_TAIL();
return result; 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 static void
and_util_notifyIllegalWords( XW_UtilCtxt* uc, BadWordInfo* bwi, and_util_notifyIllegalWords( XW_UtilCtxt* uc, BadWordInfo* bwi,
XP_U16 turn, XP_Bool turnLost ) XP_U16 turn, XP_Bool turnLost )
@ -519,18 +597,18 @@ and_util_playerScoreHeld( XW_UtilCtxt* uc, XP_U16 player )
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
static XP_Bool static XP_Bool
and_util_phoneNumbersSame( XW_UtilCtxt* uc, const XP_UCHAR* p1, and_dutil_phoneNumbersSame( XW_DUtilCtxt* duc, const XP_UCHAR* p1,
const XP_UCHAR* p2 ) const XP_UCHAR* p2 )
{ {
XP_Bool same = 0 == strcmp( p1, p2 ); XP_Bool same = 0 == strcmp( p1, p2 );
if ( !same ) { if ( !same ) {
UTIL_CBK_HEADER( "phoneNumbersSame", DUTIL_CBK_HEADER( "phoneNumbersSame",
"(Ljava/lang/String;Ljava/lang/String;)Z" ); "(Ljava/lang/String;Ljava/lang/String;)Z" );
jstring js1 = (*env)->NewStringUTF( env, p1 ); jstring js1 = (*env)->NewStringUTF( env, p1 );
jstring js2 = (*env)->NewStringUTF( env, p2 ); 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 ); deleteLocalRefs( env, js1, js2, DELETE_NO_REF );
UTIL_CBK_TAIL(); DUTIL_CBK_TAIL();
} }
return same; return same;
} }
@ -587,50 +665,50 @@ and_util_setIsServer(XW_UtilCtxt* uc, XP_Bool isServer )
#ifdef XWFEATURE_DEVID #ifdef XWFEATURE_DEVID
static const XP_UCHAR* 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; const XP_UCHAR* result = NULL;
*typ = ID_TYPE_NONE; *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 ); 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 ) { if ( NULL != jresult ) {
const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL ); const char* jchars = (*env)->GetStringUTFChars( env, jresult, NULL );
jsize len = (*env)->GetStringUTFLength( env, jresult ); jsize len = (*env)->GetStringUTFLength( env, jresult );
if ( NULL != util->devIDStorage if ( NULL != dutil->devIDStorage
&& 0 == XP_MEMCMP( util->devIDStorage, jchars, len ) ) { && 0 == XP_MEMCMP( dutil->devIDStorage, jchars, len ) ) {
XP_LOGF( "%s: already have matching devID", __func__ ); XP_LOGF( "%s: already have matching devID", __func__ );
} else { } else {
XP_LOGF( "%s: allocating storage for devID", __func__ ); XP_LOGF( "%s: allocating storage for devID", __func__ );
XP_FREEP( util->util.mpool, &util->devIDStorage ); XP_FREEP( dutil->dutil.mpool, &dutil->devIDStorage );
util->devIDStorage = XP_MALLOC( util->util.mpool, len + 1 ); dutil->devIDStorage = XP_MALLOC( dutil->dutil.mpool, len + 1 );
XP_MEMCPY( util->devIDStorage, jchars, len ); XP_MEMCPY( dutil->devIDStorage, jchars, len );
util->devIDStorage[len] = '\0'; dutil->devIDStorage[len] = '\0';
} }
(*env)->ReleaseStringUTFChars( env, jresult, jchars ); (*env)->ReleaseStringUTFChars( env, jresult, jchars );
result = (const XP_UCHAR*)util->devIDStorage; result = (const XP_UCHAR*)dutil->devIDStorage;
jbyte* elems = (*env)->GetByteArrayElements( env, jbarr, NULL ); jbyte* elems = (*env)->GetByteArrayElements( env, jbarr, NULL );
*typ = (DevIDType)elems[0]; *typ = (DevIDType)elems[0];
(*env)->ReleaseByteArrayElements( env, jbarr, elems, 0 ); (*env)->ReleaseByteArrayElements( env, jbarr, elems, 0 );
} }
deleteLocalRef( env, jbarr ); deleteLocalRef( env, jbarr );
UTIL_CBK_TAIL(); DUTIL_CBK_TAIL();
return result; return result;
} }
static void static void
and_util_deviceRegistered( XW_UtilCtxt* uc, DevIDType typ, and_dutil_deviceRegistered( XW_DUtilCtxt* duc, DevIDType typ,
const XP_UCHAR* idRelay ) const XP_UCHAR* idRelay )
{ {
UTIL_CBK_HEADER( "deviceRegistered", DUTIL_CBK_HEADER( "deviceRegistered",
"(L" PKG_PATH("jni/UtilCtxt$DevIDType") ";Ljava/lang/String;)V" ); "(L" PKG_PATH("jni/UtilCtxt$DevIDType") ";Ljava/lang/String;)V" );
jstring jstr = (*env)->NewStringUTF( env, idRelay ); jstring jstr = (*env)->NewStringUTF( env, idRelay );
jobject jtyp = intToJEnum( env, typ, jobject jtyp = intToJEnum( env, typ,
PKG_PATH("jni/UtilCtxt$DevIDType") ); 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 ); deleteLocalRefs( env, jstr, jtyp, DELETE_NO_REF );
UTIL_CBK_TAIL(); DUTIL_CBK_TAIL();
} }
#endif /* XWFEATURE_DEVID */ #endif /* XWFEATURE_DEVID */
@ -664,16 +742,23 @@ and_util_engineStopping( XW_UtilCtxt* uc )
} }
#endif #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 #ifdef COMMS_CHECKSUM
static XP_UCHAR* 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; AndDUtil* dutil = (AndDUtil*)duc;
JNIEnv* env = ENVFORME( util->ti ); JNIEnv* env = ENVFORME( dutil->ti );
AndGlobals* globals = (AndGlobals*)uc->closure; struct JNIUtilCtxt* jniutil = dutil->jniutil;
struct JNIUtilCtxt* jniutil = globals->jniutil;
jstring jsum = and_util_getMD5SumForBytes( jniutil, ptr, len ); 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 ); deleteLocalRef( env, jsum );
return result; return result;
} }
@ -682,7 +767,7 @@ and_util_md5sum( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len )
XW_UtilCtxt* XW_UtilCtxt*
makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi, makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
AndGlobals* closure ) AndGameGlobals* closure )
{ {
AndUtil* util = (AndUtil*)XP_CALLOC( mpool, sizeof(*util) ); AndUtil* util = (AndUtil*)XP_CALLOC( mpool, sizeof(*util) );
UtilVtable* vtable = (UtilVtable*)XP_CALLOC( mpool, sizeof(*vtable) ); UtilVtable* vtable = (UtilVtable*)XP_CALLOC( mpool, sizeof(*vtable) );
@ -697,7 +782,7 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
util->util.gameInfo = gi; util->util.gameInfo = gi;
#define SET_PROC(nam) vtable->m_util_##nam = and_util_##nam #define SET_PROC(nam) vtable->m_util_##nam = and_util_##nam
SET_PROC(getVTManager);
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
SET_PROC(makeStreamFromAddr); SET_PROC(makeStreamFromAddr);
#endif #endif
@ -725,10 +810,7 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
SET_PROC(clearTimer); SET_PROC(clearTimer);
SET_PROC(requestTime); SET_PROC(requestTime);
SET_PROC(altKeyDown); SET_PROC(altKeyDown);
SET_PROC(getCurSeconds);
SET_PROC(makeEmptyDict); SET_PROC(makeEmptyDict);
SET_PROC(getUserString);
SET_PROC(getUserQuantityString);
SET_PROC(notifyIllegalWords); SET_PROC(notifyIllegalWords);
#ifdef XWFEATURE_CHAT #ifdef XWFEATURE_CHAT
SET_PROC(showChat); SET_PROC(showChat);
@ -740,10 +822,6 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
SET_PROC(playerScoreHeld); SET_PROC(playerScoreHeld);
#endif #endif
#ifdef XWFEATURE_SMS
SET_PROC(phoneNumbersSame);
#endif
#ifdef XWFEATURE_BOARDWORDS #ifdef XWFEATURE_BOARDWORDS
SET_PROC(cellSquareHeld); SET_PROC(cellSquareHeld);
#endif #endif
@ -752,10 +830,6 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
SET_PROC(informMissing); SET_PROC(informMissing);
SET_PROC(addrChange); SET_PROC(addrChange);
SET_PROC(setIsServer); SET_PROC(setIsServer);
# ifdef XWFEATURE_DEVID
SET_PROC(getDevID);
SET_PROC(deviceRegistered);
# endif
#endif #endif
#ifdef XWFEATURE_SEARCHLIMIT #ifdef XWFEATURE_SEARCHLIMIT
SET_PROC(getTraySearchLimits); SET_PROC(getTraySearchLimits);
@ -764,9 +838,8 @@ makeUtil( MPFORMAL EnvThreadInfo* ti, jobject jutil, CurGameInfo* gi,
SET_PROC(engineStarting); SET_PROC(engineStarting);
SET_PROC(engineStopping); SET_PROC(engineStopping);
#endif #endif
#ifdef COMMS_CHECKSUM
SET_PROC(md5sum); SET_PROC(getDevUtilCtxt);
#endif
#undef SET_PROC #undef SET_PROC
return (XW_UtilCtxt*)util; return (XW_UtilCtxt*)util;
@ -778,31 +851,81 @@ destroyUtil( XW_UtilCtxt** utilc )
AndUtil* util = (AndUtil*)*utilc; AndUtil* util = (AndUtil*)*utilc;
JNIEnv* env = ENVFORME( util->ti ); JNIEnv* env = ENVFORME( util->ti );
for ( int ii = 0; ii < VSIZE(util->userStrings); ++ii ) { if ( NULL != util->jutil ) {
XP_UCHAR* ptr = util->userStrings[ii]; (*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 ( NULL != ptr ) {
if ( 0 == (util->userStringsBits & (1 << ii)) ) { if ( 0 == (dutil->userStringsBits & (1 << ii)) ) {
XP_FREE( util->util.mpool, ptr ); XP_FREE( dutil->dutil.mpool, ptr );
} else { } else {
XP_UCHAR** ptrs = (XP_UCHAR**)ptr; XP_UCHAR** ptrs = (XP_UCHAR**)ptr;
for ( int jj = 0; jj < MAX_QUANTITY_STRS; ++jj ) { for ( int jj = 0; jj < MAX_QUANTITY_STRS; ++jj ) {
ptr = ptrs[jj]; ptr = ptrs[jj];
if ( !!ptr ) { 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 #ifdef XWFEATURE_DEVID
XP_FREEP( util->util.mpool, &util->devIDStorage ); XP_FREEP( dutil->dutil.mpool, &dutil->devIDStorage );
#endif #endif
XP_FREE( util->util.mpool, util->util.vtable );
XP_FREE( util->util.mpool, util );
*utilc = NULL;
} }

View file

@ -25,10 +25,17 @@
#include "game.h" #include "game.h"
#include "util.h" #include "util.h"
#include "dutil.h"
#include "andglobals.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, XW_UtilCtxt* makeUtil( MPFORMAL EnvThreadInfo* ti, jobject j_util,
CurGameInfo* gi, AndGlobals* globals ); CurGameInfo* gi, AndGameGlobals* globals );
void destroyUtil( XW_UtilCtxt** util ); void destroyUtil( XW_UtilCtxt** util );
bool utilTimerFired( XW_UtilCtxt* util, XWTimerReason why, int handle ); bool utilTimerFired( XW_UtilCtxt* util, XWTimerReason why, int handle );

View file

@ -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. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -34,6 +35,7 @@
#include "dictiter.h" #include "dictiter.h"
#include "dictmgr.h" #include "dictmgr.h"
#include "nli.h" #include "nli.h"
#include "smsproto.h"
#include "utilwrapper.h" #include "utilwrapper.h"
#include "drawwrapper.h" #include "drawwrapper.h"
@ -60,9 +62,33 @@ struct _EnvThreadInfo {
typedef struct _JNIGlobalState { typedef struct _JNIGlobalState {
EnvThreadInfo ti; EnvThreadInfo ti;
DictMgrCtxt* dictMgr; DictMgrCtxt* dictMgr;
SMSProto* smsProto;
VTableMgr* vtMgr;
XW_DUtilCtxt* dutil;
JNIUtilCtxt* jniutil;
XP_Bool mpoolInUse;
MPSLOT MPSLOT
} JNIGlobalState; } 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
// #define LOG_MAPPING_ALL // #define LOG_MAPPING_ALL
@ -229,6 +255,7 @@ static void
tilesArrayToTileSet( JNIEnv* env, jintArray jtiles, TrayTileSet* tset ) tilesArrayToTileSet( JNIEnv* env, jintArray jtiles, TrayTileSet* tset )
{ {
if ( jtiles != NULL ) { if ( jtiles != NULL ) {
XP_ASSERT( !!jtiles );
jsize nTiles = (*env)->GetArrayLength( env, jtiles ); jsize nTiles = (*env)->GetArrayLength( env, jtiles );
int tmp[MAX_TRAY_TILES]; int tmp[MAX_TRAY_TILES];
getIntsFromArray( env, tmp, jtiles, nTiles, XP_FALSE ); getIntsFromArray( env, tmp, jtiles, nTiles, XP_FALSE );
@ -255,33 +282,43 @@ getState( JNIEnv* env, GamePtrType gamePtr )
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_initGlobals Java_org_eehouse_android_xw4_jni_XwJNI_initGlobals
( JNIEnv* env, jclass C ) ( JNIEnv* env, jclass C, jobject jdutil, jobject jniu )
{ {
#ifdef MEM_DEBUG #ifdef MEM_DEBUG
MemPoolCtx* mpool = mpool_make( NULL ); MemPoolCtx* mpool = mpool_make( NULL );
#endif #endif
JNIGlobalState* state = (JNIGlobalState*)XP_CALLOC( mpool, sizeof(*state) ); JNIGlobalState* globalState = (JNIGlobalState*)XP_CALLOC( mpool,
map_init( MPPARM(mpool) &state->ti, env ); sizeof(*globalState) );
state->dictMgr = dmgr_make( MPPARM_NOCOMMA( mpool ) ); map_init( MPPARM(mpool) &globalState->ti, env );
MPASSIGN( state->mpool, mpool ); globalState->jniutil = makeJNIUtil( MPPARM(mpool) env, &globalState->ti, jniu );
// LOG_RETURNF( "%p", state ); globalState->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) );
return (jint)state; 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 JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_cleanGlobals Java_org_eehouse_android_xw4_jni_XwJNI_cleanGlobals
( JNIEnv* env, jclass C, jint ptr ) ( JNIEnv* env, jclass C, jint jniGlobalPtr )
{ {
// LOG_FUNC(); // LOG_FUNC();
if ( 0 != ptr ) { if ( 0 != jniGlobalPtr ) {
JNIGlobalState* state = (JNIGlobalState*)ptr; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
XP_ASSERT( ENVFORME(&state->ti) == env );
dmgr_destroy( state->dictMgr );
#ifdef MEM_DEBUG #ifdef MEM_DEBUG
MemPoolCtx* mpool = state->mpool; MemPoolCtx* mpool = getMPool( globalState );
#endif #endif
map_destroy( &state->ti ); XP_ASSERT( ENVFORME(&globalState->ti) == env );
XP_FREE( mpool, state ); 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 ); mpool_destroy( mpool );
} }
} }
@ -511,6 +548,7 @@ loadCommonPrefs( JNIEnv* env, CommonPrefs* cp, jobject j_cp )
static XWStreamCtxt* static XWStreamCtxt*
streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream ) streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream )
{ {
XP_ASSERT( !!jstream );
int len = (*env)->GetArrayLength( env, jstream ); int len = (*env)->GetArrayLength( env, jstream );
XWStreamCtxt* stream = mem_stream_make_sized( MPPARM(mpool) vtMgr, XWStreamCtxt* stream = mem_stream_make_sized( MPPARM(mpool) vtMgr,
len, NULL, 0, NULL ); len, NULL, 0, NULL );
@ -525,15 +563,15 @@ streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream )
****************************************************/ ****************************************************/
JNIEXPORT jbyteArray JNICALL JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_gi_1to_1stream 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; jbyteArray result;
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG #ifdef MEM_DEBUG
MemPoolCtx* mpool = mpool_make( NULL ); MemPoolCtx* mpool = getMPool( globalState );
#endif #endif
CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi ); CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi );
VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr,
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) vtMgr,
NULL, 0, NULL ); NULL, 0, NULL );
game_saveToStream( NULL, gi, stream, 0 ); game_saveToStream( NULL, gi, stream, 0 );
@ -541,24 +579,20 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1to_1stream
result = streamToBArray( env, stream ); result = streamToBArray( env, stream );
stream_destroy( stream ); stream_destroy( stream );
releaseMPool( globalState );
vtmgr_destroy( MPPARM(mpool) vtMgr );
#ifdef MEM_DEBUG
mpool_destroy( mpool );
#endif
return result; return result;
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream 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 #ifdef MEM_DEBUG
MemPoolCtx* mpool = mpool_make( NULL ); MemPoolCtx* mpool = getMPool( globalState );
#endif #endif
VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env,
globalState->vtMgr, jstream );
XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, vtMgr, jstream );
CurGameInfo gi; CurGameInfo gi;
XP_MEMSET( &gi, 0, sizeof(gi) ); XP_MEMSET( &gi, 0, sizeof(gi) );
@ -572,50 +606,45 @@ Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream
gi_disposePlayerInfo( MPPARM(mpool) &gi ); gi_disposePlayerInfo( MPPARM(mpool) &gi );
stream_destroy( stream ); stream_destroy( stream );
vtmgr_destroy( MPPARM(mpool) vtMgr ); releaseMPool( globalState );
#ifdef MEM_DEBUG
mpool_destroy( mpool );
#endif
} }
JNIEXPORT jbyteArray JNICALL JNIEXPORT jbyteArray JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_nli_1to_1stream 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(); LOG_FUNC();
jbyteArray result; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG #ifdef MEM_DEBUG
MemPoolCtx* mpool = mpool_make( NULL ); MemPoolCtx* mpool = getMPool( globalState );
#endif #endif
jbyteArray result;
NetLaunchInfo nli = {0}; NetLaunchInfo nli = {0};
loadNLI( env, &nli, njli ); loadNLI( env, &nli, njli );
/* CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi ); */ /* CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi ); */
VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr,
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) vtMgr,
NULL, 0, NULL ); NULL, 0, NULL );
nli_saveToStream( &nli, stream ); nli_saveToStream( &nli, stream );
result = streamToBArray( env, stream ); result = streamToBArray( env, stream );
stream_destroy( stream ); stream_destroy( stream );
releaseMPool( globalState );
vtmgr_destroy( MPPARM(mpool) vtMgr );
#ifdef MEM_DEBUG
mpool_destroy( mpool );
#endif
return result; return result;
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_nli_1from_1stream 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(); LOG_FUNC();
JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
#ifdef MEM_DEBUG #ifdef MEM_DEBUG
MemPoolCtx* mpool = mpool_make( NULL ); MemPoolCtx* mpool = getMPool( globalState );
#endif #endif
VTableMgr* vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env,
XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, vtMgr, jstream ); globalState->vtMgr, jstream );
NetLaunchInfo nli = {0}; NetLaunchInfo nli = {0};
if ( nli_makeFromStream( &nli, stream ) ) { if ( nli_makeFromStream( &nli, stream ) ) {
@ -625,10 +654,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_nli_1from_1stream
} }
stream_destroy( stream ); stream_destroy( stream );
vtmgr_destroy( MPPARM(mpool) vtMgr ); releaseMPool( globalState );
#ifdef MEM_DEBUG
mpool_destroy( mpool );
#endif
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
@ -684,14 +710,18 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1unref
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo
( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jDictBytes, ( 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; jboolean result = false;
JNIGlobalState* state = (JNIGlobalState*)jniGlobalPtr; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
map_thread( &state->ti, env ); map_thread( &globalState->ti, env );
JNIUtilCtxt* jniutil = makeJNIUtil( MPPARM(state->mpool) env, &state->ti, jniu );
DictionaryCtxt* dict = makeDict( MPPARM(state->mpool) env, state->dictMgr, #ifdef MEM_DEBUG
jniutil, jname, jDictBytes, jpath, MemPoolCtx* mpool = getMPool( globalState );
#endif
DictionaryCtxt* dict = makeDict( MPPARM(mpool) env, globalState->dictMgr,
globalState->jniutil, jname, jDictBytes, jpath,
NULL, check ); NULL, check );
if ( NULL != dict ) { if ( NULL != dict ) {
if ( NULL != jinfo ) { if ( NULL != jinfo ) {
@ -704,8 +734,8 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo
dict_unref( dict ); dict_unref( dict );
result = true; result = true;
} }
destroyJNIUtil( env, &jniutil );
releaseMPool( globalState );
return result; return result;
} }
@ -739,10 +769,88 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getTileValue
return dict_getTileValue( (DictionaryCtxt*)dictPtr, tile ); 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 { struct _JNIState {
XWGame game; XWGame game;
JNIGlobalState* globalJNI; JNIGlobalState* globalJNI;
AndGlobals globals; AndGameGlobals globals;
// pthread_mutex_t msgMutex; // pthread_mutex_t msgMutex;
XP_U16 curSaveCount; XP_U16 curSaveCount;
XP_U16 lastSavedSize; XP_U16 lastSavedSize;
@ -761,27 +869,26 @@ struct _JNIState {
#define XWJNI_START_GLOBALS() \ #define XWJNI_START_GLOBALS() \
XWJNI_START() \ XWJNI_START() \
AndGlobals* globals = &state->globals; \ AndGameGlobals* globals = &state->globals; \
#define XWJNI_END() \ #define XWJNI_END() \
} \ } \
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_initJNI 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? */ /* Why am I doing this twice? */
/* struct timeval tv; */ /* struct timeval tv; */
/* gettimeofday( &tv, NULL ); */ /* gettimeofday( &tv, NULL ); */
/* srandom( tv.tv_sec ); */ /* srandom( tv.tv_sec ); */
#ifdef MEM_DEBUG #ifdef MEM_DEBUG
const char* tag = (*env)->GetStringUTFChars( env, jtag, NULL ); MemPoolCtx* mpool = ((JNIGlobalState*)jniGlobalPtr)->mpool;
MemPoolCtx* mpool = mpool_make( tag );
(*env)->ReleaseStringUTFChars( env, jtag, tag );
#endif #endif
JNIState* state = (JNIState*)XP_CALLOC( mpool, sizeof(*state) ); JNIState* state = (JNIState*)XP_CALLOC( mpool, sizeof(*state) );
state->globalJNI = (JNIGlobalState*)jniGlobalPtr; state->globalJNI = (JNIGlobalState*)jniGlobalPtr;
AndGlobals* globals = &state->globals; AndGameGlobals* globals = &state->globals;
globals->dutil = state->globalJNI->dutil;
globals->state = (JNIState*)state; globals->state = (JNIState*)state;
MPASSIGN( state->mpool, mpool ); MPASSIGN( state->mpool, mpool );
globals->vtMgr = make_vtablemgr(MPPARM_NOCOMMA(mpool)); globals->vtMgr = make_vtablemgr(MPPARM_NOCOMMA(mpool));
@ -807,8 +914,8 @@ JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame
( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject j_gi, ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject j_gi,
jobjectArray j_names, jobjectArray j_dicts, jobjectArray j_paths, jobjectArray j_names, jobjectArray j_dicts, jobjectArray j_paths,
jstring j_lang, jobject j_util, jobject jniu, jobject j_draw, jstring j_lang, jobject j_util, jobject j_draw, jobject j_cp,
jobject j_cp, jobject j_procs ) jobject j_procs )
{ {
XWJNI_START_GLOBALS(); XWJNI_START_GLOBALS();
EnvThreadInfo* ti = &state->globalJNI->ti; EnvThreadInfo* ti = &state->globalJNI->ti;
@ -816,7 +923,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame
globals->gi = gi; globals->gi = gi;
globals->util = makeUtil( MPPARM(mpool) ti, j_util, gi, globals->util = makeUtil( MPPARM(mpool) ti, j_util, gi,
globals ); globals );
globals->jniutil = makeJNIUtil( MPPARM(mpool) env, ti, jniu ); globals->jniutil = state->globalJNI->jniutil;
DrawCtx* dctx = NULL; DrawCtx* dctx = NULL;
if ( !!j_draw ) { if ( !!j_draw ) {
dctx = makeDraw( MPPARM(mpool) ti, 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 #ifdef MEM_DEBUG
MemPoolCtx* mpool = state->mpool; MemPoolCtx* mpool = state->mpool;
#endif #endif
AndGlobals* globals = &state->globals; AndGameGlobals* globals = &state->globals;
destroyGI( MPPARM(mpool) &globals->gi ); destroyGI( MPPARM(mpool) &globals->gi );
@ -867,22 +974,19 @@ JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1dispose
destroyDraw( &globals->dctx ); destroyDraw( &globals->dctx );
destroyXportProcs( &globals->xportProcs ); destroyXportProcs( &globals->xportProcs );
destroyUtil( &globals->util ); destroyUtil( &globals->util );
destroyJNIUtil( env, &globals->jniutil );
vtmgr_destroy( MPPARM(mpool) globals->vtMgr ); vtmgr_destroy( MPPARM(mpool) globals->vtMgr );
MAP_REMOVE( &state->globalJNI->ti, env ); MAP_REMOVE( &state->globalJNI->ti, env );
/* pthread_mutex_destroy( &state->msgMutex ); */ /* pthread_mutex_destroy( &state->msgMutex ); */
XP_FREE( mpool, state ); XP_FREE( mpool, state );
mpool_destroy( mpool );
} /* game_dispose */ } /* game_dispose */
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream
( JNIEnv* env, jclass C, GamePtrType gamePtr, jbyteArray jstream, jobject /*out*/jgi, ( JNIEnv* env, jclass C, GamePtrType gamePtr, jbyteArray jstream, jobject /*out*/jgi,
jobjectArray jdictNames, jobjectArray jdicts, jobjectArray jpaths, jobjectArray jdictNames, jobjectArray jdicts, jobjectArray jpaths,
jstring jlang, jobject jutil, jobject jniu, jobject jdraw, jobject jcp, jstring jlang, jobject jutil, jobject jdraw, jobject jcp, jobject jprocs )
jobject jprocs )
{ {
jboolean result; jboolean result;
DictionaryCtxt* dict; 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->gi = (CurGameInfo*)XP_CALLOC( mpool, sizeof(*globals->gi) );
globals->util = makeUtil( MPPARM(mpool) ti, jutil, globals->gi, globals); 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, makeDicts( MPPARM(state->globalJNI->mpool) env, state->globalJNI->dictMgr,
globals->jniutil, &dict, &dicts, jdictNames, jdicts, jpaths, globals->jniutil, &dict, &dicts, jdictNames, jdicts, jpaths,
jlang ); jlang );
@ -923,7 +1027,6 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream
destroyDraw( &globals->dctx ); destroyDraw( &globals->dctx );
destroyXportProcs( &globals->xportProcs ); destroyXportProcs( &globals->xportProcs );
destroyUtil( &globals->util ); destroyUtil( &globals->util );
destroyJNIUtil( env, &globals->jniutil );
destroyGI( MPPARM(mpool) &globals->gi ); destroyGI( MPPARM(mpool) &globals->gi );
} }
@ -1556,7 +1659,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_server_1writeFinalScores
void void
and_send_on_close( XWStreamCtxt* stream, void* closure ) and_send_on_close( XWStreamCtxt* stream, void* closure )
{ {
AndGlobals* globals = (AndGlobals*)closure; AndGameGlobals* globals = (AndGameGlobals*)closure;
JNIState* state = (JNIState*)globals->state; JNIState* state = (JNIState*)globals->state;
XP_ASSERT( !!state->game.comms ); XP_ASSERT( !!state->game.comms );
@ -2049,8 +2152,8 @@ JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_haveEnv Java_org_eehouse_android_xw4_jni_XwJNI_haveEnv
( JNIEnv* env, jclass C, jint jniGlobalPtr ) ( JNIEnv* env, jclass C, jint jniGlobalPtr )
{ {
JNIGlobalState* state = (JNIGlobalState*)jniGlobalPtr; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
jboolean result = NULL != prvEnvForMe(&state->ti); jboolean result = NULL != prvEnvForMe(&globalState->ti);
return result; return result;
} }
@ -2102,29 +2205,25 @@ static void freeIndices( DictIterData* data );
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1init Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1init
( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jDictBytes, jstring jname, ( JNIEnv* env, jclass C, jint jniGlobalPtr, jbyteArray jDictBytes, jstring jname,
jstring jpath, jobject jniu ) jstring jpath )
{ {
jint closure = 0; jint closure = 0;
JNIGlobalState* state = (JNIGlobalState*)jniGlobalPtr; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr;
JNIUtilCtxt* jniutil = makeJNIUtil( MPPARM(state->mpool) env, DictionaryCtxt* dict = makeDict( MPPARM(globalState->mpool) env,
&state->ti, jniu ); globalState->dictMgr, globalState->jniutil,
DictionaryCtxt* dict = makeDict( MPPARM(state->mpool) env, state->dictMgr, jname, jDictBytes, jpath, NULL, false );
jniutil, jname, jDictBytes, jpath, NULL,
false );
if ( !!dict ) { if ( !!dict ) {
DictIterData* data = XP_CALLOC( state->mpool, sizeof(*data) ); DictIterData* data = XP_CALLOC( globalState->mpool, sizeof(*data) );
data->env = env; data->env = env;
data->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(state->mpool) ); data->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(globalState->mpool) );
data->jniutil = jniutil; data->jniutil = globalState->jniutil;
data->dict = dict; data->dict = dict;
data->depth = 2; data->depth = 2;
#ifdef MEM_DEBUG #ifdef MEM_DEBUG
data->mpool = state->mpool; data->mpool = globalState->mpool;
#endif #endif
closure = (int)data; closure = (int)data;
} else {
destroyJNIUtil( env, &jniutil );
} }
return closure; return closure;
} }
@ -2152,7 +2251,6 @@ Java_org_eehouse_android_xw4_jni_XwJNI_dict_1iter_1destroy
#endif #endif
dict_unref( data->dict ); dict_unref( data->dict );
destroyJNIUtil( env, &data->jniutil );
freeIndices( data ); freeIndices( data );
vtmgr_destroy( MPPARM(mpool) data->vtMgr ); vtmgr_destroy( MPPARM(mpool) data->vtMgr );
XP_FREE( mpool, data ); XP_FREE( mpool, data );

View file

@ -53,7 +53,7 @@ git checkout ${TAG}${BRANCH}
cd ./xwords4/android/ cd ./xwords4/android/
./scripts/arelease.sh --apk-list $OUT_FILE ./scripts/arelease.sh --apk-list $OUT_FILE
mkdir -p /tmp/releases 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 if [ -n "$XW_RELEASE_SCP_DEST" ]; then
cat $OUT_FILE | while read APK; do cat $OUT_FILE | while read APK; do

View file

@ -54,7 +54,7 @@ fi
if [ -z "$FILES" ]; then if [ -z "$FILES" ]; then
do_build 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 $(dirname $0)/sign-align.sh --apk $f
done done
fi fi

View file

@ -166,6 +166,7 @@ board_make( MPFORMAL ModelCtxt* model, ServerCtxt* server, DrawCtx* draw,
result->draw = draw; result->draw = draw;
result->util = util; result->util = util;
result->dutil = util_getDevUtilCtxt( util );
result->gi = util->gameInfo; result->gi = util->gameInfo;
XP_ASSERT( !!result->gi ); XP_ASSERT( !!result->gi );
@ -1090,11 +1091,10 @@ board_commitTurn( BoardCtxt* board, XP_Bool phoniesConfirmed,
XP_MEMSET( &bwl, 0, sizeof(bwl) ); XP_MEMSET( &bwl, 0, sizeof(bwl) );
if ( !legal ) { if ( !legal ) {
stream = mem_stream_make( MPPARM(board->mpool) stream = mem_stream_make_raw( MPPARM(board->mpool)
util_getVTManager(board->util), NULL, dutil_getVTManager(board->dutil) );
CHANNEL_NONE, (MemStreamCloseCallback)NULL );
const XP_UCHAR* str = util_getUserString(board->util, const XP_UCHAR* str = dutil_getUserString( board->dutil,
STR_COMMIT_CONFIRM ); STR_COMMIT_CONFIRM );
stream_catString( stream, str ); stream_catString( stream, str );
@ -1319,10 +1319,8 @@ timerFiredForPen( BoardCtxt* board )
NULL, NULL, NULL ); NULL, NULL, NULL );
if ( listWords ) { if ( listWords ) {
XWStreamCtxt* stream = XWStreamCtxt* stream =
mem_stream_make( MPPARM(board->mpool) mem_stream_make_raw( MPPARM(board->mpool)
util_getVTManager(board->util), NULL, dutil_getVTManager(board->dutil) );
CHANNEL_NONE,
(MemStreamCloseCallback)NULL );
model_listWordsThrough( board->model, modelCol, modelRow, model_listWordsThrough( board->model, modelCol, modelRow,
stream ); stream );
util_cellSquareHeld( board->util, stream ); util_cellSquareHeld( board->util, stream );
@ -1428,7 +1426,7 @@ board_pushTimerSave( BoardCtxt* board )
{ {
if ( board->gi->timerEnabled ) { if ( board->gi->timerEnabled ) {
if ( board->timerSaveCount++ == 0 ) { if ( board->timerSaveCount++ == 0 ) {
board->timerStoppedTime = util_getCurSeconds( board->util ); board->timerStoppedTime = dutil_getCurSeconds( board->dutil );
#ifdef DEBUG #ifdef DEBUG
board->timerStoppedTurn = server_getCurrentTurn( board->server, board->timerStoppedTurn = server_getCurrentTurn( board->server,
NULL ); NULL );
@ -1451,7 +1449,7 @@ board_popTimerSave( BoardCtxt* board )
XP_ASSERT( board->timerStoppedTurn == turn ); XP_ASSERT( board->timerStoppedTurn == turn );
if ( --board->timerSaveCount == 0 && turn >= 0 ) { if ( --board->timerSaveCount == 0 && turn >= 0 ) {
XP_U32 curTime = util_getCurSeconds( board->util ); XP_U32 curTime = dutil_getCurSeconds( board->dutil );
XP_U32 elapsed; XP_U32 elapsed;
XP_ASSERT( board->timerStoppedTime != 0 ); XP_ASSERT( board->timerStoppedTime != 0 );

View file

@ -371,7 +371,7 @@ static XP_Bool
drawCell( BoardCtxt* board, const XP_U16 col, const XP_U16 row, XP_Bool skipBlanks ) drawCell( BoardCtxt* board, const XP_U16 col, const XP_U16 row, XP_Bool skipBlanks )
{ {
XP_Bool success = XP_TRUE; XP_Bool success = XP_TRUE;
XP_Rect cellRect; XP_Rect cellRect = {0};
Tile tile; Tile tile;
XP_Bool isBlank, isEmpty, recent, pending = XP_FALSE; XP_Bool isBlank, isEmpty, recent, pending = XP_FALSE;
XWBonusType bonus; XWBonusType bonus;

View file

@ -139,6 +139,7 @@ struct BoardCtxt {
ModelCtxt* model; ModelCtxt* model;
ServerCtxt* server; ServerCtxt* server;
DrawCtx* draw; DrawCtx* draw;
XW_DUtilCtxt* dutil;
XW_UtilCtxt* util; XW_UtilCtxt* util;
struct CurGameInfo* gi; struct CurGameInfo* gi;

View file

@ -25,6 +25,7 @@
#include "comms.h" #include "comms.h"
#include "util.h" #include "util.h"
#include "dutil.h"
#include "game.h" #include "game.h"
#include "xwstream.h" #include "xwstream.h"
#include "memstream.h" #include "memstream.h"
@ -100,6 +101,7 @@ typedef struct AddressRecord {
struct CommsCtxt { struct CommsCtxt {
XW_UtilCtxt* util; XW_UtilCtxt* util;
XW_DUtilCtxt* dutil;
XP_U32 connID; /* set from gameID: 0 means ignore; otherwise XP_U32 connID; /* set from gameID: 0 means ignore; otherwise
must match. Set by server. */ must match. Set by server. */
@ -353,8 +355,7 @@ comms_make( MPFORMAL XW_UtilCtxt* util, XP_Bool isServer,
#endif #endif
) )
{ {
CommsCtxt* comms = (CommsCtxt*)XP_MALLOC( mpool, sizeof(*comms) ); CommsCtxt* comms = (CommsCtxt*)XP_CALLOC( mpool, sizeof(*comms) );
XP_MEMSET( comms, 0, sizeof(*comms) );
#ifdef DEBUG #ifdef DEBUG
comms->tag = mpool_getTag(mpool); comms->tag = mpool_getTag(mpool);
XP_LOGF( TAGFMT(isServer=%d; forceChannel=%d), TAGPRMS, isServer, forceChannel ); 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; comms->xportFlags = comms->procs.flags;
#endif #endif
} }
comms->dutil = util_getDevUtilCtxt( util );
comms->util = util; comms->util = util;
comms->dutil = util_getDevUtilCtxt( util );
#ifdef XWFEATURE_RELAY #ifdef XWFEATURE_RELAY
init_relay( comms, nPlayersHere, nPlayersTotal ); 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 ); msg->msg = (XP_U8*)XP_MALLOC( mpool, msg->len );
stream_getBytes( stream, msg->msg, msg->len ); stream_getBytes( stream, msg->msg, msg->len );
#ifdef COMMS_CHECKSUM #ifdef COMMS_CHECKSUM
msg->checksum = util_md5sum( comms->util, msg->msg, msg->len ); msg->checksum = dutil_md5sum( comms->dutil, msg->msg, msg->len );
#endif #endif
msg->next = (MsgQueueElem*)NULL; msg->next = (MsgQueueElem*)NULL;
*prevsQueueNext = comms->msgQueueTail = msg; *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)send_via_bt_or_ip( comms, BTIPMSG_RESET, CHANNEL_NONE, typ, NULL, 0, NULL );
(void)comms_resendAll( comms, COMMS_CONN_NONE, XP_FALSE ); (void)comms_resendAll( comms, COMMS_CONN_NONE, XP_FALSE );
break; break;
#endif
#if defined XWFEATURE_SMS
case COMMS_CONN_SMS:
(void)comms_resendAll( comms, COMMS_CONN_NONE, XP_FALSE );
break;
#endif #endif
default: default:
break; break;
@ -973,7 +981,6 @@ comms_setAddr( CommsCtxt* comms, const CommsAddrRec* addr )
setDoHeartbeat( comms ); setDoHeartbeat( comms );
#endif #endif
sendConnect( comms, XP_TRUE ); sendConnect( comms, XP_TRUE );
} /* comms_setAddr */ } /* comms_setAddr */
void void
@ -1136,10 +1143,8 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
newMsgElem->sendCount = 0; newMsgElem->sendCount = 0;
#endif #endif
hdrStream = mem_stream_make( MPPARM(comms->mpool) hdrStream = mem_stream_make_raw( MPPARM(comms->mpool)
util_getVTManager(comms->util), dutil_getVTManager(comms->dutil));
NULL, 0,
(MemStreamCloseCallback)NULL );
stream_open( hdrStream ); stream_open( hdrStream );
#if 0 < COMMS_VERSION #if 0 < COMMS_VERSION
stream_putU16( hdrStream, HAS_VERSION_FLAG ); stream_putU16( hdrStream, HAS_VERSION_FLAG );
@ -1168,7 +1173,7 @@ makeElemWithID( CommsCtxt* comms, MsgID msgID, AddressRecord* rec,
} }
#ifdef COMMS_CHECKSUM #ifdef COMMS_CHECKSUM
newMsgElem->checksum = util_md5sum( comms->util, newMsgElem->msg, newMsgElem->checksum = dutil_md5sum( comms->dutil, newMsgElem->msg,
newMsgElem->len ); newMsgElem->len );
#endif #endif
return newMsgElem; return newMsgElem;
@ -1508,7 +1513,7 @@ comms_resendAll( CommsCtxt* comms, CommsConnType filter, XP_Bool force )
XP_Bool success = XP_TRUE; XP_Bool success = XP_TRUE;
XP_ASSERT( !!comms ); XP_ASSERT( !!comms );
XP_U32 now = util_getCurSeconds( comms->util ); XP_U32 now = dutil_getCurSeconds( comms->dutil );
if ( !force && (now < comms->nextResend) ) { if ( !force && (now < comms->nextResend) ) {
XP_LOGF( "%s: aborting: %d seconds left in backoff", __func__, XP_LOGF( "%s: aborting: %d seconds left in backoff", __func__,
comms->nextResend - now ); comms->nextResend - now );
@ -1664,7 +1669,7 @@ got_connect_cmd( CommsCtxt* comms, XWStreamCtxt* stream,
} }
if ( ID_TYPE_NONE == typ /* error case */ if ( ID_TYPE_NONE == typ /* error case */
|| '\0' != devID[0] ) /* new info case */ { || '\0' != devID[0] ) /* new info case */ {
util_deviceRegistered( comms->util, typ, devID ); dutil_deviceRegistered( comms->dutil, typ, devID );
} }
#endif #endif
@ -1845,7 +1850,7 @@ relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID )
static void static void
noteHBReceived( CommsCtxt* comms/* , const CommsAddrRec* addr */ ) noteHBReceived( CommsCtxt* comms/* , const CommsAddrRec* addr */ )
{ {
comms->lastMsgRcvdTime = util_getCurSeconds( comms->util ); comms->lastMsgRcvdTime = dutil_getCurSeconds( comms->dutil );
setHeartbeatTimer( comms ); setHeartbeatTimer( comms );
} }
#else #else
@ -1976,12 +1981,15 @@ getRecordFor( CommsCtxt* comms, const CommsAddrRec* addr,
break; break;
case COMMS_CONN_SMS: case COMMS_CONN_SMS:
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
if ( util_phoneNumbersSame( comms->util, addr->u.sms.phone, {
XW_DUtilCtxt* duc = util_getDevUtilCtxt( comms->util );
if ( dutil_phoneNumbersSame( duc, addr->u.sms.phone,
rec->addr.u.sms.phone ) rec->addr.u.sms.phone )
&& addr->u.sms.port == rec->addr.u.sms.port ) { && addr->u.sms.port == rec->addr.u.sms.port ) {
matched = XP_TRUE; matched = XP_TRUE;
XP_ASSERT( 0 ); XP_ASSERT( 0 );
} }
}
#endif #endif
break; break;
case COMMS_CONN_NONE: case COMMS_CONN_NONE:
@ -2227,7 +2235,7 @@ comms_checkIncomingStream( CommsCtxt* comms, XWStreamCtxt* stream,
XP_U16 len = stream_getSize( stream ); XP_U16 len = stream_getSize( stream );
// stream_getPtr pts at base, but sum excludes relay header // stream_getPtr pts at base, but sum excludes relay header
const XP_U8* ptr = initialLen - len + stream_getPtr( stream ); 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", XP_LOGF( TAGFMT() "got message of len %d with sum %s",
TAGPRMS, len, sum ); TAGPRMS, len, sum );
XP_FREE( comms->mpool, sum ); XP_FREE( comms->mpool, sum );
@ -2433,7 +2441,7 @@ heartbeat_checks( CommsCtxt* comms )
do { do {
if ( comms->lastMsgRcvdTime > 0 ) { 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); XP_U32 tooLongAgo = now - (HB_INTERVAL * 2);
if ( comms->lastMsgRcvdTime < tooLongAgo ) { if ( comms->lastMsgRcvdTime < tooLongAgo ) {
XP_LOGF( "%s: calling reset proc; last was %ld secs too long " 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 ) { if ( !!addr ) {
char buf[128]; char buf[128];
XWStreamCtxt* stream = mem_stream_make( MPPARM(comms->mpool) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(comms->mpool)
util_getVTManager(comms->util), dutil_getVTManager(comms->dutil));
NULL, 0,
(MemStreamCloseCallback)NULL );
snprintf( buf, sizeof(buf), TAGFMT() "called on %p from %s:\n", TAGPRMS, snprintf( buf, sizeof(buf), TAGFMT() "called on %p from %s:\n", TAGPRMS,
addr, caller ); addr, caller );
stream_catString( stream, buf ); 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 ); XP_LOGF( "%s(cmd=%s, destID=%x)", __func__, relayCmdToStr(cmd), destID );
XWStreamCtxt* stream; XWStreamCtxt* stream;
stream = mem_stream_make( MPPARM(comms->mpool) stream = mem_stream_make_raw( MPPARM(comms->mpool)
util_getVTManager(comms->util), dutil_getVTManager(comms->dutil) );
NULL, 0,
(MemStreamCloseCallback)NULL );
if ( stream != NULL ) { if ( stream != NULL ) {
CommsAddrRec addr; CommsAddrRec addr;
stream_open( stream ); stream_open( stream );
@ -3200,7 +3204,7 @@ putDevID( const CommsCtxt* comms, XWStreamCtxt* stream )
{ {
# if XWRELAY_PROTO_VERSION >= XWRELAY_PROTO_VERSION_CLIENTID # if XWRELAY_PROTO_VERSION >= XWRELAY_PROTO_VERSION_CLIENTID
DevIDType typ; 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 ); XP_ASSERT( ID_TYPE_NONE <= typ && typ < ID_TYPE_NTYPES );
stream_putU8( stream, typ ); stream_putU8( stream, typ );
if ( ID_TYPE_NONE != typ ) { if ( ID_TYPE_NONE != typ ) {

View file

@ -129,6 +129,22 @@ typedef enum {
OBJ_TRAY OBJ_TRAY
} BoardObjectType; } 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 */ /* I'm going to try putting all forward "class" decls in the same file */
typedef struct BoardCtxt BoardCtxt; typedef struct BoardCtxt BoardCtxt;
typedef struct CommMgrCtxt CommMgrCtxt; typedef struct CommMgrCtxt CommMgrCtxt;
@ -144,6 +160,7 @@ typedef struct XWStreamCtxt XWStreamCtxt;
typedef struct TrayContext TrayContext; typedef struct TrayContext TrayContext;
typedef struct PoolContext PoolContext; typedef struct PoolContext PoolContext;
typedef struct XW_UtilCtxt XW_UtilCtxt; typedef struct XW_UtilCtxt XW_UtilCtxt;
typedef struct XW_DUtilCtxt XW_DUtilCtxt;
/* Low two bits treated as channel, third as short-term flag indicating /* 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. */ * sender's role; rest can be random to aid detection of duplicate packets. */
@ -191,6 +208,8 @@ typedef enum {
BONUS_LAST BONUS_LAST
} XWBonusType; } XWBonusType;
#define PERSIST_KEY(str) __FILE__ ":" str
/* I need a way to communiate prefs to common/ code. For now, though, I'll /* 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 * 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 * want to deal with versioning in the common code. Second, becuase they

View file

@ -48,6 +48,7 @@ COMMONSRC = \
$(COMMONDIR)/vtabmgr.c \ $(COMMONDIR)/vtabmgr.c \
$(COMMONDIR)/dictmgr.c \ $(COMMONDIR)/dictmgr.c \
$(COMMONDIR)/dbgutil.c \ $(COMMONDIR)/dbgutil.c \
$(COMMONDIR)/smsproto.c \
# PENDING: define this in terms of above!!! # PENDING: define this in terms of above!!!
@ -85,5 +86,6 @@ COMMON5 = \
$(COMMONOBJDIR)/vtabmgr.o \ $(COMMONOBJDIR)/vtabmgr.o \
$(COMMONOBJDIR)/dictmgr.o \ $(COMMONOBJDIR)/dictmgr.o \
$(COMMONOBJDIR)/dbgutil.o \ $(COMMONOBJDIR)/dbgutil.o \
$(COMMONOBJDIR)/smsproto.o \
COMMONOBJ = $(COMMON1) $(COMMON2) $(COMMON3) $(COMMON4) $(COMMON5) COMMONOBJ = $(COMMON1) $(COMMON2) $(COMMON3) $(COMMON4) $(COMMON5)

View file

@ -44,8 +44,6 @@ extern "C" {
#define DICT_HEADER_MASK 0x08 #define DICT_HEADER_MASK 0x08
#define DICT_SYNONYMS_MASK 0x10 #define DICT_SYNONYMS_MASK 0x10
typedef XP_U8 XP_LangCode;
typedef enum { typedef enum {
INTRADE_MW_TEXT = BONUS_LAST INTRADE_MW_TEXT = BONUS_LAST
} XWMiniTextType; } XWMiniTextType;

104
xwords4/common/dutil.h Normal file
View file

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

View file

@ -80,7 +80,7 @@ makeGameID( XW_UtilCtxt* util )
XP_U32 gameID = 0; XP_U32 gameID = 0;
assertUtilOK( util ); assertUtilOK( util );
while ( 0 == gameID ) { while ( 0 == gameID ) {
gameID = util_getCurSeconds( util ); gameID = dutil_getCurSeconds( util_getDevUtilCtxt( util ) );
} }
return gameID; return gameID;
} }

View file

@ -258,6 +258,11 @@ void*
mpool_realloc( MemPoolCtx* mpool, void* ptr, XP_U32 newsize, const char* file, mpool_realloc( MemPoolCtx* mpool, void* ptr, XP_U32 newsize, const char* file,
const char* func, XP_U32 lineNo ) const char* func, XP_U32 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 {
MemPoolEntry* entry = findEntryFor( mpool, ptr, (MemPoolEntry**)NULL ); MemPoolEntry* entry = findEntryFor( mpool, ptr, (MemPoolEntry**)NULL );
if ( !entry ) { if ( !entry ) {
@ -271,7 +276,9 @@ mpool_realloc( MemPoolCtx* mpool, void* ptr, XP_U32 newsize, const char* file,
entry->lineNo = lineNo; entry->lineNo = lineNo;
entry->size = newsize; entry->size = newsize;
} }
return entry->ptr; result = entry->ptr;
}
return result;
} /* mpool_realloc */ } /* mpool_realloc */
void void

View file

@ -66,6 +66,12 @@ static StreamCtxVTable* make_vtable( MemStreamCtxt* stream );
/* Try to keep this the only entry point to this file, and to keep it at the /* Try to keep this the only entry point to this file, and to keep it at the
* top of the file (first executable code). * 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* XWStreamCtxt*
mem_stream_make( MPFORMAL VTableMgr* vtmgr, void* closure, mem_stream_make( MPFORMAL VTableMgr* vtmgr, void* closure,
XP_PlayerAddr channelNo, MemStreamCloseCallback onClose ) XP_PlayerAddr channelNo, MemStreamCloseCallback onClose )

View file

@ -32,6 +32,8 @@ extern "C" {
typedef void (*MemStreamCloseCallback)( XWStreamCtxt* stream, typedef void (*MemStreamCloseCallback)( XWStreamCtxt* stream,
void* closure ); void* closure );
XWStreamCtxt* mem_stream_make_raw( MPFORMAL VTableMgr* vtmgr);
XWStreamCtxt* mem_stream_make( MPFORMAL VTableMgr* vtmgr, XWStreamCtxt* mem_stream_make( MPFORMAL VTableMgr* vtmgr,
void* closure, void* closure,
XP_PlayerAddr addr, /* should be in a XP_PlayerAddr addr, /* should be in a

View file

@ -108,6 +108,7 @@ model_make( MPFORMAL DictionaryCtxt* dict, const PlayerDicts* dicts,
MPASSIGN(result->vol.mpool, mpool); MPASSIGN(result->vol.mpool, mpool);
result->vol.util = util; result->vol.util = util;
result->vol.dutil = util_getDevUtilCtxt( util );
result->vol.wni.proc = recordWord; result->vol.wni.proc = recordWord;
result->vol.wni.closure = &result->vol.rwi; result->vol.wni.closure = &result->vol.rwi;
@ -295,7 +296,7 @@ model_setSize( ModelCtxt* model, XP_U16 nCols )
stack_init( model->vol.stack ); stack_init( model->vol.stack );
} else { } else {
model->vol.stack = stack_make( MPPARM(model->vol.mpool) model->vol.stack = stack_make( MPPARM(model->vol.mpool)
util_getVTManager(model->vol.util)); dutil_getVTManager(model->vol.dutil));
} }
} /* model_setSize */ } /* model_setSize */
@ -2090,13 +2091,13 @@ printMovePre( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), const StackEntry* entry
} }
if ( isPass ) { 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 ); XP_SNPRINTF( buf, VSIZE(buf), "%s", format );
} else { } else {
if ( isHorizontal ) { if ( isHorizontal ) {
format = util_getUserString( model->vol.util, STRS_MOVE_ACROSS ); format = dutil_getUserString( model->vol.dutil, STRS_MOVE_ACROSS );
} else { } else {
format = util_getUserString( model->vol.util, STRS_MOVE_DOWN ); format = dutil_getUserString( model->vol.dutil, STRS_MOVE_DOWN );
} }
row = mi->commonCoord; row = mi->commonCoord;
@ -2114,7 +2115,7 @@ printMovePre( ModelCtxt* model, XP_U16 XP_UNUSED(moveN), const StackEntry* entry
} }
if ( !closure->keepHidden ) { 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 ), formatTray( model_getPlayerTiles( model, entry->playerNum ),
closure->dict, (XP_UCHAR*)traybuf, sizeof(traybuf), closure->dict, (XP_UCHAR*)traybuf, sizeof(traybuf),
XP_FALSE ); XP_FALSE );
@ -2149,17 +2150,17 @@ printMovePost( ModelCtxt* model, XP_U16 XP_UNUSED(moveN),
formatTray( (const TrayTileSet*) &entry->u.trade.newTiles, formatTray( (const TrayTileSet*) &entry->u.trade.newTiles,
dict, traybuf2, sizeof(traybuf2), closure->keepHidden ); 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 ); XP_SNPRINTF( buf, sizeof(buf), format, traybuf1, traybuf2 );
printString( stream, buf ); printString( stream, buf );
printString( stream, (XP_UCHAR*)XP_CR ); printString( stream, (XP_UCHAR*)XP_CR );
break; break;
case PHONY_TYPE: case PHONY_TYPE:
format = util_getUserString( model->vol.util, STR_PHONY_REJECTED ); format = dutil_getUserString( model->vol.dutil, STR_PHONY_REJECTED );
printString( stream, format ); printString( stream, format );
case MOVE_TYPE: 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 ); XP_SNPRINTF( buf, sizeof(buf), format, totalScore );
printString( stream, buf ); printString( stream, buf );
@ -2174,7 +2175,7 @@ printMovePost( ModelCtxt* model, XP_U16 XP_UNUSED(moveN),
if ( entry->moveType == PHONY_TYPE ) { if ( entry->moveType == PHONY_TYPE ) {
/* printString( stream, (XP_UCHAR*)"phony rejected " ); */ /* printString( stream, (XP_UCHAR*)"phony rejected " ); */
} else if ( !closure->keepHidden ) { } 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, XP_SNPRINTF( buf, sizeof(buf), format,
formatTray( &entry->u.move.newTiles, dict, formatTray( &entry->u.move.newTiles, dict,
traybuf1, sizeof(traybuf1), traybuf1, sizeof(traybuf1),
@ -2195,9 +2196,9 @@ static void
copyStack( const ModelCtxt* model, StackCtxt* destStack, copyStack( const ModelCtxt* model, StackCtxt* destStack,
const StackCtxt* srcStack ) const StackCtxt* srcStack )
{ {
XWStreamCtxt* stream = mem_stream_make( MPPARM(model->vol.mpool) XWStreamCtxt* stream =
util_getVTManager(model->vol.util), mem_stream_make_raw( MPPARM(model->vol.mpool)
NULL, 0, NULL ); dutil_getVTManager(model->vol.dutil) );
stack_writeToStream( (StackCtxt*)srcStack, stream ); stack_writeToStream( (StackCtxt*)srcStack, stream );
stack_loadFromStream( destStack, stream ); stack_loadFromStream( destStack, stream );

View file

@ -51,6 +51,7 @@ typedef struct _RecordWordsInfo {
} RecordWordsInfo; } RecordWordsInfo;
typedef struct ModelVolatiles { typedef struct ModelVolatiles {
XW_DUtilCtxt* dutil;
XW_UtilCtxt* util; XW_UtilCtxt* util;
struct CurGameInfo* gi; struct CurGameInfo* gi;
DictionaryCtxt* dict; DictionaryCtxt* dict;

View file

@ -120,9 +120,7 @@ stack_loadFromStream( StackCtxt* stack, XWStreamCtxt* stream )
stack->highWaterMark = stream_getU16( stream ); stack->highWaterMark = stream_getU16( stream );
stack->nEntries = stream_getU16( stream ); stack->nEntries = stream_getU16( stream );
stack->top = stream_getU32( stream ); stack->top = stream_getU32( stream );
stack->data = mem_stream_make( MPPARM(stack->mpool) stack->vtmgr, stack->data = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr );
NULL, 0,
(MemStreamCloseCallback)NULL );
stream_getFromStream( stack->data, stream, nBytes ); stream_getFromStream( stack->data, stream, nBytes );
} else { } else {
@ -164,8 +162,8 @@ StackCtxt*
stack_copy( const StackCtxt* stack ) stack_copy( const StackCtxt* stack )
{ {
StackCtxt* newStack = NULL; StackCtxt* newStack = NULL;
XWStreamCtxt* stream = mem_stream_make( MPPARM(stack->mpool) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(stack->mpool)
stack->vtmgr, NULL, 0, NULL ); stack->vtmgr );
stack_writeToStream( stack, stream ); stack_writeToStream( stack, stream );
newStack = stack_make( MPPARM(stack->mpool) stack->vtmgr ); newStack = stack_make( MPPARM(stack->mpool) stack->vtmgr );
@ -184,8 +182,7 @@ pushEntryImpl( StackCtxt* stack, const StackEntry* entry )
XWStreamCtxt* stream = stack->data; XWStreamCtxt* stream = stack->data;
if ( !stream ) { if ( !stream ) {
stream = mem_stream_make( MPPARM(stack->mpool) stack->vtmgr, NULL, 0, stream = mem_stream_make_raw( MPPARM(stack->mpool) stack->vtmgr );
(MemStreamCloseCallback)NULL );
stack->data = stream; stack->data = stream;
} }

View file

@ -513,7 +513,7 @@ figureMoveScore( const ModelCtxt* model, XP_U16 turn, MoveInfo* moveInfo,
score += EMPTIED_TRAY_BONUS; score += EMPTIED_TRAY_BONUS;
if ( !!stream ) { if ( !!stream ) {
const XP_UCHAR* bstr = util_getUserString( model->vol.util, const XP_UCHAR* bstr = dutil_getUserString( model->vol.dutil,
STR_BONUS_ALL ); STR_BONUS_ALL );
stream_catString( stream, bstr ); stream_catString( stream, bstr );
} }
@ -845,7 +845,7 @@ formatSummary( XWStreamCtxt* stream, const ModelCtxt* model, XP_U16 score )
{ {
XP_UCHAR buf[60]; XP_UCHAR buf[60];
XP_SNPRINTF( buf, sizeof(buf), XP_SNPRINTF( buf, sizeof(buf),
util_getUserString(model->vol.util, STRD_TURN_SCORE), dutil_getUserString(model->vol.dutil, STRD_TURN_SCORE),
score ); score );
XP_ASSERT( XP_STRLEN(buf) < sizeof(buf) ); XP_ASSERT( XP_STRLEN(buf) < sizeof(buf) );
stream_catString( stream, buf ); stream_catString( stream, buf );

View file

@ -531,7 +531,7 @@ setRoleStrings( NewGameCtx* ngc )
strID = STR_TOTALPLAYERS; 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 ); (*ngc->setAttrProc)( closure, NG_ATTR_NPLAYHEADER, value );
} /* setRoleStrings */ } /* setRoleStrings */

View file

@ -72,6 +72,7 @@ typedef struct ServerVolatiles {
ModelCtxt* model; ModelCtxt* model;
CommsCtxt* comms; CommsCtxt* comms;
XW_UtilCtxt* util; XW_UtilCtxt* util;
XW_DUtilCtxt* dutil;
CurGameInfo* gi; CurGameInfo* gi;
TurnChangeListener turnChangeListener; TurnChangeListener turnChangeListener;
void* turnChangeData; void* turnChangeData;
@ -271,6 +272,7 @@ server_make( MPFORMAL ModelCtxt* model, CommsCtxt* comms, XW_UtilCtxt* util )
result->vol.model = model; result->vol.model = model;
result->vol.comms = comms; result->vol.comms = comms;
result->vol.util = util; result->vol.util = util;
result->vol.dutil = util_getDevUtilCtxt( util );
result->vol.gi = util->gameInfo; result->vol.gi = util->gameInfo;
initServer( result ); initServer( result );
@ -672,7 +674,7 @@ sendChatToClientsExcept( ServerCtxt* server, XP_U16 skip, const XP_UCHAR* msg,
void void
server_sendChat( ServerCtxt* server, const XP_UCHAR* msg, XP_S16 from ) 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 ) { if ( server->vol.gi->serverRole == SERVER_ISCLIENT ) {
sendChatTo( server, SERVER_DEVICE, msg, from, timestamp ); sendChatTo( server, SERVER_DEVICE, msg, from, timestamp );
} else { } else {
@ -828,10 +830,8 @@ static XWStreamCtxt*
mkServerStream( ServerCtxt* server ) mkServerStream( ServerCtxt* server )
{ {
XWStreamCtxt* stream; XWStreamCtxt* stream;
stream = mem_stream_make( MPPARM(server->mpool) stream = mem_stream_make_raw( MPPARM(server->mpool)
util_getVTManager(server->vol.util), dutil_getVTManager(server->vol.dutil) );
NULL, CHANNEL_NONE,
(MemStreamCloseCallback)NULL );
XP_ASSERT( !!stream ); XP_ASSERT( !!stream );
return stream; return stream;
} /* mkServerStream */ } /* mkServerStream */
@ -849,11 +849,11 @@ makeRobotMove( ServerCtxt* server )
XP_Bool timerEnabled = gi->timerEnabled; XP_Bool timerEnabled = gi->timerEnabled;
XP_Bool canMove; XP_Bool canMove;
XP_U32 time = 0L; /* stupid compiler.... */ XP_U32 time = 0L; /* stupid compiler.... */
XW_UtilCtxt* util = server->vol.util; XW_DUtilCtxt* dutil = server->vol.dutil;
XP_Bool forceTrade = XP_FALSE; XP_Bool forceTrade = XP_FALSE;
if ( timerEnabled ) { if ( timerEnabled ) {
time = util_getCurSeconds( util ); time = dutil_getCurSeconds( dutil );
} }
#ifdef XWFEATURE_SLOW_ROBOT #ifdef XWFEATURE_SLOW_ROBOT
@ -917,7 +917,7 @@ makeRobotMove( ServerCtxt* server )
if ( !!stream ) { if ( !!stream ) {
XP_UCHAR buf[64]; XP_UCHAR buf[64];
str = util_getUserQuantityString( util, STRD_ROBOT_TRADED, str = dutil_getUserQuantityString( dutil, STRD_ROBOT_TRADED,
MAX_TRAY_TILES ); MAX_TRAY_TILES );
XP_SNPRINTF( buf, sizeof(buf), str, MAX_TRAY_TILES ); XP_SNPRINTF( buf, sizeof(buf), str, MAX_TRAY_TILES );
@ -951,7 +951,7 @@ makeRobotMove( ServerCtxt* server )
if ( timerEnabled ) { if ( timerEnabled ) {
gi->players[turn].secondsUsed += gi->players[turn].secondsUsed +=
(XP_U16)(util_getCurSeconds( util ) - time); (XP_U16)(dutil_getCurSeconds( dutil ) - time);
} else { } else {
XP_ASSERT( gi->players[turn].secondsUsed == 0 ); XP_ASSERT( gi->players[turn].secondsUsed == 0 );
} }
@ -1013,6 +1013,7 @@ showPrevScore( ServerCtxt* server )
{ {
if ( server->nv.showRobotScores ) { /* this can be changed between turns */ if ( server->nv.showRobotScores ) { /* this can be changed between turns */
XW_UtilCtxt* util = server->vol.util; XW_UtilCtxt* util = server->vol.util;
XW_DUtilCtxt* dutil = server->vol.dutil;
XWStreamCtxt* stream; XWStreamCtxt* stream;
const XP_UCHAR* str; const XP_UCHAR* str;
XP_UCHAR buf[128]; XP_UCHAR buf[128];
@ -1025,9 +1026,9 @@ showPrevScore( ServerCtxt* server )
lp = &gi->players[prevTurn]; lp = &gi->players[prevTurn];
if ( LP_IS_LOCAL(lp) ) { if ( LP_IS_LOCAL(lp) ) {
str = util_getUserString( util, STR_ROBOT_MOVED ); str = dutil_getUserString( dutil, STR_ROBOT_MOVED );
} else { } else {
str = util_getUserString( util, STRS_REMOTE_MOVED ); str = dutil_getUserString( dutil, STRS_REMOTE_MOVED );
} }
XP_SNPRINTF( buf, sizeof(buf), str, lp->name ); XP_SNPRINTF( buf, sizeof(buf), str, lp->name );
str = buf; str = buf;
@ -2243,7 +2244,7 @@ makeTradeReportIf( ServerCtxt* server, const TrayTileSet* tradedTiles )
if ( server->nv.showRobotScores ) { if ( server->nv.showRobotScores ) {
XP_UCHAR tradeBuf[64]; XP_UCHAR tradeBuf[64];
const XP_UCHAR* tradeStr = const XP_UCHAR* tradeStr =
util_getUserQuantityString( server->vol.util, STRD_ROBOT_TRADED, dutil_getUserQuantityString( server->vol.dutil, STRD_ROBOT_TRADED,
tradedTiles->nTiles ); tradedTiles->nTiles );
XP_SNPRINTF( tradeBuf, sizeof(tradeBuf), tradeStr, XP_SNPRINTF( tradeBuf, sizeof(tradeBuf), tradeStr,
tradedTiles->nTiles ); tradedTiles->nTiles );
@ -2504,6 +2505,9 @@ server_commitMove( ServerCtxt* server, TrayTileSet* newTilesP )
nextTurn( server, PICK_NEXT ); nextTurn( server, PICK_NEXT );
} }
XP_LOGF( "%s(): player %d now has %d tiles", __func__, turn,
model_getNumTilesInTray( model, turn ) );
return XP_TRUE; return XP_TRUE;
} /* server_commitMove */ } /* server_commitMove */
@ -2706,7 +2710,7 @@ setTurn( ServerCtxt* server, XP_S16 turn )
|| (!amServer(server) || (0 == server->nv.pendingRegistrations))); || (!amServer(server) || (0 == server->nv.pendingRegistrations)));
if ( server->nv.currentTurn != turn || 1 == server->vol.gi->nPlayers ) { if ( server->nv.currentTurn != turn || 1 == server->vol.gi->nPlayers ) {
server->nv.currentTurn = turn; server->nv.currentTurn = turn;
server->nv.lastMoveTime = util_getCurSeconds( server->vol.util ); server->nv.lastMoveTime = dutil_getCurSeconds( server->vol.dutil );
callTurnChangeListener( server ); callTurnChangeListener( server );
} }
} }
@ -3025,7 +3029,7 @@ server_formatDictCounts( ServerCtxt* server, XWStreamCtxt* stream,
Tile tile; Tile tile;
XP_U16 nChars, nPrinted; XP_U16 nChars, nPrinted;
XP_UCHAR buf[48]; XP_UCHAR buf[48];
const XP_UCHAR* fmt = util_getUserString( server->vol.util, const XP_UCHAR* fmt = dutil_getUserString( server->vol.dutil,
STRS_VALUES_HEADER ); STRS_VALUES_HEADER );
const XP_UCHAR* langName; const XP_UCHAR* langName;
@ -3096,7 +3100,7 @@ server_formatRemainingTiles( ServerCtxt* server, XWStreamCtxt* stream,
XP_ASSERT( !!server->vol.model ); XP_ASSERT( !!server->vol.model );
const XP_UCHAR* fmt = util_getUserQuantityString( server->vol.util, const XP_UCHAR* fmt = dutil_getUserQuantityString( server->vol.dutil,
STRD_REMAINS_HEADER, STRD_REMAINS_HEADER,
nLeft ); nLeft );
XP_SNPRINTF( buf, sizeof(buf), fmt, nLeft ); XP_SNPRINTF( buf, sizeof(buf), fmt, nLeft );
@ -3136,7 +3140,7 @@ server_formatRemainingTiles( ServerCtxt* server, XWStreamCtxt* stream,
XP_ASSERT( offset < sizeof(cntsBuf) ); XP_ASSERT( offset < sizeof(cntsBuf) );
} }
fmt = util_getUserQuantityString( server->vol.util, STRD_REMAINS_EXPL, fmt = dutil_getUserQuantityString( server->vol.dutil, STRD_REMAINS_EXPL,
nLeft ); nLeft );
XP_SNPRINTF( buf, sizeof(buf), fmt, nLeft ); XP_SNPRINTF( buf, sizeof(buf), fmt, nLeft );
stream_catString( stream, buf ); stream_catString( stream, buf );
@ -3226,9 +3230,9 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
XP_S16 quitter = server->nv.quitter; XP_S16 quitter = server->nv.quitter;
XP_Bool quitterDone = XP_FALSE; XP_Bool quitterDone = XP_FALSE;
ModelCtxt* model = server->vol.model; ModelCtxt* model = server->vol.model;
const XP_UCHAR* addString = util_getUserString( server->vol.util, const XP_UCHAR* addString = dutil_getUserString( server->vol.dutil,
STRD_REMAINING_TILES_ADD ); STRD_REMAINING_TILES_ADD );
const XP_UCHAR* subString = util_getUserString( server->vol.util, const XP_UCHAR* subString = dutil_getUserString( server->vol.dutil,
STRD_UNUSED_TILES_SUB ); STRD_UNUSED_TILES_SUB );
XP_UCHAR* timeStr; XP_UCHAR* timeStr;
CurGameInfo* gi = server->vol.gi; CurGameInfo* gi = server->vol.gi;
@ -3279,8 +3283,7 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
XP_U16 penalty = player_timePenalty( gi, thisIndex ); XP_U16 penalty = player_timePenalty( gi, thisIndex );
if ( penalty > 0 ) { if ( penalty > 0 ) {
XP_SNPRINTF( timeBuf, sizeof(timeBuf), XP_SNPRINTF( timeBuf, sizeof(timeBuf),
util_getUserString( dutil_getUserString( server->vol.dutil,
server->vol.util,
STRD_TIME_PENALTY_SUB ), STRD_TIME_PENALTY_SUB ),
penalty ); /* positive for formatting */ penalty ); /* positive for formatting */
timeStr = timeBuf; timeStr = timeBuf;
@ -3296,12 +3299,12 @@ server_writeFinalScores( ServerCtxt* server, XWStreamCtxt* stream )
const XP_UCHAR* name = emptyStringIfNull(gi->players[thisIndex].name); const XP_UCHAR* name = emptyStringIfNull(gi->players[thisIndex].name);
if ( 0 == placeKey ) { if ( 0 == placeKey ) {
const XP_UCHAR* fmt = util_getUserString( server->vol.util, const XP_UCHAR* fmt = dutil_getUserString( server->vol.dutil,
STRDSD_PLACER ); STRDSD_PLACER );
XP_SNPRINTF( buf, sizeof(buf), fmt, place, XP_SNPRINTF( buf, sizeof(buf), fmt, place,
name, scores.arr[thisIndex] ); name, scores.arr[thisIndex] );
} else { } else {
const XP_UCHAR* fmt = util_getUserString( server->vol.util, const XP_UCHAR* fmt = dutil_getUserString( server->vol.dutil,
placeKey ); placeKey );
XP_SNPRINTF( buf, sizeof(buf), fmt, name, XP_SNPRINTF( buf, sizeof(buf), fmt, name,
scores.arr[thisIndex] ); scores.arr[thisIndex] );

View file

@ -30,20 +30,6 @@
extern "C" { extern "C" {
#endif #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 ServerCtxt ServerCtxt; */
/* typedef struct ServerVtable { */ /* typedef struct ServerVtable { */

870
xwords4/common/smsproto.c Normal file
View file

@ -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 <unistd.h>
#include <pthread.h>
#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

85
xwords4/common/smsproto.h Normal file
View file

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

View file

@ -179,6 +179,26 @@ stringToStream( XWStreamCtxt* stream, const XP_UCHAR* str )
stream_putBytes( stream, str, len ); stream_putBytes( stream, str, len );
} /* putStringToStream */ } /* 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;
}
/***************************************************************************** /*****************************************************************************
* *
****************************************************************************/ ****************************************************************************/

View file

@ -56,6 +56,9 @@ XP_UCHAR* p_stringFromStream( MPFORMAL XWStreamCtxt* stream
XP_U16 stringFromStreamHere( XWStreamCtxt* stream, XP_UCHAR* buf, XP_U16 len ); XP_U16 stringFromStreamHere( XWStreamCtxt* stream, XP_UCHAR* buf, XP_U16 len );
void stringToStream( XWStreamCtxt* stream, const XP_UCHAR* str ); 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 XP_UCHAR* p_copyString( MPFORMAL const XP_UCHAR* instr
#ifdef MEM_DEBUG #ifdef MEM_DEBUG
, const char* file, const char* func, XP_U32 lineNo , const char* file, const char* func, XP_U32 lineNo

View file

@ -1,6 +1,6 @@
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */ /* -*-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. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -26,9 +26,8 @@
#include "dawg.h" #include "dawg.h"
#include "model.h" #include "model.h"
#include "board.h" #include "board.h"
#include "mempool.h"
#include "vtabmgr.h"
#include "comms.h" #include "comms.h"
#include "dutil.h"
#include "xwrelay.h" #include "xwrelay.h"
@ -87,8 +86,6 @@ typedef XP_Bool (*XWTimerProc)( void* closure, XWTimerReason why );
*/ */
typedef struct UtilVtable { typedef struct UtilVtable {
VTableMgr* (*m_util_getVTManager)(XW_UtilCtxt* uc);
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
XWStreamCtxt* (*m_util_makeStreamFromAddr )(XW_UtilCtxt* uc, XWStreamCtxt* (*m_util_makeStreamFromAddr )(XW_UtilCtxt* uc,
XP_PlayerAddr channelNo ); XP_PlayerAddr channelNo );
@ -144,21 +141,8 @@ typedef struct UtilVtable {
void (*m_util_requestTime)( XW_UtilCtxt* uc ); void (*m_util_requestTime)( XW_UtilCtxt* uc );
XP_Bool (*m_util_altKeyDown)( 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 ); 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, void (*m_util_notifyIllegalWords)( XW_UtilCtxt* uc, BadWordInfo* bwi,
XP_U16 turn, XP_Bool turnLost ); XP_U16 turn, XP_Bool turnLost );
@ -172,11 +156,6 @@ typedef struct UtilVtable {
void (*m_util_cellSquareHeld)( XW_UtilCtxt* uc, XWStreamCtxt* words ); void (*m_util_cellSquareHeld)( XW_UtilCtxt* uc, XWStreamCtxt* words );
#endif #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 #ifndef XWFEATURE_STANDALONE_ONLY
void (*m_util_informMissing)(XW_UtilCtxt* uc, XP_Bool isServer, void (*m_util_informMissing)(XW_UtilCtxt* uc, XP_Bool isServer,
const CommsAddrRec* addr, XP_U16 nDevs, const CommsAddrRec* addr, XP_U16 nDevs,
@ -201,9 +180,7 @@ typedef struct UtilVtable {
void (*m_util_engineStopping)( XW_UtilCtxt* uc ); void (*m_util_engineStopping)( XW_UtilCtxt* uc );
#endif #endif
#ifdef COMMS_CHECKSUM XW_DUtilCtxt* (*m_util_getDevUtilCtxt)( XW_UtilCtxt* uc );
XP_UCHAR* (*m_util_md5sum)( XW_UtilCtxt* uc, const XP_U8* ptr, XP_U16 len );
#endif
} UtilVtable; } UtilVtable;
@ -217,9 +194,6 @@ struct XW_UtilCtxt {
MPSLOT MPSLOT
}; };
#define util_getVTManager(uc) \
(uc)->vtable->m_util_getVTManager((uc))
#define util_makeStreamFromAddr(uc,a) \ #define util_makeStreamFromAddr(uc,a) \
(uc)->vtable->m_util_makeStreamFromAddr((uc),(a)) (uc)->vtable->m_util_makeStreamFromAddr((uc),(a))
@ -286,24 +260,9 @@ struct XW_UtilCtxt {
#define util_altKeyDown( uc ) \ #define util_altKeyDown( uc ) \
(uc)->vtable->m_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 ) \ #define util_makeEmptyDict( uc ) \
(uc)->vtable->m_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 ) \ #define util_notifyIllegalWords( uc, w, p, b ) \
(uc)->vtable->m_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) \ #define util_cellSquareHeld(uc, s) \
(uc)->vtable->m_util_cellSquareHeld( (uc), (s) ) (uc)->vtable->m_util_cellSquareHeld( (uc), (s) )
#endif #endif
#ifdef XWFEATURE_SMS
#define util_phoneNumbersSame(uc,p1,p2) \
(uc)->vtable->m_util_phoneNumbersSame( (uc), (p1), (p2) )
#endif
#ifndef XWFEATURE_STANDALONE_ONLY #ifndef XWFEATURE_STANDALONE_ONLY
# define util_informMissing( uc, is, ct, nd, nm ) \ # define util_informMissing( uc, is, ct, nd, nm ) \
@ -355,8 +310,7 @@ struct XW_UtilCtxt {
# define util_engineStopping( uc ) # define util_engineStopping( uc )
# endif # endif
#ifdef COMMS_CHECKSUM # define util_getDevUtilCtxt(uc) \
# define util_md5sum( uc, p, l ) (uc)->vtable->m_util_md5sum((uc), (p), (l)) (uc)->vtable->m_util_getDevUtilCtxt( (uc) )
#endif
#endif #endif

View file

@ -8,3 +8,4 @@ discon_ok2.sh_logs
test_backsend.sh_* test_backsend.sh_*
*.db *.db
dawg2dict dawg2dict
discon_ok2.py_logs

View file

@ -225,6 +225,7 @@ OBJ = \
$(BUILD_PLAT_DIR)/linuxutl.o \ $(BUILD_PLAT_DIR)/linuxutl.o \
$(BUILD_PLAT_DIR)/gamesdb.o \ $(BUILD_PLAT_DIR)/gamesdb.o \
$(BUILD_PLAT_DIR)/relaycon.o \ $(BUILD_PLAT_DIR)/relaycon.o \
$(BUILD_PLAT_DIR)/lindutil.o \
$(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS) $(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS)
LIBS = -lm -lpthread -luuid -lcurl -ljson-c $(GPROFFLAG) LIBS = -lm -lpthread -luuid -lcurl -ljson-c $(GPROFFLAG)

View file

@ -64,6 +64,7 @@
#include "linuxudp.h" #include "linuxudp.h"
#include "gamesdb.h" #include "gamesdb.h"
#include "relaycon.h" #include "relaycon.h"
#include "smsproto.h"
#ifdef CURSES_SMALL_SCREEN #ifdef CURSES_SMALL_SCREEN
# define MENU_WINDOW_HEIGHT 1 # define MENU_WINDOW_HEIGHT 1
@ -346,9 +347,8 @@ cursesShowFinalScores( CursesAppGlobals* globals )
XWStreamCtxt* stream; XWStreamCtxt* stream;
XP_UCHAR* text; XP_UCHAR* text;
stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool) stream = mem_stream_make_raw( MPPARM(globals->cGlobals.util->mpool)
globals->cGlobals.params->vtMgr, globals->cGlobals.params->vtMgr );
globals, CHANNEL_NONE, NULL );
server_writeFinalScores( globals->cGlobals.game.server, stream ); server_writeFinalScores( globals->cGlobals.game.server, stream );
text = strFromStream( stream ); text = strFromStream( stream );
@ -1363,13 +1363,6 @@ initClientSocket( CursesAppGlobals* globals, char* serverName )
} /* initClientSocket */ } /* initClientSocket */
#endif #endif
static VTableMgr*
curses_util_getVTManager(XW_UtilCtxt* uc)
{
CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure;
return globals->cGlobals.params->vtMgr;
} /* linux_util_getVTManager */
static void static void
curses_util_informNeedPassword( XW_UtilCtxt* XP_UNUSED(uc), curses_util_informNeedPassword( XW_UtilCtxt* XP_UNUSED(uc),
XP_U16 XP_UNUSED_DBG(playerNum), XP_U16 XP_UNUSED_DBG(playerNum),
@ -1413,9 +1406,8 @@ curses_util_remSelected( XW_UtilCtxt* uc )
XWStreamCtxt* stream; XWStreamCtxt* stream;
XP_UCHAR* text; XP_UCHAR* text;
stream = mem_stream_make( MPPARM(globals->cGlobals.util->mpool) stream = mem_stream_make_raw( MPPARM(globals->cGlobals.util->mpool)
globals->cGlobals.params->vtMgr, globals->cGlobals.params->vtMgr );
globals, CHANNEL_NONE, NULL );
board_formatRemainingTiles( globals->cGlobals.game.board, stream ); board_formatRemainingTiles( globals->cGlobals.game.board, stream );
text = strFromStream( 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_userError = curses_util_userError;
util->vtable->m_util_getVTManager = curses_util_getVTManager;
util->vtable->m_util_informNeedPassword = curses_util_informNeedPassword; util->vtable->m_util_informNeedPassword = curses_util_informNeedPassword;
util->vtable->m_util_yOffsetChange = curses_util_yOffsetChange; util->vtable->m_util_yOffsetChange = curses_util_yOffsetChange;
#ifdef XWFEATURE_TURNCHANGENOTIFY #ifdef XWFEATURE_TURNCHANGENOTIFY
@ -1740,7 +1731,8 @@ curses_requestMsgs( gpointer data )
{ {
CursesAppGlobals* globals = (CursesAppGlobals*)data; CursesAppGlobals* globals = (CursesAppGlobals*)data;
XP_UCHAR devIDBuf[64] = {0}; 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] ) { if ( '\0' != devIDBuf[0] ) {
relaycon_requestMsgs( globals->cGlobals.params, devIDBuf ); relaycon_requestMsgs( globals->cGlobals.params, devIDBuf );
} else { } else {
@ -1772,13 +1764,13 @@ cursesDevIDReceived( void* closure, const XP_UCHAR* devID,
{ {
CursesAppGlobals* globals = (CursesAppGlobals*)closure; CursesAppGlobals* globals = (CursesAppGlobals*)closure;
CommonGlobals* cGlobals = &globals->cGlobals; CommonGlobals* cGlobals = &globals->cGlobals;
sqlite3* pDb = cGlobals->pDb; sqlite3* pDb = cGlobals->params->pDb;
if ( !!devID ) { if ( !!devID ) {
XP_LOGF( "%s(devID=%s)", __func__, devID ); XP_LOGF( "%s(devID=%s)", __func__, devID );
/* If we already have one, make sure it's the same! Else store. */ /* If we already have one, make sure it's the same! Else store. */
gchar buf[64]; 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 ); && 0 == strcmp( buf, devID );
if ( !have ) { if ( !have ) {
db_store( pDb, KEY_RDEVID, devID ); db_store( pDb, KEY_RDEVID, devID );
@ -1998,9 +1990,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
XP_Bool idIsNew = XP_TRUE; XP_Bool idIsNew = XP_TRUE;
if ( !!params->dbName ) { if ( !!params->dbName ) {
sqlite3* pDb = openGamesDB( params->dbName ); params->pDb = openGamesDB( params->dbName );
/* Gross that both need to be set, but they do. */
params->pDb = g_globals.cGlobals.pDb = pDb;
/* Check if we have a local ID already. If we do and it's /* Check if we have a local ID already. If we do and it's
changed, we care. */ changed, we care. */
@ -2026,18 +2016,18 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
gchar buf[32]; gchar buf[32];
const gchar* myPhone = params->connInfo.sms.phone; const gchar* myPhone = params->connInfo.sms.myPhone;
if ( !!myPhone ) { if ( !!myPhone ) {
db_store( params->pDb, KEY_SMSPHONE, myPhone ); db_store( params->pDb, KEY_SMSPHONE, myPhone );
} else if ( !myPhone && db_fetch( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) { } else if ( !myPhone && db_fetch_safe( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) {
params->connInfo.sms.phone = myPhone = buf; params->connInfo.sms.myPhone = myPhone = buf;
} }
XP_U16 myPort = params->connInfo.sms.port; XP_U16 myPort = params->connInfo.sms.port;
gchar portbuf[8]; gchar portbuf[8];
if ( 0 < myPort ) { if ( 0 < myPort ) {
sprintf( portbuf, "%d", myPort ); sprintf( portbuf, "%d", myPort );
db_store( params->pDb, KEY_SMSPORT, portbuf ); 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 ); 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 ); 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 #endif
XWStreamCtxt* stream = NULL; XWStreamCtxt* stream = NULL;
@ -2056,9 +2051,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
GSList* games = listGames( params->pDb ); GSList* games = listGames( params->pDb );
if ( !!games ) { if ( !!games ) {
XP_ASSERT( 1 == g_slist_length(games) ); /* for now */ XP_ASSERT( 1 == g_slist_length(games) ); /* for now */
stream = mem_stream_make( MEMPOOL params->vtMgr, stream = mem_stream_make_raw( MEMPOOL params->vtMgr);
&g_globals.cGlobals, CHANNEL_NONE,
NULL );
sqlite3_int64 selRow = *(sqlite3_int64*)games->data; sqlite3_int64 selRow = *(sqlite3_int64*)games->data;
/* XP_UCHAR buf[32]; */ /* XP_UCHAR buf[32]; */
/* XP_SNPRINTF( buf, sizeof(buf), "%lld", selRow ); */ /* 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 ) ) { } else if ( !!params->fileName && file_exists( params->fileName ) ) {
mpool_setTag( MEMPOOL "file" ); mpool_setTag( MEMPOOL "file" );
stream = streamFromFile( &g_globals.cGlobals, params->fileName, stream = streamFromFile( &g_globals.cGlobals, params->fileName );
&g_globals );
#ifdef USE_SQLITE #ifdef USE_SQLITE
} else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) {
XP_UCHAR buf[32]; XP_UCHAR buf[32];
XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID ); XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID );
mpool_setTag( MEMPOOL buf ); mpool_setTag( MEMPOOL buf );
stream = streamFromDB( &g_globals.cGlobals, &g_globals ); stream = streamFromDB( &g_globals.cGlobals );
#endif #endif
} }
@ -2141,7 +2133,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
# ifdef XWFEATURE_SMS # ifdef XWFEATURE_SMS
case COMMS_CONN_SMS: case COMMS_CONN_SMS:
addr_addType( &addr, 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 ); sizeof(addr.u.sms.phone) - 1 );
addr.u.sms.port = params->connInfo.sms.port; addr.u.sms.port = params->connInfo.sms.port;
break; break;
@ -2238,7 +2230,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
endwin(); endwin();
if ( !!params->dbName ) { if ( !!params->dbName ) {
closeGamesDB( g_globals.cGlobals.pDb ); closeGamesDB( params->pDb );
} }
relaycon_cleanup( params ); relaycon_cleanup( params );

View file

@ -27,8 +27,9 @@
#define SNAP_WIDTH 150 #define SNAP_WIDTH 150
#define SNAP_HEIGHT 150 #define SNAP_HEIGHT 150
static void getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, static XP_Bool getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf,
int len ); int* len );
#ifdef DEBUG #ifdef DEBUG
static char* sqliteErr2str( int err ); static char* sqliteErr2str( int err );
#endif #endif
@ -160,13 +161,16 @@ writeToDB( XWStreamCtxt* stream, void* closure )
{ {
CommonGlobals* cGlobals = (CommonGlobals*)closure; CommonGlobals* cGlobals = (CommonGlobals*)closure;
sqlite3_int64 selRow = cGlobals->selRow; sqlite3_int64 selRow = cGlobals->selRow;
sqlite3* pDb = cGlobals->pDb; sqlite3* pDb = cGlobals->params->pDb;
XP_Bool newGame = -1 == selRow; XP_Bool newGame = -1 == selRow;
selRow = writeBlobColumnStream( stream, pDb, selRow, "game" ); selRow = writeBlobColumnStream( stream, pDb, selRow, "game" );
if ( newGame ) { /* new row; need to insert blob first */ if ( newGame ) { /* new row; need to insert blob first */
cGlobals->selRow = selRow; cGlobals->selRow = selRow;
XP_LOGF( "%s(): new game at row %lld", __func__, selRow );
} else {
assert( selRow == cGlobals->selRow );
} }
(*cGlobals->onSave)( cGlobals->onSaveClosure, selRow, newGame ); (*cGlobals->onSave)( cGlobals->onSaveClosure, selRow, newGame );
@ -184,11 +188,12 @@ addSnapshot( CommonGlobals* cGlobals )
addSurface( dctx, SNAP_WIDTH, SNAP_HEIGHT ); addSurface( dctx, SNAP_WIDTH, SNAP_HEIGHT );
board_drawSnapshot( board, (DrawCtx*)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 ); getImage( dctx, stream );
removeSurface( dctx ); removeSurface( dctx );
writeBlobColumnStream( stream, cGlobals->pDb, cGlobals->selRow, "snap" ); cGlobals->selRow = writeBlobColumnStream( stream, cGlobals->params->pDb,
// XP_ASSERT( cGlobals->selRow == newRow ); cGlobals->selRow, "snap" );
stream_destroy( stream ); stream_destroy( stream );
} }
@ -264,8 +269,8 @@ summarize( CommonGlobals* cGlobals )
cGlobals->selRow ); cGlobals->selRow );
XP_LOGF( "query: %s", buf ); XP_LOGF( "query: %s", buf );
sqlite3_stmt* stmt = NULL; sqlite3_stmt* stmt = NULL;
int result = sqlite3_prepare_v2( cGlobals->pDb, buf, -1, &stmt, NULL ); int result = sqlite3_prepare_v2( cGlobals->params->pDb, buf, -1, &stmt, NULL );
assertPrintResult( cGlobals->pDb, result, SQLITE_OK ); assertPrintResult( cGlobals->params->pDb, result, SQLITE_OK );
result = sqlite3_step( stmt ); result = sqlite3_step( stmt );
if ( SQLITE_DONE != result ) { if ( SQLITE_DONE != result ) {
XP_LOGF( "sqlite3_step=>%s", sqliteErr2str( result ) ); XP_LOGF( "sqlite3_step=>%s", sqliteErr2str( result ) );
@ -337,7 +342,8 @@ getRelayIDsToRowsMap( sqlite3* pDb )
case SQLITE_ROW: /* have data */ case SQLITE_ROW: /* have data */
{ {
XP_UCHAR relayID[32]; XP_UCHAR relayID[32];
getColumnText( ppStmt, 0, relayID, VSIZE(relayID) ); int len = VSIZE(relayID);
getColumnText( ppStmt, 0, relayID, &len );
gpointer key = g_strdup( relayID ); gpointer key = g_strdup( relayID );
sqlite3_int64* value = g_malloc( sizeof( value ) ); sqlite3_int64* value = g_malloc( sizeof( value ) );
*value = sqlite3_column_int64( ppStmt, 1 ); *value = sqlite3_column_int64( ppStmt, 1 );
@ -375,7 +381,8 @@ getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
if ( SQLITE_ROW == result ) { if ( SQLITE_ROW == result ) {
success = XP_TRUE; success = XP_TRUE;
int col = 0; 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->gameOver = 1 == sqlite3_column_int( ppStmt, col++ );
gib->turn = sqlite3_column_int( ppStmt, col++ ); gib->turn = sqlite3_column_int( ppStmt, col++ );
gib->turnLocal = 1 == 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->nTotal = sqlite3_column_int( ppStmt, col++ );
gib->nMissing = sqlite3_column_int( ppStmt, col++ ); gib->nMissing = sqlite3_column_int( ppStmt, col++ );
gib->seed = 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->gameID = sqlite3_column_int( ppStmt, col++ );
gib->lastMoveTime = 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 ); snprintf( gib->name, sizeof(gib->name), "Game %lld", rowid );
#ifdef PLATFORM_GTK #ifdef PLATFORM_GTK
@ -488,6 +497,7 @@ loadInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid )
void void
deleteGame( sqlite3* pDb, sqlite3_int64 rowid ) deleteGame( sqlite3* pDb, sqlite3_int64 rowid )
{ {
XP_ASSERT( !!pDb );
char query[256]; char query[256];
snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid ); snprintf( query, sizeof(query), "DELETE FROM games WHERE rowid = %lld", rowid );
sqlite3_stmt* ppStmt; sqlite3_stmt* ppStmt;
@ -502,9 +512,9 @@ deleteGame( sqlite3* pDb, sqlite3_int64 rowid )
void void
db_store( sqlite3* pDb, const gchar* key, const gchar* value ) db_store( sqlite3* pDb, const gchar* key, const gchar* value )
{ {
char buf[256]; XP_ASSERT( !!pDb );
snprintf( buf, sizeof(buf), gchar* buf =
"INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')", g_strdup_printf( "INSERT OR REPLACE INTO pairs (key, value) VALUES ('%s', '%s')",
key, value ); key, value );
sqlite3_stmt *ppStmt; sqlite3_stmt *ppStmt;
int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL ); int result = sqlite3_prepare_v2( pDb, buf, -1, &ppStmt, NULL );
@ -513,33 +523,51 @@ db_store( sqlite3* pDb, const gchar* key, const gchar* value )
assertPrintResult( pDb, result, SQLITE_DONE ); assertPrintResult( pDb, result, SQLITE_DONE );
XP_USE( result ); XP_USE( result );
sqlite3_finalize( ppStmt ); sqlite3_finalize( ppStmt );
g_free( buf );
} }
XP_Bool FetchResult
db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint buflen ) db_fetch( sqlite3* pDb, const gchar* key, gchar* buf, gint* buflen )
{ {
XP_ASSERT( !!pDb );
FetchResult result = NOT_THERE;
char query[256]; char query[256];
snprintf( query, sizeof(query), snprintf( query, sizeof(query),
"SELECT value from pairs where key = '%s'", key ); "SELECT value from pairs where key = '%s'", key );
sqlite3_stmt *ppStmt; sqlite3_stmt *ppStmt;
int result = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL ); int sqlResult = sqlite3_prepare_v2( pDb, query, -1, &ppStmt, NULL );
XP_Bool found = SQLITE_OK == result; XP_Bool found = SQLITE_OK == sqlResult;
if ( found ) { if ( found ) {
result = sqlite3_step( ppStmt ); result = sqlite3_step( ppStmt );
found = SQLITE_ROW == result; found = SQLITE_ROW == result;
if ( found ) { if ( found ) {
getColumnText( ppStmt, 0, buf, buflen ); if ( getColumnText( ppStmt, 0, buf, buflen ) ) {
result = SUCCESS;
} else { } else {
result = BUFFER_TOO_SMALL;
}
} else if ( !!buf ) {
buf[0] = '\0'; buf[0] = '\0';
} }
} }
sqlite3_finalize( ppStmt ); 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 void
db_remove( sqlite3* pDb, const gchar* key ) db_remove( sqlite3* pDb, const gchar* key )
{ {
XP_ASSERT( !!pDb );
char query[256]; char query[256];
snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key ); snprintf( query, sizeof(query), "DELETE FROM pairs WHERE key = '%s'", key );
sqlite3_stmt *ppStmt; sqlite3_stmt *ppStmt;
@ -551,15 +579,19 @@ db_remove( sqlite3* pDb, const gchar* key )
sqlite3_finalize( ppStmt ); sqlite3_finalize( ppStmt );
} }
static void static XP_Bool
getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, getColumnText( sqlite3_stmt *ppStmt, int iCol, XP_UCHAR* buf, int *len )
int XP_UNUSED_DBG(len) )
{ {
const unsigned char* txt = sqlite3_column_text( ppStmt, iCol ); int colLen = sqlite3_column_bytes( ppStmt, iCol );
int needLen = sqlite3_column_bytes( ppStmt, iCol );
XP_ASSERT( needLen < len ); XP_Bool success = colLen < *len;
XP_MEMCPY( buf, txt, needLen ); *len = colLen + 1; /* sqlite does not store the null byte */
buf[needLen] = '\0'; if ( success ) {
const unsigned char* colTxt = sqlite3_column_text( ppStmt, iCol );
XP_MEMCPY( buf, colTxt, colLen );
buf[colLen] = '\0';
}
return success;
} }
#ifdef DEBUG #ifdef DEBUG

View file

@ -47,7 +47,7 @@ typedef struct _GameInfo {
} GameInfo; } GameInfo;
sqlite3* openGamesDB( const char* dbName ); sqlite3* openGamesDB( const char* dbName );
void closeGamesDB( sqlite3* dbp ); void closeGamesDB( sqlite3* pDb );
void writeToDB( XWStreamCtxt* stream, void* closure ); void writeToDB( XWStreamCtxt* stream, void* closure );
sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb ); sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb );
@ -55,15 +55,15 @@ sqlite3_int64 writeNewGameToDB( XWStreamCtxt* stream, sqlite3* pDb );
void summarize( CommonGlobals* cGlobals ); void summarize( CommonGlobals* cGlobals );
/* Return GSList whose data is (ptrs to) rowids */ /* Return GSList whose data is (ptrs to) rowids */
GSList* listGames( sqlite3* dbp ); GSList* listGames( sqlite3* pDb );
/* free list and data allocated by above */ /* free list and data allocated by above */
void freeGamesList( GSList* games ); void freeGamesList( GSList* games );
/* Mapping of relayID -> rowid */ /* Mapping of relayID -> rowid */
GHashTable* getRelayIDsToRowsMap( sqlite3* pDb ); GHashTable* getRelayIDsToRowsMap( sqlite3* pDb );
XP_Bool getGameInfo( sqlite3* dbp, sqlite3_int64 rowid, GameInfo* gib ); XP_Bool getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib );
void getRowsForGameID( sqlite3* dbp, XP_U32 gameID, sqlite3_int64* rowids, void getRowsForGameID( sqlite3* pDb, XP_U32 gameID, sqlite3_int64* rowids,
int* nRowIDs ); int* nRowIDs );
XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid ); XP_Bool loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid );
void saveInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb, void saveInviteAddrs( XWStreamCtxt* stream, sqlite3* pDb,
@ -78,8 +78,12 @@ void deleteGame( sqlite3* pDb, sqlite3_int64 rowid );
#define KEY_SMSPORT "SMSPORT" #define KEY_SMSPORT "SMSPORT"
#define KEY_WIN_LOC "WIN_LOC" #define KEY_WIN_LOC "WIN_LOC"
void db_store( sqlite3* dbp, const gchar* key, const gchar* value ); void db_store( sqlite3* pDb, const gchar* key, const gchar* value );
XP_Bool db_fetch( sqlite3* dbp, const gchar* key, gchar* buf, gint buflen ); void db_remove( sqlite3* pDb, const gchar* key );
void db_remove( sqlite3* dbp, 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 #endif

View file

@ -86,7 +86,7 @@ static void gtkShowFinalScores( const GtkGameGlobals* globals,
XP_Bool ignoreTimeout ); XP_Bool ignoreTimeout );
static void send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers, static void send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
XP_U32 devID, const XP_UCHAR* relayID, XP_U32 devID, const XP_UCHAR* relayID,
const XP_UCHAR* phone ); const CommsAddrRec* addrs );
#define GTK_TRAY_HT_ROWS 3 #define GTK_TRAY_HT_ROWS 3
@ -589,19 +589,18 @@ createOrLoadObjects( GtkGameGlobals* globals )
setTransportProcs( &procs, globals ); setTransportProcs( &procs, globals );
if ( !!params->fileName && file_exists( params->fileName ) ) { if ( !!params->fileName && file_exists( params->fileName ) ) {
stream = streamFromFile( cGlobals, params->fileName, globals ); stream = streamFromFile( cGlobals, params->fileName );
#ifdef USE_SQLITE #ifdef USE_SQLITE
} else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) { } else if ( !!params->dbFileName && file_exists( params->dbFileName ) ) {
XP_UCHAR buf[32]; XP_UCHAR buf[32];
XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID ); XP_SNPRINTF( buf, sizeof(buf), "%d", params->dbFileID );
mpool_setTag( MEMPOOL buf ); mpool_setTag( MEMPOOL buf );
stream = streamFromDB( cGlobals, globals ); stream = streamFromDB( cGlobals );
#endif #endif
} else if ( !!cGlobals->pDb && 0 <= cGlobals->selRow ) { } else if ( !!params->pDb && 0 <= cGlobals->selRow ) {
stream = mem_stream_make( MPPARM(cGlobals->util->mpool) stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
params->vtMgr, params->vtMgr );
cGlobals, CHANNEL_NONE, NULL ); if ( !loadGame( stream, params->pDb, cGlobals->selRow ) ) {
if ( !loadGame( stream, cGlobals->pDb, cGlobals->selRow ) ) {
stream_destroy( stream ); stream_destroy( stream );
stream = NULL; stream = NULL;
} }
@ -874,10 +873,9 @@ on_board_window_shown( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
CommsCtxt* comms = cGlobals->game.comms; CommsCtxt* comms = cGlobals->game.comms;
if ( !!comms /*&& COMMS_CONN_NONE == comms_getConTypes( comms )*/ ) { if ( !!comms /*&& COMMS_CONN_NONE == comms_getConTypes( comms )*/ ) {
/* If it has pending invite info, send the invitation! */ /* If it has pending invite info, send the invitation! */
XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr, cGlobals->params->vtMgr );
cGlobals, CHANNEL_NONE, NULL ); if ( loadInviteAddrs( stream, cGlobals->params->pDb, cGlobals->selRow ) ) {
if ( loadInviteAddrs( stream, cGlobals->pDb, cGlobals->selRow ) ) {
CommsAddrRec addr = {0}; CommsAddrRec addr = {0};
addrFromStream( &addr, stream ); addrFromStream( &addr, stream );
comms_setAddr( cGlobals->game.comms, &addr ); 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 ); XP_LOGF( "%s: inviteDlg => %d", __func__, confirmed );
if ( confirmed ) { if ( confirmed ) {
send_invites( cGlobals, nPlayers, devID, NULL, NULL ); send_invites( cGlobals, nPlayers, devID, NULL, &inviteAddr );
} }
} /* handle_invite_button */ } /* handle_invite_button */
static void static void
send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers, send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
XP_U32 devID, const XP_UCHAR* relayID, XP_U32 devID, const XP_UCHAR* relayID,
const XP_UCHAR* phone ) const CommsAddrRec* addrs )
{ {
CommsAddrRec addr = {0}; CommsAddrRec addr = {0};
CommsCtxt* comms = cGlobals->game.comms; CommsCtxt* comms = cGlobals->game.comms;
XP_ASSERT( comms ); XP_ASSERT( comms );
comms_getAddr( comms, &addr ); comms_getAddr( comms, &addr );
gint forceChannel = 0; /* PENDING */ gint forceChannel = 1; /* 1 is what Android does. Limits to two-device games */
NetLaunchInfo nli = {0}; NetLaunchInfo nli = {0};
nli_init( &nli, cGlobals->gi, &addr, nPlayers, forceChannel ); nli_init( &nli, cGlobals->gi, &addr, nPlayers, forceChannel );
@ -1647,9 +1645,8 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
#ifdef DEBUG #ifdef DEBUG
{ {
XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr, cGlobals->params->vtMgr );
NULL, CHANNEL_NONE, NULL );
nli_saveToStream( &nli, stream ); nli_saveToStream( &nli, stream );
NetLaunchInfo tmp; NetLaunchInfo tmp;
nli_makeFromStream( &tmp, stream ); nli_makeFromStream( &tmp, stream );
@ -1658,11 +1655,13 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
} }
#endif #endif
if ( !!phone ) { if ( !!addrs->u.sms.phone ) {
XP_ASSERT( 0 ); /* not implemented */ gchar gameName[64];
/* linux_sms_invite( cGlobals->params, gi, &addr, gameName, */ snprintf( gameName, VSIZE(gameName), "Game %d", cGlobals->gi->gameID );
/* nPlayers, forceChannel, */
/* inviteAddr.u.sms.phone, inviteAddr.u.sms.port ); */ linux_sms_invite( cGlobals->params, cGlobals->gi, &addr, gameName,
nPlayers, forceChannel,
addrs->u.sms.phone, addrs->u.sms.port );
} }
if ( 0 != devID || !!relayID ) { if ( 0 != devID || !!relayID ) {
XP_ASSERT( 0 != devID || (!!relayID && !!relayID[0]) ); XP_ASSERT( 0 != devID || (!!relayID && !!relayID[0]) );
@ -1722,14 +1721,6 @@ gtkUserError( GtkGameGlobals* globals, const char* format, ... )
va_end(ap); va_end(ap);
} /* gtkUserError */ } /* gtkUserError */
static VTableMgr*
gtk_util_getVTManager(XW_UtilCtxt* uc)
{
GtkGameGlobals* globals = (GtkGameGlobals*)uc->closure;
return globals->cGlobals.params->vtMgr;
} /* linux_util_getVTManager */
static gint static gint
ask_blank( gpointer data ) ask_blank( gpointer data )
{ {
@ -1934,9 +1925,8 @@ gtkShowFinalScores( const GtkGameGlobals* globals, XP_Bool ignoreTimeout )
XP_UCHAR* text; XP_UCHAR* text;
const CommonGlobals* cGlobals = &globals->cGlobals; const CommonGlobals* cGlobals = &globals->cGlobals;
stream = mem_stream_make( MPPARM(cGlobals->util->mpool) stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr, cGlobals->params->vtMgr );
NULL, CHANNEL_NONE, NULL );
server_writeFinalScores( cGlobals->game.server, stream ); server_writeFinalScores( cGlobals->game.server, stream );
text = strFromStream( stream ); text = strFromStream( stream );
@ -2272,9 +2262,8 @@ gtk_util_remSelected( XW_UtilCtxt* uc )
XWStreamCtxt* stream; XWStreamCtxt* stream;
XP_UCHAR* text; XP_UCHAR* text;
stream = mem_stream_make( MEMPOOL stream = mem_stream_make_raw( MEMPOOL
globals->cGlobals.params->vtMgr, globals->cGlobals.params->vtMgr );
globals, CHANNEL_NONE, NULL );
board_formatRemainingTiles( globals->cGlobals.game.board, stream ); board_formatRemainingTiles( globals->cGlobals.game.board, stream );
text = strFromStream( stream ); text = strFromStream( stream );
stream_destroy( 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_userError = gtk_util_userError;
util->vtable->m_util_notifyMove = gtk_util_notifyMove; util->vtable->m_util_notifyMove = gtk_util_notifyMove;
util->vtable->m_util_notifyTrade = gtk_util_notifyTrade; 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_notifyPickTileBlank = gtk_util_notifyPickTileBlank;
util->vtable->m_util_informNeedPickTiles = gtk_util_informNeedPickTiles; util->vtable->m_util_informNeedPickTiles = gtk_util_informNeedPickTiles;
util->vtable->m_util_informNeedPassword = gtk_util_informNeedPassword; util->vtable->m_util_informNeedPassword = gtk_util_informNeedPassword;
@ -2935,11 +2923,9 @@ loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params,
CommonGlobals* cGlobals = &globals->cGlobals; CommonGlobals* cGlobals = &globals->cGlobals;
cGlobals->selRow = rowid; cGlobals->selRow = rowid;
cGlobals->pDb = pDb; XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) params->vtMgr );
params->vtMgr, cGlobals, XP_Bool loaded = loadGame( stream, pDb, rowid );
CHANNEL_NONE, NULL );
XP_Bool loaded = loadGame( stream, cGlobals->pDb, rowid );
if ( loaded ) { if ( loaded ) {
if ( NULL == cGlobals->dict ) { if ( NULL == cGlobals->dict ) {
cGlobals->dict = makeDictForStream( cGlobals, stream ); cGlobals->dict = makeDictForStream( cGlobals, stream );

View file

@ -305,7 +305,7 @@ makeSMSPage( GtkConnsState* state, PageData* data )
GtkWidget* vbox = boxWithUseCheck( state, data ); GtkWidget* vbox = boxWithUseCheck( state, data );
XP_Bool hasSMS = addr_hasType( state->addr, data->pageType ); XP_Bool hasSMS = addr_hasType( state->addr, data->pageType );
const gchar* phone = hasSMS ? 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 ); GtkWidget* hbox = makeLabeledField( "My phone", &state->smsphone, phone );
gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 );
gtk_widget_set_sensitive( state->smsphone, !state->readOnly ); gtk_widget_set_sensitive( state->smsphone, !state->readOnly );

View file

@ -237,7 +237,7 @@ makeSMSPage( GtkInviteState* state, PageData* data )
GtkWidget* vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 ); GtkWidget* vbox = gtk_box_new( GTK_ORIENTATION_VERTICAL, 0 );
XP_Bool hasSMS = addr_hasType( state->addr, data->pageType ); XP_Bool hasSMS = addr_hasType( state->addr, data->pageType );
const gchar* phone = hasSMS ? 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 ); GtkWidget* hbox = makeLabeledField( "Invitee phone", &state->smsphone, phone );
gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 ); gtk_box_pack_start( GTK_BOX(vbox), hbox, FALSE, TRUE, 0 );

View file

@ -21,11 +21,13 @@
#ifdef PLATFORM_GTK #ifdef PLATFORM_GTK
#include "strutils.h" #include "strutils.h"
#include "smsproto.h"
#include "main.h" #include "main.h"
#include "gtkmain.h" #include "gtkmain.h"
#include "gamesdb.h" #include "gamesdb.h"
#include "gtkboard.h" #include "gtkboard.h"
#include "linuxmain.h" #include "linuxmain.h"
#include "linuxutl.h"
#include "relaycon.h" #include "relaycon.h"
#include "linuxsms.h" #include "linuxsms.h"
#include "gtkask.h" #include "gtkask.h"
@ -275,7 +277,6 @@ handle_newgame_button( GtkWidget* XP_UNUSED(widget), void* closure )
freeGlobals( globals ); freeGlobals( globals );
} else { } else {
GtkWidget* gameWindow = globals->window; GtkWidget* gameWindow = globals->window;
globals->cGlobals.pDb = apg->params->pDb;
globals->cGlobals.selRow = -1; globals->cGlobals.selRow = -1;
recordOpened( apg, globals ); recordOpened( apg, globals );
gtk_widget_show( gameWindow ); gtk_widget_show( gameWindow );
@ -293,7 +294,6 @@ open_row( GtkAppGlobals* apg, sqlite3_int64 row, XP_Bool isNew )
apg->params->needsNewGame = XP_FALSE; apg->params->needsNewGame = XP_FALSE;
GtkGameGlobals* globals = malloc( sizeof(*globals) ); GtkGameGlobals* globals = malloc( sizeof(*globals) );
initGlobals( globals, apg->params, NULL ); initGlobals( globals, apg->params, NULL );
globals->cGlobals.pDb = apg->params->pDb;
globals->cGlobals.selRow = row; globals->cGlobals.selRow = row;
recordOpened( apg, globals ); recordOpened( apg, globals );
gtk_widget_show( globals->window ); gtk_widget_show( globals->window );
@ -315,10 +315,10 @@ handle_open_button( GtkWidget* XP_UNUSED(widget), void* closure )
void void
make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals ) make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
{ {
// LaunchParams* params = apg->params; LaunchParams* params = apg->params;
XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) XP_ASSERT( params == cGlobals->params );
cGlobals->params->vtMgr, XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
NULL, CHANNEL_NONE, NULL ); params->vtMgr );
/* Create new game. But has no addressing info, so need to set that /* Create new game. But has no addressing info, so need to set that
aside for later. */ aside for later. */
@ -333,16 +333,15 @@ make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
game_saveNewGame( MPPARM(cGlobals->util->mpool) &gi, game_saveNewGame( MPPARM(cGlobals->util->mpool) &gi,
cGlobals->util, &cGlobals->cp, stream ); cGlobals->util, &cGlobals->cp, stream );
sqlite3_int64 rowID = writeNewGameToDB( stream, cGlobals->pDb ); sqlite3_int64 rowID = writeNewGameToDB( stream, params->pDb );
stream_destroy( stream ); stream_destroy( stream );
gi_disposePlayerInfo( MPPARM(cGlobals->util->mpool) &gi ); gi_disposePlayerInfo( MPPARM(cGlobals->util->mpool) &gi );
/* If it's a multi-device game, save enough information with it than when /* 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. */ opened it can invite the other device[s] join the rematch. */
if ( !!comms ) { if ( !!comms ) {
XWStreamCtxt* stream = mem_stream_make( MPPARM(cGlobals->util->mpool) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr, params->vtMgr );
NULL, CHANNEL_NONE, NULL );
CommsAddrRec addr; CommsAddrRec addr;
comms_getAddr( comms, &addr ); comms_getAddr( comms, &addr );
addrToStream( stream, &addr ); addrToStream( stream, &addr );
@ -364,7 +363,7 @@ make_rematch( GtkAppGlobals* apg, const CommonGlobals* cGlobals )
} }
addrToStream( stream, &addrs[ii] ); addrToStream( stream, &addrs[ii] );
} }
saveInviteAddrs( stream, cGlobals->pDb, rowID ); saveInviteAddrs( stream, params->pDb, rowID );
stream_destroy( stream ); stream_destroy( stream );
} }
@ -406,7 +405,7 @@ handle_delete_button( GtkWidget* XP_UNUSED(widget), void* closure )
deleteGame( params->pDb, rowid ); deleteGame( params->pDb, rowid );
XP_UCHAR devIDBuf[64] = {0}; 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] ) { if ( '\0' != devIDBuf[0] ) {
relaycon_deleted( params, devIDBuf, clientToken ); relaycon_deleted( params, devIDBuf, clientToken );
} else { } else {
@ -482,7 +481,7 @@ setWindowTitle( GtkAppGlobals* apg )
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
int len = strlen( title ); int len = strlen( title );
snprintf( &title[len], VSIZE(title) - len, " (phone: %s, port: %d)", 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 #endif
#ifdef XWFEATURE_RELAY #ifdef XWFEATURE_RELAY
XP_U32 relayID = linux_getDevIDRelay( params ); XP_U32 relayID = linux_getDevIDRelay( params );
@ -501,7 +500,7 @@ trySetWinConfig( GtkAppGlobals* apg )
int height = 400; int height = 400;
gchar buf[64]; 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 ); sscanf( buf, "%d:%d:%d:%d", &xx, &yy, &width, &height );
} }
@ -684,7 +683,6 @@ relayInviteReceived( void* closure, NetLaunchInfo* invite )
// globals->cGlobals.addr = *returnAddr; // globals->cGlobals.addr = *returnAddr;
GtkWidget* gameWindow = globals->window; GtkWidget* gameWindow = globals->window;
globals->cGlobals.pDb = apg->params->pDb;
globals->cGlobals.selRow = -1; globals->cGlobals.selRow = -1;
recordOpened( apg, globals ); recordOpened( apg, globals );
gtk_widget_show( gameWindow ); gtk_widget_show( gameWindow );
@ -730,7 +728,7 @@ requestMsgs( gpointer data )
{ {
GtkAppGlobals* apg = (GtkAppGlobals*)data; GtkAppGlobals* apg = (GtkAppGlobals*)data;
XP_UCHAR devIDBuf[64] = {0}; 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] ) { if ( '\0' != devIDBuf[0] ) {
relaycon_requestMsgs( apg->params, devIDBuf ); relaycon_requestMsgs( apg->params, devIDBuf );
} else { } else {
@ -755,8 +753,9 @@ smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName),
{ {
GtkAppGlobals* apg = (GtkAppGlobals*)closure; GtkAppGlobals* apg = (GtkAppGlobals*)closure;
LaunchParams* params = apg->params; LaunchParams* params = apg->params;
XP_LOGF( "%s(gameName=%s, gameID=%d, dictName=%s, nPlayers=%d, nHere=%d)", XP_LOGF( "%s(gameName=%s, gameID=%d, dictName=%s, nPlayers=%d, "
__func__, gameName, gameID, dictName, nPlayers, nHere ); "nHere=%d, forceChannel=%d)", __func__, gameName, gameID, dictName,
nPlayers, nHere, forceChannel );
CurGameInfo gi = {0}; CurGameInfo gi = {0};
gi_copy( MPPARM(params->mpool) &gi, &params->pgi ); gi_copy( MPPARM(params->mpool) &gi, &params->pgi );
@ -765,6 +764,7 @@ smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName),
gi.gameID = gameID; gi.gameID = gameID;
gi.dictLang = dictLang; gi.dictLang = dictLang;
gi.forceChannel = forceChannel; gi.forceChannel = forceChannel;
gi.serverRole = SERVER_ISCLIENT; /* recipient of invitation is client */
replaceStringIfDifferent( params->mpool, &gi.dictName, dictName ); replaceStringIfDifferent( params->mpool, &gi.dictName, dictName );
GtkGameGlobals* globals = malloc( sizeof(*globals) ); GtkGameGlobals* globals = malloc( sizeof(*globals) );
@ -773,7 +773,6 @@ smsInviteReceived( void* closure, const XP_UCHAR* XP_UNUSED_DBG(gameName),
globals->cGlobals.addr = *returnAddr; globals->cGlobals.addr = *returnAddr;
GtkWidget* gameWindow = globals->window; GtkWidget* gameWindow = globals->window;
globals->cGlobals.pDb = apg->params->pDb;
globals->cGlobals.selRow = -1; globals->cGlobals.selRow = -1;
recordOpened( apg, globals ); recordOpened( apg, globals );
gtk_widget_show( gameWindow ); gtk_widget_show( gameWindow );
@ -896,18 +895,18 @@ gtkmain( LaunchParams* params )
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
gchar buf[32]; gchar buf[32];
const gchar* myPhone = params->connInfo.sms.phone; const gchar* myPhone = params->connInfo.sms.myPhone;
if ( !!myPhone ) { if ( !!myPhone ) {
db_store( params->pDb, KEY_SMSPHONE, myPhone ); db_store( params->pDb, KEY_SMSPHONE, myPhone );
} else if ( !myPhone && db_fetch( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) { } else if ( !myPhone && db_fetch_safe( params->pDb, KEY_SMSPHONE, buf, VSIZE(buf) ) ) {
params->connInfo.sms.phone = myPhone = buf; params->connInfo.sms.myPhone = myPhone = buf;
} }
XP_U16 myPort = params->connInfo.sms.port; XP_U16 myPort = params->connInfo.sms.port;
gchar portbuf[8]; gchar portbuf[8];
if ( 0 < myPort ) { if ( 0 < myPort ) {
sprintf( portbuf, "%d", myPort ); sprintf( portbuf, "%d", myPort );
db_store( params->pDb, KEY_SMSPORT, portbuf ); 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 ); params->connInfo.sms.port = myPort = atoi( portbuf );
} }
if ( !!myPhone && 0 < myPort ) { if ( !!myPhone && 0 < myPort ) {
@ -921,7 +920,13 @@ gtkmain( LaunchParams* params )
XP_LOGF( "not activating SMS: I don't have a phone" ); 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 #endif
makeGamesWindow( &apg ); makeGamesWindow( &apg );
} else if ( !!params->dbFileName ) { } else if ( !!params->dbFileName ) {

315
xwords4/linux/lindutil.c Normal file
View file

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

28
xwords4/linux/lindutil.h Normal file
View file

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

View file

@ -57,7 +57,9 @@
#include "main.h" #include "main.h"
#include "gamesdb.h" #include "gamesdb.h"
#include "linuxdict.h" #include "linuxdict.h"
#include "lindutil.h"
#include "relaycon.h" #include "relaycon.h"
#include "smsproto.h"
#ifdef PLATFORM_NCURSES #ifdef PLATFORM_NCURSES
# include "cursesmain.h" # include "cursesmain.h"
#endif #endif
@ -95,7 +97,7 @@ file_exists( const char* fileName )
} /* file_exists */ } /* file_exists */
XWStreamCtxt* XWStreamCtxt*
streamFromFile( CommonGlobals* cGlobals, char* name, void* closure ) streamFromFile( CommonGlobals* cGlobals, char* name )
{ {
XP_U8* buf; XP_U8* buf;
struct stat statBuf; struct stat statBuf;
@ -110,9 +112,8 @@ streamFromFile( CommonGlobals* cGlobals, char* name, void* closure )
} }
fclose( f ); fclose( f );
stream = mem_stream_make( MPPARM(cGlobals->util->mpool) stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr, cGlobals->params->vtMgr );
closure, CHANNEL_NONE, NULL );
stream_putBytes( stream, buf, statBuf.st_size ); stream_putBytes( stream, buf, statBuf.st_size );
free( buf ); free( buf );
@ -121,7 +122,7 @@ streamFromFile( CommonGlobals* cGlobals, char* name, void* closure )
#ifdef USE_SQLITE #ifdef USE_SQLITE
XWStreamCtxt* XWStreamCtxt*
streamFromDB( CommonGlobals* cGlobals, void* closure ) streamFromDB( CommonGlobals* cGlobals )
{ {
LOG_FUNC(); LOG_FUNC();
XWStreamCtxt* stream = NULL; XWStreamCtxt* stream = NULL;
@ -139,9 +140,8 @@ streamFromDB( CommonGlobals* cGlobals, void* closure )
XP_U8 buf[size]; XP_U8 buf[size];
res = sqlite3_blob_read( ppBlob, buf, size, 0 ); res = sqlite3_blob_read( ppBlob, buf, size, 0 );
if ( SQLITE_OK == res ) { if ( SQLITE_OK == res ) {
stream = mem_stream_make( MPPARM(cGlobals->util->mpool) stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
params->vtMgr, params->vtMgr );
closure, CHANNEL_NONE, NULL );
stream_putBytes( stream, buf, size ); stream_putBytes( stream, buf, size );
} }
} }
@ -236,7 +236,7 @@ requestMsgsIdle( gpointer data )
{ {
CommonGlobals* cGlobals = (CommonGlobals*)data; CommonGlobals* cGlobals = (CommonGlobals*)data;
XP_UCHAR devIDBuf[64] = {0}; 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] ) { if ( '\0' != devIDBuf[0] ) {
relaycon_requestMsgs( cGlobals->params, devIDBuf ); relaycon_requestMsgs( cGlobals->params, devIDBuf );
} else { } else {
@ -352,8 +352,9 @@ void
saveGame( CommonGlobals* cGlobals ) saveGame( CommonGlobals* cGlobals )
{ {
LOG_FUNC(); LOG_FUNC();
sqlite3* pDb = cGlobals->params->pDb;
if ( !!cGlobals->game.model && if ( !!cGlobals->game.model &&
(!!cGlobals->params->fileName || !!cGlobals->pDb) ) { (!!cGlobals->params->fileName || !!pDb) ) {
XP_Bool doSave = XP_TRUE; XP_Bool doSave = XP_TRUE;
XP_Bool newGame = !file_exists( cGlobals->params->fileName ) XP_Bool newGame = !file_exists( cGlobals->params->fileName )
|| -1 == cGlobals->selRow; || -1 == cGlobals->selRow;
@ -364,13 +365,12 @@ saveGame( CommonGlobals* cGlobals )
} }
if ( doSave ) { if ( doSave ) {
if ( !!cGlobals->pDb ) { if ( !!pDb ) {
summarize( cGlobals ); summarize( cGlobals );
} }
XWStreamCtxt* outStream; XWStreamCtxt* outStream;
MemStreamCloseCallback onClose = !!cGlobals->pDb? MemStreamCloseCallback onClose = !!pDb? writeToDB : writeToFile;
writeToDB : writeToFile;
outStream = outStream =
mem_stream_make_sized( MPPARM(cGlobals->util->mpool) mem_stream_make_sized( MPPARM(cGlobals->util->mpool)
cGlobals->params->vtMgr, cGlobals->params->vtMgr,
@ -398,8 +398,7 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs,
{ {
LOG_FUNC(); LOG_FUNC();
LaunchParams* params = cGlobals->params; LaunchParams* params = cGlobals->params;
XWStreamCtxt* stream = XWStreamCtxt* stream = streamFromFile( cGlobals, params->fileName );
streamFromFile( cGlobals, params->fileName, cGlobals );
#ifdef DEBUG #ifdef DEBUG
XP_Bool opened = XP_Bool opened =
@ -431,9 +430,8 @@ handle_messages_from( CommonGlobals* cGlobals, const TransportProcs* procs,
XP_LOGF( "%s: 2: unexpected nRead: %zd", __func__, nRead ); XP_LOGF( "%s: 2: unexpected nRead: %zd", __func__, nRead );
break; break;
} }
stream = mem_stream_make( MPPARM(cGlobals->util->mpool) stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
params->vtMgr, cGlobals, CHANNEL_NONE, params->vtMgr );
NULL );
stream_putBytes( stream, buf, len ); stream_putBytes( stream, buf, len );
(void)processMessage( cGlobals, stream, NULL, XP_TRUE ); (void)processMessage( cGlobals, stream, NULL, XP_TRUE );
stream_destroy( stream ); stream_destroy( stream );
@ -447,8 +445,7 @@ read_pipe_then_close( CommonGlobals* cGlobals, const TransportProcs* procs )
{ {
LOG_FUNC(); LOG_FUNC();
LaunchParams* params = cGlobals->params; LaunchParams* params = cGlobals->params;
XWStreamCtxt* stream = XWStreamCtxt* stream = streamFromFile( cGlobals, params->fileName );
streamFromFile( cGlobals, params->fileName, cGlobals );
#ifdef DEBUG #ifdef DEBUG
XP_Bool opened = 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 ); XP_LOGF( "%s: 2: unexpected nRead: %zd", __func__, nRead );
break; break;
} }
stream = mem_stream_make( MPPARM(cGlobals->util->mpool) stream = mem_stream_make_raw( MPPARM(cGlobals->util->mpool)
params->vtMgr, cGlobals, CHANNEL_NONE, params->vtMgr );
NULL );
stream_putBytes( stream, buf, len ); stream_putBytes( stream, buf, len );
(void)processMessage( cGlobals, stream, NULL, XP_TRUE ); (void)processMessage( cGlobals, stream, NULL, XP_TRUE );
stream_destroy( stream ); stream_destroy( stream );
@ -643,6 +639,7 @@ typedef enum {
,CMD_DROPSENDRELAY ,CMD_DROPSENDRELAY
,CMD_DROPRCVRELAY ,CMD_DROPRCVRELAY
,CMD_DROPSENDSMS ,CMD_DROPSENDSMS
,CMD_SMSFAILPCT
,CMD_DROPRCVSMS ,CMD_DROPRCVSMS
,CMD_FORCECHANNEL ,CMD_FORCECHANNEL
@ -656,6 +653,7 @@ typedef enum {
#endif #endif
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
,CMD_SMSNUMBER /* SMS phone number */ ,CMD_SMSNUMBER /* SMS phone number */
,CMD_SERVER_SMSNUMBER
,CMD_SMSPORT ,CMD_SMSPORT
#endif #endif
#ifdef XWFEATURE_RELAY #ifdef XWFEATURE_RELAY
@ -684,6 +682,7 @@ typedef enum {
,CMD_NHIDDENROWS ,CMD_NHIDDENROWS
#endif #endif
,CMD_ASKTIME ,CMD_ASKTIME
,CMD_SMSTEST
,N_CMDS ,N_CMDS
} XwLinuxCmd; } XwLinuxCmd;
@ -764,6 +763,7 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_DROPSENDRELAY, false, "drop-send-relay", "start new games with relay send disabled" } ,{ 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_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_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_DROPRCVSMS, false, "drop-receive-sms", "start new games with sms receive disabled" }
,{ CMD_FORCECHANNEL, true, "force-channel", "force (clients) to use this hostid/channel" } ,{ CMD_FORCECHANNEL, true, "force-channel", "force (clients) to use this hostid/channel" }
@ -779,6 +779,7 @@ static CmdInfoRec CmdInfoRecs[] = {
#endif #endif
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
,{ CMD_SMSNUMBER, true, "sms-number", "this devices's sms phone number" } ,{ 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" } ,{ CMD_SMSPORT, true, "sms-port", "this devices's sms port" }
#endif #endif
#ifdef XWFEATURE_RELAY #ifdef XWFEATURE_RELAY
@ -811,6 +812,7 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_ASKTIME, true, "ask-timeout", ,{ CMD_ASKTIME, true, "ask-timeout",
"Wait this many ms before cancelling dialog (default 500 ms; 0 means forever)" } "Wait this many ms before cancelling dialog (default 500 ms; 0 means forever)" }
#endif #endif
,{ CMD_SMSTEST, false, "run-sms-test", "Run smsproto_runTests() on startup"}
}; };
static struct option* static struct option*
@ -910,7 +912,7 @@ linux_getDevIDRelay( LaunchParams* params )
{ {
XP_U32 result = 0; XP_U32 result = 0;
gchar buf[32]; 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 ); sscanf( buf, "%X", &result );
XP_LOGF( "%s(): %s => %x", __func__, buf, result ); XP_LOGF( "%s(): %s => %x", __func__, buf, result );
} }
@ -918,6 +920,12 @@ linux_getDevIDRelay( LaunchParams* params )
return result; return result;
} }
XP_U32
linux_getCurSeconds()
{
return (XP_U32)time(NULL);//tv.tv_sec;
}
const XP_UCHAR* const XP_UCHAR*
linux_getDevID( LaunchParams* params, DevIDType* typ ) linux_getDevID( LaunchParams* params, DevIDType* typ )
{ {
@ -928,11 +936,11 @@ linux_getDevID( LaunchParams* params, DevIDType* typ )
if ( !!params->lDevID ) { if ( !!params->lDevID ) {
result = params->lDevID; result = params->lDevID;
*typ = ID_TYPE_LINUX; *typ = ID_TYPE_LINUX;
} else if ( db_fetch( params->pDb, KEY_RDEVID, params->devIDStore, } else if ( db_fetch_safe( params->pDb, KEY_RDEVID, params->devIDStore,
sizeof(params->devIDStore) ) ) { sizeof(params->devIDStore) ) ) {
result = params->devIDStore; result = params->devIDStore;
*typ = '\0' == result[0] ? ID_TYPE_ANON : ID_TYPE_RELAY; *typ = '\0' == result[0] ? ID_TYPE_ANON : ID_TYPE_RELAY;
} else if ( db_fetch( params->pDb, KEY_LDEVID, params->devIDStore, } else if ( db_fetch_safe( params->pDb, KEY_LDEVID, params->devIDStore,
sizeof(params->devIDStore) ) ) { sizeof(params->devIDStore) ) ) {
result = params->devIDStore; result = params->devIDStore;
*typ = '\0' == result[0] ? ID_TYPE_ANON : ID_TYPE_LINUX; *typ = '\0' == result[0] ? ID_TYPE_ANON : ID_TYPE_LINUX;
@ -947,7 +955,7 @@ void
linux_doInitialReg( LaunchParams* params, XP_Bool idIsNew ) linux_doInitialReg( LaunchParams* params, XP_Bool idIsNew )
{ {
gchar rDevIDBuf[64]; gchar rDevIDBuf[64];
if ( !db_fetch( params->pDb, KEY_RDEVID, rDevIDBuf, if ( !db_fetch_safe( params->pDb, KEY_RDEVID, rDevIDBuf,
sizeof(rDevIDBuf) ) ) { sizeof(rDevIDBuf) ) ) {
rDevIDBuf[0] = '\0'; rDevIDBuf[0] = '\0';
} }
@ -964,7 +972,7 @@ linux_setupDevidParams( LaunchParams* params )
{ {
XP_Bool idIsNew = XP_TRUE; XP_Bool idIsNew = XP_TRUE;
gchar oldLDevID[256]; 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 )) ) { && (!params->lDevID || 0 == strcmp( oldLDevID, params->lDevID )) ) {
idIsNew = XP_FALSE; idIsNew = XP_FALSE;
} else { } 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 ); comms_getAddr( cGlobals->game.comms, &addr );
addrRec = &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, nSent = linux_sms_send( cGlobals->params, buf, buflen,
addrRec->u.sms.phone, addrRec->u.sms.port, phone, addrRec->u.sms.port, gameID );
gameID );
} }
break; break;
#endif #endif
@ -1434,9 +1448,8 @@ stream_from_msgbuf( CommonGlobals* globals, const unsigned char* bufPtr,
XP_U16 nBytes ) XP_U16 nBytes )
{ {
XWStreamCtxt* result; XWStreamCtxt* result;
result = mem_stream_make( MPPARM(globals->util->mpool) result = mem_stream_make_raw( MPPARM(globals->util->mpool)
globals->params->vtMgr, globals->params->vtMgr );
globals, CHANNEL_NONE, NULL );
stream_putBytes( result, bufPtr, nBytes ); stream_putBytes( result, bufPtr, nBytes );
return result; return result;
@ -1950,7 +1963,7 @@ initFromParams( CommonGlobals* cGlobals, LaunchParams* params )
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
case COMMS_CONN_SMS: case COMMS_CONN_SMS:
addr_addType( addr, 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 ); sizeof(addr->u.sms.phone) - 1 );
addr->u.sms.port = params->connInfo.sms.port; addr->u.sms.port = params->connInfo.sms.port;
break; break;
@ -2001,12 +2014,15 @@ initParams( LaunchParams* params )
/* params->util->vtable->m_util_addrChange = linux_util_addrChange; */ /* params->util->vtable->m_util_addrChange = linux_util_addrChange; */
/* params->util->vtable->m_util_setIsServer = linux_util_setIsServer; */ /* params->util->vtable->m_util_setIsServer = linux_util_setIsServer; */
#endif #endif
params->dutil = dutils_init( MPPARM(params->mpool) params->vtMgr, params );
} }
static void static void
freeParams( LaunchParams* params ) freeParams( LaunchParams* params )
{ {
vtmgr_destroy( MPPARM(params->mpool) params->vtMgr ); vtmgr_destroy( MPPARM(params->mpool) params->vtMgr );
dutils_free( &params->dutil );
dmgr_destroy( params->dictMgr ); dmgr_destroy( params->dictMgr );
gi_disposePlayerInfo( MPPARM(params->mpool) &params->pgi ); gi_disposePlayerInfo( MPPARM(params->mpool) &params->pgi );
@ -2272,7 +2288,11 @@ main( int argc, char** argv )
break; break;
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
case CMD_SMSNUMBER: /* SMS phone number */ 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 ); addr_addType( &mainParams.addr, COMMS_CONN_SMS );
break; break;
case CMD_SMSPORT: case CMD_SMSPORT:
@ -2427,6 +2447,10 @@ main( int argc, char** argv )
case CMD_DROPSENDSMS: case CMD_DROPSENDSMS:
mainParams.commsDisableds[COMMS_CONN_SMS][1] = XP_TRUE; mainParams.commsDisableds[COMMS_CONN_SMS][1] = XP_TRUE;
break; break;
case CMD_SMSFAILPCT:
mainParams.smsSendFailPct = atoi(optarg);
XP_ASSERT( mainParams.smsSendFailPct >= 0 && mainParams.smsSendFailPct <= 100 );
break;
case CMD_DROPRCVSMS: case CMD_DROPRCVSMS:
mainParams.commsDisableds[COMMS_CONN_SMS][0] = XP_TRUE; mainParams.commsDisableds[COMMS_CONN_SMS][0] = XP_TRUE;
break; break;
@ -2488,6 +2512,10 @@ main( int argc, char** argv )
mainParams.askTimeout = atoi(optarg); mainParams.askTimeout = atoi(optarg);
break; break;
#endif #endif
case CMD_SMSTEST:
mainParams.runSMSTest = XP_TRUE;
break;
default: default:
done = true; done = true;
break; break;

View file

@ -64,9 +64,8 @@ void sendOnClose( XWStreamCtxt* stream, void* closure );
void catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter ); void catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter );
XP_Bool file_exists( const char* fileName ); XP_Bool file_exists( const char* fileName );
XWStreamCtxt* streamFromFile( CommonGlobals* cGlobals, char* name, XWStreamCtxt* streamFromFile( CommonGlobals* cGlobals, char* name );
void* closure ); XWStreamCtxt* streamFromDB( CommonGlobals* cGlobals );
XWStreamCtxt* streamFromDB( CommonGlobals* cGlobals, void* closure );
void writeToFile( XWStreamCtxt* stream, void* closure ); void writeToFile( XWStreamCtxt* stream, void* closure );
XP_Bool getDictPath( const LaunchParams *params, const char* name, XP_Bool getDictPath( const LaunchParams *params, const char* name,
char* result, int resultLen ); char* result, int resultLen );

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "make -j MEMDEBUG=TRUE";-*- */ /* -*- 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. * reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@ -20,15 +20,18 @@
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
#include <errno.h>
#include <glib.h> #include <glib.h>
#include <glib/gstdio.h> #include <glib/gstdio.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/inotify.h>
#include "linuxsms.h" #include "linuxsms.h"
#include "linuxutl.h"
#include "strutils.h" #include "strutils.h"
#include "smsproto.h"
#include "linuxmain.h"
#define SMS_DIR "/tmp/xw_sms" #define SMS_DIR "/tmp/xw_sms"
#define LOCK_FILE ".lock" #define LOCK_FILE ".lock"
@ -54,26 +57,34 @@
#define ADDR_FMT "from: %s %d\n" #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 { typedef struct _LinSMSData {
int fd, wd;
XP_UCHAR myQueue[256]; XP_UCHAR myQueue[256];
XP_U16 myPort; XP_U16 myPort;
FILE* lock; FILE* lock;
XP_U16 count;
const gchar* myPhone; const gchar* myPhone;
const SMSProcs* procs; const SMSProcs* procs;
void* procClosure; void* procClosure;
SMSProto* protoState;
} LinSMSData; } 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; typedef enum { NONE, INVITE, DATA, DEATH, ACK, } SMS_CMD;
#define SMS_PROTO_VERSION 0 #define SMS_PROTO_VERSION 0
@ -103,13 +114,24 @@ unlock_queue( LinSMSData* storage )
} }
static XP_S16 static XP_S16
send_sms( LinSMSData* storage, XWStreamCtxt* stream, write_fake_sms( LaunchParams* params, XWStreamCtxt* stream,
const XP_UCHAR* phone, XP_U16 port ) const XP_UCHAR* phone, XP_U16 port )
{ {
XP_S16 nSent;
XP_U16 pct = XP_RANDOM() % 100;
XP_Bool skipWrite = pct < params->smsSendFailPct;
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 ); const XP_U8* buf = stream_getPtr( stream );
XP_U16 buflen = stream_getSize( stream ); XP_U16 buflen = stream_getSize( stream );
XP_LOGF( "%s(phone=%s, port=%d, len=%d)", __func__, phone,
port, buflen );
XP_S16 nSent = -1;
XP_ASSERT( !!storage ); XP_ASSERT( !!storage );
char path[256]; char path[256];
@ -119,17 +141,19 @@ send_sms( LinSMSData* storage, XWStreamCtxt* stream,
gchar* str64 = g_base64_encode( buf, buflen ); gchar* str64 = g_base64_encode( buf, buflen );
#endif #endif
XP_U16 count = ++storage->count; formatQueuePath( phone, port, path, sizeof(path) );
makeQueuePath( phone, port, path, sizeof(path) );
XP_LOGF( "%s: writing msg %d to %s", __func__, count, path ); /* Random-number-based name is fine, as we pick based on age. */
int rint = makeRandomInt();
g_mkdir_with_parents( path, 0777 ); /* just in case */ g_mkdir_with_parents( path, 0777 ); /* just in case */
int len = strlen( path ); int len = strlen( path );
snprintf( &path[len], sizeof(path)-len, "/%d", count ); snprintf( &path[len], sizeof(path)-len, "/%u", rint );
XP_UCHAR sms[buflen*2]; /* more like (buflen*4/3) */ XP_UCHAR sms[buflen*2]; /* more like (buflen*4/3) */
XP_U16 smslen = sizeof(sms); XP_U16 smslen = sizeof(sms);
binToSms( sms, &smslen, buf, buflen ); binToSms( sms, &smslen, buf, buflen );
XP_ASSERT( smslen == strlen(sms) ); XP_ASSERT( smslen == strlen(sms) );
XP_LOGF( "%s: writing msg to %s", __func__, path );
#ifdef DEBUG #ifdef DEBUG
XP_ASSERT( !strcmp( str64, sms ) ); XP_ASSERT( !strcmp( str64, sms ) );
@ -139,7 +163,8 @@ send_sms( LinSMSData* storage, XWStreamCtxt* stream,
XP_U16 lenout = sizeof( testout ); XP_U16 lenout = sizeof( testout );
XP_ASSERT( smsToBin( testout, &lenout, sms, smslen ) ); XP_ASSERT( smsToBin( testout, &lenout, sms, smslen ) );
XP_ASSERT( lenout == buflen ); XP_ASSERT( lenout == buflen );
XP_ASSERT( XP_MEMCMP( testout, buf, smslen ) ); // valgrind doesn't like this; punting on figuring out
// XP_ASSERT( XP_MEMCMP( testout, buf, smslen ) );
#endif #endif
FILE* fp = fopen( path, "w" ); FILE* fp = fopen( path, "w" );
@ -153,8 +178,10 @@ send_sms( LinSMSData* storage, XWStreamCtxt* stream,
nSent = buflen; nSent = buflen;
LOG_RETURNF( "%d", nSent );
}
return nSent; return nSent;
} /* linux_sms_send */ } /* write_fake_sms */
static XP_S16 static XP_S16
decodeAndDelete( LinSMSData* storage, const gchar* name, decodeAndDelete( LinSMSData* storage, const gchar* name,
@ -185,6 +212,7 @@ decodeAndDelete( LinSMSData* storage, const gchar* name,
*strstr(eol, "\n" ) = '\0'; *strstr(eol, "\n" ) = '\0';
XP_U16 inlen = strlen(eol); /* skip \n */ XP_U16 inlen = strlen(eol); /* skip \n */
XP_LOGF( "%s(): decoding message from file %s", __func__, name );
XP_U8 out[inlen]; XP_U8 out[inlen];
XP_U16 outlen = sizeof(out); XP_U16 outlen = sizeof(out);
XP_Bool valid = smsToBin( out, &outlen, eol, inlen ); XP_Bool valid = smsToBin( out, &outlen, eol, inlen );
@ -194,7 +222,8 @@ decodeAndDelete( LinSMSData* storage, const gchar* name,
nRead = outlen; nRead = outlen;
addr_setType( addr, COMMS_CONN_SMS ); addr_setType( addr, COMMS_CONN_SMS );
XP_STRNCPY( addr->u.sms.phone, phone, sizeof(addr->u.sms.phone) ); 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; addr->u.sms.port = port;
} }
} else { } else {
@ -234,13 +263,24 @@ static void
dispatch_data( LinSMSData* storage, XP_U16 XP_UNUSED(proto), dispatch_data( LinSMSData* storage, XP_U16 XP_UNUSED(proto),
XWStreamCtxt* stream, const CommsAddrRec* addr ) XWStreamCtxt* stream, const CommsAddrRec* addr )
{ {
LOG_FUNC();
XP_U32 gameID = stream_getU32( stream ); XP_U32 gameID = stream_getU32( stream );
XP_U16 len = stream_getSize( stream ); XP_U16 len = stream_getSize( stream );
XP_U8 data[len]; XP_U8 data[len];
stream_getBytes( stream, data, len ); stream_getBytes( stream, data, len );
(*storage->procs->msgReceived)( storage->procClosure, addr, gameID, const XP_UCHAR* fromPhone = addr->u.sms.phone;
SMSMsgArray* arr = smsproto_prepInbound( storage->protoState, fromPhone,
data, len ); 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 static void
@ -248,9 +288,8 @@ parseAndDispatch( LaunchParams* params, uint8_t* buf, int len,
CommsAddrRec* addr ) CommsAddrRec* addr )
{ {
LinSMSData* storage = getStorage( params ); LinSMSData* storage = getStorage( params );
XWStreamCtxt* stream = mem_stream_make( MPPARM(params->mpool) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
params->vtMgr, params->vtMgr );
NULL, CHANNEL_NONE, NULL );
stream_setVersion( stream, CUR_STREAM_VERS ); stream_setVersion( stream, CUR_STREAM_VERS );
stream_putBytes( stream, buf, len ); stream_putBytes( stream, buf, len );
@ -274,66 +313,6 @@ parseAndDispatch( LaunchParams* params, uint8_t* buf, int len,
stream_destroy( stream ); 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 void
linux_sms_init( LaunchParams* params, const gchar* myPhone, XP_U16 myPort, linux_sms_init( LaunchParams* params, const gchar* myPhone, XP_U16 myPort,
const SMSProcs* procs, void* procClosure ) const SMSProcs* procs, void* procClosure )
@ -346,18 +325,19 @@ linux_sms_init( LaunchParams* params, const gchar* myPhone, XP_U16 myPort,
storage->myPort = myPort; storage->myPort = myPort;
storage->procs = procs; storage->procs = procs;
storage->procClosure = procClosure; 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 ); XP_LOGF( "%s: my queue: %s", __func__, storage->myQueue );
storage->myPort = params->connInfo.sms.port; storage->myPort = params->connInfo.sms.port;
(void)g_mkdir_with_parents( storage->myQueue, 0777 ); (void)g_mkdir_with_parents( storage->myQueue, 0777 );
int fd = inotify_init(); /* Look for preexisting or new files every half second. Easier than
storage->fd = fd; inotify, especially when you add the need to handle files written while
storage->wd = inotify_add_watch( fd, storage->myQueue, IN_MODIFY ); not running. */
(void)g_idle_add( check_for_files_once, params );
(*procs->socketAdded)( params, fd, sms_receive ); (void)g_timeout_add( 500, check_for_files, params );
} /* linux_sms_init */ } /* linux_sms_init */
void void
@ -368,8 +348,7 @@ linux_sms_invite( LaunchParams* params, const CurGameInfo* gi,
{ {
LOG_FUNC(); LOG_FUNC();
XWStreamCtxt* stream; XWStreamCtxt* stream;
stream = mem_stream_make( MPPARM(params->mpool) params->vtMgr, stream = mem_stream_make_raw( MPPARM(params->mpool) params->vtMgr );
NULL, CHANNEL_NONE, NULL );
writeHeader( stream, INVITE ); writeHeader( stream, INVITE );
stream_putU32( stream, gi->gameID ); stream_putU32( stream, gi->gameID );
stringToStream( stream, gameName ); stringToStream( stream, gameName );
@ -381,8 +360,7 @@ linux_sms_invite( LaunchParams* params, const CurGameInfo* gi,
addrToStream( stream, addr ); addrToStream( stream, addr );
LinSMSData* storage = getStorage( params ); write_fake_sms( params, stream, toPhone, toPort );
send_sms( storage, stream, toPhone, toPort );
stream_destroy( stream ); stream_destroy( stream );
} }
@ -391,26 +369,161 @@ XP_S16
linux_sms_send( LaunchParams* params, const XP_U8* buf, linux_sms_send( LaunchParams* params, const XP_U8* buf,
XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port, XP_U16 buflen, const XP_UCHAR* phone, XP_U16 port,
XP_U32 gameID ) 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 ); XP_LOGF( "%s(len=%d)", __func__, buflen );
XWStreamCtxt* stream = mem_stream_make( MPPARM(params->mpool) params->vtMgr, XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
NULL, CHANNEL_NONE, NULL ); params->vtMgr );
writeHeader( stream, DATA ); writeHeader( stream, DATA );
stream_putU32( stream, gameID ); stream_putU32( stream, gameID );
stream_putBytes( stream, buf, buflen ); stream_putBytes( stream, buf, buflen );
LinSMSData* storage = getStorage( params ); (void)write_fake_sms( params, stream, phone, port );
if ( 0 >= send_sms( storage, stream, phone, port ) ) {
buflen = -1;
}
stream_destroy( stream ); 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 void
linux_sms_cleanup( LaunchParams* params ) linux_sms_cleanup( LaunchParams* params )
{ {
LinSMSData* storage = getStorage( params );
smsproto_free( storage->protoState );
XP_FREEP( params->mpool, &params->smsStorage ); XP_FREEP( params->mpool, &params->smsStorage );
} }

View file

@ -266,151 +266,13 @@ linux_util_getSquareBonus( XW_UtilCtxt* uc, XP_U16 nCols,
return result; return result;
} /* linux_util_getSquareBonus */ } /* linux_util_getSquareBonus */
static XP_U32 static XW_DUtilCtxt*
linux_util_getCurSeconds( XW_UtilCtxt* XP_UNUSED(uc) ) linux_util_getDevUtilCtxt( XW_UtilCtxt* 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 )
{ {
CommonGlobals* cGlobals = (CommonGlobals*)uc->closure; 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 void
linux_util_vt_init( MPFORMAL XW_UtilCtxt* util ) 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_makeEmptyDict = linux_util_makeEmptyDict;
util->vtable->m_util_getSquareBonus = linux_util_getSquareBonus; 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_getDevUtilCtxt = linux_util_getDevUtilCtxt;
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
} }
void void
@ -666,16 +517,6 @@ storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 msglen,
return inUse; 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 void
writeNoConnMsgs( CommonGlobals* cGlobals, int fd ) writeNoConnMsgs( CommonGlobals* cGlobals, int fd )
{ {
@ -688,7 +529,8 @@ writeNoConnMsgs( CommonGlobals* cGlobals, int fd )
guint nMsgs = g_slist_length( list ); guint nMsgs = g_slist_length( list );
XP_ASSERT( 0 < nMsgs ); 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_putU16( stream, 1 ); /* number of relayIDs */
stream_catString( stream, relayID ); stream_catString( stream, relayID );
stream_putU8( stream, '\n' ); stream_putU8( stream, '\n' );

View file

@ -48,7 +48,6 @@ void initNoConnStorage( CommonGlobals* cGlobals );
XP_Bool storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 len, XP_Bool storeNoConnMsg( CommonGlobals* cGlobals, const XP_U8* msg, XP_U16 len,
const XP_UCHAR* relayID ); const XP_UCHAR* relayID );
void writeNoConnMsgs( CommonGlobals* cGlobals, int fd ); void writeNoConnMsgs( CommonGlobals* cGlobals, int fd );
XWStreamCtxt* make_simple_stream( CommonGlobals* cGlobals );
#ifdef STREAM_VERS_BIGBOARD #ifdef STREAM_VERS_BIGBOARD
void setSquareBonuses( const CommonGlobals* cGlobals ); void setSquareBonuses( const CommonGlobals* cGlobals );
@ -56,4 +55,6 @@ void setSquareBonuses( const CommonGlobals* cGlobals );
# define setSquareBonuses( cg ) # define setSquareBonuses( cg )
#endif #endif
XP_U32 linux_getCurSeconds();
#endif #endif

View file

@ -32,6 +32,7 @@
#include "game.h" #include "game.h"
#include "vtabmgr.h" #include "vtabmgr.h"
#include "dictmgr.h" #include "dictmgr.h"
#include "dutil.h"
typedef struct ServerInfo { typedef struct ServerInfo {
XP_U16 nRemotePlayers; XP_U16 nRemotePlayers;
@ -58,6 +59,7 @@ typedef struct LaunchParams {
char* dbName; char* dbName;
sqlite3* pDb; /* null unless opened */ sqlite3* pDb; /* null unless opened */
XP_U16 saveFailPct; XP_U16 saveFailPct;
XP_U16 smsSendFailPct;
const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS]; const XP_UCHAR* playerDictNames[MAX_NUM_PLAYERS];
#ifdef USE_SQLITE #ifdef USE_SQLITE
char* dbFileName; char* dbFileName;
@ -77,6 +79,7 @@ typedef struct LaunchParams {
#endif #endif
VTableMgr* vtMgr; VTableMgr* vtMgr;
DictMgrCtxt* dictMgr; DictMgrCtxt* dictMgr;
XW_DUtilCtxt* dutil;
XP_U16 nLocalPlayers; XP_U16 nLocalPlayers;
XP_U16 nHidden; XP_U16 nHidden;
XP_U16 gameSeed; XP_U16 gameSeed;
@ -106,6 +109,7 @@ typedef struct LaunchParams {
XP_Bool useCurses; XP_Bool useCurses;
XP_Bool useUdp; XP_Bool useUdp;
XP_Bool useHTTP; XP_Bool useHTTP;
XP_Bool runSMSTest;
XP_Bool noHTTPAuto; XP_Bool noHTTPAuto;
XP_U16 splitPackets; XP_U16 splitPackets;
XP_U16 chatsInterval; /* 0 means disabled */ XP_U16 chatsInterval; /* 0 means disabled */
@ -150,7 +154,8 @@ typedef struct LaunchParams {
#endif #endif
#ifdef XWFEATURE_SMS #ifdef XWFEATURE_SMS
struct { struct {
const char* phone; const char* myPhone;
const char* serverPhone;
int port; int port;
} sms; } sms;
#endif #endif
@ -197,7 +202,6 @@ struct CommonGlobals {
XP_U16 lastStreamSize; XP_U16 lastStreamSize;
XP_U16 nMissing; XP_U16 nMissing;
XP_Bool manualFinal; /* use asked for final scores */ XP_Bool manualFinal; /* use asked for final scores */
sqlite3* pDb;
sqlite3_int64 selRow; sqlite3_int64 selRow;
SocketAddedFunc socketAdded; SocketAddedFunc socketAdded;

View file

@ -151,7 +151,7 @@ write_callback(void *contents, size_t size, size_t nmemb, void* data)
ws->curSize = 1L; 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; size_t oldLen = ws->curSize;
const size_t newLength = size * nmemb; const size_t newLength = size * nmemb;
XP_ASSERT( (oldLen + newLength) > 0 ); XP_ASSERT( (oldLen + newLength) > 0 );
@ -332,9 +332,8 @@ relaycon_invite( LaunchParams* params, XP_U32 destDevID,
indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, destDevID ); indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, destDevID );
} }
XWStreamCtxt* stream = mem_stream_make( MPPARM(params->mpool) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(params->mpool)
params->vtMgr, params, params->vtMgr );
CHANNEL_NONE, NULL );
nli_saveToStream( invit, stream ); nli_saveToStream( invit, stream );
XP_U16 len = stream_getSize( stream ); XP_U16 len = stream_getSize( stream );
indx += writeShort( &tmpbuf[indx], sizeof(tmpbuf) - indx, len ); indx += writeShort( &tmpbuf[indx], sizeof(tmpbuf) - indx, len );
@ -679,9 +678,8 @@ process( RelayConStorage* storage, XP_U8* buf, ssize_t nRead )
#endif #endif
getNetLong( &ptr ); getNetLong( &ptr );
XP_U16 len = getNetShort( &ptr ); XP_U16 len = getNetShort( &ptr );
XWStreamCtxt* stream = mem_stream_make( MPPARM(storage->params->mpool) XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(storage->params->mpool)
storage->params->vtMgr, storage, storage->params->vtMgr );
CHANNEL_NONE, NULL );
stream_putBytes( stream, ptr, len ); stream_putBytes( stream, ptr, len );
NetLaunchInfo invit; NetLaunchInfo invit;
XP_Bool success = nli_makeFromStream( &invit, stream ); XP_Bool success = nli_makeFromStream( &invit, stream );

View file

@ -2,6 +2,7 @@
import re, os, sys, getopt, shutil, threading, requests, json, glob import re, os, sys, getopt, shutil, threading, requests, json, glob
import argparse, datetime, random, signal, subprocess, time import argparse, datetime, random, signal, subprocess, time
from shutil import rmtree
# LOGDIR=./$(basename $0)_logs # LOGDIR=./$(basename $0)_logs
# APP_NEW="" # APP_NEW=""
@ -155,14 +156,14 @@ def player_params(args, NLOCALS, NPLAYERS, NAME_INDX):
def logReaderStub(dev): dev.logReaderMain() def logReaderStub(dev): dev.logReaderMain()
class Device(): class Device():
sConnnameMap = {}
sHasLDevIDMap = {} sHasLDevIDMap = {}
sConnNamePat = re.compile('.*got_connect_cmd: connName: "([^"]+)".*$') sConnNamePat = re.compile('.*got_connect_cmd: connName: "([^"]+)".*$')
sGameOverPat = re.compile('.*\[unused tiles\].*') 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=\'([^\']+)\'.*') 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.game = game
self.indx = indx self.indx = indx
self.args = args self.args = args
@ -176,13 +177,15 @@ class Device():
self.nInGame = nInGame self.nInGame = nInGame
# runtime stuff; init now # runtime stuff; init now
self.proc = None self.proc = None
self.connname = None self.peers = peers
self.devID = '' self.devID = ''
self.launchCount = 0 self.launchCount = 0
self.allDone = False # when true, can be killed self.allDone = False # when true, can be killed
self.nTilesLeft = None self.nTilesLeftPool = None
self.nTilesLeftTray = None
self.relayID = None self.relayID = None
self.relaySeed = 0 self.relaySeed = 0
self.locked = False
with open(self.logPath, "w") as log: with open(self.logPath, "w") as log:
log.write('New cmdline: ' + self.app + ' ' + (' '.join([str(p) for p in self.params]))) log.write('New cmdline: ' + self.app + ' ' + (' '.join([str(p) for p in self.params])))
@ -198,23 +201,20 @@ class Device():
nLines += 1 nLines += 1
log.write(line + os.linesep) log.write(line + os.linesep)
# check for connname self.locked = True
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)
# check for game over # check for game over
if not self.gameOver: if not self.gameOver:
match = Device.sGameOverPat.match(line) match = Device.sGameOverPat.match(line)
if match: self.gameOver = True if match: self.gameOver = True
# Check every line for tiles left # Check every line for tiles left in pool
match = Device.sTilesLeftPat.match(line) match = Device.sTilesLeftPoolPat.match(line)
if match: self.nTilesLeft = int(match.group(1)) 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: if not self.relayID:
match = Device.sRelayIDPat.match(line) match = Device.sRelayIDPat.match(line)
@ -222,6 +222,8 @@ class Device():
self.relaySeed = int(match.group(1)) self.relaySeed = int(match.group(1))
self.relayID = match.group(2) self.relayID = match.group(2)
self.locked = False
# print('logReaderMain done, wrote lines:', nLines, 'to', self.logPath); # print('logReaderMain done, wrote lines:', nLines, 'to', self.logPath);
def launch(self): def launch(self):
@ -233,7 +235,6 @@ class Device():
args += [self.app] + [str(p) for p in self.params] args += [self.app] + [str(p) for p in self.params]
if self.devID: args.extend( ' '.split(self.devID)) if self.devID: args.extend( ' '.split(self.devID))
self.launchCount += 1 self.launchCount += 1
# self.logStream = open(self.logPath, flag)
self.proc = subprocess.Popen(args, stdout = subprocess.DEVNULL, self.proc = subprocess.Popen(args, stdout = subprocess.DEVNULL,
stderr = subprocess.PIPE, universal_newlines = True) stderr = subprocess.PIPE, universal_newlines = True)
self.pid = self.proc.pid self.pid = self.proc.pid
@ -278,6 +279,7 @@ class Device():
shutil.move(self.db, self.args.LOGDIR + '/done') shutil.move(self.db, self.args.LOGDIR + '/done')
def send_dead(self): def send_dead(self):
if self.args.ADD_RELAY:
JSON = json.dumps([{'relayID': self.relayID, 'seed': self.relaySeed}]) JSON = json.dumps([{'relayID': self.relayID, 'seed': self.relaySeed}])
url = 'http://%s/xw4/relay.py/kill' % (self.args.HOST) url = 'http://%s/xw4/relay.py/kill' % (self.args.HOST)
params = {'params' : JSON} params = {'params' : JSON}
@ -287,8 +289,12 @@ class Device():
print('got exception sending to', url, params, '; is relay.py running as apache module?') print('got exception sending to', url, params, '; is relay.py running as apache module?')
def getTilesCount(self): def getTilesCount(self):
return {'index': self.indx, 'nTilesLeft': self.nTilesLeft, assert not self.locked
'launchCount': self.launchCount, 'game': self.game, return {'index': self.indx,
'nTilesLeftPool': self.nTilesLeftPool,
'nTilesLeftTray': self.nTilesLeftTray,
'launchCount': self.launchCount,
'game': self.game,
} }
def update_ldevid(self): def update_ldevid(self):
@ -313,17 +319,15 @@ class Device():
def check_game(self): def check_game(self):
if self.gameOver and not self.allDone: if self.gameOver and not self.allDone:
allDone = False
if len(Device.sConnnameMap[self.connname]) == self.nInGame:
allDone = True allDone = True
for dev in Device.sConnnameMap[self.connname]: for dev in self.peers:
if dev == self: continue if dev == self: continue
if not dev.gameOver: if not dev.gameOver:
allDone = False allDone = False
break break
if allDone: if allDone:
for dev in Device.sConnnameMap[self.connname]: for dev in self.peers:
assert self.game == dev.game assert self.game == dev.game
dev.allDone = True dev.allDone = True
@ -361,7 +365,9 @@ def build_cmds(args):
PLAT_PARMS += ['--curses', '--close-stdin'] 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) ROOM = 'ROOM_%.3d' % (GAME % args.NROOMS)
PHONE_BASE = '%.4d' % (GAME % args.NROOMS)
NDEVS = pick_ndevs(args) NDEVS = pick_ndevs(args)
LOCALS = figure_locals(args, NDEVS) # as array LOCALS = figure_locals(args, NDEVS) # as array
NPLAYERS = sum(LOCALS) NPLAYERS = sum(LOCALS)
@ -376,27 +382,27 @@ def build_cmds(args):
DB = '{}/{:02d}_{:02d}_DB.sql3'.format(args.LOGDIR, GAME, DEV) DB = '{}/{:02d}_{:02d}_DB.sql3'.format(args.LOGDIR, GAME, DEV)
LOG = '{}/{:02d}_{:02d}_LOG.txt'.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 = player_params(args, NLOCALS, NPLAYERS, DEV)
PARAMS += PLAT_PARMS 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']
if args.UNDO_PCT > 0:
PARAMS += ['--undo-pct', args.UNDO_PCT] # We SHOULD support having both SMS and relay working...
PARAMS += [ '--game-dict', DICT, '--relay-port', args.PORT, '--host', args.HOST] if args.ADD_RELAY:
PARAMS += ['--slow-robot', '1:3', '--skip-confirm'] PARAMS += [ '--relay-port', args.PORT, '--room', ROOM, '--host', args.HOST]
PARAMS += ['--db', DB]
if random.randint(0,100) % 100 < g_UDP_PCT_START: if random.randint(0,100) % 100 < g_UDP_PCT_START:
PARAMS += ['--use-udp'] 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]
PARAMS += ['--slow-robot', '1:3', '--skip-confirm']
PARAMS += ['--db', DB]
PARAMS += ['--drop-nth-packet', g_DROP_N] PARAMS += ['--drop-nth-packet', g_DROP_N]
if random.randint(0, 100) < args.HTTP_PCT: if random.randint(0, 100) < args.HTTP_PCT:
@ -424,224 +430,14 @@ def build_cmds(args):
# print('PARAMS:', PARAMS) # 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() dev.update_ldevid()
devs.append(dev) devs.append(dev)
COUNTER += 1 COUNTER += 1
return devs 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): def summarizeTileCounts(devs, endTime, state):
global gDeadLaunches global gDeadLaunches
shouldGoOn = True shouldGoOn = True
@ -675,13 +471,22 @@ def summarizeTileCounts(devs, endTime, state):
nLaunches += launchCount nLaunches += launchCount
fmtData[1]['data'].append('{:{width}d}'.format(launchCount, width=colWidth)) fmtData[1]['data'].append('{:{width}d}'.format(launchCount, width=colWidth))
nTiles = datum['nTilesLeft'] # Format tiles left. It's the number in the bag/pool until
fmtData[2]['data'].append(nTiles is None and ('-' * colWidth) or '{:{width}d}'.format(nTiles, width=colWidth)) # that drops to 0, then the number in the tray preceeded by
if not nTiles is None: totalTiles += int(nTiles) # '+'. 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('')
print('devs left: {}; tiles left: {}; total launches: {}; {}/{}' print('devs left: {}; bag tiles left: {}; total launches: {}; {}/{}'
.format(nDevs, totalTiles, nLaunches, datetime.datetime.now(), endTime )) .format(nDevs, totalTiles, nLaunches, datetime.datetime.now(), endTime ))
fmt = '{head:>%d} {data}' % headWidth fmt = '{head:>%d} {data}' % headWidth
for datum in fmtData: datum['data'] = ' '.join(datum['data']) for datum in fmtData: datum['data'] = ' '.join(datum['data'])
@ -708,19 +513,21 @@ gDone = False
def run_cmds(args, devs): def run_cmds(args, devs):
nCores = countCores() nCores = countCores()
endTime = datetime.datetime.now() + datetime.timedelta(minutes = args.TIMEOUT_MINS) endTime = datetime.datetime.now() + datetime.timedelta(minutes = args.TIMEOUT_MINS)
LOOPCOUNT = 0
printState = {} printState = {}
lastPrint = datetime.datetime.now()
while len(devs) > 0 and not gDone: while len(devs) > 0 and not gDone:
if countCores() > nCores: if countCores() > nCores:
print('core file count increased; exiting') print('core file count increased; exiting')
break break
if datetime.datetime.now() > endTime: now = datetime.datetime.now()
if now > endTime:
print('outta time; outta here') print('outta time; outta here')
break break
LOOPCOUNT += 1 # print stats every 5 seconds
if 0 == LOOPCOUNT % 20: if now - lastPrint > datetime.timedelta(seconds = 5):
lastPrint = now
if not summarizeTileCounts(devs, endTime, printState): if not summarizeTileCounts(devs, endTime, printState):
print('no change in too long; exiting') print('no change in too long; exiting')
break break
@ -742,20 +549,16 @@ def run_cmds(args, devs):
# PIDS[$KEY]=$PID # PIDS[$KEY]=$PID
# ROOM_PIDS[$ROOM]=$PID # ROOM_PIDS[$ROOM]=$PID
# MINEND[$KEY]=$(($NOW + $MINRUN)) # MINEND[$KEY]=$(($NOW + $MINRUN))
elif not dev.minTimeExpired(): elif dev.minTimeExpired():
# print('sleeping...')
time.sleep(1.0)
else:
dev.kill() dev.kill()
if dev.handleAllDone(): if dev.handleAllDone():
devs.remove(dev) devs.remove(dev)
# if g_DROP_N >= 0: dev.increment_drop() else:
# update_ldevid $KEY time.sleep(1.0)
# if we get here via a break, kill any remaining games # if we get here via a break, kill any remaining games
if devs: if devs:
print('stopping %d remaining games' % (len(devs))) print('stopping {} remaining games'.format(len(devs)))
for dev in devs: for dev in devs:
if dev.running(): dev.kill() if dev.running(): dev.kill()
@ -828,6 +631,7 @@ def mkParser():
help = 'No game will have fewer devices than this') help = 'No game will have fewer devices than this')
parser.add_argument('--max-devs', dest = 'MAXDEVS', type = int, default = 4, parser.add_argument('--max-devs', dest = 'MAXDEVS', type = int, default = 4,
help = 'No game will have more devices than this') help = 'No game will have more devices than this')
parser.add_argument('--min-run', dest = 'MINRUN', type = int, default = 2, parser.add_argument('--min-run', dest = 'MINRUN', type = int, default = 2,
help = 'Keep each run alive at least this many seconds') help = 'Keep each run alive at least this many seconds')
# # echo " [--new-app <path/to/app] \\" >&2 # # echo " [--new-app <path/to/app] \\" >&2
@ -852,6 +656,10 @@ def mkParser():
parser.add_argument('--undo-pct', dest = 'UNDO_PCT', default = 0, type = int) 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('--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, parser.add_argument('--with-valgrind', dest = 'VALGRIND', default = False,
action = 'store_true') action = 'store_true')
@ -1058,6 +866,10 @@ def main():
signal.signal(signal.SIGINT, termHandler) signal.signal(signal.SIGINT, termHandler)
args = parseArgs() 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) devs = build_cmds(args)
nDevs = len(devs) nDevs = len(devs)
run_cmds(args, devs) run_cmds(args, devs)

View file

@ -1135,7 +1135,7 @@ DBMgr::StoreMessage( const char* const connName, int destHid,
void void
DBMgr::decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx, DBMgr::decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx,
int byteaIndex, uint8_t* buf, size_t* buflen ) int byteaIndex, vector<uint8_t>& buf )
{ {
const char* from = NULL; const char* from = NULL;
if ( useB64 ) { if ( useB64 ) {
@ -1146,22 +1146,19 @@ DBMgr::decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx,
from = PQgetvalue( result, rowIndx, byteaIndex ); from = PQgetvalue( result, rowIndx, byteaIndex );
} }
size_t to_length;
if ( useB64 ) { if ( useB64 ) {
gsize out_len; gsize out_len;
guchar* txt = g_base64_decode( (const gchar*)from, &out_len ); guchar* txt = g_base64_decode( (const gchar*)from, &out_len );
to_length = out_len; buf.insert( buf.end(), txt, txt + out_len );
assert( to_length <= *buflen ); assert( buf.size() == out_len );
memcpy( buf, txt, to_length );
g_free( txt ); g_free( txt );
} else { } else {
uint8_t* bytes = PQunescapeBytea( (const uint8_t*)from, size_t to_length;
&to_length ); uint8_t* bytes = PQunescapeBytea( (const uint8_t*)from, &to_length );
assert( to_length <= *buflen ); buf.insert( buf.end(), bytes, bytes + to_length );
memcpy( buf, bytes, to_length ); assert( buf.size() == to_length );
PQfreemem( bytes ); PQfreemem( bytes );
} }
*buflen = to_length;
} }
void void
@ -1193,12 +1190,9 @@ DBMgr::storedMessagesImpl( string test, vector<DBMgr::MsgInfo>& msgs,
bool hasConnname = connname != NULL && '\0' != connname[0]; bool hasConnname = connname != NULL && '\0' != connname[0];
MsgInfo msg( id, token, hasConnname ); MsgInfo msg( id, token, hasConnname );
uint8_t buf[1024]; decodeMessage( result, m_useB64, ii, 1, 2, msg.msg );
size_t buflen = sizeof(buf);
decodeMessage( result, m_useB64, ii, 1, 2, buf, &buflen );
size_t msglen = atoi( PQgetvalue( result, ii, 3 ) ); size_t msglen = atoi( PQgetvalue( result, ii, 3 ) );
assert( 0 == msglen || buflen == msglen ); assert( 0 == msglen || msg.msg.size() == msglen );
msg.msg.insert( msg.msg.end(), buf, &buf[buflen] );
msgs.push_back( msg ); msgs.push_back( msg );
} }
PQclear( result ); PQclear( result );

View file

@ -166,7 +166,8 @@ class DBMgr {
int getCountWhere( const char* table, string& test ); int getCountWhere( const char* table, string& test );
void RemoveStoredMessages( string& msgIDs ); void RemoveStoredMessages( string& msgIDs );
void decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx, void decodeMessage( PGresult* result, bool useB64, int rowIndx, int b64indx,
int byteaIndex, uint8_t* buf, size_t* buflen ); int byteaIndex, vector<uint8_t>& buf );
void storedMessagesImpl( string query, vector<DBMgr::MsgInfo>& msgs, void storedMessagesImpl( string query, vector<DBMgr::MsgInfo>& msgs,
bool nullConnnameOK ); bool nullConnnameOK );
int CountStoredMessages( const char* const connName, int hid ); int CountStoredMessages( const char* const connName, int hid );

View file

@ -57,8 +57,7 @@ DevMgr::rememberDevice( DevIDRelay devid, const AddrInfo::AddrUnion* saddr )
pair<map<DevIDRelay,UDPAddrRec>::iterator, bool> result = pair<map<DevIDRelay,UDPAddrRec>::iterator, bool> result =
m_devAddrMap.insert( pair<DevIDRelay,UDPAddrRec>( devid, rec ) ); m_devAddrMap.insert( pair<DevIDRelay,UDPAddrRec>( devid, rec ) );
if ( !result.second ) { if ( !result.second ) {
logf( XW_LOGINFO, "%s: replacing address for %d; was %s, now %s", logf( XW_LOGINFO, "%s: replacing address for %d", __func__, devid );
__func__, devid, result.first->second, rec );
result.first->second = rec; result.first->second = rec;
} }

View file

@ -35,16 +35,16 @@ UDPAckTrack::nextPacketID( XWRelayReg cmd )
{ {
uint32_t result = 0; uint32_t result = 0;
if ( shouldAck( cmd ) ) { if ( shouldAck( cmd ) ) {
result = get()->nextPacketIDImpl(); result = get()->nextPacketIDImpl( cmd );
assert( PACKETID_NONE != result ); assert( PACKETID_NONE != result );
} }
return result; return result;
} }
/* static*/ void /* static*/ string
UDPAckTrack::recordAck( uint32_t packetID ) UDPAckTrack::recordAck( uint32_t packetID )
{ {
get()->recordAckImpl( packetID ); return get()->recordAckImpl( packetID );
} }
/* static */ bool /* static */ bool
@ -97,33 +97,37 @@ UDPAckTrack::ackLimit()
} }
uint32_t uint32_t
UDPAckTrack::nextPacketIDImpl() UDPAckTrack::nextPacketIDImpl( XWRelayReg cmd )
{ {
MutexLock ml( &m_mutex ); MutexLock ml( &m_mutex );
uint32_t result = ++m_nextID; uint32_t result = ++m_nextID;
AckRecord record; AckRecord record( cmd , result );
m_pendings.insert( pair<uint32_t,AckRecord>(result, record) ); m_pendings.insert( pair<uint32_t,AckRecord>(result, record) );
return result; return result;
} }
void string
UDPAckTrack::recordAckImpl( uint32_t packetID ) UDPAckTrack::recordAckImpl( uint32_t packetID )
{ {
string str;
map<uint32_t, AckRecord>::iterator iter; map<uint32_t, AckRecord>::iterator iter;
MutexLock ml( &m_mutex ); MutexLock ml( &m_mutex );
iter = m_pendings.find( packetID ); iter = m_pendings.find( packetID );
if ( m_pendings.end() == iter ) { if ( m_pendings.end() == iter ) {
logf( XW_LOGERROR, "%s: packet ID %d not found", __func__, packetID ); logf( XW_LOGERROR, "%s: packet ID %d not found", __func__, packetID );
} else { } else {
time_t took = time( NULL ) - iter->second.m_createTime; AckRecord& rec = iter->second;
str = rec.toStr();
time_t took = time( NULL ) - rec.m_createTime;
if ( 5 < took ) { if ( 5 < took ) {
logf( XW_LOGERROR, "%s: packet ID %d took %d seconds to get acked", logf( XW_LOGERROR, "%s: packet %s took %d seconds to get acked",
__func__, packetID, took ); __func__, str.c_str(), took );
} }
callProc( iter, true ); callProc( iter, true );
m_pendings.erase( iter ); m_pendings.erase( iter );
} }
return str;
} }
bool bool
@ -134,8 +138,8 @@ UDPAckTrack::setOnAckImpl( OnAckProc proc, uint32_t packetID, void* data )
MutexLock ml( &m_mutex ); MutexLock ml( &m_mutex );
map<uint32_t, AckRecord>::iterator iter = m_pendings.find( packetID ); map<uint32_t, AckRecord>::iterator iter = m_pendings.find( packetID );
if ( m_pendings.end() != iter ) { if ( m_pendings.end() != iter ) {
iter->second.proc = proc; iter->second.m_proc = proc;
iter->second.data = data; iter->second.m_data = data;
} }
} }
return canAdd; return canAdd;
@ -180,12 +184,12 @@ void
UDPAckTrack::callProc( const map<uint32_t, AckRecord>::iterator iter, bool acked ) UDPAckTrack::callProc( const map<uint32_t, AckRecord>::iterator iter, bool acked )
{ {
const AckRecord* record = &(iter->second); const AckRecord* record = &(iter->second);
OnAckProc proc = record->proc; OnAckProc proc = record->m_proc;
if ( NULL != proc ) { if ( NULL != proc ) {
uint32_t packetID = iter->first; uint32_t packetID = iter->first;
logf( XW_LOGINFO, "%s(packetID=%d, acked=%d, proc=%p)", __func__, logf( XW_LOGINFO, "%s(packetID=%d, acked=%d, proc=%p)", __func__,
packetID, acked, proc ); packetID, acked, proc );
(*proc)( acked, packetID, record->data ); (*proc)( acked, packetID, record->m_data );
} }
} }
@ -195,15 +199,16 @@ UDPAckTrack::threadProc()
for ( ; ; ) { for ( ; ; ) {
time_t limit = ackLimit(); time_t limit = ackLimit();
sleep( limit / 2 ); sleep( limit / 2 );
vector<uint32_t> older; vector<string> older;
{ {
MutexLock ml( &m_mutex ); MutexLock ml( &m_mutex );
time_t now = time( NULL ); time_t now = time( NULL );
map<uint32_t, AckRecord>::iterator iter; map<uint32_t, AckRecord>::iterator iter;
for ( iter = m_pendings.begin(); m_pendings.end() != iter; ) { for ( iter = m_pendings.begin(); m_pendings.end() != iter; ) {
time_t took = now - iter->second.m_createTime; AckRecord& rec = iter->second;
time_t took = now - rec.m_createTime;
if ( limit < took ) { if ( limit < took ) {
older.push_back( iter->first ); older.push_back( rec.toStr() );
callProc( iter, false ); callProc( iter, false );
m_pendings.erase( iter++ ); m_pendings.erase( iter++ );
} else { } else {
@ -212,14 +217,14 @@ UDPAckTrack::threadProc()
} }
} }
if ( 0 < older.size() ) { if ( 0 < older.size() ) {
StrWPF leaked; string leaked;
vector<uint32_t>::const_iterator iter = older.begin(); vector<string>::const_iterator iter = older.begin();
for ( ; ; ) { for ( ; ; ) {
leaked.catf( "%d", *iter ); leaked += iter->c_str();
if ( ++iter == older.end() ) { if ( ++iter == older.end() ) {
break; break;
} }
leaked.catf( ", " ); leaked += ", ";
} }
logf( XW_LOGERROR, "%s: these packets leaked (were not ack'd " logf( XW_LOGERROR, "%s: these packets leaked (were not ack'd "
"within %d seconds): %s", __func__, "within %d seconds): %s", __func__,

View file

@ -1,6 +1,7 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */ /* -*- compile-command: "make -j3"; -*- */
/* /*
* Copyright 2013 by Eric House (xwords@eehouse.org). All rights reserved. * Copyright 2013 - 2018 by Eric House (xwords@eehouse.org). All rights
* reserved.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -20,6 +21,8 @@
#ifndef _UDPACK_H_ #ifndef _UDPACK_H_
#define _UDPACK_H_ #define _UDPACK_H_
#include <stdio.h>
#include "xwrelay_priv.h" #include "xwrelay_priv.h"
#include "xwrelay.h" #include "xwrelay.h"
#include "strwpf.h" #include "strwpf.h"
@ -28,10 +31,26 @@ typedef void (*OnAckProc)( bool acked, uint32_t packetID, void* data );
class AckRecord { class AckRecord {
public: public:
AckRecord() { m_createTime = time( NULL ); proc = NULL; } AckRecord( XWRelayReg cmd, uint32_t id ) {
m_createTime = time( NULL );
m_proc = NULL;
m_cmd = cmd;
m_id = id;
}
string toStr()
{
char buf[64];
sprintf( buf, "%d/%s", m_id, msgToStr( m_cmd ) );
string str(buf);
return str;
}
time_t m_createTime; time_t m_createTime;
OnAckProc proc; OnAckProc m_proc;
void* data; XWRelayReg m_cmd;
void* m_data;
private:
uint32_t m_id;
}; };
class UDPAckTrack { class UDPAckTrack {
@ -39,7 +58,7 @@ class UDPAckTrack {
static const uint32_t PACKETID_NONE = 0; static const uint32_t PACKETID_NONE = 0;
static uint32_t nextPacketID( XWRelayReg cmd ); static uint32_t nextPacketID( XWRelayReg cmd );
static void recordAck( uint32_t packetID ); static string recordAck( uint32_t packetID );
static bool setOnAck( OnAckProc proc, uint32_t packetID, void* data ); static bool setOnAck( OnAckProc proc, uint32_t packetID, void* data );
static bool shouldAck( XWRelayReg cmd ); static bool shouldAck( XWRelayReg cmd );
/* called from ctrl port */ /* called from ctrl port */
@ -51,8 +70,8 @@ class UDPAckTrack {
static void* thread_main( void* arg ); static void* thread_main( void* arg );
UDPAckTrack(); UDPAckTrack();
time_t ackLimit(); time_t ackLimit();
uint32_t nextPacketIDImpl(); uint32_t nextPacketIDImpl( XWRelayReg cmd );
void recordAckImpl( uint32_t packetID ); string recordAckImpl( uint32_t packetID );
bool setOnAckImpl( OnAckProc proc, uint32_t packetID, void* data ); bool setOnAckImpl( OnAckProc proc, uint32_t packetID, void* data );
void callProc( const map<uint32_t, AckRecord>::iterator iter, bool acked ); void callProc( const map<uint32_t, AckRecord>::iterator iter, bool acked );
void printAcksImpl( StrWPF& out ); void printAcksImpl( StrWPF& out );

View file

@ -1702,7 +1702,7 @@ retrieveMessages( DevID& devID, const AddrInfo* addr )
} }
} }
static const char* const char*
msgToStr( XWRelayReg msg ) msgToStr( XWRelayReg msg )
{ {
const char* str; const char* str;
@ -1840,7 +1840,7 @@ handle_udp_packet( PacketThreadClosure* ptc )
} }
case XWPDEV_KEEPALIVE: case XWPDEV_KEEPALIVE:
case XWPDEV_RQSTMSGS: { // here case XWPDEV_RQSTMSGS: {
DevID devID( ID_TYPE_RELAY ); DevID devID( ID_TYPE_RELAY );
if ( getVLIString( &ptr, end, devID.m_devIDString ) ) { if ( getVLIString( &ptr, end, devID.m_devIDString ) ) {
const AddrInfo* addr = ptc->addr(); const AddrInfo* addr = ptc->addr();
@ -1855,8 +1855,8 @@ handle_udp_packet( PacketThreadClosure* ptc )
case XWPDEV_ACK: { case XWPDEV_ACK: {
uint32_t packetID; uint32_t packetID;
if ( vli2un( &ptr, end, &packetID ) ) { if ( vli2un( &ptr, end, &packetID ) ) {
logf( XW_LOGINFO, "%s: got ack for packet %d", __func__, packetID ); string str = UDPAckTrack::recordAck( packetID );
UDPAckTrack::recordAck( packetID ); logf( XW_LOGINFO, "%s: got ack for packet %s", __func__, str.c_str() );
} }
break; break;
} }

View file

@ -69,6 +69,7 @@ int read_packet( int sock, uint8_t* buf, int buflen );
void onMsgAcked( bool acked, uint32_t packetID, void* data ); void onMsgAcked( bool acked, uint32_t packetID, void* data );
const char* cmdToStr( XWRELAY_Cmd cmd ); const char* cmdToStr( XWRELAY_Cmd cmd );
const char* msgToStr( XWRelayReg msg );
extern class ListenerMgr g_listeners; extern class ListenerMgr g_listeners;