Merge branch 'android_branch' of github.com:eehouse/xwords into android_branch
|
@ -1,29 +1,23 @@
|
|||
Here's how I'm building crosswords for Android.
|
||||
(Updated Dec 2017)
|
||||
|
||||
First, cd into the directory xwords4/android/XWords4. Everything
|
||||
Here's how I'm building CrossWords for Android.
|
||||
|
||||
First, cd into the directory xwords4/android. Everything
|
||||
happens there.
|
||||
|
||||
IF (and it's a big if) you have all the necessary tools installed (the
|
||||
Android SDK and NDK, apache ant, and probably other stuff I've
|
||||
forgotten), two commands will build it, the first a one-time deal and
|
||||
the second repeated every time you made a change.
|
||||
To build and install the debug version of CrossWords:
|
||||
|
||||
The build process requires a file called local.properties that must be
|
||||
generated locally. Generate it before your first build by running
|
||||
# ./gradlew clean insXw4Deb
|
||||
|
||||
# ../scripts/setup_local_props.sh
|
||||
To build and install the debug version of CrossDbg (a variant meant
|
||||
for development that can co-exist with CrossWords):
|
||||
|
||||
Then build using this command:
|
||||
# ./gradlew -PuseCrashlytics insXw4dDeb
|
||||
|
||||
# ant debug
|
||||
|
||||
Or, if you have a device or emulator attached (listed by 'adb
|
||||
devices'), try
|
||||
|
||||
# ant debug install
|
||||
|
||||
Making a release build requires that you've set up your keys. (I did
|
||||
this too long ago to remember how but the info's easy to find). Once
|
||||
that's done, just tether your device and type
|
||||
|
||||
# ant release install
|
||||
I do all development on Debian and Ubuntu Linux systems. I have built
|
||||
on MacOS, where once you get all the necessary tools installed via
|
||||
homebrew there's only one problem I'm aware of: the parameter 'white'
|
||||
that's passed to convert by android/scripts/images.mk on Linux systems
|
||||
needs to be 'black' on MacOS. I have no clue why. If you don't make
|
||||
this change the subset of actionbar icons that are generated from .svg
|
||||
files will be black-on-black.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
def INITIAL_CLIENT_VERS = 8
|
||||
def VERSION_CODE_BASE = 125
|
||||
def VERSION_NAME = '4.4.129'
|
||||
def VERSION_CODE_BASE = 128
|
||||
def VERSION_NAME = '4.4.132'
|
||||
def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY")
|
||||
def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID")
|
||||
def BUILD_INFO_NAME = "build-info.txt"
|
||||
|
@ -74,6 +74,19 @@ android {
|
|||
|
||||
buildConfigField "String", "GCM_SENDER_ID", "\"$GCM_SENDER_ID\""
|
||||
}
|
||||
|
||||
xw4fdroid {
|
||||
dimension "variant"
|
||||
applicationId "org.eehouse.android.xw4"
|
||||
manifestPlaceholders = [ APP_ID: applicationId ]
|
||||
resValue "string", "app_name", "CrossWords"
|
||||
resValue "string", "nbs_port", "3344"
|
||||
resValue "string", "invite_prefix", "/and/"
|
||||
buildConfigField "boolean", "WIDIR_ENABLED", "false"
|
||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
|
||||
|
||||
buildConfigField "String", "GCM_SENDER_ID", "\"\""
|
||||
}
|
||||
xw4d {
|
||||
dimension "variant"
|
||||
minSdkVersion 8
|
||||
|
@ -158,6 +171,14 @@ android {
|
|||
jniLibs.srcDir "../libs-xw4dDebug"
|
||||
}
|
||||
}
|
||||
xw4fdroid {
|
||||
release {
|
||||
jniLibs.srcDir "../libs-xw4fdroidRelease"
|
||||
}
|
||||
debug {
|
||||
jniLibs.srcDir "../libs-xw4fdroidDebug"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
|
@ -176,10 +197,12 @@ android {
|
|||
|
||||
dependencies {
|
||||
// Look into replacing this with a fetch too PENDING
|
||||
compile files('../libs/gcm.jar')
|
||||
xw4Compile files('../libs/gcm.jar')
|
||||
|
||||
compile 'com.android.support:support-v4:23.4.0'
|
||||
|
||||
// 2.6.8 is probably as far forward as I can go without upping my
|
||||
// min-supported SDK version
|
||||
xw4dCompile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
|
||||
transitive = true;
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>CrossWords 4.4.129 release</h2>
|
||||
<h2>CrossWords 4.4.132 release</h2>
|
||||
|
||||
<p>Quick fix for a another crash reported via the Play Store</p>
|
||||
<p>This release makes communication with the relay more robust.</p>
|
||||
|
||||
<div id="survey">
|
||||
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
|
||||
|
@ -25,7 +25,14 @@
|
|||
|
||||
<h3>New with this release</h3>
|
||||
<ul>
|
||||
<li>Fix crash that only showed up in games using timers</li>
|
||||
<li>Communicate with relay via http when the custom protocol
|
||||
isn't working (e.g. when a wifi router or firewall blocks
|
||||
it.)</li>
|
||||
<li>Improved translations for French, Japanese and Norwegian
|
||||
(by Weblate volunteers)</li>
|
||||
<li>Add a few menubar icons (thanks to The Noun Project)</li>
|
||||
<li>Fix taps on left end of items in the Games List not always
|
||||
selecting/unselecting them</li>
|
||||
</ul>
|
||||
|
||||
<p>(The full changelog
|
||||
|
|
|
@ -550,7 +550,7 @@ public class BTService extends XWService {
|
|||
} else {
|
||||
short len = is.readShort();
|
||||
byte[] nliData = new byte[len];
|
||||
is.read( nliData );
|
||||
is.readFully( nliData );
|
||||
nli = XwJNI.nliFromStream( nliData );
|
||||
}
|
||||
|
||||
|
@ -573,26 +573,20 @@ public class BTService extends XWService {
|
|||
int gameID = dis.readInt();
|
||||
switch ( cmd ) {
|
||||
case MESG_SEND:
|
||||
short len = dis.readShort();
|
||||
byte[] buffer = new byte[len];
|
||||
int nRead = dis.read( buffer, 0, len );
|
||||
if ( nRead == len ) {
|
||||
BluetoothDevice host = socket.getRemoteDevice();
|
||||
addAddr( host );
|
||||
byte[] buffer = new byte[dis.readShort()];
|
||||
dis.readFully( buffer );
|
||||
BluetoothDevice host = socket.getRemoteDevice();
|
||||
addAddr( host );
|
||||
|
||||
CommsAddrRec addr = new CommsAddrRec( host.getName(),
|
||||
host.getAddress() );
|
||||
ReceiveResult rslt
|
||||
= BTService.this.receiveMessage( BTService.this,
|
||||
gameID, m_btMsgSink,
|
||||
buffer, addr );
|
||||
CommsAddrRec addr = new CommsAddrRec( host.getName(),
|
||||
host.getAddress() );
|
||||
ReceiveResult rslt
|
||||
= BTService.this.receiveMessage( BTService.this,
|
||||
gameID, m_btMsgSink,
|
||||
buffer, addr );
|
||||
|
||||
result = rslt == ReceiveResult.GAME_GONE ?
|
||||
BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
|
||||
} else {
|
||||
Log.e( TAG, "receiveMessage(): read only %d of %d bytes",
|
||||
nRead, len );
|
||||
}
|
||||
result = rslt == ReceiveResult.GAME_GONE ?
|
||||
BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
|
||||
break;
|
||||
case MESG_GAMEGONE:
|
||||
postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
|
||||
|
|
|
@ -188,10 +188,8 @@ public class BiDiSockWrap {
|
|||
DataInputStream inStream
|
||||
= new DataInputStream( mSocket.getInputStream() );
|
||||
while ( mRunThreads ) {
|
||||
short len = inStream.readShort();
|
||||
Log.d( TAG, "got len: %d", len );
|
||||
byte[] packet = new byte[len];
|
||||
inStream.read( packet );
|
||||
byte[] packet = new byte[inStream.readShort()];
|
||||
inStream.readFully( packet );
|
||||
mIface.gotPacket( BiDiSockWrap.this, packet );
|
||||
}
|
||||
} catch( IOException ioe ) {
|
||||
|
|
|
@ -204,6 +204,18 @@ public class BoardDelegate extends DelegateBase
|
|||
}
|
||||
};
|
||||
ab.setNegativeButton( R.string.button_rematch, lstnr );
|
||||
|
||||
// If we're not already in the "archive" group, offer to move
|
||||
if ( !inArchiveGroup() ) {
|
||||
lstnr = new OnClickListener() {
|
||||
public void onClick( DialogInterface dlg,
|
||||
int whichButton ) {
|
||||
showArchiveNA();
|
||||
}
|
||||
};
|
||||
ab.setNeutralButton( R.string.button_archive, lstnr );
|
||||
}
|
||||
|
||||
} else if ( DlgID.DLG_CONNSTAT == dlgID
|
||||
&& BuildConfig.DEBUG && null != m_connTypes
|
||||
&& (m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY )
|
||||
|
@ -828,6 +840,9 @@ public class BoardDelegate extends DelegateBase
|
|||
enable = m_gameOver && rematchSupported( false );
|
||||
Utils.setItemVisible( menu, R.id.board_menu_rematch, enable );
|
||||
|
||||
enable = m_gameOver && !inArchiveGroup();
|
||||
Utils.setItemVisible( menu, R.id.board_menu_archive, enable );
|
||||
|
||||
boolean netGame = null != m_gi
|
||||
&& DeviceRole.SERVER_STANDALONE != m_gi.serverRole;
|
||||
Utils.setItemVisible( menu, R.id.gamel_menu_checkmoves, netGame );
|
||||
|
@ -871,6 +886,10 @@ public class BoardDelegate extends DelegateBase
|
|||
doRematchIf();
|
||||
break;
|
||||
|
||||
case R.id.board_menu_archive:
|
||||
showArchiveNA();
|
||||
break;
|
||||
|
||||
case R.id.board_menu_trade_commit:
|
||||
cmd = JNICmd.CMD_COMMIT;
|
||||
break;
|
||||
|
@ -1092,6 +1111,10 @@ public class BoardDelegate extends DelegateBase
|
|||
makeOkOnlyBuilder( R.string.after_restart ).show();
|
||||
break;
|
||||
|
||||
case ARCHIVE_ACTION:
|
||||
archiveAndClose();
|
||||
break;
|
||||
|
||||
case ENABLE_SMS_DO:
|
||||
post( new Runnable() {
|
||||
public void run() {
|
||||
|
@ -2575,6 +2598,37 @@ public class BoardDelegate extends DelegateBase
|
|||
return wordsArray;
|
||||
}
|
||||
|
||||
private boolean inArchiveGroup()
|
||||
{
|
||||
String archiveName = LocUtils
|
||||
.getString( m_activity, R.string.group_name_archive );
|
||||
long archiveGroup = DBUtils.getGroup( m_activity, archiveName );
|
||||
long curGroup = DBUtils.getGroupForGame( m_activity, m_rowid );
|
||||
return curGroup == archiveGroup;
|
||||
}
|
||||
|
||||
private void showArchiveNA()
|
||||
{
|
||||
makeNotAgainBuilder( R.string.not_again_archive,
|
||||
R.string.key_na_archive,
|
||||
Action.ARCHIVE_ACTION )
|
||||
.show();
|
||||
}
|
||||
|
||||
private void archiveAndClose()
|
||||
{
|
||||
String archiveName = LocUtils
|
||||
.getString( m_activity, R.string.group_name_archive );
|
||||
long archiveGroupID = DBUtils.getGroup( m_activity, archiveName );
|
||||
|
||||
if ( DBUtils.GROUPID_UNSPEC == archiveGroupID ) {
|
||||
archiveGroupID = DBUtils.addGroup( m_activity, archiveName );
|
||||
}
|
||||
DBUtils.moveGame( m_activity, m_rowid, archiveGroupID );
|
||||
waitCloseGame( false );
|
||||
finish();
|
||||
}
|
||||
|
||||
// For now, supported if standalone or either BT or SMS used for transport
|
||||
private boolean rematchSupported( boolean showMulti )
|
||||
{
|
||||
|
|
|
@ -387,32 +387,6 @@ public class CommsTransport implements TransportProcs,
|
|||
return nSent;
|
||||
}
|
||||
|
||||
public void relayStatus( CommsRelayState newState )
|
||||
{
|
||||
Log.i( TAG, "relayStatus called; state=%s", newState.toString() );
|
||||
|
||||
switch( newState ) {
|
||||
case COMMS_RELAYSTATE_UNCONNECTED:
|
||||
case COMMS_RELAYSTATE_DENIED:
|
||||
case COMMS_RELAYSTATE_CONNECT_PENDING:
|
||||
ConnStatusHandler.updateStatus( m_context, null,
|
||||
CommsConnType.COMMS_CONN_RELAY,
|
||||
false );
|
||||
break;
|
||||
case COMMS_RELAYSTATE_CONNECTED:
|
||||
case COMMS_RELAYSTATE_RECONNECTED:
|
||||
ConnStatusHandler.updateStatusOut( m_context, null,
|
||||
CommsConnType.COMMS_CONN_RELAY,
|
||||
true );
|
||||
break;
|
||||
case COMMS_RELAYSTATE_ALLCONNECTED:
|
||||
ConnStatusHandler.updateStatusIn( m_context, null,
|
||||
CommsConnType.COMMS_CONN_RELAY,
|
||||
true );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void relayConnd( String room, int devOrder, boolean allHere,
|
||||
int nMissing )
|
||||
{
|
||||
|
|
|
@ -81,7 +81,9 @@ public class DBUtils {
|
|||
private static long s_cachedRowID = ROWID_NOTFOUND;
|
||||
private static byte[] s_cachedBytes = null;
|
||||
|
||||
public static enum GameChangeType { GAME_CHANGED, GAME_CREATED, GAME_DELETED };
|
||||
public static enum GameChangeType { GAME_CHANGED, GAME_CREATED,
|
||||
GAME_DELETED, GAME_MOVED,
|
||||
};
|
||||
|
||||
public static interface DBChangeListener {
|
||||
public void gameSaved( long rowid, GameChangeType change );
|
||||
|
@ -1701,6 +1703,29 @@ public class DBUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static long getGroup( Context context, String name )
|
||||
{
|
||||
long result = GROUPID_UNSPEC;
|
||||
String[] columns = { ROW_ID };
|
||||
String selection = DBHelper.GROUPNAME + " = ?";
|
||||
String[] selArgs = { name };
|
||||
|
||||
initDB( context );
|
||||
synchronized( s_dbHelper ) {
|
||||
Cursor cursor = s_db.query( DBHelper.TABLE_NAME_GROUPS, columns,
|
||||
selection, selArgs,
|
||||
null, // groupBy
|
||||
null, // having
|
||||
null // orderby
|
||||
);
|
||||
if ( cursor.moveToNext() ) {
|
||||
result = cursor.getLong( cursor.getColumnIndex( ROW_ID ) );
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long addGroup( Context context, String name )
|
||||
{
|
||||
long rowid = GROUPID_UNSPEC;
|
||||
|
@ -1759,13 +1784,14 @@ public class DBUtils {
|
|||
}
|
||||
|
||||
// Change group id of a game
|
||||
public static void moveGame( Context context, long gameid, long groupID )
|
||||
public static void moveGame( Context context, long rowid, long groupID )
|
||||
{
|
||||
Assert.assertTrue( GROUPID_UNSPEC != groupID );
|
||||
ContentValues values = new ContentValues();
|
||||
values.put( DBHelper.GROUPID, groupID );
|
||||
updateRow( context, DBHelper.TABLE_NAME_SUM, gameid, values );
|
||||
updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
|
||||
invalGroupsCache();
|
||||
notifyListeners( rowid, GameChangeType.GAME_MOVED );
|
||||
}
|
||||
|
||||
private static String getChatHistoryStr( Context context, long rowid )
|
||||
|
|
|
@ -31,8 +31,6 @@ package org.eehouse.android.xw4;
|
|||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.android.gcm.GCMRegistrar;
|
||||
|
||||
public class DevID {
|
||||
private static final String TAG = DevID.class.getSimpleName();
|
||||
|
||||
|
@ -137,7 +135,7 @@ public class DevID {
|
|||
if ( 0 != storedVers && storedVers < curVers ) {
|
||||
result = ""; // Don't trust what registrar has
|
||||
} else {
|
||||
result = GCMRegistrar.getRegistrationId( context );
|
||||
result = GCMStub.getRegistrationId( context );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1238,7 +1238,7 @@ public class DictsDelegate extends ListDelegateBase
|
|||
// parse less data
|
||||
String name = null;
|
||||
String proc = String.format( "listDicts?lc=%s", m_lc );
|
||||
HttpURLConnection conn = NetUtils.makeHttpConn( m_context, proc );
|
||||
HttpURLConnection conn = NetUtils.makeHttpUpdateConn( m_context, proc );
|
||||
if ( null != conn ) {
|
||||
JSONObject theOne = null;
|
||||
String langName = null;
|
||||
|
@ -1320,7 +1320,7 @@ public class DictsDelegate extends ListDelegateBase
|
|||
public Boolean doInBackground( Void... unused )
|
||||
{
|
||||
boolean success = false;
|
||||
HttpURLConnection conn = NetUtils.makeHttpConn( m_context, "listDicts" );
|
||||
HttpURLConnection conn = NetUtils.makeHttpUpdateConn( m_context, "listDicts" );
|
||||
if ( null != conn ) {
|
||||
String json = NetUtils.runConn( conn, new JSONObject() );
|
||||
if ( !isCancelled() ) {
|
||||
|
|
|
@ -84,6 +84,7 @@ public class DlgDelegate {
|
|||
TRAY_PICKED,
|
||||
INVITE_INFO,
|
||||
DISABLE_DUALPANE,
|
||||
ARCHIVE_ACTION,
|
||||
|
||||
// Dict Browser
|
||||
FINISH_ACTION,
|
||||
|
|
|
@ -482,14 +482,12 @@ public class GameUtils {
|
|||
if ( force ) {
|
||||
HashMap<Long,CommsConnTypeSet> games =
|
||||
DBUtils.getGamesWithSendsPending( context );
|
||||
if ( 0 < games.size() ) {
|
||||
new ResendTask( context, games, filter, proc ).execute();
|
||||
new ResendTask( context, games, filter, proc ).execute();
|
||||
|
||||
System.arraycopy( sendTimes, 0, /* src */
|
||||
sendTimes, 1, /* dest */
|
||||
sendTimes.length - 1 );
|
||||
sendTimes[0] = now;
|
||||
}
|
||||
System.arraycopy( sendTimes, 0, /* src */
|
||||
sendTimes, 1, /* dest */
|
||||
sendTimes.length - 1 );
|
||||
sendTimes[0] = now;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1196,7 +1194,7 @@ public class GameUtils {
|
|||
for ( CommsConnType typ : conTypes ) {
|
||||
switch ( typ ) {
|
||||
case COMMS_CONN_RELAY:
|
||||
tellRelayDied( context, summary, informNow );
|
||||
// see below
|
||||
break;
|
||||
case COMMS_CONN_BT:
|
||||
BTService.gameDied( context, addr.bt_btAddr, gameID );
|
||||
|
@ -1210,6 +1208,14 @@ public class GameUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// comms doesn't have a relay address for us until the game's
|
||||
// in play (all devices registered, at least.) To enable
|
||||
// deleting on relay half-games that we created but nobody
|
||||
// joined, special-case this one.
|
||||
if ( summary.inRelayGame() ) {
|
||||
tellRelayDied( context, summary, informNow );
|
||||
}
|
||||
|
||||
gamePtr.release();
|
||||
}
|
||||
|
@ -1251,7 +1257,7 @@ public class GameUtils {
|
|||
private HashMap<Long,CommsConnTypeSet> m_games;
|
||||
private ResendDoneProc m_doneProc;
|
||||
private CommsConnType m_filter;
|
||||
private MultiMsgSink m_sink;
|
||||
private int m_nSent = 0;
|
||||
|
||||
public ResendTask( Context context, HashMap<Long,CommsConnTypeSet> games,
|
||||
CommsConnType filter, ResendDoneProc proc )
|
||||
|
@ -1280,14 +1286,15 @@ public class GameUtils {
|
|||
GameLock lock = new GameLock( rowid, false );
|
||||
if ( lock.tryLock() ) {
|
||||
CurGameInfo gi = new CurGameInfo( m_context );
|
||||
m_sink = new MultiMsgSink( m_context, rowid );
|
||||
GamePtr gamePtr = loadMakeGame( m_context, gi, m_sink, lock );
|
||||
MultiMsgSink sink = new MultiMsgSink( m_context, rowid );
|
||||
GamePtr gamePtr = loadMakeGame( m_context, gi, sink, lock );
|
||||
if ( null != gamePtr ) {
|
||||
int nSent = XwJNI.comms_resendAll( gamePtr, true,
|
||||
m_filter, false );
|
||||
gamePtr.release();
|
||||
Log.d( TAG, "ResendTask.doInBackground(): sent %d "
|
||||
+ "messages for rowid %d", nSent, rowid );
|
||||
m_nSent += sink.numSent();
|
||||
} else {
|
||||
Log.d( TAG, "ResendTask.doInBackground(): loadMakeGame()"
|
||||
+ " failed for rowid %d", rowid );
|
||||
|
@ -1312,8 +1319,7 @@ public class GameUtils {
|
|||
protected void onPostExecute( Void unused )
|
||||
{
|
||||
if ( null != m_doneProc ) {
|
||||
int nSent = null == m_sink ? 0 : m_sink.numSent();
|
||||
m_doneProc.onResendDone( m_context, nSent );
|
||||
m_doneProc.onResendDone( m_context, m_nSent );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -755,9 +755,18 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
lstnr = new OnClickListener() {
|
||||
public void onClick( DialogInterface dlg, int item ) {
|
||||
String name = namer.getName();
|
||||
DBUtils.addGroup( m_activity, name );
|
||||
mkListAdapter();
|
||||
showNewGroupIf();
|
||||
long hasName = DBUtils.getGroup( m_activity, name );
|
||||
if ( DBUtils.GROUPID_UNSPEC == hasName ) {
|
||||
DBUtils.addGroup( m_activity, name );
|
||||
mkListAdapter();
|
||||
showNewGroupIf();
|
||||
} else {
|
||||
String msg = LocUtils
|
||||
.getString( m_activity,
|
||||
R.string.duplicate_group_name_fmt,
|
||||
name );
|
||||
makeOkOnlyBuilder( msg ).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
lstnr2 = new OnClickListener() {
|
||||
|
@ -1060,8 +1069,6 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
invalidateOptionsMenuIf();
|
||||
setTitle();
|
||||
}
|
||||
|
||||
mkListAdapter();
|
||||
}
|
||||
|
||||
public void invalidateOptionsMenuIf()
|
||||
|
@ -1133,6 +1140,9 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
mkListAdapter();
|
||||
setSelGame( rowid );
|
||||
break;
|
||||
case GAME_MOVED:
|
||||
mkListAdapter();
|
||||
break;
|
||||
default:
|
||||
Assert.fail();
|
||||
break;
|
||||
|
|
|
@ -119,10 +119,6 @@ public class MultiMsgSink implements TransportProcs {
|
|||
return nSent;
|
||||
}
|
||||
|
||||
public void relayStatus( CommsRelayState newState )
|
||||
{
|
||||
}
|
||||
|
||||
public void relayErrorProc( XWRELAY_ERROR relayErr )
|
||||
{
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import android.text.TextUtils;
|
|||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
|
@ -88,49 +90,29 @@ public class NetUtils {
|
|||
m_obits = obits;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Socket socket = makeProxySocket( m_context, 10000 );
|
||||
if ( null != socket ) {
|
||||
int strLens = 0;
|
||||
int nObits = 0;
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try {
|
||||
JSONArray params = new JSONArray();
|
||||
for ( int ii = 0; ii < m_obits.length; ++ii ) {
|
||||
String relayID = m_obits[ii].m_relayID;
|
||||
if ( null != relayID ) {
|
||||
++nObits;
|
||||
strLens += relayID.length() + 1; // 1 for /n
|
||||
}
|
||||
JSONObject one = new JSONObject();
|
||||
one.put( "relayID", m_obits[ii].m_relayID );
|
||||
one.put( "seed", m_obits[ii].m_seed );
|
||||
params.put( one );
|
||||
}
|
||||
HttpURLConnection conn = makeHttpRelayConn( m_context, "kill" );
|
||||
String resStr = runConn( conn, params );
|
||||
Log.d( TAG, "runViaWeb(): kill(%s) => %s", params, resStr );
|
||||
|
||||
try {
|
||||
DataOutputStream outStream =
|
||||
new DataOutputStream( socket.getOutputStream() );
|
||||
outStream.writeShort( 2 + 2 + (2*nObits) + strLens );
|
||||
outStream.writeByte( NetUtils.PROTOCOL_VERSION );
|
||||
outStream.writeByte( NetUtils.PRX_DEVICE_GONE );
|
||||
outStream.writeShort( m_obits.length );
|
||||
|
||||
for ( int ii = 0; ii < m_obits.length; ++ii ) {
|
||||
String relayID = m_obits[ii].m_relayID;
|
||||
if ( null != relayID ) {
|
||||
outStream.writeShort( m_obits[ii].m_seed );
|
||||
outStream.writeBytes( relayID );
|
||||
outStream.write( '\n' );
|
||||
}
|
||||
}
|
||||
|
||||
outStream.flush();
|
||||
|
||||
DataInputStream dis =
|
||||
new DataInputStream( socket.getInputStream() );
|
||||
short resLen = dis.readShort();
|
||||
socket.close();
|
||||
|
||||
if ( resLen == 0 ) {
|
||||
if ( null != resStr ) {
|
||||
JSONObject result = new JSONObject( resStr );
|
||||
if ( 0 == result.optInt( "err", -1 ) ) {
|
||||
DBUtils.clearObits( m_context, m_obits );
|
||||
}
|
||||
} catch ( java.io.IOException ioe ) {
|
||||
Log.ex( TAG, ioe );
|
||||
}
|
||||
} catch ( JSONException ex ) {
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,8 +121,7 @@ public class NetUtils {
|
|||
{
|
||||
DBUtils.Obit[] obits = DBUtils.listObits( context );
|
||||
if ( null != obits && 0 < obits.length ) {
|
||||
InformThread thread = new InformThread( context, obits );
|
||||
thread.start();
|
||||
new InformThread( context, obits ).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +165,7 @@ public class NetUtils {
|
|||
short len = dis.readShort();
|
||||
if ( len > 0 ) {
|
||||
byte[] packet = new byte[len];
|
||||
dis.read( packet );
|
||||
dis.readFully( packet );
|
||||
msgs[ii][jj] = packet;
|
||||
}
|
||||
}
|
||||
|
@ -214,14 +195,26 @@ public class NetUtils {
|
|||
return host;
|
||||
}
|
||||
|
||||
protected static HttpURLConnection makeHttpConn( Context context,
|
||||
String proc )
|
||||
protected static HttpURLConnection makeHttpRelayConn( Context context,
|
||||
String proc )
|
||||
{
|
||||
String url = XWPrefs.getDefaultRelayUrl( context );
|
||||
return makeHttpConn( context, url, proc );
|
||||
}
|
||||
|
||||
protected static HttpURLConnection makeHttpUpdateConn( Context context,
|
||||
String proc )
|
||||
{
|
||||
String url = XWPrefs.getDefaultUpdateUrl( context );
|
||||
return makeHttpConn( context, url, proc );
|
||||
}
|
||||
|
||||
private static HttpURLConnection makeHttpConn( Context context,
|
||||
String path, String proc )
|
||||
{
|
||||
HttpURLConnection result = null;
|
||||
try {
|
||||
String url = String.format( "%s/%s",
|
||||
XWPrefs.getDefaultUpdateUrl( context ),
|
||||
proc );
|
||||
String url = String.format( "%s/%s", path, proc );
|
||||
result = (HttpURLConnection)new URL(url).openConnection();
|
||||
} catch ( java.net.MalformedURLException mue ) {
|
||||
Assert.assertNull( result );
|
||||
|
@ -233,11 +226,21 @@ public class NetUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
protected static String runConn( HttpURLConnection conn, JSONArray param )
|
||||
{
|
||||
return runConn( conn, param.toString() );
|
||||
}
|
||||
|
||||
protected static String runConn( HttpURLConnection conn, JSONObject param )
|
||||
{
|
||||
return runConn( conn, param.toString() );
|
||||
}
|
||||
|
||||
private static String runConn( HttpURLConnection conn, String param )
|
||||
{
|
||||
String result = null;
|
||||
Map<String, String> params = new HashMap<String, String>();
|
||||
params.put( k_PARAMS, param.toString() );
|
||||
params.put( k_PARAMS, param );
|
||||
String paramsString = getPostDataString( params );
|
||||
|
||||
if ( null != paramsString ) {
|
||||
|
@ -273,7 +276,8 @@ public class NetUtils {
|
|||
}
|
||||
result = new String( bas.toByteArray() );
|
||||
} else {
|
||||
Log.w( TAG, "runConn: responseCode: %d", responseCode );
|
||||
Log.w( TAG, "runConn: responseCode: %d for url: %s",
|
||||
responseCode, conn.getURL() );
|
||||
}
|
||||
} catch ( java.net.ProtocolException pe ) {
|
||||
Log.ex( TAG, pe );
|
||||
|
@ -285,17 +289,18 @@ public class NetUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
// This handles multiple params but only every gets passed one!
|
||||
private static String getPostDataString( Map<String, String> params )
|
||||
{
|
||||
String result = null;
|
||||
try {
|
||||
ArrayList<String> pairs = new ArrayList<String>();
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
String[] pair = { null, null };
|
||||
// String[] pair = { null, null };
|
||||
for ( Map.Entry<String, String> entry : params.entrySet() ){
|
||||
pair[0] = URLEncoder.encode( entry.getKey(), "UTF-8" );
|
||||
pair[1] = URLEncoder.encode( entry.getValue(), "UTF-8" );
|
||||
pairs.add( TextUtils.join( "=", pair ) );
|
||||
pairs.add( URLEncoder.encode( entry.getKey(), "UTF-8" )
|
||||
+ "="
|
||||
+ URLEncoder.encode( entry.getValue(), "UTF-8" ) );
|
||||
}
|
||||
result = TextUtils.join( "&", pairs );
|
||||
} catch ( java.io.UnsupportedEncodingException uee ) {
|
||||
|
|
|
@ -94,7 +94,7 @@ public class RefreshNamesTask extends AsyncTask<Void, Void, String[]> {
|
|||
// Can't figure out how to read a null-terminated string
|
||||
// from DataInputStream so parse it myself.
|
||||
byte[] bytes = new byte[len];
|
||||
dis.read( bytes );
|
||||
dis.readFully( bytes );
|
||||
|
||||
int index = -1;
|
||||
for ( int ii = 0; ii < nRooms; ++ii ) {
|
||||
|
|
|
@ -26,6 +26,7 @@ import android.content.Intent;
|
|||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
|
@ -38,6 +39,10 @@ import org.eehouse.android.xw4.jni.UtilCtxt.DevIDType;
|
|||
import org.eehouse.android.xw4.jni.XwJNI;
|
||||
import org.eehouse.android.xw4.loc.LocUtils;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
|
@ -46,13 +51,18 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class RelayService extends XWService
|
||||
implements NetStateCache.StateChangedIf {
|
||||
|
@ -60,6 +70,8 @@ public class RelayService extends XWService
|
|||
private static final int MAX_SEND = 1024;
|
||||
private static final int MAX_BUF = MAX_SEND - 2;
|
||||
private static final int REG_WAIT_INTERVAL = 10;
|
||||
private static final int INITIAL_BACKOFF = 5;
|
||||
private static final int UDP_FAIL_LIMIT = 5;
|
||||
|
||||
// One day, in seconds. Probably should be configurable.
|
||||
private static final long MAX_KEEPALIVE_SECS = 24 * 60 * 60;
|
||||
|
@ -90,8 +102,9 @@ public class RelayService extends XWService
|
|||
private static final String ROWID = "ROWID";
|
||||
private static final String BINBUFFER = "BINBUFFER";
|
||||
|
||||
private static HashSet<Integer> s_packetsSent = new HashSet<Integer>();
|
||||
private static int s_nextPacketID = 1;
|
||||
private static List<PacketData> s_packetsSentUDP = new ArrayList<>();
|
||||
private static List<PacketData> s_packetsSentWeb = new ArrayList<>();
|
||||
private static AtomicInteger s_nextPacketID = new AtomicInteger();
|
||||
private static boolean s_gcmWorking = false;
|
||||
private static boolean s_registered = false;
|
||||
private static CommsAddrRec s_addr =
|
||||
|
@ -100,16 +113,14 @@ public class RelayService extends XWService
|
|||
private static long s_curNextTimer;
|
||||
static { resetBackoffTimer(); }
|
||||
|
||||
private Thread m_fetchThread = null;
|
||||
private Thread m_UDPReadThread = null;
|
||||
private Thread m_UDPWriteThread = null;
|
||||
private DatagramSocket m_UDPSocket;
|
||||
private LinkedBlockingQueue<PacketData> m_queue =
|
||||
new LinkedBlockingQueue<PacketData>();
|
||||
private Thread m_fetchThread = null; // no longer used
|
||||
private AtomicReference<UDPThreads> m_UDPThreadsRef = new AtomicReference<>();
|
||||
private Handler m_handler;
|
||||
private Runnable m_onInactivity;
|
||||
private int m_maxIntervalSeconds = 0;
|
||||
private long m_lastGamePacketReceived;
|
||||
private int m_nativeFailScore;
|
||||
private boolean m_skipUPDSet;
|
||||
private static DevIDType s_curType = DevIDType.ID_TYPE_NONE;
|
||||
private static long s_regStartTime = 0;
|
||||
|
||||
|
@ -160,7 +171,7 @@ public class RelayService extends XWService
|
|||
{
|
||||
boolean enabled = ! XWPrefs
|
||||
.getPrefsBoolean( context, R.string.key_disable_relay, false );
|
||||
Log.d( TAG, "relayEnabled() => %b", enabled );
|
||||
// Log.d( TAG, "relayEnabled() => %b", enabled );
|
||||
return enabled;
|
||||
}
|
||||
|
||||
|
@ -306,7 +317,7 @@ public class RelayService extends XWService
|
|||
// Exists to get incoming data onto the main thread
|
||||
private static void postData( Context context, long rowid, byte[] msg )
|
||||
{
|
||||
Log.d( TAG, "postData(): packet of length %d for token %d",
|
||||
Log.d( TAG, "postData(): packet of length %d for token (rowid) %d",
|
||||
msg.length, rowid );
|
||||
if ( DBUtils.haveGame( context, rowid ) ) {
|
||||
Intent intent = getIntentTo( context, MsgCmds.RECEIVE )
|
||||
|
@ -374,6 +385,8 @@ public class RelayService extends XWService
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
m_skipUPDSet = XWPrefs.getSkipToWebAPI( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -388,7 +401,7 @@ public class RelayService extends XWService
|
|||
cmd = null;
|
||||
}
|
||||
if ( null != cmd ) {
|
||||
Log.d( TAG, "onStartCommand(): cmd=%s", cmd.toString() );
|
||||
// Log.d( TAG, "onStartCommand(): cmd=%s", cmd.toString() );
|
||||
switch( cmd ) {
|
||||
case PROCESS_GAME_MSGS:
|
||||
String[] relayIDs = new String[1];
|
||||
|
@ -403,7 +416,7 @@ public class RelayService extends XWService
|
|||
byte[][][] msgss = expandMsgsArray( intent );
|
||||
for ( byte[][] msgs : msgss ) {
|
||||
for ( byte[] msg : msgs ) {
|
||||
gotPacket( msg, true );
|
||||
gotPacket( msg, true, false );
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -449,7 +462,7 @@ public class RelayService extends XWService
|
|||
case TIMER_FIRED:
|
||||
if ( !NetStateCache.netAvail( this ) ) {
|
||||
Log.w( TAG, "not connecting: no network" );
|
||||
} else if ( startFetchThreadIf() ) {
|
||||
} else if ( startFetchThreadIfNotUDP() ) {
|
||||
// do nothing
|
||||
} else if ( registerWithRelayIfNot() ) {
|
||||
requestMessages();
|
||||
|
@ -510,9 +523,9 @@ public class RelayService extends XWService
|
|||
}
|
||||
}
|
||||
|
||||
private boolean startFetchThreadIf()
|
||||
private boolean startFetchThreadIfNotUDP()
|
||||
{
|
||||
// DbgUtils.logf( "startFetchThreadIf()" );
|
||||
// DbgUtils.logf( "startFetchThreadIfNotUDP()" );
|
||||
boolean handled = relayEnabled( this ) && !XWApp.UDP_ENABLED;
|
||||
if ( handled && null == m_fetchThread ) {
|
||||
m_fetchThread = new Thread( null, new Runnable() {
|
||||
|
@ -542,152 +555,69 @@ public class RelayService extends XWService
|
|||
private void startUDPThreadsIfNot()
|
||||
{
|
||||
if ( XWApp.UDP_ENABLED && relayEnabled( this ) ) {
|
||||
if ( null == m_UDPReadThread ) {
|
||||
m_UDPReadThread = new Thread( null, new Runnable() {
|
||||
public void run() {
|
||||
|
||||
connectSocket(); // block until this is done
|
||||
startWriteThread();
|
||||
|
||||
Log.i( TAG, "read thread running" );
|
||||
byte[] buf = new byte[1024];
|
||||
for ( ; ; ) {
|
||||
DatagramPacket packet =
|
||||
new DatagramPacket( buf, buf.length );
|
||||
try {
|
||||
m_UDPSocket.receive( packet );
|
||||
resetExitTimer();
|
||||
gotPacket( packet );
|
||||
} catch ( java.io.InterruptedIOException iioe ) {
|
||||
// DbgUtils.logf( "FYI: udp receive timeout" );
|
||||
} catch( java.io.IOException ioe ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.i( TAG, "read thread exiting" );
|
||||
}
|
||||
}, getClass().getName() );
|
||||
m_UDPReadThread.start();
|
||||
} else {
|
||||
Log.i( TAG, "m_UDPReadThread not null and assumed to be running" );
|
||||
synchronized ( m_UDPThreadsRef ) {
|
||||
if ( null == m_UDPThreadsRef.get() ) {
|
||||
UDPThreads threads = new UDPThreads();
|
||||
m_UDPThreadsRef.set( threads );
|
||||
threads.start();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.i( TAG, "startUDPThreadsIfNot(): UDP disabled" );
|
||||
}
|
||||
} // startUDPThreadsIfNot
|
||||
|
||||
private void connectSocket()
|
||||
private boolean skipNativeSend()
|
||||
{
|
||||
if ( null == m_UDPSocket ) {
|
||||
int port = XWPrefs.getDefaultRelayPort( this );
|
||||
String host = XWPrefs.getDefaultRelayHost( this );
|
||||
try {
|
||||
m_UDPSocket = new DatagramSocket();
|
||||
m_UDPSocket.setSoTimeout(30 * 1000); // timeout so we can log
|
||||
boolean skip = m_nativeFailScore > UDP_FAIL_LIMIT || m_skipUPDSet;
|
||||
// Log.d( TAG, "skipNativeSend(score=%d)) => %b", m_nativeFailScore, skip );
|
||||
return skip;
|
||||
}
|
||||
|
||||
InetAddress addr = InetAddress.getByName( host );
|
||||
m_UDPSocket.connect( addr, port ); // remember this address
|
||||
Log.d( TAG, "connectSocket(%s:%d): m_UDPSocket now %H",
|
||||
host, port, m_UDPSocket );
|
||||
} catch( java.net.SocketException se ) {
|
||||
Log.ex( TAG, se );
|
||||
Assert.fail();
|
||||
} catch( java.net.UnknownHostException uhe ) {
|
||||
Log.ex( TAG, uhe );
|
||||
// So it's a map. The timer iterates over the whole map, which should
|
||||
// never be *that* big, and pulls everything older than 10 seconds. If
|
||||
// anything in that list isn't an ACK (since ACKs will always be there
|
||||
// because they're not ACK'd) then the whole thing gets resent.
|
||||
|
||||
private void noteSent( PacketData packet, boolean fromUDP )
|
||||
{
|
||||
Log.d( TAG, "Sent (fromUDP=%b) packet: cmd=%s, id=%d",
|
||||
fromUDP, packet.m_cmd.toString(), packet.m_packetID );
|
||||
if ( fromUDP || packet.m_cmd != XWRelayReg.XWPDEV_ACK ) {
|
||||
List<PacketData> list = fromUDP ? s_packetsSentUDP : s_packetsSentWeb;
|
||||
synchronized( list ) {
|
||||
list.add(packet );
|
||||
}
|
||||
} else {
|
||||
Assert.assertTrue( m_UDPSocket.isConnected() );
|
||||
Log.i( TAG, "m_UDPSocket not null" );
|
||||
}
|
||||
}
|
||||
|
||||
private void startWriteThread()
|
||||
private void noteSent( List<PacketData> packets, boolean fromUDP )
|
||||
{
|
||||
if ( null == m_UDPWriteThread ) {
|
||||
m_UDPWriteThread = new Thread( null, new Runnable() {
|
||||
public void run() {
|
||||
Log.i( TAG, "write thread starting" );
|
||||
for ( ; ; ) {
|
||||
PacketData outData;
|
||||
try {
|
||||
outData = m_queue.take();
|
||||
} catch ( InterruptedException ie ) {
|
||||
Log.w( TAG, "write thread killed" );
|
||||
break;
|
||||
}
|
||||
if ( null == outData
|
||||
|| 0 == outData.getLength() ) {
|
||||
Log.i( TAG, "stopping write thread" );
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
DatagramPacket outPacket = outData.assemble();
|
||||
m_UDPSocket.send( outPacket );
|
||||
int pid = outData.m_packetID;
|
||||
Log.d( TAG, "Sent udp packet, cmd=%s, id=%d,"
|
||||
+ " of length %d",
|
||||
outData.m_cmd.toString(),
|
||||
pid, outPacket.getLength());
|
||||
synchronized( s_packetsSent ) {
|
||||
s_packetsSent.add( pid );
|
||||
}
|
||||
resetExitTimer();
|
||||
ConnStatusHandler.showSuccessOut();
|
||||
} catch ( java.net.SocketException se ) {
|
||||
Log.ex( TAG, se );
|
||||
Log.i( TAG, "Restarting threads to force"
|
||||
+ " new socket" );
|
||||
m_handler.post( new Runnable() {
|
||||
public void run() {
|
||||
stopUDPThreadsIf();
|
||||
}
|
||||
} );
|
||||
} catch ( java.io.IOException ioe ) {
|
||||
Log.ex( TAG, ioe );
|
||||
} catch ( NullPointerException npe ) {
|
||||
Log.w( TAG, "network problem; dropping packet" );
|
||||
}
|
||||
}
|
||||
Log.i( TAG, "write thread exiting" );
|
||||
}
|
||||
}, getClass().getName() );
|
||||
m_UDPWriteThread.start();
|
||||
} else {
|
||||
Log.i( TAG, "m_UDPWriteThread not null and assumed to "
|
||||
+ "be running" );
|
||||
long nowMS = System.currentTimeMillis();
|
||||
List<PacketData> map = fromUDP ? s_packetsSentUDP : s_packetsSentWeb;
|
||||
Log.d( TAG, "noteSent(fromUDP=%b): adding %d; size before: %d",
|
||||
fromUDP, packets.size(), map.size() );
|
||||
for ( PacketData packet : packets ) {
|
||||
if ( fromUDP ) {
|
||||
packet.setSentMS( nowMS );
|
||||
}
|
||||
noteSent( packet, fromUDP );
|
||||
}
|
||||
Log.d( TAG, "noteSent(fromUDP=%b): size after: %d", fromUDP, map.size() );
|
||||
}
|
||||
|
||||
private void stopUDPThreadsIf()
|
||||
{
|
||||
if ( null != m_UDPWriteThread ) {
|
||||
// can't add null
|
||||
m_queue.add( new PacketData() );
|
||||
try {
|
||||
Log.d( TAG, "joining m_UDPWriteThread" );
|
||||
m_UDPWriteThread.join();
|
||||
Log.d( TAG, "SUCCESSFULLY joined m_UDPWriteThread" );
|
||||
} catch( java.lang.InterruptedException ie ) {
|
||||
Log.ex( TAG, ie );
|
||||
}
|
||||
m_UDPWriteThread = null;
|
||||
m_queue.clear();
|
||||
}
|
||||
if ( null != m_UDPSocket && null != m_UDPReadThread ) {
|
||||
m_UDPSocket.close();
|
||||
try {
|
||||
m_UDPReadThread.join();
|
||||
} catch( java.lang.InterruptedException ie ) {
|
||||
Log.ex( TAG, ie );
|
||||
}
|
||||
m_UDPReadThread = null;
|
||||
m_UDPSocket = null;
|
||||
DbgUtils.assertOnUIThread();
|
||||
|
||||
UDPThreads threads = m_UDPThreadsRef.getAndSet( null );
|
||||
if ( null != threads ) {
|
||||
threads.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// MIGHT BE Running on reader thread
|
||||
private void gotPacket( byte[] data, boolean skipAck )
|
||||
private void gotPacket( byte[] data, boolean skipAck, boolean fromUDP )
|
||||
{
|
||||
boolean resetBackoff = false;
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream( data );
|
||||
|
@ -736,7 +666,7 @@ public class RelayService extends XWService
|
|||
case XWPDEV_MSG:
|
||||
int token = dis.readInt();
|
||||
byte[] msg = new byte[dis.available()];
|
||||
dis.read( msg );
|
||||
dis.readFully( msg );
|
||||
postData( this, token, msg );
|
||||
|
||||
// game-related packets only count
|
||||
|
@ -756,9 +686,8 @@ public class RelayService extends XWService
|
|||
resetBackoff = true;
|
||||
intent = getIntentTo( this, MsgCmds.GOT_INVITE );
|
||||
int srcDevID = dis.readInt();
|
||||
short len = dis.readShort();
|
||||
byte[] nliData = new byte[len];
|
||||
dis.read( nliData );
|
||||
byte[] nliData = new byte[dis.readShort()];
|
||||
dis.readFully( nliData );
|
||||
NetLaunchInfo nli = XwJNI.nliFromStream( nliData );
|
||||
intent.putExtra( INVITE_FROM, srcDevID );
|
||||
String asStr = nli.toString();
|
||||
|
@ -767,7 +696,7 @@ public class RelayService extends XWService
|
|||
startService( intent );
|
||||
break;
|
||||
case XWPDEV_ACK:
|
||||
noteAck( vli2un( dis ) );
|
||||
noteAck( vli2un( dis ), fromUDP );
|
||||
break;
|
||||
// case XWPDEV_MSGFWDOTHERS:
|
||||
// Assert.assertTrue( 0 == dis.readByte() ); // protocol; means "invite", I guess.
|
||||
|
@ -786,7 +715,7 @@ public class RelayService extends XWService
|
|||
if ( resetBackoff ) {
|
||||
resetBackoffTimer();
|
||||
}
|
||||
}
|
||||
} // gotPacket()
|
||||
|
||||
private void gotPacket( DatagramPacket packet )
|
||||
{
|
||||
|
@ -796,7 +725,7 @@ public class RelayService extends XWService
|
|||
byte[] data = new byte[packetLen];
|
||||
System.arraycopy( packet.getData(), 0, data, 0, packetLen );
|
||||
// DbgUtils.logf( "RelayService::gotPacket: %d bytes of data", packetLen );
|
||||
gotPacket( data, false );
|
||||
gotPacket( data, false, true );
|
||||
} // gotPacket
|
||||
|
||||
private boolean shouldRegister()
|
||||
|
@ -872,13 +801,17 @@ public class RelayService extends XWService
|
|||
|
||||
private void requestMessagesImpl( XWRelayReg reg )
|
||||
{
|
||||
ByteArrayOutputStream bas = new ByteArrayOutputStream();
|
||||
try {
|
||||
String devid = getDevID( null );
|
||||
DevIDType[] typp = new DevIDType[1];
|
||||
String devid = getDevID( typp );
|
||||
if ( null != devid ) {
|
||||
ByteArrayOutputStream bas = new ByteArrayOutputStream();
|
||||
DataOutputStream out = new DataOutputStream( bas );
|
||||
writeVLIString( out, devid );
|
||||
// Log.d( TAG, "requestMessagesImpl(): devid: %s; type: " + typp[0], devid );
|
||||
postPacket( bas, reg );
|
||||
} else {
|
||||
Log.d(TAG, "requestMessagesImpl(): devid is null" );
|
||||
}
|
||||
} catch ( java.io.IOException ioe ) {
|
||||
Log.ex( TAG, ioe );
|
||||
|
@ -995,16 +928,19 @@ public class RelayService extends XWService
|
|||
private String getVLIString( DataInputStream dis )
|
||||
throws java.io.IOException
|
||||
{
|
||||
int len = vli2un( dis );
|
||||
byte[] tmp = new byte[len];
|
||||
dis.read( tmp );
|
||||
byte[] tmp = new byte[vli2un( dis )];
|
||||
dis.readFully( tmp );
|
||||
String result = new String( tmp );
|
||||
return result;
|
||||
}
|
||||
|
||||
private void postPacket( ByteArrayOutputStream bas, XWRelayReg cmd )
|
||||
{
|
||||
m_queue.add( new PacketData( bas, cmd ) );
|
||||
startUDPThreadsIfNot();
|
||||
UDPThreads threads = m_UDPThreadsRef.get();
|
||||
if ( threads != null ) {
|
||||
threads.add( new PacketData( bas, cmd ) );
|
||||
}
|
||||
// 0 ok; thread will often have sent already!
|
||||
// DbgUtils.logf( "postPacket() done; %d in queue", m_queue.size() );
|
||||
}
|
||||
|
@ -1068,6 +1004,277 @@ public class RelayService extends XWService
|
|||
}
|
||||
}
|
||||
|
||||
private class UDPThreads {
|
||||
private DatagramSocket m_UDPSocket;
|
||||
private LinkedBlockingQueue<PacketData> m_queue =
|
||||
new LinkedBlockingQueue<PacketData>();
|
||||
private Thread m_UDPReadThread;
|
||||
private Thread m_UDPWriteThread;
|
||||
|
||||
UDPThreads() {}
|
||||
|
||||
void start()
|
||||
{
|
||||
m_UDPReadThread = new Thread( null, new Runnable() {
|
||||
public void run() {
|
||||
|
||||
connectSocket(); // block until this is done
|
||||
startWriteThread();
|
||||
|
||||
Log.i( TAG, "read thread running" );
|
||||
byte[] buf = new byte[1024];
|
||||
for ( ; ; ) {
|
||||
DatagramPacket packet =
|
||||
new DatagramPacket( buf, buf.length );
|
||||
try {
|
||||
m_UDPSocket.receive( packet );
|
||||
resetExitTimer();
|
||||
gotPacket( packet );
|
||||
} catch ( java.io.InterruptedIOException iioe ) {
|
||||
// DbgUtils.logf( "FYI: udp receive timeout" );
|
||||
} catch( java.io.IOException ioe ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Log.i( TAG, "read thread exiting" );
|
||||
}
|
||||
}, getClass().getName() );
|
||||
m_UDPReadThread.start();
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
m_queue.add( new EOQPacketData() ); // will kill the writer thread
|
||||
}
|
||||
|
||||
void add( PacketData packet )
|
||||
{
|
||||
m_queue.add( packet );
|
||||
}
|
||||
|
||||
private void connectSocket()
|
||||
{
|
||||
if ( null == m_UDPSocket ) {
|
||||
int port = XWPrefs.getDefaultRelayPort( RelayService.this );
|
||||
String host = XWPrefs.getDefaultRelayHost( RelayService.this );
|
||||
try {
|
||||
m_UDPSocket = new DatagramSocket();
|
||||
m_UDPSocket.setSoTimeout(30 * 1000); // timeout so we can log
|
||||
|
||||
InetAddress addr = InetAddress.getByName( host );
|
||||
m_UDPSocket.connect( addr, port ); // remember this address
|
||||
Log.d( TAG, "connectSocket(%s:%d): m_UDPSocket now %H",
|
||||
host, port, m_UDPSocket );
|
||||
} catch( java.net.SocketException se ) {
|
||||
Log.ex( TAG, se );
|
||||
Assert.fail();
|
||||
} catch( java.net.UnknownHostException uhe ) {
|
||||
Log.ex( TAG, uhe );
|
||||
}
|
||||
} else {
|
||||
Assert.assertTrue( m_UDPSocket.isConnected() );
|
||||
Log.i( TAG, "m_UDPSocket not null" );
|
||||
}
|
||||
}
|
||||
|
||||
private void startWriteThread()
|
||||
{
|
||||
Assert.assertNull( m_UDPWriteThread );
|
||||
|
||||
m_UDPWriteThread = new Thread( null, new Runnable() {
|
||||
public void run() {
|
||||
Log.i( TAG, "write thread starting" );
|
||||
for ( boolean gotEOQ = false; !gotEOQ; ) {
|
||||
List<PacketData> dataListUDP = new ArrayList<>();
|
||||
List<PacketData> dataListWeb = new ArrayList<>();
|
||||
PacketData outData;
|
||||
try {
|
||||
long ts = s_packetsSentUDP.size() > 0 ? 10 : 3600;
|
||||
Log.d( TAG, "blocking %d sec on poll()", ts );
|
||||
for ( outData = m_queue.poll(ts, TimeUnit.SECONDS);
|
||||
null != outData;
|
||||
outData = m_queue.poll() ) { // doesn't block
|
||||
if ( outData instanceof EOQPacketData ) {
|
||||
gotEOQ = true;
|
||||
break;
|
||||
} else if ( skipNativeSend() || outData.getForWeb() ) {
|
||||
dataListWeb.add (outData );
|
||||
} else {
|
||||
dataListUDP.add( outData );
|
||||
}
|
||||
}
|
||||
} catch ( InterruptedException ie ) {
|
||||
Log.w( TAG, "write thread killed" );
|
||||
break;
|
||||
}
|
||||
|
||||
sendViaWeb( dataListWeb );
|
||||
sendViaUDP( dataListUDP );
|
||||
|
||||
resetExitTimer();
|
||||
runUDPAckTimer();
|
||||
|
||||
ConnStatusHandler.showSuccessOut();
|
||||
}
|
||||
|
||||
Log.i( TAG, "write thread killing read thread" );
|
||||
|
||||
// now kill the read thread
|
||||
m_UDPSocket.close();
|
||||
try {
|
||||
m_UDPReadThread.join();
|
||||
} catch( java.lang.InterruptedException ie ) {
|
||||
Log.ex( TAG, ie );
|
||||
}
|
||||
|
||||
Log.i( TAG, "write thread exiting" );
|
||||
}
|
||||
}, getClass().getName() );
|
||||
m_UDPWriteThread.start();
|
||||
}
|
||||
|
||||
private int sendViaWeb( List<PacketData> packets )
|
||||
{
|
||||
Log.d( TAG, "sendViaWeb(): sending %d at once", packets.size() );
|
||||
int sentLen = 0;
|
||||
if ( packets.size() > 0 ) {
|
||||
HttpURLConnection conn = NetUtils.makeHttpRelayConn( RelayService.this, "post" );
|
||||
if ( null == conn ) {
|
||||
Log.e( TAG, "sendViaWeb(): null conn for POST" );
|
||||
} else {
|
||||
try {
|
||||
JSONArray dataArray = new JSONArray();
|
||||
for ( PacketData packet : packets ) {
|
||||
Assert.assertFalse( packet instanceof EOQPacketData );
|
||||
byte[] datum = packet.assemble();
|
||||
dataArray.put( Utils.base64Encode(datum) );
|
||||
sentLen += datum.length;
|
||||
}
|
||||
JSONObject params = new JSONObject();
|
||||
params.put( "data", dataArray );
|
||||
|
||||
String result = NetUtils.runConn( conn, params );
|
||||
boolean succeeded = null != result;
|
||||
if ( succeeded ) {
|
||||
Log.d( TAG, "sendViaWeb(): POST(%s) => %s", params, result );
|
||||
JSONObject resultObj = new JSONObject( result );
|
||||
JSONArray resData = resultObj.getJSONArray( "data" );
|
||||
int nReplies = resData.length();
|
||||
// Log.d( TAG, "sendViaWeb(): got %d replies", nReplies );
|
||||
|
||||
noteSent( packets, false ); // before we process the acks below :-)
|
||||
|
||||
for ( int ii = 0; ii < nReplies; ++ii ) {
|
||||
byte[] datum = Utils.base64Decode( resData.getString( ii ) );
|
||||
// PENDING: skip ack or not
|
||||
gotPacket( datum, false, false );
|
||||
}
|
||||
} else {
|
||||
Log.e( TAG, "sendViaWeb(): failed result for POST" );
|
||||
|
||||
}
|
||||
|
||||
ConnStatusHandler.updateStatus( RelayService.this, null,
|
||||
CommsConnType.COMMS_CONN_RELAY,
|
||||
succeeded );
|
||||
} catch ( JSONException ex ) {
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
}
|
||||
}
|
||||
return sentLen;
|
||||
}
|
||||
|
||||
private int sendViaUDP( List<PacketData> packets )
|
||||
{
|
||||
int sentLen = 0;
|
||||
|
||||
if ( packets.size() > 0 ) {
|
||||
noteSent( packets, true );
|
||||
for ( PacketData packet : packets ) {
|
||||
boolean getOut = true;
|
||||
byte[] data = packet.assemble();
|
||||
try {
|
||||
DatagramPacket udpPacket = new DatagramPacket( data, data.length );
|
||||
m_UDPSocket.send( udpPacket );
|
||||
|
||||
sentLen += udpPacket.getLength();
|
||||
// packet.setSentMS( nowMS );
|
||||
getOut = false;
|
||||
} catch ( java.net.SocketException se ) {
|
||||
Log.ex( TAG, se );
|
||||
Log.i( TAG, "Restarting threads to force new socket" );
|
||||
ConnStatusHandler.updateStatusOut( RelayService.this, null,
|
||||
CommsConnType.COMMS_CONN_RELAY,
|
||||
true );
|
||||
|
||||
m_handler.post( new Runnable() {
|
||||
public void run() {
|
||||
stopUDPThreadsIf();
|
||||
}
|
||||
} );
|
||||
break;
|
||||
} catch ( java.io.IOException ioe ) {
|
||||
Log.ex( TAG, ioe );
|
||||
} catch ( NullPointerException npe ) {
|
||||
Log.w( TAG, "network problem; dropping packet" );
|
||||
}
|
||||
if ( getOut ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ConnStatusHandler.updateStatus( RelayService.this, null,
|
||||
CommsConnType.COMMS_CONN_RELAY,
|
||||
sentLen > 0 );
|
||||
}
|
||||
|
||||
return sentLen;
|
||||
}
|
||||
|
||||
private long m_lastRunMS = 0;
|
||||
private void runUDPAckTimer()
|
||||
{
|
||||
long nowMS = System.currentTimeMillis();
|
||||
if ( m_lastRunMS + 3000 > nowMS ) { // never more frequently than 3 sec.
|
||||
// Log.d( TAG, "runUDPAckTimer(): too soon, so skipping" );
|
||||
} else {
|
||||
m_lastRunMS = nowMS;
|
||||
|
||||
long minSentMS = nowMS - 10000; // 10 seconds ago
|
||||
long prevSentMS = 0;
|
||||
List<PacketData> forResend = new ArrayList<>();
|
||||
boolean foundNonAck = false;
|
||||
synchronized ( s_packetsSentUDP ) {
|
||||
Iterator<PacketData> iter;
|
||||
for ( iter = s_packetsSentUDP.iterator(); iter.hasNext(); ) {
|
||||
PacketData packet = iter.next();
|
||||
long sentMS = packet.getSentMS();
|
||||
Assert.assertTrue( prevSentMS <= sentMS );
|
||||
prevSentMS = sentMS;
|
||||
if ( sentMS > minSentMS ) {
|
||||
break;
|
||||
}
|
||||
|
||||
forResend.add( packet );
|
||||
if ( packet.m_cmd != XWRelayReg.XWPDEV_ACK ) {
|
||||
foundNonAck = true;
|
||||
++m_nativeFailScore;
|
||||
}
|
||||
iter.remove();
|
||||
}
|
||||
Log.d( TAG, "runUDPAckTimer(): %d too-new packets remaining",
|
||||
s_packetsSentUDP.size() );
|
||||
}
|
||||
if ( foundNonAck ) {
|
||||
Log.d( TAG, "runUDPAckTimer(): reposting %d packets", forResend.size() );
|
||||
m_queue.addAll( forResend );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class AsyncSender extends AsyncTask<Void, Void, Void> {
|
||||
private Context m_context;
|
||||
private HashMap<String,ArrayList<byte[]>> m_msgHash;
|
||||
|
@ -1129,6 +1336,7 @@ public class RelayService extends XWService
|
|||
}
|
||||
// Now open a real socket, write size and proto, and
|
||||
// copy in the formatted buffer
|
||||
|
||||
Socket socket = NetUtils.makeProxySocket( m_context, 8000 );
|
||||
if ( null != socket ) {
|
||||
DataOutputStream outStream =
|
||||
|
@ -1205,24 +1413,52 @@ public class RelayService extends XWService
|
|||
{
|
||||
int nextPacketID = 0;
|
||||
if ( XWRelayReg.XWPDEV_ACK != cmd ) {
|
||||
synchronized( s_packetsSent ) {
|
||||
nextPacketID = ++s_nextPacketID;
|
||||
}
|
||||
nextPacketID = s_nextPacketID.incrementAndGet();
|
||||
}
|
||||
return nextPacketID;
|
||||
}
|
||||
|
||||
private static void noteAck( int packetID )
|
||||
private void noteAck( int packetID, boolean fromUDP )
|
||||
{
|
||||
synchronized( s_packetsSent ) {
|
||||
if ( s_packetsSent.contains( packetID ) ) {
|
||||
s_packetsSent.remove( packetID );
|
||||
Assert.assertTrue( packetID != 0 );
|
||||
List<PacketData> map = fromUDP ? s_packetsSentUDP : s_packetsSentWeb;
|
||||
synchronized( map ) {
|
||||
PacketData packet = null;
|
||||
Iterator<PacketData> iter = map.iterator();
|
||||
for ( iter = map.iterator(); iter.hasNext(); ) {
|
||||
PacketData next = iter.next();
|
||||
if ( next.m_packetID == packetID ) {
|
||||
packet = next;
|
||||
iter.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( packet != null ) {
|
||||
// Log.d( TAG, "noteAck(fromUDP=%b): removed for id %d: %s",
|
||||
// fromUDP, packetID, packet );
|
||||
if ( fromUDP ) {
|
||||
--m_nativeFailScore;
|
||||
}
|
||||
} else {
|
||||
Log.w( TAG, "Weird: got ack %d but never sent", packetID );
|
||||
}
|
||||
Log.d( TAG, "noteAck(): Got ack for %d; there are %d unacked packets",
|
||||
packetID, s_packetsSent.size() );
|
||||
if ( BuildConfig.DEBUG ) {
|
||||
ArrayList<String> pstrs = new ArrayList<>();
|
||||
for ( PacketData datum : map ) {
|
||||
pstrs.add( String.format("%d", datum.m_packetID ) );
|
||||
}
|
||||
Log.d( TAG, "noteAck(fromUDP=%b): Got ack for %d; there are %d unacked packets: %s",
|
||||
fromUDP, packetID, map.size(), TextUtils.join( ",", pstrs ) );
|
||||
}
|
||||
}
|
||||
|
||||
// If we get an ACK, things are working, even if it's not found above
|
||||
// (which would be the case for an ACK sent via web, which we don't
|
||||
// save.)
|
||||
ConnStatusHandler.updateStatus( this, null,
|
||||
CommsConnType.COMMS_CONN_RELAY,
|
||||
true );
|
||||
}
|
||||
|
||||
// Called from any thread
|
||||
|
@ -1247,7 +1483,7 @@ public class RelayService extends XWService
|
|||
registerWithRelay();
|
||||
} else {
|
||||
stopUDPThreadsIf();
|
||||
startFetchThreadIf();
|
||||
startFetchThreadIfNotUDP();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1334,7 +1570,7 @@ public class RelayService extends XWService
|
|||
result = figureBackoffSeconds();
|
||||
}
|
||||
|
||||
Log.d( TAG, "getMaxIntervalSeconds() => %d", result );
|
||||
Log.d( TAG, "getMaxIntervalSeconds() => %d", result ); // WFT? went from 40 to 1000
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1370,7 +1606,8 @@ public class RelayService extends XWService
|
|||
private boolean shouldMaintainConnection()
|
||||
{
|
||||
boolean result = relayEnabled( this )
|
||||
&& (XWApp.GCM_IGNORED || !s_gcmWorking);
|
||||
&& (!s_gcmWorking || XWPrefs.getIgnoreGCM( this ));
|
||||
|
||||
if ( result ) {
|
||||
long interval = Utils.getCurSeconds() - m_lastGamePacketReceived;
|
||||
result = interval < MAX_KEEPALIVE_SECS;
|
||||
|
@ -1396,18 +1633,19 @@ public class RelayService extends XWService
|
|||
long now = Utils.getCurSeconds();
|
||||
if ( s_curNextTimer <= now ) {
|
||||
if ( 0 == s_curBackoff ) {
|
||||
s_curBackoff = 15;
|
||||
s_curBackoff = INITIAL_BACKOFF;
|
||||
} else {
|
||||
s_curBackoff = Math.min( 2 * s_curBackoff, result );
|
||||
}
|
||||
s_curBackoff = Math.min( 2 * s_curBackoff, result );
|
||||
s_curNextTimer += s_curBackoff;
|
||||
}
|
||||
|
||||
diff = s_curNextTimer - now;
|
||||
}
|
||||
Assert.assertTrue( diff < Integer.MAX_VALUE );
|
||||
Log.d( TAG, "figureBackoffSeconds() => %d", diff );
|
||||
result = (int)diff;
|
||||
result = (int)diff;
|
||||
}
|
||||
// Log.d( TAG, "figureBackoffSeconds() => %d", result );
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1421,7 +1659,14 @@ public class RelayService extends XWService
|
|||
}
|
||||
|
||||
private class PacketData {
|
||||
public PacketData() { m_bas = null; }
|
||||
public ByteArrayOutputStream m_bas;
|
||||
public XWRelayReg m_cmd;
|
||||
public byte[] m_header;
|
||||
public int m_packetID;
|
||||
private long m_created;
|
||||
private long m_sentUDP;
|
||||
|
||||
private PacketData() {}
|
||||
|
||||
public PacketData( ByteArrayOutputStream bas, XWRelayReg cmd )
|
||||
{
|
||||
|
@ -1429,6 +1674,17 @@ public class RelayService extends XWService
|
|||
m_cmd = cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format( "{cmd: %s; age: %d ms}", m_cmd,
|
||||
System.currentTimeMillis() - m_created );
|
||||
}
|
||||
|
||||
void setSentMS( long ms ) { m_sentUDP = ms; }
|
||||
long getSentMS() { return m_sentUDP; }
|
||||
boolean getForWeb() { return m_sentUDP != 0; }
|
||||
|
||||
public int getLength()
|
||||
{
|
||||
int result = 0;
|
||||
|
@ -1441,13 +1697,13 @@ public class RelayService extends XWService
|
|||
return result;
|
||||
}
|
||||
|
||||
public DatagramPacket assemble()
|
||||
public byte[] assemble()
|
||||
{
|
||||
byte[] dest = new byte[getLength()];
|
||||
System.arraycopy( m_header, 0, dest, 0, m_header.length );
|
||||
byte[] data = new byte[getLength()];
|
||||
System.arraycopy( m_header, 0, data, 0, m_header.length );
|
||||
byte[] basData = m_bas.toByteArray();
|
||||
System.arraycopy( basData, 0, dest, m_header.length, basData.length );
|
||||
return new DatagramPacket( dest, dest.length );
|
||||
System.arraycopy( basData, 0, data, m_header.length, basData.length );
|
||||
return data;
|
||||
}
|
||||
|
||||
private void makeHeader()
|
||||
|
@ -1466,10 +1722,8 @@ public class RelayService extends XWService
|
|||
Log.ex( TAG, ioe );
|
||||
}
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream m_bas;
|
||||
public XWRelayReg m_cmd;
|
||||
public byte[] m_header;
|
||||
public int m_packetID;
|
||||
}
|
||||
|
||||
// Exits only to exist, so instanceof can distinguish
|
||||
private class EOQPacketData extends PacketData {}
|
||||
}
|
||||
|
|
|
@ -522,7 +522,7 @@ public class SMSService extends XWService {
|
|||
case DATA:
|
||||
int gameID = dis.readInt();
|
||||
byte[] rest = new byte[dis.available()];
|
||||
dis.read( rest );
|
||||
dis.readFully( rest );
|
||||
if ( feedMessage( gameID, rest, new CommsAddrRec( phone ) ) ) {
|
||||
SMSResendReceiver.resetTimer( this );
|
||||
}
|
||||
|
@ -618,7 +618,7 @@ public class SMSService extends XWService {
|
|||
} else {
|
||||
SMS_CMD cmd = SMS_CMD.values()[dis.readByte()];
|
||||
byte[] rest = new byte[dis.available()];
|
||||
dis.read( rest );
|
||||
dis.readFully( rest );
|
||||
receive( cmd, rest, senderPhone );
|
||||
success = true;
|
||||
}
|
||||
|
|
|
@ -258,7 +258,8 @@ public class UpdateCheckReceiver extends BroadcastReceiver {
|
|||
|
||||
@Override protected String doInBackground( Void... unused )
|
||||
{
|
||||
HttpURLConnection conn = NetUtils.makeHttpConn( m_context, "getUpdates" );
|
||||
HttpURLConnection conn
|
||||
= NetUtils.makeHttpUpdateConn( m_context, "getUpdates" );
|
||||
String json = null;
|
||||
if ( null != conn ) {
|
||||
json = NetUtils.runConn( conn, m_params );
|
||||
|
|
|
@ -443,7 +443,7 @@ public class Utils {
|
|||
{
|
||||
// Note: an int is big enough for *seconds* (not milliseconds) since 1970
|
||||
// until 2038
|
||||
long millis = new Date().getTime();
|
||||
long millis = System.currentTimeMillis();
|
||||
int result = (int)(millis / 1000);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ public class XWApp extends Application {
|
|||
public static final boolean ATTACH_SUPPORTED = false;
|
||||
public static final boolean LOG_LIFECYLE = false;
|
||||
public static final boolean DEBUG_EXP_TIMERS = false;
|
||||
public static final boolean GCM_IGNORED = false;
|
||||
public static final boolean UDP_ENABLED = true;
|
||||
public static final boolean SMS_INVITE_ENABLED = true;
|
||||
public static final boolean LOCUTILS_ENABLED = false;
|
||||
|
|
|
@ -67,6 +67,16 @@ public class XWPrefs {
|
|||
return getPrefsBoolean( context, R.string.key_enable_nfc_toself, false );
|
||||
}
|
||||
|
||||
public static boolean getIgnoreGCM( Context context )
|
||||
{
|
||||
return getPrefsBoolean( context, R.string.key_ignore_gcm, false );
|
||||
}
|
||||
|
||||
public static boolean getToastGCM( Context context )
|
||||
{
|
||||
return getPrefsBoolean( context, R.string.key_show_gcm, false );
|
||||
}
|
||||
|
||||
public static boolean getRelayInviteToSelfEnabled( Context context )
|
||||
{
|
||||
return getPrefsBoolean( context, R.string.key_enable_relay_toself, false );
|
||||
|
@ -115,6 +125,16 @@ public class XWPrefs {
|
|||
return getPrefsString( context, R.string.key_update_url );
|
||||
}
|
||||
|
||||
public static String getDefaultRelayUrl( Context context )
|
||||
{
|
||||
return getPrefsString( context, R.string.key_relay_url );
|
||||
}
|
||||
|
||||
public static boolean getSkipToWebAPI( Context context )
|
||||
{
|
||||
return getPrefsBoolean( context, R.string.key_relay_via_http_first, false );
|
||||
}
|
||||
|
||||
public static int getDefaultProxyPort( Context context )
|
||||
{
|
||||
String val = getPrefsString( context, R.string.key_proxy_port );
|
||||
|
|
|
@ -82,7 +82,7 @@ class XWService extends Service {
|
|||
s_seen.add( inviteID );
|
||||
}
|
||||
}
|
||||
Log.d( TAG, "checkNotDupe(%s) => %b", inviteID, !isDupe );
|
||||
Log.d( TAG, "checkNotDupe('%s') => %b", inviteID, !isDupe );
|
||||
return !isDupe;
|
||||
}
|
||||
|
||||
|
|
|
@ -266,15 +266,19 @@ public class JNIThread extends Thread {
|
|||
}
|
||||
|
||||
public boolean busy()
|
||||
{ // synchronize this!!!
|
||||
{
|
||||
boolean result = false;
|
||||
|
||||
// Docs: The returned iterator is a "weakly consistent" iterator that
|
||||
// will never throw ConcurrentModificationException, and guarantees to
|
||||
// traverse elements as they existed upon construction of the
|
||||
// iterator, and may (but is not guaranteed to) reflect any
|
||||
// modifications subsequent to construction.
|
||||
Iterator<QueueElem> iter = m_queue.iterator();
|
||||
while ( iter.hasNext() ) {
|
||||
if ( iter.next().m_isUIEvent ) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
while ( iter.hasNext() && !result ) {
|
||||
result = iter.next().m_isUIEvent;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ public interface TransportProcs {
|
|||
, COMMS_RELAYSTATE_RECONNECTED
|
||||
, COMMS_RELAYSTATE_ALLCONNECTED
|
||||
};
|
||||
void relayStatus( CommsRelayState newState );
|
||||
|
||||
void relayConnd( String room, int devOrder, boolean allHere, int nMissing );
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
<LinearLayout android:id="@+id/right_side"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:longClickable="true"
|
||||
>
|
||||
|
|
|
@ -6,6 +6,13 @@
|
|||
android:title="@string/board_menu_invite"
|
||||
/>
|
||||
|
||||
<item android:id="@+id/board_menu_archive"
|
||||
android:title="@string/button_archive"
|
||||
/>
|
||||
<item android:id="@+id/board_menu_rematch"
|
||||
android:title="@string/button_rematch"
|
||||
/>
|
||||
|
||||
<group android:id="@+id/group_done">
|
||||
<!-- title set in BoardActivity -->
|
||||
<item android:id="@+id/board_menu_done"
|
||||
|
@ -78,21 +85,21 @@
|
|||
</menu>
|
||||
</item>
|
||||
|
||||
<item android:id="@+id/games_menu_study"
|
||||
android:title="@string/gamel_menu_study"
|
||||
/>
|
||||
<item android:id="@+id/games_menu_study"
|
||||
android:title="@string/gamel_menu_study"
|
||||
/>
|
||||
|
||||
<item android:id="@+id/gamel_menu_checkmoves"
|
||||
android:title="@string/gamel_menu_checkmoves"
|
||||
/>
|
||||
<item android:id="@+id/gamel_menu_checkmoves"
|
||||
android:title="@string/gamel_menu_checkmoves"
|
||||
/>
|
||||
|
||||
<item android:id="@+id/board_menu_file_prefs"
|
||||
android:title="@string/menu_prefs"
|
||||
android:alphabeticShortcut="P"
|
||||
/>
|
||||
<item android:id="@+id/board_menu_game_netstats"
|
||||
android:title="@string/board_menu_game_netstats" />
|
||||
<item android:id="@+id/board_menu_game_invites"
|
||||
android:title="@string/board_menu_game_showInvites" />
|
||||
<item android:id="@+id/board_menu_file_prefs"
|
||||
android:title="@string/menu_prefs"
|
||||
android:alphabeticShortcut="P"
|
||||
/>
|
||||
<item android:id="@+id/board_menu_game_netstats"
|
||||
android:title="@string/board_menu_game_netstats" />
|
||||
<item android:id="@+id/board_menu_game_invites"
|
||||
android:title="@string/board_menu_game_showInvites" />
|
||||
|
||||
</menu>
|
||||
|
|
|
@ -5,9 +5,15 @@
|
|||
<group android:id="@+id/group_done">
|
||||
<!-- title set in BoardActivity -->
|
||||
|
||||
<item android:id="@+id/board_menu_archive"
|
||||
android:title="@string/button_archive"
|
||||
android:showAsAction="ifRoom"
|
||||
android:icon="@drawable/archive__gen"
|
||||
/>
|
||||
<item android:id="@+id/board_menu_rematch"
|
||||
android:title="@string/button_rematch"
|
||||
android:showAsAction="ifRoom"
|
||||
android:icon="@drawable/rematch__gen"
|
||||
/>
|
||||
<item android:id="@+id/board_menu_done"
|
||||
android:alphabeticShortcut="D"
|
||||
|
@ -19,13 +25,14 @@
|
|||
android:title="@string/board_menu_trade"
|
||||
android:alphabeticShortcut="T"
|
||||
android:showAsAction="ifRoom"
|
||||
android:icon="@drawable/trade__gen"
|
||||
/>
|
||||
</group>
|
||||
|
||||
<item android:id="@+id/board_menu_trade_cancel"
|
||||
android:title="@string/button_trade_cancel"
|
||||
android:showAsAction="ifRoom"
|
||||
android:icon="@drawable/back__gen"
|
||||
android:icon="@drawable/untrade__gen"
|
||||
/>
|
||||
|
||||
<item android:id="@+id/board_menu_trade_commit"
|
||||
|
|
|
@ -36,7 +36,9 @@
|
|||
|
||||
<string name="key_relay_host">key_relay_host</string>
|
||||
<string name="key_relay_port">key_relay_port2</string>
|
||||
<string name="key_relay_via_http_first">key_relay_via_http_first</string>
|
||||
<string name="key_update_url">key_update_url</string>
|
||||
<string name="key_relay_url">key_relay_url</string>
|
||||
<string name="key_update_prerel">key_update_prerel</string>
|
||||
<string name="key_proxy_port">key_proxy_port</string>
|
||||
<string name="key_sms_port">key_sms_port</string>
|
||||
|
@ -111,6 +113,7 @@
|
|||
<string name="key_notagain_trading">key_notagain_trading</string>
|
||||
<string name="key_notagain_hidenewgamebuttons">key_notagain_hidenewgamebuttons</string>
|
||||
<string name="key_na_lookup">key_na_lookup</string>
|
||||
<string name="key_na_archive">key_na_archive</string>
|
||||
<string name="key_na_browse">key_na_browse</string>
|
||||
<string name="key_na_browseall">key_na_browseall</string>
|
||||
<string name="key_na_values">key_na_values</string>
|
||||
|
@ -123,6 +126,8 @@
|
|||
<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_relay_toself">key_enable_relay_toself</string>
|
||||
<string name="key_ignore_gcm">key_ignore_gcm</string>
|
||||
<string name="key_show_gcm">key_show_gcm</string>
|
||||
<string name="key_nag_intervals">key_nag_intervals</string>
|
||||
<string name="key_download_path">key_download_path</string>
|
||||
<string name="key_got_langdict">key_got_langdict</string>
|
||||
|
@ -149,6 +154,7 @@
|
|||
|
||||
<string name="dict_url">http://eehouse.org/and_wordlists</string>
|
||||
<string name="default_update_url">http://eehouse.org/xw4/info.py</string>
|
||||
<string name="default_relay_url">http://eehouse.org/xw4/relay.py</string>
|
||||
|
||||
<!--string name="dict_url">http://10.0.2.2/~eehouse/and_dicts</string-->
|
||||
|
||||
|
|
|
@ -1692,7 +1692,7 @@
|
|||
<string name="about_vers_fmt">CrossWords for Android, Version %1$s,
|
||||
rev %2$s, built on %3$s.</string>
|
||||
<!-- copyright info -->
|
||||
<string name="about_copyright">Copyright (C) 1998-2017 by Eric
|
||||
<string name="about_copyright">Copyright (C) 1998-2018 by Eric
|
||||
House. This free/open source software is released under the GNU Public
|
||||
License.</string>
|
||||
|
||||
|
@ -1709,7 +1709,9 @@
|
|||
|
||||
<!-- Another paragraph giving credit for work done other than by
|
||||
Eric House and translators -->
|
||||
<string name="about_credits">Toolbar icons by Sarah Chu.</string>
|
||||
<string name="about_credits">Toolbar icons by Sarah Chu. Navbar
|
||||
icons from the Noun Project: \"archive\" by Trendy; \"rematch\" by
|
||||
Becris; and \"swap\" by iconomania.</string>
|
||||
|
||||
<!-- text of dialog showing the set of changes made since the last
|
||||
release -->
|
||||
|
@ -1743,6 +1745,14 @@
|
|||
<string name="not_again_lookup">This button lets you look up,
|
||||
online, the words just played.</string>
|
||||
|
||||
<string name="not_again_archive">Archiving uses a special group
|
||||
called \"Archive\" to store finished games you want to keep. And,
|
||||
since deleting an entire archive is easy, archiving is also a
|
||||
great way to mark games for deletion – if that\'s what you prefer
|
||||
to do.\n\n(Deleting the Archive group is safe because it will be
|
||||
created anew when needed.)
|
||||
</string>
|
||||
|
||||
<!-- -->
|
||||
<string name="button_move">Move</string>
|
||||
<string name="button_newgroup">New group</string>
|
||||
|
@ -2158,6 +2168,10 @@
|
|||
game with the same players and parameters as the one that
|
||||
just ended. -->
|
||||
<string name="button_rematch">Rematch</string>
|
||||
<string name="button_archive">Archive\u200C</string>
|
||||
<string name="group_name_archive">Archive</string>
|
||||
|
||||
<string name="duplicate_group_name_fmt">The group \"%1$s\" already exists.</string>
|
||||
|
||||
<string name="button_reconnect">Reconnect</string>
|
||||
|
||||
|
@ -2474,6 +2488,8 @@
|
|||
<string name="advanced">For debugging</string>
|
||||
<string name="advanced_summary">You should never need these...</string>
|
||||
<string name="relay_host">Relay host</string>
|
||||
<string name="relay_via_http_first">Use Web APIs first</string>
|
||||
<string name="relay_via_http_first_summary">(instead of as fallback for custom protocol)</string>
|
||||
<string name="dict_host">Wordlist download URL</string>
|
||||
<string name="logging_on">Enable logging</string>
|
||||
<string name="logging_on_summary">(release builds only)</string>
|
||||
|
@ -2513,6 +2529,7 @@
|
|||
<string name="game_summary_field_gameid">gameid</string>
|
||||
<string name="game_summary_field_npackets">Pending packet count</string>
|
||||
<string name="expl_update_url">Update checks URL</string>
|
||||
<string name="expl_relay_url">URL for relay web API</string>
|
||||
|
||||
<string name="got_langdict_title">Fetch default wordlist for language</string>
|
||||
<string name="got_langdict_summary">Don\'t try a second time</string>
|
||||
|
@ -2584,6 +2601,12 @@
|
|||
<string name="enable_relay_toself_title">Enable relay invites to self</string>
|
||||
<string name="enable_relay_toself_summary">(To aid testing and debugging)</string>
|
||||
|
||||
<string name="ignore_gcm_title">Ignore incoming GCM messages</string>
|
||||
<string name="ignore_gcm_summary">Mimic life without a google account</string>
|
||||
|
||||
<string name="show_sms_title">Show SMS sends, receives</string>
|
||||
<string name="show_gcm_title">Show GCM receives</string>
|
||||
|
||||
<!-- Shown after "resend messages" menuitem chosen -->
|
||||
<plurals name="resent_msgs_fmt">
|
||||
<item quantity="one">One move sent</item>
|
||||
|
|
|
@ -390,6 +390,55 @@
|
|||
android:defaultValue="false"
|
||||
/>
|
||||
|
||||
<PreferenceScreen android:title="@string/pref_group_relay_title"
|
||||
android:summary="@string/pref_group_relay_summary"
|
||||
>
|
||||
<CheckBoxPreference android:key="@string/key_enable_relay_toself"
|
||||
android:title="@string/enable_relay_toself_title"
|
||||
android:summary="@string/enable_relay_toself_summary"
|
||||
android:defaultValue="false"
|
||||
/>
|
||||
<CheckBoxPreference android:key="@string/key_ignore_gcm"
|
||||
android:title="@string/ignore_gcm_title"
|
||||
android:summary="@string/ignore_gcm_summary"
|
||||
android:defaultValue="false"
|
||||
/>
|
||||
<CheckBoxPreference android:key="@string/key_show_gcm"
|
||||
android:title="@string/show_gcm_title"
|
||||
android:defaultValue="false"
|
||||
/>
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_relay_host"
|
||||
android:title="@string/relay_host"
|
||||
android:defaultValue="@string/default_host"
|
||||
/>
|
||||
|
||||
<CheckBoxPreference android:key="@string/key_relay_via_http_first"
|
||||
android:title="@string/relay_via_http_first"
|
||||
android:summary="@string/relay_via_http_first_summary"
|
||||
android:defaultValue="false"
|
||||
/>
|
||||
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_relay_url"
|
||||
android:title="@string/expl_relay_url"
|
||||
android:defaultValue="@string/default_relay_url"
|
||||
/>
|
||||
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_relay_port"
|
||||
android:title="@string/relay_port"
|
||||
android:defaultValue="10997"
|
||||
android:numeric="decimal"
|
||||
/>
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_proxy_port"
|
||||
android:title="@string/proxy_port"
|
||||
android:defaultValue="10998"
|
||||
android:numeric="decimal"
|
||||
/>
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen android:title="@string/pref_group_sms_title"
|
||||
android:summary="@string/pref_group_sms_summary"
|
||||
>
|
||||
|
@ -407,34 +456,7 @@
|
|||
/>
|
||||
|
||||
<CheckBoxPreference android:key="@string/key_show_sms"
|
||||
android:title="Show SMS sends, receives"
|
||||
android:defaultValue="false"
|
||||
/>
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen android:title="@string/pref_group_relay_title"
|
||||
android:summary="@string/pref_group_relay_summary"
|
||||
>
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_relay_host"
|
||||
android:title="@string/relay_host"
|
||||
android:defaultValue="@string/default_host"
|
||||
/>
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_relay_port"
|
||||
android:title="@string/relay_port"
|
||||
android:defaultValue="10997"
|
||||
android:numeric="decimal"
|
||||
/>
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_proxy_port"
|
||||
android:title="@string/proxy_port"
|
||||
android:defaultValue="10998"
|
||||
android:numeric="decimal"
|
||||
/>
|
||||
<CheckBoxPreference android:key="@string/key_enable_relay_toself"
|
||||
android:title="@string/enable_relay_toself_title"
|
||||
android:summary="@string/enable_relay_toself_summary"
|
||||
android:title="@string/show_sms_title"
|
||||
android:defaultValue="false"
|
||||
/>
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -34,6 +34,8 @@ import junit.framework.Assert;
|
|||
public class GCMIntentService extends GCMBaseIntentService {
|
||||
private static final String TAG = GCMIntentService.class.getSimpleName();
|
||||
|
||||
private Boolean m_toastGCM;
|
||||
|
||||
public GCMIntentService()
|
||||
{
|
||||
super( BuildConfig.GCM_SENDER_ID );
|
||||
|
@ -67,14 +69,19 @@ public class GCMIntentService extends GCMBaseIntentService {
|
|||
protected void onMessage( Context context, Intent intent )
|
||||
{
|
||||
Log.d( TAG, "onMessage()" );
|
||||
notifyRelayService( context, true );
|
||||
|
||||
String value;
|
||||
boolean ignoreIt = XWApp.GCM_IGNORED;
|
||||
if ( ignoreIt ) {
|
||||
Log.d( TAG, "received GCM but ignoring it" );
|
||||
if ( null == m_toastGCM ) {
|
||||
m_toastGCM = new Boolean( XWPrefs.getToastGCM( context ) );
|
||||
}
|
||||
|
||||
if ( XWPrefs.getIgnoreGCM( context ) ) {
|
||||
String logMsg = "received GCM but ignoring it";
|
||||
Log.d( TAG, logMsg );
|
||||
DbgUtils.showf( context, logMsg );
|
||||
} else {
|
||||
value = intent.getStringExtra( "checkUpdates" );
|
||||
notifyRelayService( context, true );
|
||||
|
||||
String value = intent.getStringExtra( "checkUpdates" );
|
||||
if ( null != value && Boolean.parseBoolean( value ) ) {
|
||||
UpdateCheckReceiver.checkVersions( context, true );
|
||||
}
|
||||
|
@ -82,6 +89,9 @@ public class GCMIntentService extends GCMBaseIntentService {
|
|||
value = intent.getStringExtra( "getMoves" );
|
||||
if ( null != value && Boolean.parseBoolean( value ) ) {
|
||||
RelayService.timerFired( context );
|
||||
if ( m_toastGCM ) {
|
||||
DbgUtils.showf( context, "onMessage(): got 'getMoves'" );
|
||||
}
|
||||
}
|
||||
|
||||
value = intent.getStringExtra( "msgs64" );
|
||||
|
@ -90,6 +100,11 @@ public class GCMIntentService extends GCMBaseIntentService {
|
|||
try {
|
||||
JSONArray msgs64 = new JSONArray( value );
|
||||
String[] strs64 = new String[msgs64.length()];
|
||||
if ( m_toastGCM ) {
|
||||
DbgUtils.showf( context, "onMessage(): got %d msgs",
|
||||
strs64.length );
|
||||
}
|
||||
|
||||
for ( int ii = 0; ii < strs64.length; ++ii ) {
|
||||
strs64[ii] = msgs64.optString(ii);
|
||||
}
|
||||
|
@ -100,6 +115,7 @@ public class GCMIntentService extends GCMBaseIntentService {
|
|||
}
|
||||
} catch (org.json.JSONException jse ) {
|
||||
Log.ex( TAG, jse );
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,10 +161,8 @@ public class GCMIntentService extends GCMBaseIntentService {
|
|||
|
||||
private void notifyRelayService( Context context, boolean working )
|
||||
{
|
||||
if ( working && XWApp.GCM_IGNORED ) {
|
||||
working = false;
|
||||
if ( !XWPrefs.getIgnoreGCM( context ) ) {
|
||||
RelayService.gcmConfirmed( context, working );
|
||||
}
|
||||
RelayService.gcmConfirmed( context, working );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
|
||||
/*
|
||||
* Copyright 2010 - 2015 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;
|
||||
|
||||
import android.content.Context;
|
||||
import com.google.android.gcm.GCMRegistrar;
|
||||
|
||||
class GCMStub {
|
||||
|
||||
public static String getRegistrationId( Context context )
|
||||
{
|
||||
return GCMRegistrar.getRegistrationId( context );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* -*- compile-command: "find-and-gradle.sh insXw4dDeb"; -*- */
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
class GCMStub {
|
||||
public static String getRegistrationId( Context context )
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
1
xwords4/android/app/src/xw4d/res/values/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
strings.xml
|
|
@ -0,0 +1,27 @@
|
|||
/* -*- compile-command: "find-and-gradle.sh insXw4Deb"; -*- */
|
||||
/*
|
||||
* Copyright 2009 - 2012 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;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class CrashTrack {
|
||||
public static void init( Context context ) {} // does nothing here
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/* -*- compile-command: "find-and-gradle.sh -PuseCrashlytics insXw4dDeb"; -*- */
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
/**
|
||||
* The ancient GCMIntentService I copied from sample code seems to have
|
||||
* trouble (burns battery using the WAKELOCK, specifically) when used with an
|
||||
* app that doesn't have a registration ID. So let's not use that code.
|
||||
*/
|
||||
|
||||
public class GCMIntentService {
|
||||
private static final String TAG = GCMIntentService.class.getSimpleName();
|
||||
|
||||
public static void init( Application app )
|
||||
{
|
||||
Log.d( TAG, "doing nothing" );
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
../../../../../../xw4d/java/org/eehouse/android/xw4/GCMStub.java
|
87
xwords4/android/img_src/archive.svg
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
data-name="Layer 1"
|
||||
viewBox="0 0 66 82"
|
||||
x="0px"
|
||||
y="0px"
|
||||
id="svg3396"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="archive.svg"
|
||||
width="66"
|
||||
height="82">
|
||||
<metadata
|
||||
id="metadata3416">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>01</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs3414" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1163"
|
||||
id="namedview3412"
|
||||
showgrid="true"
|
||||
showborder="false"
|
||||
inkscape:zoom="6.616"
|
||||
inkscape:cx="-17.332527"
|
||||
inkscape:cy="66.335551"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3396"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3510"
|
||||
originx="-17"
|
||||
originy="-14" />
|
||||
</sodipodi:namedview>
|
||||
<title
|
||||
id="title3398">01</title>
|
||||
<path
|
||||
d="M 66,22 0,22 0,82 66,82 Z M 60,76 6,76 6,28 60,28 Z"
|
||||
id="path3400"
|
||||
inkscape:connector-curvature="0" />
|
||||
<polygon
|
||||
points="64.3,58.3 64.3,46.7 64.3,46.7 58.3,46.7 58.3,52.3 41.7,52.3 41.7,46.7 35.7,46.7 35.7,58.3 "
|
||||
id="polygon3402"
|
||||
transform="translate(-17,-9)" />
|
||||
<rect
|
||||
x="8"
|
||||
y="11"
|
||||
width="50"
|
||||
height="6"
|
||||
id="rect3404" />
|
||||
<rect
|
||||
x="13"
|
||||
y="0"
|
||||
width="40"
|
||||
height="6"
|
||||
id="rect3406" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120"
|
||||
height="120" xml:space="preserve">
|
||||
<g
|
||||
id="g12"
|
||||
transform="matrix(1.25,0,0,-1.25,0,120)">
|
||||
<g transform='translate(46.03,16.24)' id='g1584'>
|
||||
<path style='fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none' d='M 0,0 5.5,5.502 -16.87,27.87 35.7,27.87 35.7,35.65 -16.87,35.65 5.5,58.02 0,63.52 -31.76,31.76 0,0 z' id='path1586'/>
|
||||
</g></g>
|
||||
</svg>
|
Before Width: | Height: | Size: 609 B |
|
@ -1,9 +1,49 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120"
|
||||
height="120" xml:space="preserve">
|
||||
<g
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
width="75.012497"
|
||||
height="90.025002"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="content_copy.svg"><metadata
|
||||
id="metadata10"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs8" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="787"
|
||||
inkscape:window-height="480"
|
||||
id="namedview6"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1.9666667"
|
||||
inkscape:cx="37.5125"
|
||||
inkscape:cy="45.0125"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><g
|
||||
id="g12"
|
||||
transform="matrix(1.25,0,0,-1.25,0,120)">
|
||||
<path style='fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none' d='M 29.99,72.01 78,72.01 78,11.99 29.99,11.99 29.99,72.01 z M 72,66 36,66 36,17.99 72,17.99 72,66 z M 55.34,53 42,53 42,55 55.34,55 55.34,53 z M 66,47 41.99,47 41.99,49 66,49 66,47 z M 59.34,41 42,41 42,43 59.34,43 59.34,41 z M 55.34,35 42,35 42,37 55.34,37 55.34,35 z M 64.67,28.99 41.99,28.99 41.99,30.99 64.67,30.99 64.67,28.99 z M 60.01,78.01 23.99,78.01 23.99,29.99 26.99,29.99 26.99,23.99 23.99,23.99 17.99,23.99 17.99,84.01 66,84.01 66,78.01 66,75.01 60.01,75.01 60.01,78.01 z' id='path1220'/></g>
|
||||
</svg>
|
||||
transform="matrix(1.25,0,0,-1.25,-22.4875,105.0125)"><path
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 29.99,72.01 48.01,0 0,-60.02 -48.01,0 0,60.02 z M 72,66 36,66 36,17.99 72,17.99 72,66 Z M 55.34,53 42,53 l 0,2 13.34,0 0,-2 z M 66,47 l -24.01,0 0,2 24.01,0 0,-2 z M 59.34,41 42,41 l 0,2 17.34,0 0,-2 z m -4,-6 -13.34,0 0,2 13.34,0 0,-2 z m 9.33,-6.01 -22.68,0 0,2 22.68,0 0,-2 z m -4.66,49.02 -36.02,0 0,-48.02 3,0 0,-6 -3,0 -6,0 0,60.02 48.01,0 0,-6 0,-3 -5.99,0 0,3 z"
|
||||
id="path1220"
|
||||
inkscape:connector-curvature="0" /></g></svg>
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 2.1 KiB |
|
@ -1,18 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="120"
|
||||
height="120" xml:space="preserve">
|
||||
<g
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
width="59.5"
|
||||
height="85.212502"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="content_discard.svg"><metadata
|
||||
id="metadata15"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs13" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1594"
|
||||
inkscape:window-height="887"
|
||||
id="namedview11"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1.9666667"
|
||||
inkscape:cx="18.055085"
|
||||
inkscape:cy="42.6"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" /><g
|
||||
id="g12"
|
||||
transform="matrix(1.25,0,0,-1.25,0,120)">
|
||||
<g id='g1254'>
|
||||
<g id='g1256'>
|
||||
<g transform='translate(68.92,54.16)' id='g1262'>
|
||||
<path style='fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none' d='M 0,0 C -0.046,0.281 -0.218,0.557 -0.476,0.82 -2.555,-1.292 -10.92,-1.642 -20.92,-1.642 -30.92,-1.642 -39.28,-1.292 -41.37,0.82 -41.62,0.557 -41.78,0.281 -41.84,0 L -41.86,0 -41.86,-0.152 C -41.86,-0.17 -41.86,-0.188 -41.86,-0.206 -41.86,-0.247 -41.85,-0.283 -41.85,-0.322 L -40.22,-33.68 -40.21,-33.68 C -40.07,-36.29 -37.07,-40.24 -20.92,-40.24 -4.766,-40.24 -1.77,-36.29 -1.629,-33.68 L -1.617,-33.68 0.013,-0.322 C 0.016,-0.283 0.028,-0.247 0.028,-0.206 0.028,-0.188 0.016,-0.17 0.016,-0.152 L 0.028,0 0,0 z' id='path1264'/>
|
||||
</g>
|
||||
<g transform='translate(42.16,71.94)' id='g1266'>
|
||||
<path style='fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none' d='M 0,0 -0.657,-0.041 -0.657,3.221 C -0.657,4.647 -0.458,5.815 -0.223,5.815 L 2.809,5.815 8.002,5.815 11.03,5.815 C 11.27,5.815 11.46,4.647 11.46,3.221 L 11.46,0.012 C 9.658,0.105 7.779,0.164 5.835,0.164 3.82,0.164 1.863,0.105 0,0 M 15.79,-0.326 15.79,7.549 C 15.79,8.971 14.63,10.15 13.19,10.15 L -2.392,10.15 C -3.813,10.15 -4.982,8.971 -4.982,7.549 L -4.982,-0.416 C -12.69,-1.29 -17.96,-3.06 -17.96,-5.11 L -17.96,-9.003 C -17.96,-9.802 -17.15,-10.56 -15.7,-11.25 -11.9,-13.03 -3.686,-14.27 5.835,-14.27 15.36,-14.27 23.58,-13.03 27.38,-11.25 28.82,-10.56 29.64,-9.802 29.64,-9.003 L 29.64,-5.11 C 29.64,-2.988 23.96,-1.161 15.79,-0.326' id='path1268'/>
|
||||
</g>
|
||||
</g>
|
||||
</g></g>
|
||||
</svg>
|
||||
transform="matrix(1.25,0,0,-1.25,-30.25,102.6125)"><g
|
||||
id="g1254"><g
|
||||
id="g1256"><g
|
||||
transform="translate(68.92,54.16)"
|
||||
id="g1262"><path
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 c -0.046,0.281 -0.218,0.557 -0.476,0.82 -2.079,-2.112 -10.444,-2.462 -20.444,-2.462 -10,0 -18.36,0.35 -20.45,2.462 C -41.62,0.557 -41.78,0.281 -41.84,0 l -0.02,0 0,-0.152 c 0,-0.018 0,-0.036 0,-0.054 0,-0.041 0.01,-0.077 0.01,-0.116 l 1.63,-33.358 0.01,0 c 0.14,-2.61 3.14,-6.56 19.29,-6.56 16.154,0 19.15,3.95 19.291,6.56 l 0.012,0 1.63,33.358 c 0.003,0.039 0.015,0.075 0.015,0.116 0,0.018 -0.012,0.036 -0.012,0.054 L 0.028,0 0,0 Z"
|
||||
id="path1264"
|
||||
inkscape:connector-curvature="0" /></g><g
|
||||
transform="translate(42.16,71.94)"
|
||||
id="g1266"><path
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
d="m 0,0 -0.657,-0.041 0,3.262 c 0,1.426 0.199,2.594 0.434,2.594 l 3.032,0 5.193,0 3.028,0 c 0.24,0 0.43,-1.168 0.43,-2.594 l 0,-3.209 C 9.658,0.105 7.779,0.164 5.835,0.164 3.82,0.164 1.863,0.105 0,0 m 15.79,-0.326 0,7.875 c 0,1.422 -1.16,2.601 -2.6,2.601 l -15.582,0 c -1.421,0 -2.59,-1.179 -2.59,-2.601 l 0,-7.965 C -12.69,-1.29 -17.96,-3.06 -17.96,-5.11 l 0,-3.893 c 0,-0.799 0.81,-1.557 2.26,-2.247 3.8,-1.78 12.014,-3.02 21.535,-3.02 9.525,0 17.745,1.24 21.545,3.02 1.44,0.69 2.26,1.448 2.26,2.247 l 0,3.893 c 0,2.122 -5.68,3.949 -13.85,4.784"
|
||||
id="path1268"
|
||||
inkscape:connector-curvature="0" /></g></g></g></g></svg>
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.1 KiB |
54
xwords4/android/img_src/rematch.svg
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 64 64"
|
||||
xml:space="preserve"
|
||||
id="svg3414"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="noun_945427_cc.svg"
|
||||
width="64"
|
||||
height="64"><metadata
|
||||
id="metadata3432"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs3430" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview3428"
|
||||
showgrid="false"
|
||||
inkscape:zoom="10.3375"
|
||||
inkscape:cx="17.006046"
|
||||
inkscape:cy="32.261185"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3414" /><path
|
||||
d="m 34.47353,49.135982 0.666,1.885 c 7.458,-2.635 13.285,-8.723 15.587,-16.285 l -1.914,-0.582 c -0.799,2.625 -2.061,5.057 -3.69,7.198 l -6.809,-0.851 -1.746,-2.037 -1.518,1.301 1.662,1.939 -1.818,7.27 c -0.14,0.052 -0.278,0.112 -0.42,0.162 z m 4.088,-6.588 5.015,0.627 c -1.82,1.939 -3.972,3.564 -6.367,4.782 l 1.352,-5.409 z"
|
||||
id="path3416"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 26.80653,50.444982 c -2.846,0 -5.57,-0.523 -8.088,-1.472 l -1.817,-7.27 5.364,-6.258 9.081,0 0.56,0.653 1.52,-1.301 -0.504,-0.588 2.719,-9.063 7.283,-3.642 6.47,1.617 c 0.266,1.402 0.412,2.846 0.412,4.324 0,0.644 -0.026,1.289 -0.078,1.917 l 1.992,0.166 c 0.058,-0.684 0.086,-1.385 0.086,-2.083 0,-13.785 -11.215,-25.0000002 -25,-25.0000002 -13.785,0 -25.0000003,11.2150002 -25.0000003,25.0000002 0,13.785 11.2150003,25 25.0000003,25 0.698,0 1.399,-0.028 2.083,-0.086 l -0.166,-1.992 c -0.628,0.052 -1.273,0.078 -1.917,0.078 z m 4.256,-17 -8.512,0 -2.586,-8.619 6.842,-5.131 6.842,5.131 -2.586,8.619 z m 13.074,-13.698 1.86,-4.96 c 1.24,1.874 2.212,3.939 2.864,6.141 l -4.724,-1.181 z m 0.444,-6.882 -2.573,6.861 -7.096,3.548 -7.105,-5.329 0,-8.9650002 5.424,-3.616 c 4.505,1.313 8.445,3.965 11.35,7.5010002 z m -14.039,-8.1120002 -3.735,2.49 -3.735,-2.49 c 1.216,-0.2 2.463,-0.308 3.735,-0.308 1.272,0 2.519,0.108 3.735,0.308 z m -10.159,0.611 5.424,3.616 0,8.9650002 -7.105,5.329 -7.096,-3.548 -2.5730003,-6.861 C 11.93753,9.3289818 15.87753,6.6769818 20.38253,5.3639818 Z M 9.4765297,19.746982 l -4.724,1.181 c 0.652,-2.202 1.623,-4.267 2.864,-6.141 l 1.86,4.96 z m -5.257,3.375 6.4700003,-1.617 7.283,3.642 2.719,9.064 -5.392,6.291 -6.7980003,0.85 c -2.943,-3.867 -4.695,-8.685 -4.695,-13.907 0,-1.478 0.146,-2.922 0.413,-4.323 z m 5.8270003,20.051 5.005,-0.625 1.35,5.399 c -2.388,-1.217 -4.536,-2.838 -6.355,-4.774 z"
|
||||
id="path3418"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 60.47553,40.046982 -1.832,0.801 c 0.771,1.768 1.163,3.652 1.163,5.597 0,7.72 -6.28,14 -14,14 -3.721,0 -7.176,-1.447 -9.775,-4 l 2.775,0 0,-2 -5,0 c -0.553,0 -1,0.447 -1,1 l 0,5 2,0 0,-2.41 c 2.951,2.815 6.828,4.41 11,4.41 8.822,0 16,-7.178 16,-16 0,-2.223 -0.447,-4.375 -1.331,-6.398 z"
|
||||
id="path3420"
|
||||
inkscape:connector-curvature="0" /><path
|
||||
d="m 31.80653,46.444982 c 0,-7.72 6.28,-14 14,-14 3.721,0 7.176,1.447 9.775,4 l -2.775,0 0,2 5,0 c 0.553,0 1,-0.447 1,-1 l 0,-5 -2,0 0,2.41 c -2.951,-2.815 -6.828,-4.41 -11,-4.41 -8.822,0 -16,7.178 -16,16 0,2.222 0.447,4.373 1.328,6.394 l 1.834,-0.801 c -0.771,-1.766 -1.162,-3.648 -1.162,-5.593 z"
|
||||
id="path3422"
|
||||
inkscape:connector-curvature="0" /></svg>
|
After Width: | Height: | Size: 4 KiB |
50
xwords4/android/img_src/trade.svg
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 53.834 53.583248"
|
||||
enable-background="new 0 0 100 100"
|
||||
xml:space="preserve"
|
||||
id="svg3590"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="trade.svg"
|
||||
width="53.834"
|
||||
height="53.583248"><metadata
|
||||
id="metadata3602"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs3600" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview3598"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="5.3400704"
|
||||
inkscape:cx="46.514458"
|
||||
inkscape:cy="14.292"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3590" /><path
|
||||
d="m 4.5,22.53725 0,-8.546 40.651,0 -5.652,5.652 c -0.879,0.879 -0.879,2.304 0,3.182 0.439,0.439 1.015,0.659 1.591,0.659 0.576,0 1.151,-0.22 1.591,-0.659 l 9.494,-9.493 c 0.014,-0.014 0.023,-0.031 0.037,-0.044 0.087,-0.092 0.17,-0.189 0.241,-0.295 0.024,-0.036 0.04,-0.077 0.063,-0.115 0.052,-0.088 0.104,-0.176 0.145,-0.271 0.02,-0.047 0.028,-0.097 0.045,-0.144 0.031,-0.091 0.064,-0.181 0.084,-0.276 0.029,-0.145 0.045,-0.294 0.045,-0.445 0,-0.151 -0.016,-0.3 -0.045,-0.445 -0.02,-0.097 -0.054,-0.187 -0.085,-0.278 -0.016,-0.047 -0.024,-0.096 -0.044,-0.142 -0.041,-0.1 -0.097,-0.192 -0.151,-0.284 -0.02,-0.034 -0.033,-0.07 -0.056,-0.103 -0.082,-0.123 -0.176,-0.237 -0.279,-0.34 l -9.491,-9.491 c -0.879,-0.879 -2.303,-0.879 -3.182,0 -0.879,0.878 -0.879,2.303 0,3.182 l 5.651,5.651 -42.903,0 c -1.242,0 -2.25,1.007 -2.25,2.25 l 0,10.796 c 0,1.243 1.008,2.25 2.25,2.25 1.242,0 2.25,-1.008 2.25,-2.251 z m 47.084,6.258 c -1.242,0 -2.25,1.007 -2.25,2.25 l 0,8.546 -40.651,0 5.652,-5.652 c 0.879,-0.879 0.879,-2.304 0,-3.182 -0.879,-0.879 -2.303,-0.878 -3.182,0 l -9.494,9.493 c -0.014,0.013 -0.022,0.03 -0.035,0.043 -0.088,0.092 -0.172,0.189 -0.243,0.296 -0.023,0.035 -0.038,0.074 -0.06,0.11 -0.054,0.09 -0.107,0.179 -0.147,0.276 -0.02,0.046 -0.028,0.095 -0.044,0.141 -0.031,0.092 -0.065,0.183 -0.085,0.279 -0.029,0.146 -0.045,0.294 -0.045,0.445 0,0.151 0.016,0.299 0.045,0.445 0.02,0.097 0.054,0.189 0.085,0.281 0.016,0.046 0.025,0.094 0.044,0.139 0.042,0.102 0.098,0.195 0.153,0.289 0.02,0.032 0.033,0.067 0.054,0.098 0.082,0.123 0.175,0.237 0.279,0.341 l 9.491,9.491 c 0.439,0.439 1.015,0.659 1.591,0.659 0.576,0 1.151,-0.22 1.591,-0.659 0.879,-0.878 0.879,-2.303 0,-3.182 l -5.651,-5.651 42.902,0 c 1.242,0 2.25,-1.007 2.25,-2.25 l 0,-10.796 c 0,-1.243 -1.008,-2.25 -2.25,-2.25 z"
|
||||
id="path3592"
|
||||
inkscape:connector-curvature="0" /></svg>
|
After Width: | Height: | Size: 3.4 KiB |
59
xwords4/android/img_src/untrade.svg
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 70 70"
|
||||
enable-background="new 0 0 100 100"
|
||||
xml:space="preserve"
|
||||
id="svg3590"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="untrade.svg"
|
||||
width="70"
|
||||
height="70"><metadata
|
||||
id="metadata3602"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs3600" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1163"
|
||||
id="namedview3598"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="5.3400704"
|
||||
inkscape:cx="-12.866321"
|
||||
inkscape:cy="22.385961"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3590" /><path
|
||||
d="m 12.159535,30.485512 0,-8.546 40.651,0 -5.652,5.652 c -0.879,0.879 -0.879,2.304 0,3.182 0.439,0.439 1.015,0.659 1.591,0.659 0.576,0 1.151,-0.22 1.591,-0.659 l 9.494,-9.493 c 0.014,-0.014 0.023,-0.031 0.037,-0.044 0.087,-0.092 0.17,-0.189 0.241,-0.295 0.024,-0.036 0.04,-0.077 0.063,-0.115 0.052,-0.088 0.104,-0.176 0.145,-0.271 0.02,-0.047 0.028,-0.097 0.045,-0.144 0.031,-0.091 0.064,-0.181 0.084,-0.276 0.029,-0.145 0.045,-0.294 0.045,-0.445 0,-0.151 -0.016,-0.3 -0.045,-0.445 -0.02,-0.097 -0.054,-0.187 -0.085,-0.278 -0.016,-0.047 -0.024,-0.096 -0.044,-0.142 -0.041,-0.1 -0.097,-0.192 -0.151,-0.284 -0.02,-0.034 -0.033,-0.07 -0.056,-0.103 -0.082,-0.123 -0.176,-0.237 -0.279,-0.34 l -9.491,-9.4909998 c -0.879,-0.879 -2.303,-0.879 -3.182,0 -0.879,0.878 -0.879,2.3029998 0,3.1819998 l 5.651,5.651 -42.9029996,0 c -1.242,0 -2.25,1.007 -2.25,2.25 l 0,10.796 c 0,1.243 1.008,2.25 2.25,2.25 1.2419996,0 2.2499996,-1.008 2.2499996,-2.251 z m 47.084,6.258 c -1.242,0 -2.25,1.007 -2.25,2.25 l 0,8.546 -40.651,0 5.652,-5.652 c 0.879,-0.879 0.879,-2.304 0,-3.182 -0.879,-0.879 -2.303,-0.878 -3.182,0 l -9.4939996,9.493 c -0.014,0.013 -0.022,0.03 -0.035,0.043 -0.088,0.092 -0.172,0.189 -0.243,0.296 -0.023,0.035 -0.038,0.074 -0.06,0.11 -0.054,0.09 -0.107,0.179 -0.147,0.276 -0.02,0.046 -0.028,0.095 -0.044,0.141 -0.031,0.092 -0.065,0.183 -0.085,0.279 -0.029,0.146 -0.045,0.294 -0.045,0.445 0,0.151 0.016,0.299 0.045,0.445 0.02,0.097 0.054,0.189 0.085,0.281 0.016,0.046 0.025,0.094 0.044,0.139 0.042,0.102 0.098,0.195 0.153,0.289 0.02,0.032 0.033,0.067 0.054,0.098 0.082,0.123 0.175,0.237 0.279,0.341 l 9.4909996,9.491 c 0.439,0.439 1.015,0.659 1.591,0.659 0.576,0 1.151,-0.22 1.591,-0.659 0.879,-0.878 0.879,-2.303 0,-3.182 l -5.651,-5.651 42.902,0 c 1.242,0 2.25,-1.007 2.25,-2.25 l 0,-10.796 c 0,-1.243 -1.008,-2.25 -2.25,-2.25 z"
|
||||
id="path3592"
|
||||
inkscape:connector-curvature="0" /><circle
|
||||
style="fill:none;stroke:#000000;stroke-width:3.17759514;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4159"
|
||||
cx="35"
|
||||
cy="35"
|
||||
r="33.411201" /><path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.00030446;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 13.380987,12.086444 44.81032,48.282085"
|
||||
id="path4171"
|
||||
inkscape:connector-curvature="0" /></svg>
|
After Width: | Height: | Size: 3.9 KiB |
|
@ -754,7 +754,7 @@ android_debugf( const char* format, ... )
|
|||
}
|
||||
|
||||
(void)__android_log_write( ANDROID_LOG_DEBUG,
|
||||
# if defined VARIANT_xw4
|
||||
# if defined VARIANT_xw4 || defined VARIANT_xw4fdroid
|
||||
"xw4"
|
||||
# elif defined VARIANT_xw4d
|
||||
"x4bg"
|
||||
|
|
|
@ -95,19 +95,8 @@ and_xport_send( const XP_U8* buf, XP_U16 len, const XP_UCHAR* msgNo,
|
|||
}
|
||||
|
||||
static void
|
||||
and_xport_relayStatus( void* closure, CommsRelayState newState )
|
||||
and_xport_relayStatus( void* XP_UNUSED(closure), CommsRelayState XP_UNUSED(newState) )
|
||||
{
|
||||
AndTransportProcs* aprocs = (AndTransportProcs*)closure;
|
||||
if ( NULL != aprocs->jxport ) {
|
||||
JNIEnv* env = ENVFORME( aprocs->ti );
|
||||
const char* sig = "(L" PKG_PATH("jni/TransportProcs$CommsRelayState") ";)V";
|
||||
jmethodID mid = getMethodID( env, aprocs->jxport, "relayStatus", sig );
|
||||
|
||||
jobject jenum = intToJEnum( env, newState,
|
||||
PKG_PATH("jni/TransportProcs$CommsRelayState") );
|
||||
(*env)->CallVoidMethod( env, aprocs->jxport, mid, jenum );
|
||||
deleteLocalRef( env, jenum );
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
@ -648,7 +648,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getUUID
|
|||
{
|
||||
jstring jstr =
|
||||
#ifdef XWFEATURE_BLUETOOTH
|
||||
# if defined VARIANT_xw4
|
||||
# if defined VARIANT_xw4 || defined VARIANT_xw4fdroid
|
||||
(*env)->NewStringUTF( env, XW_BT_UUID )
|
||||
# elif defined VARIANT_xw4d
|
||||
(*env)->NewStringUTF( env, XW_BT_UUID_DBG )
|
||||
|
@ -1673,8 +1673,6 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1receiveMessage
|
|||
{
|
||||
jboolean result;
|
||||
XWJNI_START_GLOBALS();
|
||||
XP_ASSERT( state->game.comms );
|
||||
XP_ASSERT( state->game.server );
|
||||
|
||||
XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, globals->vtMgr,
|
||||
jstream );
|
||||
|
@ -1686,31 +1684,7 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1receiveMessage
|
|||
addrp = &addr;
|
||||
}
|
||||
|
||||
/* pthread_mutex_lock( &state->msgMutex ); */
|
||||
|
||||
ServerCtxt* server = state->game.server;
|
||||
CommsMsgState commsState;
|
||||
result = comms_checkIncomingStream( state->game.comms, stream, addrp,
|
||||
&commsState );
|
||||
if ( result ) {
|
||||
(void)server_do( server );
|
||||
|
||||
result = server_receiveMessage( server, stream );
|
||||
}
|
||||
comms_msgProcessed( state->game.comms, &commsState, !result );
|
||||
|
||||
/* pthread_mutex_unlock( &state->msgMutex ); */
|
||||
|
||||
if ( result ) {
|
||||
/* in case MORE work's pending. Multiple calls are required in at
|
||||
least one case, where I'm a host handling client registration *AND*
|
||||
I'm a robot. Only one server_do and I'll never make that first
|
||||
robot move. That's because comms can't detect a duplicate initial
|
||||
packet (in validateInitialMessage()). */
|
||||
for ( int ii = 0; ii < 5; ++ii ) {
|
||||
(void)server_do( server );
|
||||
}
|
||||
}
|
||||
result = game_receiveMessage( &state->game, stream, addrp );
|
||||
|
||||
stream_destroy( stream );
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="MissingTranslation"
|
||||
>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="button_new_game">Spiel hinzufügen</string>
|
||||
<string name="button_new_group">Gruppe hinzufügen</string>
|
||||
|
||||
|
@ -11,8 +9,8 @@
|
|||
|
||||
<string name="missing_player">(noch nicht hier …)</string>
|
||||
|
||||
<string name="summary_relay_conf_fmt">Konfiguriert für Raum „%1$s“</string>
|
||||
<string name="summary_relay_wait_fmt">Auf Spieler in Raum „%1$s“ warten</string>
|
||||
<string name="summary_relay_conf_fmt">Konfiguriert für Raum „%1$s”</string>
|
||||
<string name="summary_relay_wait_fmt">Auf Spieler in Raum „%1$s” warten</string>
|
||||
<string name="summary_relay_conn_fmt">Spiel läuft in Raum „%1$s“</string>
|
||||
<string name="summary_relay_gameover_fmt">Spiel vorbei in Raum „%1$s“</string>
|
||||
|
||||
|
|
|
@ -2285,8 +2285,12 @@ seulement des informations sur les jetons. Il n\'y a pas de mots à parcourir.</
|
|||
vous voulez intégrer dans cette partie. Utilisez le bouton \"%2$s\" si vous ne
|
||||
voyez pas un périphérique que vous attendez.</string>-->
|
||||
<plurals name="invite_bt_desc_fmt">
|
||||
<item quantity="one">Sélectionnez l\'appareil que vous voulez intégrer dans cette partie. Utilisez le bouton \"%2$s\" si vous ne voyez pas un appareil que vous attendez.</item>
|
||||
<item quantity="other">Sélectionnez jusqu\'à %1$d appareils que vous voulez intégrer dans cette partie. Utilisez le bouton \"%2$s\" si vous ne voyez pas un appareil que vous attendez.</item>
|
||||
<item quantity="one">Sélectionnez l\'appareil que vous voulez intégrer dans cette partie.
|
||||
\n
|
||||
\nUtilisez le bouton \"%2$s\" si vous ne voyez pas un appareil que vous attendez.</item>
|
||||
<item quantity="other">Sélectionnez jusqu\'à %1$d appareils que vous voulez intégrer dans cette partie.
|
||||
\n
|
||||
\nUtilisez le bouton \"%2$s\" si vous ne voyez pas un appareil que vous attendez.</item>
|
||||
</plurals>
|
||||
<!-- -->
|
||||
<!--<string name="bt_resend_fmt">Bluetooth send to %1$s failed; retry %3$d in
|
||||
|
@ -2354,7 +2358,7 @@ le bouton \"Importer le contact\" pour ajouter des gens que vous voulez
|
|||
inviter, ou le bouton + pour saisir des numéros directement.</string>
|
||||
<!-- -->
|
||||
<!--<string name="get_sms_number">Enter phone number:</string>-->
|
||||
<string name="get_sms_number">Saisir un numéro de téléphone :</string>
|
||||
<string name="get_sms_number">Numéro de téléphone d\'appareil :</string>
|
||||
<!-- -->
|
||||
<!--<string name="confirm_clear">Are you sure you want to delete the
|
||||
checked phone number[s]?</string>-->
|
||||
|
@ -2498,9 +2502,9 @@ vous faire payer pour chaque message !\n\nLes parties par SMS devraient-elles
|
|||
désactivées, donc aucun coup ne sera envoyé pour cette partie. (Si vous voulez
|
||||
activer les parties par SMS, allez dans Paramètres->Paramètres des parties en
|
||||
réseau.)</string>-->
|
||||
<string name="warn_sms_disabled">Les parties par SMS sont actuellement
|
||||
désactivées, donc aucun coup ne sera envoyé pour cette partie. (Si vous voulez
|
||||
activer les parties par SMS, allez dans Paramètres->Paramètres des parties en réseau.)</string>
|
||||
<string name="warn_sms_disabled">Les parties par SMS sont actuellement désactivées. Aucun coup ne sera envoyé par SMS.
|
||||
\n
|
||||
\nVous pouvez activer les parties par SMS maintenant, ou plus tard.</string>
|
||||
|
||||
<!-- -->
|
||||
<!--<string name="gamel_menu_checkupdates">Check for updates</string>-->
|
||||
|
@ -2597,7 +2601,7 @@ mots ?</string>
|
|||
<!--<string name="list_group_delete">Delete group</string>-->
|
||||
<string name="list_group_delete">Supprimer le groupe</string>
|
||||
<!--<string name="list_group_rename">Rename</string>-->
|
||||
<string name="list_group_rename">Renommer</string>
|
||||
<string name="list_group_rename">Renommer le groupe</string>
|
||||
<!--<string name="list_group_default">Put new games here</string>-->
|
||||
<string name="list_group_default">Mettre les nouvelles parties ici</string>
|
||||
<!--<string name="list_group_moveup">Move up</string>-->
|
||||
|
@ -2675,8 +2679,8 @@ effacées.)</string>-->
|
|||
then act on selected games, e.g. to delete them, using the menu or
|
||||
\"Actionbar.\"</string>-->
|
||||
<string name="not_again_newselect">Toucher une partie l\'ouvre.
|
||||
|
||||
Vous pouvez aussi toucher les icônes sur la gauche pour sélectionner ou désélectionner des parties, par exemples pour les supprimer, en utilisant le menu ou la \"barre d\'action\".</string>
|
||||
\n
|
||||
\nVous pouvez aussi toucher les icônes sur la gauche pour sélectionner ou désélectionner des parties, par exemples pour les supprimer, en utilisant le menu ou la \"barre d\'action\".</string>
|
||||
|
||||
<!--<string name="not_again_backclears">The back button clears any
|
||||
selection instead of exiting. Hit it again to exit the
|
||||
|
@ -2932,7 +2936,7 @@ autorisés.\n\nCochez la case \"Montrer les listes téléchargeables\" en haut
|
|||
pour voir ce qui est disponible.</string>
|
||||
|
||||
<!--<string name="force_tablet_title">Force tablet layout</string>-->
|
||||
<string name="force_tablet_title">Forcer la mise en page de tablette</string>
|
||||
<string name="force_tablet_title">Utiliser la mise en page de tablette (côte à côte) ?</string>
|
||||
<!--<string name="force_tablet_summary">Even if my screen is too small</string>-->
|
||||
<string name="force_tablet_summary">Même si mon écran est trop petit</string>
|
||||
|
||||
|
@ -3046,10 +3050,9 @@ vous doit faire une mise à jour avant de pouvoir continuer.</string>
|
|||
device. No moves will be sent via Bluetooth.\n\nYou can enable
|
||||
Bluetooth now, or later.
|
||||
</string>-->
|
||||
<string name="warn_bt_disabled">"Le Bluetooth est actuellement éteint sur cet appareil. Aucun coup ne sera envoyé par Bluetooth.
|
||||
|
||||
Vous pouvez activer le Bluetooth maintenant, ou plus tard.
|
||||
"</string>
|
||||
<string name="warn_bt_disabled">Le Bluetooth est actuellement éteint sur cet appareil. Aucun coup ne sera envoyé par Bluetooth.
|
||||
\n
|
||||
\nVous pouvez activer le Bluetooth maintenant, ou plus tard.</string>
|
||||
<!--XLATE-ME-->
|
||||
<!--<string name="button_enable_sms">Enable SMS</string>-->
|
||||
<string name="button_enable_sms">Activer les SMS</string>
|
||||
|
@ -3131,11 +3134,9 @@ voir et rejoindre</string>
|
|||
visible.\n\n(If you later want to unhide them go to the Appearance
|
||||
section of App settings).
|
||||
</string>-->
|
||||
<string name="not_again_hidenewgamebuttons">Ces deux boutons font la même
|
||||
chose que les deux premiers items dans la barre d\'action de cette fenêtre (ou
|
||||
du menu). Si vous voulez, vous pouvez cacher les boutons pour rendre plus de
|
||||
parties visibles.\n\n(Si vous voulez les faire réapparaître, allez dans la
|
||||
section Apparence des paramètres de l\'application.)</string>
|
||||
<string name="not_again_hidenewgamebuttons">Les deux boutons au bas de cet écran et les deux premiers items dans sa barre d\'action (ou son menu) font la même chose. Si vous voulez, vous pouvez cacher les boutons pour rendre plus de parties visibles.
|
||||
\n
|
||||
\n(Si vous voulez faire réapparaître les boutons, allez dans la section Apparence des paramètres de l\'application.)</string>
|
||||
<!--XLATE-ME-->
|
||||
<!--<string name="waiting_title">Waiting for players</string>-->
|
||||
<string name="waiting_title">En attente de joueurs</string>
|
||||
|
@ -3472,11 +3473,11 @@ pour la langue</string>
|
|||
<string name="fragment_button">Double panneau</string>
|
||||
|
||||
<string name="relay_behavior">Paramètres des parties par relai</string>
|
||||
<string name="relay_behavior_summary">"Paramètres des parties connectées à Internet "</string>
|
||||
<string name="disable_relay">"Désactiver le jeu par relai "</string>
|
||||
<string name="relay_behavior_summary">Paramètres des parties connectées à Internet</string>
|
||||
<string name="disable_relay">Désactiver le jeu par relai</string>
|
||||
<string name="disable_relay_summary">Désactiver toute communication par Internet</string>
|
||||
|
||||
<string name="warn_relay_disabled">"Le jeu par relai est pour l\'instant désactivé sur cet appareil. Aucun coup ne sera envoyé ou reçu par le relai. "</string>
|
||||
<string name="warn_relay_disabled">Le jeu par relai est pour l\'instant désactivé sur cet appareil. Aucun coup ne sera envoyé ou reçu par le relai.</string>
|
||||
|
||||
<string name="warn_relay_later">Vous pouvez activer le jeu par relai maintenant, ou plus tard.</string>
|
||||
<string name="warn_relay_remove">Vous pouvez activer le jeu par relai maintenant, ou le retirer de cette partie.</string>
|
||||
|
@ -3516,7 +3517,7 @@ Merci de me faire savoir si vous aimez cette fonctionnalité, de signaler les pl
|
|||
|
||||
<string name="post_dualpane_title">Redémarrer CrossWords</string>
|
||||
<string name="dualpane_restart">Fermeture de l\'application…</string>
|
||||
<string name="after_restart">Ce changement ne s\'appliquera pas tant que vous n\'aurez pas relancé Crosswords.</string>
|
||||
<string name="after_restart">Ce changement s\'appliquera après que vous ayez relancé CrossWords.</string>
|
||||
<string name="invite_choice_p2p">Wifi Direct</string>
|
||||
<string name="bt_pair_settings">En appairer d\'autres</string>
|
||||
|
||||
|
@ -3527,28 +3528,23 @@ Merci de me faire savoir si vous aimez cette fonctionnalité, de signaler les pl
|
|||
<item quantity="other">Sélectionnez les noms des appareils WiFi Direct que vous souhaitez inviter à votre nouvelle partie, puis touchez \"%2$s\".</item>
|
||||
</plurals>
|
||||
|
||||
<string name="invite_p2p_desc_extra">Seuls les appareils actuellement disponibles sont affichés. Si un appareil à proximité ne s\'affiche pas, assurez-vous que le WiFi est allumé, que Crosswords est installé, et que le WiFi Direct est activé.</string>
|
||||
<string name="invite_p2p_desc_extra">Seuls les appareils actuellement disponibles sont affichés. Si un appareil à proximité ne s\'affiche pas, assurez-vous que le WiFi est allumé, que CrossWords est installé, et que le WiFi Direct est activé.</string>
|
||||
|
||||
<string name="empty_p2p_inviter">Il n\'y a actuellement pas d\'appareils accessibles par WiFi Direct qui ont Crosswords installé.</string>
|
||||
<string name="empty_p2p_inviter">Il n\'y a actuellement pas d\'appareils accessibles par WiFi Direct qui ont CrossWords installé.</string>
|
||||
|
||||
<string name="not_again_comms_p2p">Utiliser le WiFi Direct pour jouer contre un appareil faisant du WiFi Direct, sur lequel Crosswords est installé.</string>
|
||||
<string name="not_again_comms_p2p">Utiliser le WiFi Direct pour jouer contre un appareil faisant du WiFi Direct, sur lequel CrossWords est installé.</string>
|
||||
|
||||
<string name="missing_perms">Cette partie est configurée pour communiquer par SMS mais Crosswords n\'a pas la permission de le faire. Vous pouvez toujours ouvrir cette partie, mais elle pourrait ne pas être en capacité d\'envoyer ou recevoir des coups.
|
||||
|
||||
Vous pouvez la ré-ouvrir pour que la permission soit redemandée. Ou vous pouvez retirer le réglage de communication par SMS.</string>
|
||||
|
||||
<string name="download_rationale">"Crosswords a besoin d\'accéder à un stockage temporaire pour conserver ce que vous êtes sur le point de télécharger.
|
||||
"</string>
|
||||
<string name="download_rationale">Crosswords a besoin d\'accéder à un stockage temporaire pour conserver ce que vous êtes sur le point de télécharger.</string>
|
||||
|
||||
<string name="sms_invite_rationale">"
|
||||
Crosswords a besoin de la permission d\'envoyer une invitation par SMS.
|
||||
"</string>
|
||||
<string name="sms_invite_rationale">Crosswords a besoin de la permission d\'envoyer une invitation par SMS.</string>
|
||||
|
||||
<string name="toast_no_permission">Permission non accordée</string>
|
||||
|
||||
<string name="contacts_rationale">"
|
||||
Crosswords veut accéder à vos contacts pour pouvoir mettre un nom sur les numéros de téléphones qui vous envoient des invitations par SMS. Vous pourrez toujours recevoir des invitations si vous n\'accordez pas cette permission, mais seul le numéro de téléphone de l\'expéditeur s\'affichera.
|
||||
"</string>
|
||||
<string name="contacts_rationale">Crosswords veut accéder à vos contacts pour pouvoir mettre un nom sur les numéros de téléphones qui vous envoient des invitations par SMS. Vous pourrez toujours recevoir des invitations si vous n\'accordez pas cette permission, mais seul le numéro de téléphone de l\'expéditeur s\'affichera.</string>
|
||||
|
||||
<string name="button_ask_again">Redemander</string>
|
||||
<string name="contact_not_found">Pas dans les contacts</string>
|
||||
|
@ -3556,11 +3552,9 @@ Vous pouvez la ré-ouvrir pour que la permission soit redemandée. Ou vous pouve
|
|||
<string name="button_skip">Sauter</string>
|
||||
|
||||
<string name="perms_rationale_title">Permissions Android</string>
|
||||
<string name="phone_state_rationale">"
|
||||
Certains téléphones peuvent échanger des SMS \"de données\". Crosswords souhaite vous offrir cette possibilité mais a d\'abord besoin d\'interroger votre appareil sur lui-même (pour savoir si c\'est un téléphone et, le cas échéant, de quel type).
|
||||
|
||||
Si votre appareil ne peut pas envoyer de SMS de données (par ex. parce que ce n\'est pas un téléphone) ou que vous ne souhaiterez jamais jouer par SMS (par ex. parce que vous payez pour chaque message), il est raisonnable que refuser cette permission de manière permanente.
|
||||
"</string>
|
||||
<string name="phone_state_rationale">Certains téléphones peuvent échanger des SMS \"de données\". CrossWords souhaite vous offrir cette possibilité mais a d\'abord besoin d\'interroger votre appareil sur lui-même (pour savoir si c\'est un téléphone et, le cas échéant, de quel type).
|
||||
\n
|
||||
\nSi votre appareil ne peut pas envoyer de SMS de données (par ex. parce que ce n\'est pas un téléphone) ou que vous ne souhaiterez jamais jouer par SMS (par ex. parce que vous payez pour chaque message), il est raisonnable que refuser cette permission de manière permanente.</string>
|
||||
|
||||
<string name="title_enable_p2p">Activer Wi-Fi Direct</string>
|
||||
<string name="summary_enable_p2p">Expérimental, utilise beaucoup de batterie</string>
|
||||
|
@ -3585,4 +3579,25 @@ Si votre appareil ne peut pas envoyer de SMS de données (par ex. parce que ce n
|
|||
<item quantity="other">Êtes-vous sûr de vouloir effacer les %1$d enregistrements RelayID sélectionnés ?</item>
|
||||
</plurals>
|
||||
|
||||
</resources>
|
||||
<string name="not_again_archive">L\'archivage utilise un groupe spécial nommé \"Archive\" pour enregistrer les parties finies que vous souhaitez garder. Et, vu qu\'effacer une archive entière est facile, l\'archivage est aussi une bonne manière de sélectionner des parties à effacer – si c\'est ce que vous préférez faire.
|
||||
\n
|
||||
\n(Effacer le groupe Archive ne cause pas de problème car il sera recréé de zéro si nécessaire.)</string>
|
||||
|
||||
<string name="button_archive">Archive</string>
|
||||
<string name="group_name_archive">Archive</string>
|
||||
|
||||
<string name="duplicate_group_name_fmt">Le groupe \"%1$s\" existe déjà.</string>
|
||||
|
||||
<string name="gamel_menu_writegit">Copier l\'info git dans le presse-papier</string>
|
||||
<string name="dicts_storage_rationale">CrossWords peut enregistrer et accéder à des listes de mots dans l\'espace de Téléchargements de votre appareil mais a besoin de la permission d\'y accéder à cet endroit.
|
||||
\n
|
||||
\nVous pouvez refuser cette permission sans crainte si vous ne téléchargerez jamais de listes de mots excepté depuis CrossWords et n\'en avez jamais enregistrées là.</string>
|
||||
|
||||
<string name="phone_lookup_rationale">Pour rejouer une revanche qui utilise les SMS, CrossWords a besoin de la permission d\'accéder à votre numéro de téléphone.</string>
|
||||
|
||||
<string name="phone_lookup_rationale_drop">Jouer cette revanche par SMS uniquement n\'est pas possible sans cette permission.</string>
|
||||
<string name="phone_lookup_rationale_others">Sans cette permission, la revanche continuera mais créera une partie qui ne peut pas être jouée par SMS.</string>
|
||||
|
||||
<string name="move_dict_rationale">Enregistrer une liste de mots dans l\'espace de Téléchargements nécessite la permission.</string>
|
||||
|
||||
</resources>
|
||||
|
|
645
xwords4/android/res_src/values-nb-rNO/strings.xml
Normal file
|
@ -0,0 +1,645 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources><string name="button_new_group">Legg til gruppe</string>
|
||||
|
||||
<string name="str_game_name_fmt">%1$s (%2$s)</string>
|
||||
|
||||
<string name="robot_name_fmt">%1$s (maskin)</string>
|
||||
|
||||
<string name="missing_player">(ikke her enda…)</string>
|
||||
|
||||
<string name="summary_relay_conf_fmt">Satt opp for rom \"%1$s\"</string>
|
||||
<string name="summary_relay_wait_fmt">Venter for spiller i rom \"%1$s\"</string>
|
||||
<string name="summary_relay_conn_fmt">Spill underveis i rom \"%1$s\"</string>
|
||||
<string name="summary_relay_gameover_fmt">Spill over i rom \"%1$s\"</string>
|
||||
|
||||
<string name="summary_invites_out">Spillere invitert</string>
|
||||
<string name="summary_invites_out_fmt">Spillere invitert til rom \"%1$s\"</string>
|
||||
|
||||
<string name="gameOver">Spill over</string>
|
||||
<plurals name="moves_fmt">
|
||||
<item quantity="one">%1$d trekk gjort</item>
|
||||
<item quantity="other">%1$d trekk gjort</item>
|
||||
</plurals>
|
||||
|
||||
<string name="button_delete">Slett</string>
|
||||
<string name="button_reset">Tilbakestill</string>
|
||||
|
||||
<string name="gamel_menu_dicts">Ordlister…</string>
|
||||
|
||||
<string name="menu_prefs">Programinnstillinger</string>
|
||||
|
||||
<string name="gamel_menu_checkmoves">Sjekk trekk</string>
|
||||
|
||||
<string name="board_menu_file_about">Om</string>
|
||||
|
||||
<string name="list_item_config">Spillinnstillinger…</string>
|
||||
<string name="list_item_rename">Gi nytt navn…</string>
|
||||
<string name="list_item_move">Flytt til gruppe…</string>
|
||||
|
||||
<string name="list_item_delete">Slett spill</string>
|
||||
<string name="list_item_reset">Tilbakestill</string>
|
||||
<string name="list_item_new_from">Nytt fra</string>
|
||||
<string name="list_item_copy">Kopier</string>
|
||||
<string name="game_rename_title">Gi nytt navn</string>
|
||||
<string name="rename_label">Endre navnet på dette spillet til:</string>
|
||||
<string name="rename_label_caveat">Endre navnet på dette spillet (kun på denne enheten) til:</string>
|
||||
|
||||
<string name="title_dicts_list">CrossWords-ordlister</string>
|
||||
<string name="download_more">Last ned flere…</string>
|
||||
|
||||
<string name="loc_builtin">Innebygd</string>
|
||||
|
||||
<string name="loc_downloads">Nedlastinger</string>
|
||||
|
||||
<string name="dicts_item_move">Endre nedlastingsplassering</string>
|
||||
|
||||
<string name="dicts_item_select">Gjør til forvalg</string>
|
||||
|
||||
<plurals name="confirm_delete_dict_fmt">
|
||||
<item quantity="one">Er du sikker på at du vil slette ordlisten %1$s?</item>
|
||||
<item quantity="other">Er du sikker på at du vil slette ordlistene %1$s?</item>
|
||||
</plurals>
|
||||
|
||||
<string name="confirm_deleteonly_dicts_fmt">Sletting av %1$s vil bety at du står uten noen %2$s-ordlister. Ett eller flere spill vil ikke kunne åpnes (til du laster ned en erstatningsliste).</string>
|
||||
|
||||
<string name="button_default_human">Menneske</string>
|
||||
<string name="button_default_robot">Maskin</string>
|
||||
<string name="button_default_both">Begge</string>
|
||||
|
||||
<string name="move_dict_fmt">Plassering for ordlisten %1$s</string>
|
||||
|
||||
<string name="loc_internal">Intern</string>
|
||||
<string name="loc_external">Ekstern</string>
|
||||
|
||||
<string name="title_game_config_fmt">Innstillinger for %1$s</string>
|
||||
|
||||
<string name="title_gamenet_config_fmt">%1$s-innstillinger (nettverksbasert)</string>
|
||||
|
||||
<string name="title_langs_list">Språk (basert på installerte ordlister)</string>
|
||||
|
||||
<string name="game_locked">Lås innstillinger</string>
|
||||
|
||||
<string name="players_label_standalone">Spillere (trykk for å redigere)</string>
|
||||
|
||||
<string name="button_add_player">Legg til spiller</string>
|
||||
|
||||
<string name="button_juggle_players">Veksle spillere</string>
|
||||
|
||||
<string name="lang_label">Spillspråk</string>
|
||||
<string name="langdict_label">Spillspråk/ordliste</string>
|
||||
|
||||
<string name="connect_label_fmt">Tilkobling (via %1$s)</string>
|
||||
|
||||
<string name="join_room">Ta del i offentlig rom</string>
|
||||
|
||||
<string name="new_room_hint">Romnavn</string>
|
||||
|
||||
<string name="room_public">Gjør nytt navn offentlig</string>
|
||||
|
||||
<string name="room_public_prompt">Sett offentlig rom</string>
|
||||
|
||||
<string name="public_names_progress_fmt">Henter offentlige rom for %1$d-spillerrom i %2$s.</string>
|
||||
|
||||
<string name="no_name_found_fmt">Ingen offentlige rom funnet for %1$d-spillerrom i %2$s. Prøv å gjenoppfriske eller å opprette ditt eget.</string>
|
||||
|
||||
<string name="settings_label">Andre innstillinger</string>
|
||||
|
||||
<string name="hints_allowed">Tillat hint</string>
|
||||
|
||||
<string name="nethints_allowed">Tillat hint (nettverksbasert)</string>
|
||||
|
||||
<string name="use_timer">Skru på spillklokke</string>
|
||||
|
||||
<string name="minutes_label">Minutter per spiller</string>
|
||||
|
||||
<string name="robot_spinner_prompt">Hvor smart skal maskinen være?</string>
|
||||
|
||||
<string name="robot_smart">Smart maskin</string>
|
||||
<string name="robot_smarter">Smartere maskin</string>
|
||||
<string name="robot_smartest">Smartest maskin</string>
|
||||
|
||||
<string name="confirm_save_title">Bekreft lagring</string>
|
||||
|
||||
<string name="play">Ta del i spill</string>
|
||||
|
||||
<string name="player_label">Navn:</string>
|
||||
|
||||
<string name="dict_lang_label_fmt">Ordliste (i %1$s)</string>
|
||||
|
||||
<string name="dicts_list_prompt_fmt">Installerte ordlister (i %1$s)</string>
|
||||
|
||||
<string name="robot_label">Maskinspiller</string>
|
||||
|
||||
<string name="password_label">Passord</string>
|
||||
|
||||
|
||||
<string name="button_trade_commit">Bekreft bytte</string>
|
||||
<string name="button_trade_cancel">Avbryt bytte</string>
|
||||
|
||||
<string name="entering_trade">Trykk på flisene for å velge…</string>
|
||||
|
||||
|
||||
<string name="no_moves_made">(Ingen trekk enda)</string>
|
||||
|
||||
<string name="invit_expl_bt_fmt">Invitasjon sendt via Blåtann til tilknyttet enhet \"%1$s\" på %2$s</string>
|
||||
<string name="invit_expl_notarget_fmt">Invitasjon sendt via %1$s på %2$s. Ukjent mottaker.</string>
|
||||
|
||||
<string name="relay_alert">Tilkoblingsproblem</string>
|
||||
|
||||
<string name="msg_no_room">Ingen vert har registrert et rom ved det navnet.</string>
|
||||
|
||||
<string name="board_menu_game_history">Spillhistorikk…</string>
|
||||
|
||||
<string name="board_menu_game_resign">Kapituler</string>
|
||||
|
||||
<string name="board_menu_game_resend">Send trekk på nytt</string>
|
||||
|
||||
<string name="str_robot_moved_fmt">"Maskinen %1$s gjorde dette trekket: "</string>
|
||||
<string name="player_edit_title">Rediger spiller</string>
|
||||
|
||||
<string name="badwords_accept">" Ønsker du fremdeles å godta dette trekket?"</string>
|
||||
<string name="badwords_title">Illegale ord</string>
|
||||
|
||||
<string name="board_menu_done">Tur over</string>
|
||||
|
||||
<string name="board_menu_trade">Bytt</string>
|
||||
|
||||
<string name="board_menu_undo_last">Angre siste</string>
|
||||
|
||||
<string name="board_submenu_game">Spill →</string>
|
||||
|
||||
<string name="board_menu_game_left">Fliser som gjenstår…</string>
|
||||
|
||||
<plurals name="strd_robot_traded_fmt">
|
||||
<item quantity="one">utvekslet ei flis.</item>
|
||||
<item quantity="other">utvekslet %1$d flis.</item>
|
||||
</plurals>
|
||||
<string name="strs_new_tiles_fmt">Nye fliser: %1$s</string>
|
||||
<string name="strss_traded_for_fmt">Utvekslet %1$s for %2$s.</string>
|
||||
<string name="vs_join">" mot "</string>
|
||||
|
||||
<string name="str_bonus_all">Bonus for bruk av alle flis: 50
|
||||
\n</string>
|
||||
<string name="strd_turn_score_fmt">Poengsum for tur: %1$d
|
||||
\n</string>
|
||||
|
||||
<plurals name="strd_remains_header_fmt">
|
||||
<item quantity="one">%1$d flis igjen å trekke fra.</item>
|
||||
<item quantity="other"></item>
|
||||
</plurals>
|
||||
<string name="button_revert_colors">Gjenopprett farger</string>
|
||||
<string name="confirm_revert_all">Er du sikker på at du ønsker å tilbakestille alle innstillingene til forvalg?</string>
|
||||
|
||||
<string name="prefs_defaults">Nye spillforvalg</string>
|
||||
<string name="prefs_defaults_summary">Forvalgte innstillinger for nye spill</string>
|
||||
|
||||
<string name="prefs_names">Spillernavn</string>
|
||||
|
||||
<string name="prefs_names_summary">Forvalgte spillernavn</string>
|
||||
|
||||
<string name="pref_player1_name">Første spiller</string>
|
||||
<string name="pref_player2_name">Andre spiller</string>
|
||||
<string name="pref_player3_name">Tredje spiller</string>
|
||||
<string name="pref_player4_name">Fjerde spiller</string>
|
||||
|
||||
<string name="pref_human_name">Menneskelig spiller</string>
|
||||
|
||||
<string name="prefs_dicts">Ordlister</string>
|
||||
<string name="prefs_dicts_summary">Forvalgte ordlister</string>
|
||||
|
||||
<string name="default_dict">Ordlister for mennesker</string>
|
||||
<string name="default_robodict">Ordlister for roboter</string>
|
||||
|
||||
<string name="hints_allowed_sum">Skru på hint-funksjonen</string>
|
||||
|
||||
<string name="nethints_allowed_sum">Skru på hint for to-enhets -spill</string>
|
||||
|
||||
<string name="init_autojuggle">Sjongler spillere</string>
|
||||
<string name="init_autojuggle_sum">Tilfeldig, for nye spill</string>
|
||||
|
||||
<string name="board_size">Brettstørrelse</string>
|
||||
|
||||
<string name="prefs_appearance">Utseende</string>
|
||||
<string name="prefs_appearance_summary">Innstillinger for utseende</string>
|
||||
|
||||
<string name="summary_field">Inkluder i spilliste</string>
|
||||
|
||||
<string name="game_summary_field_empty"><Ingenting></string>
|
||||
<string name="game_summary_field_language">Spillspråk</string>
|
||||
<string name="game_summary_field_opponents">Motstander[e]</string>
|
||||
<string name="game_summary_field_state">Spilltilstand</string>
|
||||
|
||||
<string name="hide_title">Skjul tittellinje</string>
|
||||
<string name="hide_title_summary">Å skjule spillnavn levner litt mer plass til utvidelse av brettet</string>
|
||||
|
||||
<string name="hide_newgames_title">Skjul knapp for nytt spill</string>
|
||||
<string name="hide_newgames_summary">Å skjule knappen for nytt spill på hovedskjermen gjør spillet mer synlig</string>
|
||||
|
||||
<string name="show_arrow">Vis brettpil</string>
|
||||
<string name="keep_screenon">Behold skjerm påslått</string>
|
||||
<string name="keep_screenon_summary">Behold brettskjermen påslått i 10 minutter</string>
|
||||
|
||||
<string name="prefs_colors">Individuelle farger</string>
|
||||
<string name="prefs_colors_summary">Rediger fargene brukt på brettet</string>
|
||||
|
||||
<string name="bonus_l2x">Dobbeltbokstav</string>
|
||||
<string name="bonus_l3x">Trippelbokstav</string>
|
||||
<string name="bonus_w2x">Dobbelord</string>
|
||||
<string name="bonus_w3x">Trippelord</string>
|
||||
|
||||
<string name="tile_back">Flisbakgrunn</string>
|
||||
|
||||
<string name="empty">Tom celle/bakgrunn</string>
|
||||
|
||||
<string name="background">Brettbakgrunn</string>
|
||||
|
||||
<string name="red">Rød</string>
|
||||
<string name="green">Grønn</string>
|
||||
<string name="blue">Blå</string>
|
||||
|
||||
|
||||
<string name="prefs_behavior">Oppførsel</string>
|
||||
<string name="explain_robot">Forklar andre trekk</string>
|
||||
<string name="title_sort_tiles">Sorter nye flis</string>
|
||||
<string name="peek_other_summary">Å trykke noens navn på resultattavla viser vedkommendes flis</string>
|
||||
|
||||
<string name="network_behavior">Innstillinger for nettverksspill</string>
|
||||
<string name="network_behavior_summary">Innstillinger som har innvirkning på nettverksspill</string>
|
||||
<string name="connect_never">Aldri sjekk</string>
|
||||
<string name="connect_five_mins">Hvert femte minutt</string>
|
||||
<string name="connect_fifteen_mins">Hvert kvarter</string>
|
||||
<string name="connect_thirty_mins">Hver halvtime</string>
|
||||
<string name="connect_one_hour">Hver time</string>
|
||||
<string name="connect_six_hours">Hver sjette time</string>
|
||||
<string name="connect_daily">Én gang om dagen</string>
|
||||
|
||||
<string name="notify_sound">Spill lyd</string>
|
||||
|
||||
<string name="notify_vibrate">Vibrer</string>
|
||||
|
||||
<string name="notify_other_summary">Når motstanderens trekk mottas</string>
|
||||
|
||||
<string name="newgame_invite">Inviter nå</string>
|
||||
<string name="newgame_invite_more">Mer info</string>
|
||||
<string name="invite_choice_sms">SMS (tekstmelding)</string>
|
||||
<string name="invite_choice_email">E-post</string>
|
||||
<string name="invite_choice_bt">Blåtann</string>
|
||||
<string name="invite_choice_title">Invitasjon av spillere: Hvordan?</string>
|
||||
<string name="chat_local_id">"Meg: "</string>
|
||||
<string name="chat_other_id">"Ikke meg: "</string>
|
||||
<string name="chat_send">Send</string>
|
||||
<string name="chat_hint">Skriv her…</string>
|
||||
|
||||
<string name="chat_menu_clear">Tøm historikk</string>
|
||||
|
||||
|
||||
<string name="no_empty_rooms">Dette spillet kan ikke koble til uten et romnavn.</string>
|
||||
|
||||
|
||||
|
||||
<string name="str_tiles_not_in_line">Alle spilte flis må være i en linje.</string>
|
||||
|
||||
<string name="str_too_few_tiles_left_to_trade">For få flis igjen å bytte.</string>
|
||||
|
||||
<string name="downloading_dict_fmt">Laster ned %1$s…</string>
|
||||
|
||||
<string name="no_dict_title">Ordliste ikke funnet</string>
|
||||
|
||||
<string name="button_close_game">Lukk spill</string>
|
||||
|
||||
<string name="no_dict_fmt">Spillet \"%1$s\" krever en %2$s-ordliste. Last ned én før du åpner det.</string>
|
||||
|
||||
<string name="button_download">Last ned</string>
|
||||
|
||||
<string name="button_substdict">Erstatt</string>
|
||||
|
||||
<string name="msg_ask_password_fmt">Passord for \"%1$s\":</string>
|
||||
|
||||
<string name="game_fmt">Spill %1$d</string>
|
||||
|
||||
<string name="player_fmt">Spiller %1$d</string>
|
||||
|
||||
<string name="notify_chat_title_fmt">Sludremelding i spillet %1$s</string>
|
||||
<string name="notify_chat_body_fmt">%1$s: %2$s</string>
|
||||
<string name="button_yes">Ja</string>
|
||||
<string name="button_no">Nei</string>
|
||||
|
||||
<string name="button_save">Lagre</string>
|
||||
<string name="button_discard">Forkast</string>
|
||||
|
||||
<string name="button_retry">Prøv igjen</string>
|
||||
|
||||
<string name="tile_button_txt_fmt">%1$s (%2$d)</string>
|
||||
<string name="tile_pick_summary_fmt">Nåværende valg: %1$s</string>
|
||||
|
||||
<string name="tiles_left_title">Gjenstående flis</string>
|
||||
|
||||
<string name="history_title">Spillhistorikk</string>
|
||||
|
||||
<string name="query_title">Et spørsmål…</string>
|
||||
|
||||
<string name="newbie_title">Her er et tips</string>
|
||||
|
||||
<string name="button_notagain">Ikke vis igjen</string>
|
||||
|
||||
<string name="not_again_undo">Denne knappen angrer eller gjentar nåværende trekk.</string>
|
||||
<string name="default_name_title">Forvalgt spillernavn</string>
|
||||
<string name="default_name_message">Skriv inn navnet ditt her. Det vil bli brukt til opprettelse av nye spill. (Du kan endre det senere i \"Nytt spillforvalg\"-delen i innstillingene.)</string>
|
||||
|
||||
<string name="changes_title">Nylige endringer</string>
|
||||
<string name="changes_button">Nylige endringer</string>
|
||||
|
||||
<string name="button_lookup_fmt">Slå opp %1$s</string>
|
||||
<string name="button_done">Ferdig</string>
|
||||
<string name="button_done_fmt">Ferdig med %1$s</string>
|
||||
|
||||
<string name="pick_url_title_fmt">Slå opp %1$s i</string>
|
||||
|
||||
<string name="board_menu_pass">Hopp over</string>
|
||||
|
||||
<string name="not_again_lookup">Denne knappen lar deg slå opp ord som akkurat ble spilt på nett.</string>
|
||||
|
||||
<string name="button_move">Flytt</string>
|
||||
<string name="button_newgroup">Ny gruppe</string>
|
||||
|
||||
<string name="button_search">Finn</string>
|
||||
<string name="word_search_hint">Første bokstaver</string>
|
||||
<string name="tilepick_all_fmt">Plukk %1$d for meg</string>
|
||||
<string name="disableds_title">Avskrudde adressetyper</string>
|
||||
|
||||
<string name="dict_browse_title_fmt">%1$s (%2$d ord ved bruk av %3$d-%4$d flis)</string>
|
||||
<string name="dict_browse_title1_fmt">%1$s (%2$d ord ved bruk av %3$d flis</string>
|
||||
<string name="dict_browse_nowords_fmt">Ingen ord i %1$s starter med %2$s.</string>
|
||||
<string name="not_again_browse">Denne knappen åpner ordlisteutforskeren i gjeldende spillers ordliste.</string>
|
||||
<string name="not_again_browseall">Denne knappen åpner ordlisteutforskeren i ordlisten du ønsker.</string>
|
||||
<string name="alert_empty_dict_fmt">Ordlisten %1$s inneholder kun flisinformasjon. Det er ingen ord å utforske.</string>
|
||||
|
||||
<string name="min_len">Min. lengde</string>
|
||||
<string name="max_len">Maks. lengde</string>
|
||||
<string name="prompt_min_len">Ord som ikke er kortere enn</string>
|
||||
<string name="prompt_max_len">Ord som ikke er lengre enn</string>
|
||||
|
||||
<string name="email_author_chooser">Send kommentar via</string>
|
||||
|
||||
<string name="summary_wait_guest">Ikke tilkoblet</string>
|
||||
<string name="summary_gameover">Spill over</string>
|
||||
<string name="summary_conn">Spill underveis</string>
|
||||
<string name="invite_notice_title">Nytt spill via invitasjon</string>
|
||||
<string name="new_bt_body_fmt">En spiller på enheten %1$s ønsker å starte et spill</string>
|
||||
<string name="new_relay_body">Trykk for å åpne et nytt spill</string>
|
||||
|
||||
|
||||
<string name="bt_resend_fmt">"Blåtannsforsendelse til %1$s mislyktes; prøv %3$d i %2$d sekunder."</string>
|
||||
<string name="bt_fail_fmt">Blåtannsforsendelser til %1$s har mislyktes for mange ganger. Åpne spillet igjen og prøv på nytt.</string>
|
||||
<string name="bt_invite_title">Blåtannsinvitasjon</string>
|
||||
<string name="sms_invite_title">SMS-invitasjon</string>
|
||||
<string name="game_name_label">Nytt spillnavn:</string>
|
||||
<string name="new_name_body_fmt">%1$s har invitert deg til å spille</string>
|
||||
<string name="button_sms_add">Importer kontakt</string>
|
||||
<string name="button_relay_add">Skann spill</string>
|
||||
<string name="get_sms_number">Enhetens telefonnummer:</string>
|
||||
<string name="get_sms_name">Kontaktnavn (valgfritt):</string>
|
||||
<string name="get_relay_name">Enhetsnavn (valgfritt):</string>
|
||||
<string name="get_relay_number">Skriv inn enhets-ID:</string>
|
||||
|
||||
<string name="summary_conn_sms_fmt">Spill underveis med %1$s</string>
|
||||
<string name="menu_hint_prev">Forrige hint</string>
|
||||
|
||||
<string name="menu_hint_next">Neste hint</string>
|
||||
|
||||
<string name="board_menu_undo_current">Angre/gjenta</string>
|
||||
|
||||
<string name="menu_chat">Sludring</string>
|
||||
<string name="chat_sender_fmt">%1$s:</string>
|
||||
<string name="menu_toggle_values">Veksle verdier</string>
|
||||
|
||||
<string name="board_menu_dict">Utforsk ordliste</string>
|
||||
|
||||
<string name="connstat_lastsend_fmt">Siste forsendelse var %1$s (%2$s)</string>
|
||||
<string name="connstat_lastreceipt_fmt">Siste kvittering var %1$s</string>
|
||||
<string name="connstat_noreceipt">Ingen meldinger mottatt.</string>
|
||||
<string name="connstat_sms">SMS/tekstmelding</string>
|
||||
<string name="enable_sms">Tillat spill via SMS</string>
|
||||
<string name="enable_sms_summary">Kun hvis du har ubegrensede tekstmeldinger!</string>
|
||||
|
||||
<string name="confirm_sms_title">Bekreft din SMS-plan</string>
|
||||
<string name="button_enable_sms">Skru på SMS</string>
|
||||
<string name="button_enable_bt">Skru på Blåtann</string>
|
||||
<string name="button_later">Senere</string>
|
||||
|
||||
<string name="gamel_menu_checkupdates">Se etter oppdateringer</string>
|
||||
<string name="checkupdates_none_found">Alt er oppdatert.</string>
|
||||
<string name="new_dict_avail">Ny ordliste tilgjengelig</string>
|
||||
<string name="new_dict_avail_fmt">Trykk for å oppgradere %1$s</string>
|
||||
<string name="new_app_avail_fmt">Ny %1$s-versjon</string>
|
||||
<string name="new_app_avail">Trykk for å laste ned og installere</string>
|
||||
<string name="inform_dict_diffdict_fmt">Du bruker ordlista %1$s, men spillverten bruker %2$s. Ønsker du å bruke %3$s også?</string>
|
||||
<string name="inform_dict_download">" (Du må i tilfelle late den ned først.)"</string>
|
||||
<string name="missing_dict_title">Spillinvitasjon venter</string>
|
||||
<string name="missing_dict_detail">Trykk for å laste ned manglende ordliste</string>
|
||||
<string name="invite_dict_missing_title">Manglende ordliste</string>
|
||||
<string name="button_decline">Avslå</string>
|
||||
|
||||
<string name="download_done">Nedlasting fullført</string>
|
||||
<string name="download_failed">Nedlasting mislyktes</string>
|
||||
|
||||
<string name="default_loc">Lagre ordlister internt</string>
|
||||
|
||||
<string name="download_path_title">Nedlastingsmappe</string>
|
||||
|
||||
<string name="newgroup_label">Navnet på din nye gruppe:</string>
|
||||
|
||||
<string name="list_group_delete">Slett gruppe</string>
|
||||
<string name="list_group_rename">Gi nytt navn</string>
|
||||
<string name="list_group_default">Putt nye spill her</string>
|
||||
<string name="list_group_moveup">Flytt oppover</string>
|
||||
<string name="list_group_movedown">Flytt nedover</string>
|
||||
|
||||
<string name="group_cur_games">Mine spill</string>
|
||||
<string name="group_new_games">Nye spill</string>
|
||||
|
||||
<string name="rename_group_label">Endre navnet på denne gruppen til:</string>
|
||||
<string name="game_name_group_title">Navngi gruppe</string>
|
||||
|
||||
<string name="cannot_delete_default_group_fmt">Gruppen for nye spill, %1$s, kan ikke slettes.</string>
|
||||
|
||||
<plurals name="group_name_fmt">
|
||||
<item quantity="one">%1$s (%2$d spill)</item>
|
||||
<item quantity="other">%1$s (%2$d spill)</item>
|
||||
</plurals>
|
||||
|
||||
<string name="button_reconnect">Koble til igjen</string>
|
||||
|
||||
<string name="change_group">Flytt valgte spill til:</string>
|
||||
|
||||
<string name="sel_games_fmt">Spill: %1$d</string>
|
||||
<string name="sel_groups_fmt">Grupper: %1$d</string>
|
||||
<string name="summary_thumbsize">Miniatyrbildestørrelse</string>
|
||||
<string name="thumb_off">Avskrudd</string>
|
||||
|
||||
<string name="cur_menu_marker_fmt">%1$s (i bruk)</string>
|
||||
<string name="add_to_study_fmt">Legg til %1$s i studieliste</string>
|
||||
<string name="title_studyon">Skru på studielister</string>
|
||||
<string name="summary_studyon">Tilby å legge til og vise lister over ord å huske</string>
|
||||
<string name="gamel_menu_study">Studieliste…</string>
|
||||
|
||||
<string name="slmenu_copy_sel">Kopier til utklippstavle</string>
|
||||
<string name="slmenu_clear_sel">Slett valgte</string>
|
||||
<string name="add_done_fmt">%1$s lagt til i %2$s studieliste</string>
|
||||
<string name="studylist_title_fmt">Studieliste for %1$s</string>
|
||||
|
||||
<string name="study_langpick">Dine ord for:</string>
|
||||
|
||||
<string name="lookup_title">Ordoppslag</string>
|
||||
|
||||
<string name="slmenu_select_all">Velg alle</string>
|
||||
<string name="slmenu_deselect_all">Fravelt alle</string>
|
||||
<string name="sel_items_fmt">Valgt: %1$d</string>
|
||||
|
||||
<string name="loc_menu_xlate">Oversett</string>
|
||||
<string name="loc_lang_blessed">%1$s (offisiell)</string>
|
||||
<string name="loc_lang_local">%1$s (din)</string>
|
||||
|
||||
<plurals name="new_xlations_fmt">
|
||||
<item quantity="one">Installerte én ny oversettelse</item>
|
||||
<item quantity="other">Installerte %1$d nye oversettelser</item>
|
||||
</plurals>
|
||||
|
||||
<string name="xlations_enabled_title">Skru på lokal oversetting</string>
|
||||
<string name="xlations_enabled_summary">Legg til valget i hver skjermmeny</string>
|
||||
|
||||
<string name="loc_filters_prompt">Filtrer etter:</string>
|
||||
<string name="loc_search_prompt">Søk etter:</string>
|
||||
<string name="loc_filters_all">Alle</string>
|
||||
<string name="loc_filters_screen">Siste skjerm</string>
|
||||
<string name="loc_filters_menu">Seneste meny</string>
|
||||
<string name="loc_filters_modified">Endret av meg</string>
|
||||
|
||||
<string name="lang_name_english">Engelsk</string>
|
||||
<string name="lang_name_french">Fransk</string>
|
||||
<string name="lang_name_german">Tysk</string>
|
||||
<string name="lang_name_turkish">Tyrkisk</string>
|
||||
<string name="lang_name_arabic">Arabisk</string>
|
||||
<string name="lang_name_spanish">Spansk</string>
|
||||
<string name="lang_name_swedish">Svensk</string>
|
||||
<string name="lang_name_polish">Polsk</string>
|
||||
<string name="lang_name_danish">Dansk</string>
|
||||
<string name="lang_name_italian">Italiensk</string>
|
||||
<string name="lang_name_dutch">Hollandsk</string>
|
||||
<string name="lang_name_catalan">Katalansk</string>
|
||||
<string name="lang_name_portuguese">Portugisisk</string>
|
||||
<string name="lang_name_russian">Russisk</string>
|
||||
<string name="lang_name_czech">Tsjekkisk</string>
|
||||
<string name="lang_name_greek">Gresk</string>
|
||||
<string name="lang_name_slovak">Slovakisk</string>
|
||||
|
||||
<string name="loc_item_clear">Tøm</string>
|
||||
<string name="loc_item_check">Sjekk</string>
|
||||
<string name="remote_digesting">Behandler ordlisteinformasjon…</string>
|
||||
<string name="delete_dicts">Slett ordliste[r]</string>
|
||||
|
||||
<string name="show_remote">Vis nedlastbare</string>
|
||||
<string name="progress_title">Laster ned</string>
|
||||
|
||||
<string name="dict_on_server">Trykk for detaljer</string>
|
||||
<plurals name="lang_name_fmt">
|
||||
<item quantity="one">%1$s (%2$d ordlister)</item>
|
||||
<item quantity="other">%1$s (%2$d ordlister)</item>
|
||||
</plurals>
|
||||
|
||||
<string name="dict_desc_fmt">%1$s (%2$s/%3$d ord)</string>
|
||||
|
||||
<string name="lang_unknown">Ukjent</string>
|
||||
|
||||
<string name="force_tablet_title">Bruk brett- (side-ved-side) -oppsett?</string>
|
||||
<string name="force_tablet_default">Bruk forvalg for min enhet</string>
|
||||
<plurals name="nag_hours_fmt">
|
||||
<item quantity="one">%1$d time</item>
|
||||
<item quantity="other">%1$d timer</item>
|
||||
</plurals>
|
||||
<plurals name="nag_days_fmt">
|
||||
<item quantity="one">%1$d dag</item>
|
||||
<item quantity="other">%1$d dager</item>
|
||||
</plurals>
|
||||
<string name="nag_body_fmt">%1$s flyttet for mer enn %2$s siden.</string>
|
||||
<string name="nag_warn_last_fmt">Siste advarsel: %1$s</string>
|
||||
<string name="prev_player">Din motstander</string>
|
||||
|
||||
<plurals name="lmi_move_fmt">
|
||||
<item quantity="one">%1$s spilte %2$s for %3$d poeng</item>
|
||||
<item quantity="other"></item>
|
||||
</plurals>
|
||||
<string name="lmi_phony_fmt">%1$s mistet en tur</string>
|
||||
<string name="default_language">Forvalgt språk</string>
|
||||
|
||||
<string name="title_addrs_pref">Kommuniser via</string>
|
||||
|
||||
<string name="new_game">Ny én-enhets -spill</string>
|
||||
<string name="new_game_networked">Nytt nettverksbasert spill</string>
|
||||
<string name="rematch_name_fmt">%1$s</string>
|
||||
|
||||
<string name="new_game_message_nodflt">Dette spillet må settes opp før det kan åpnes.</string>
|
||||
|
||||
<string name="use_defaults">Bruk forvalg</string>
|
||||
|
||||
<plurals name="nplayers_fmt">
|
||||
<item quantity="one">Én spiller</item>
|
||||
<item quantity="other">%1$d spillere</item>
|
||||
</plurals>
|
||||
<string name="network_advanced_title">Avansert</string>
|
||||
<string name="network_advanced_summary">For erfarne spillere</string>
|
||||
<string name="invite_multi_title">Inviter flere</string>
|
||||
<string name="enable_pubroom_title">Skru på offentlige rom</string>
|
||||
<string name="enable_pubroom_summary">Rom andre kan se og ta del i</string>
|
||||
|
||||
|
||||
<string name="set_pref">Skjul knapper</string>
|
||||
|
||||
<string name="waiting_title">Venter på spillere</string>
|
||||
<string name="waiting_invite_title">Venter på svar</string>
|
||||
<string name="waiting_rematch_title">Omkamp underveis</string>
|
||||
<string name="button_wait">Vent</string>
|
||||
<string name="button_edit">Rediger</string>
|
||||
<string name="button_discard_changes">Forkast endringer</string>
|
||||
|
||||
<string name="disable_nags_title">Turpåminnelser</string>
|
||||
<string name="advanced">For feilretting</string>
|
||||
<string name="advanced_summary">Du skal ikke trenge disse…</string>
|
||||
<string name="logging_on">Skru på loggføring</string>
|
||||
<string name="debug_features">Skru på feilrettingsfunksjoner</string>
|
||||
<string name="board_menu_game_netstats">Nettverksstatistikk</string>
|
||||
<string name="board_menu_game_showInvites">Vis invitasjoner</string>
|
||||
<string name="name_dict_fmt">%1$s/%2$s</string>
|
||||
<string name="gamel_menu_storedb">Skriv spill til SD-kort</string>
|
||||
<string name="gamel_menu_loaddb">Last spill fra SD-kort</string>
|
||||
<string name="enable_dupes_summary">Godta invitasjoner mer enn én gang</string>
|
||||
<string name="enable_sms_toself_title">Kortslutt SMS til deg selv</string>
|
||||
<string name="force_radio_title">Lat som du har radio</string>
|
||||
<string name="radio_name_real">Ikke lat som</string>
|
||||
<string name="radio_name_gsm">GSM</string>
|
||||
<string name="radio_name_cdma">CDMA</string>
|
||||
|
||||
<string name="pref_group_sms_title">SMS-ting</string>
|
||||
<string name="pref_group_l10n_title">Lokaliserings-ting</string>
|
||||
<string name="checking_title">Sjekker</string>
|
||||
<string name="checking_for_fmt">Ser etter ordlister i %1$s…</string>
|
||||
<string name="button_enable">Skru på</string>
|
||||
|
||||
<string name="str_no_hint_found">Finner ingen trekk</string>
|
||||
|
||||
<plurals name="resent_msgs_fmt">
|
||||
<item quantity="one">Ett trekk sendt</item>
|
||||
<item quantity="other">%1$s trekk sendt</item>
|
||||
</plurals>
|
||||
|
||||
<string name="clip_label">Invitasjonsnettadresse</string>
|
||||
<string name="list_item_select">Velg</string>
|
||||
<string name="list_item_deselect">Fravelg</string>
|
||||
|
||||
<string name="remove_sms">Fjern SMS</string>
|
||||
<string name="button_ask_again">Spør igjen</string>
|
||||
<string name="sms_send_failed">SMS-forsendelse mislyktes</string>
|
||||
<string name="perms_rationale_title">Android-tilganger</string>
|
||||
<string name="toast_no_permission">Tilgang ikke gitt</string>
|
||||
|
||||
</resources>
|
9
xwords4/android/scripts/adb-pull-apk.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e -u
|
||||
|
||||
APP_ID=org.eehouse.android.xw4
|
||||
|
||||
APK_PATH=$(adb shell pm path $APP_ID)
|
||||
APK_PATH=${APK_PATH/package:/}
|
||||
adb pull $APK_PATH
|
|
@ -141,7 +141,7 @@ def writeDoc(doc, src, dest):
|
|||
def checkOrConvertString(engNames, elem, verbose):
|
||||
name = elem.get('name')
|
||||
if not elem.text:
|
||||
print "elem", name, "is empty"
|
||||
print "ERROR: elem", name, "is empty"
|
||||
sys.exit(1)
|
||||
elif not name in engNames or elem.text.startswith(s_prefix):
|
||||
ok = False
|
||||
|
|
|
@ -7,7 +7,10 @@ import mk_for_download, mygit
|
|||
import xwconfig
|
||||
|
||||
# I'm not checking my key in...
|
||||
import mykey
|
||||
try :
|
||||
import mykey
|
||||
except:
|
||||
print('unable to load mykey')
|
||||
|
||||
from stat import ST_CTIME
|
||||
try:
|
||||
|
|
|
@ -2128,13 +2128,13 @@ board_requestHint( BoardCtxt* board,
|
|||
result = nTiles > 0;
|
||||
}
|
||||
|
||||
XP_Bool canMove = XP_FALSE;
|
||||
if ( result ) {
|
||||
#ifdef XWFEATURE_SEARCHLIMIT
|
||||
BdHintLimits limits;
|
||||
BdHintLimits* lp = NULL;
|
||||
#endif
|
||||
XP_Bool wasVisible;
|
||||
XP_Bool canMove;
|
||||
|
||||
wasVisible = setArrowVisible( board, XP_FALSE );
|
||||
|
||||
|
@ -2194,11 +2194,9 @@ board_requestHint( BoardCtxt* board,
|
|||
}
|
||||
setArrowVisible( board, wasVisible );
|
||||
}
|
||||
} else {
|
||||
util_userError( board->util, ERR_NO_HINT_FOUND );
|
||||
}
|
||||
|
||||
if ( !result ) {
|
||||
if ( !canMove ) {
|
||||
util_userError( board->util, ERR_NO_HINT_FOUND );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,6 +274,9 @@ CommsRelayState2Str( CommsRelayState state )
|
|||
CASE_STR(COMMS_RELAYSTATE_CONNECTED);
|
||||
CASE_STR(COMMS_RELAYSTATE_RECONNECTED);
|
||||
CASE_STR(COMMS_RELAYSTATE_ALLCONNECTED);
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
CASE_STR(COMMS_RELAYSTATE_USING_HTTP);
|
||||
#endif
|
||||
default:
|
||||
XP_ASSERT(0);
|
||||
}
|
||||
|
@ -459,7 +462,10 @@ reset_internal( CommsCtxt* comms, XP_Bool isServer,
|
|||
if ( 0 != comms->nextChannelNo ) {
|
||||
XP_LOGF( "%s: comms->nextChannelNo: %d", __func__, comms->nextChannelNo );
|
||||
}
|
||||
XP_ASSERT( 0 == comms->nextChannelNo ); /* firing... */
|
||||
/* This tends to fire when games reconnect to the relay after the DB's
|
||||
been wiped and connect in a different order from that in which they did
|
||||
originally. So comment it out. */
|
||||
// XP_ASSERT( 0 == comms->nextChannelNo );
|
||||
// comms->nextChannelNo = 0;
|
||||
if ( resetRelay ) {
|
||||
comms->channelSeed = 0;
|
||||
|
@ -1773,7 +1779,7 @@ relayPreProcess( CommsCtxt* comms, XWStreamCtxt* stream, XWHostID* senderID )
|
|||
}
|
||||
|
||||
if ( consumed ) {
|
||||
XP_LOGF( "%s: rejecting data message", __func__ );
|
||||
XP_LOGF( "%s: rejecting data message (consumed)", __func__ );
|
||||
} else {
|
||||
*senderID = srcID;
|
||||
}
|
||||
|
@ -2375,6 +2381,19 @@ comms_isConnected( const CommsCtxt* const comms )
|
|||
return result;
|
||||
}
|
||||
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
void
|
||||
comms_gameJoined( CommsCtxt* comms, const XP_UCHAR* connname, XWHostID hid )
|
||||
{
|
||||
LOG_FUNC();
|
||||
XP_ASSERT( XP_STRLEN( connname ) + 1 < sizeof(comms->rr.connName) );
|
||||
XP_STRNCPY( comms->rr.connName, connname, sizeof(comms->rr.connName) );
|
||||
comms->rr.myHostID = hid;
|
||||
comms->forceChannel = hid;
|
||||
set_relay_state( comms, COMMS_RELAYSTATE_USING_HTTP );
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined COMMS_HEARTBEAT || defined XWFEATURE_COMMSACK
|
||||
static void
|
||||
sendEmptyMsg( CommsCtxt* comms, AddressRecord* rec )
|
||||
|
@ -3097,14 +3116,34 @@ sendNoConn( CommsCtxt* comms, const MsgQueueElem* elem, XWHostID destID )
|
|||
static XP_Bool
|
||||
relayConnect( CommsCtxt* comms )
|
||||
{
|
||||
XP_Bool success = XP_TRUE;
|
||||
LOG_FUNC();
|
||||
if ( addr_hasType( &comms->addr, COMMS_CONN_RELAY ) && !comms->rr.connecting ) {
|
||||
comms->rr.connecting = XP_TRUE;
|
||||
success = send_via_relay( comms, comms->rr.connName[0]?
|
||||
XWRELAY_GAME_RECONNECT : XWRELAY_GAME_CONNECT,
|
||||
comms->rr.myHostID, NULL, 0, NULL );
|
||||
comms->rr.connecting = XP_FALSE;
|
||||
XP_Bool success = XP_TRUE;
|
||||
if ( addr_hasType( &comms->addr, COMMS_CONN_RELAY ) ) {
|
||||
if ( 0 ) {
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
} else if ( comms->rr.connName[0] ) {
|
||||
set_relay_state( comms, COMMS_RELAYSTATE_USING_HTTP );
|
||||
} else {
|
||||
CommsAddrRec addr;
|
||||
comms_getAddr( comms, &addr );
|
||||
DevIDType ignored; /* but should it be? */
|
||||
(*comms->procs.requestJoin)( comms->procs.closure,
|
||||
util_getDevID( comms->util, &ignored ),
|
||||
addr.u.ip_relay.invite, /* room */
|
||||
comms->rr.nPlayersHere,
|
||||
comms->rr.nPlayersTotal,
|
||||
comms_getChannelSeed(comms),
|
||||
comms->util->gameInfo->dictLang );
|
||||
success = XP_FALSE;
|
||||
#else
|
||||
} else if ( !comms->rr.connecting ) {
|
||||
comms->rr.connecting = XP_TRUE;
|
||||
success = send_via_relay( comms, comms->rr.connName[0]?
|
||||
XWRELAY_GAME_RECONNECT : XWRELAY_GAME_CONNECT,
|
||||
comms->rr.myHostID, NULL, 0, NULL );
|
||||
comms->rr.connecting = XP_FALSE;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return success;
|
||||
} /* relayConnect */
|
||||
|
|
|
@ -32,7 +32,7 @@ EXTERN_C_START
|
|||
#define CONN_ID_NONE 0L
|
||||
|
||||
typedef XP_U32 MsgID; /* this is too big!!! PENDING */
|
||||
typedef XP_U8 XWHostID;
|
||||
typedef XP_U8 XWHostID;
|
||||
|
||||
typedef enum {
|
||||
COMMS_CONN_NONE /* I want errors on uninited case */
|
||||
|
@ -56,6 +56,9 @@ typedef enum {
|
|||
, COMMS_RELAYSTATE_CONNECTED
|
||||
, COMMS_RELAYSTATE_RECONNECTED
|
||||
, COMMS_RELAYSTATE_ALLCONNECTED
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
, COMMS_RELAYSTATE_USING_HTTP /* connection state doesn't matter */
|
||||
#endif
|
||||
} CommsRelayState;
|
||||
|
||||
#ifdef XWFEATURE_BLUETOOTH
|
||||
|
@ -90,7 +93,7 @@ typedef struct _CommsAddrRec {
|
|||
XP_U16 port_ip;
|
||||
} ip;
|
||||
struct {
|
||||
XP_UCHAR invite[MAX_INVITE_LEN + 1];
|
||||
XP_UCHAR invite[MAX_INVITE_LEN + 1]; /* room!!!! */
|
||||
XP_UCHAR hostName[MAX_HOSTNAME_LEN + 1];
|
||||
XP_U32 ipAddr; /* looked up from above */
|
||||
XP_U16 port;
|
||||
|
@ -135,6 +138,12 @@ typedef void (*RelayErrorProc)( void* closure, XWREASON relayErr );
|
|||
typedef XP_Bool (*RelayNoConnProc)( const XP_U8* buf, XP_U16 len,
|
||||
const XP_UCHAR* msgNo,
|
||||
const XP_UCHAR* relayID, void* closure );
|
||||
# ifdef RELAY_VIA_HTTP
|
||||
typedef void (*RelayRequestJoinProc)( void* closure, const XP_UCHAR* devID,
|
||||
const XP_UCHAR* room, XP_U16 nPlayersHere,
|
||||
XP_U16 nPlayersTotal, XP_U16 seed,
|
||||
XP_U16 lang );
|
||||
# endif
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
|
@ -161,6 +170,9 @@ typedef struct _TransportProcs {
|
|||
RelayConndProc rconnd;
|
||||
RelayErrorProc rerror;
|
||||
RelayNoConnProc sendNoConn;
|
||||
# ifdef RELAY_VIA_HTTP
|
||||
RelayRequestJoinProc requestJoin;
|
||||
# endif
|
||||
#endif
|
||||
void* closure;
|
||||
} TransportProcs;
|
||||
|
@ -248,6 +260,10 @@ XP_Bool comms_checkComplete( const CommsAddrRec* const addr );
|
|||
XP_Bool comms_canChat( const CommsCtxt* comms );
|
||||
XP_Bool comms_isConnected( const CommsCtxt* const comms );
|
||||
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
void comms_gameJoined( CommsCtxt* comms, const XP_UCHAR* connname, XWHostID hid );
|
||||
#endif
|
||||
|
||||
CommsConnType addr_getType( const CommsAddrRec* addr );
|
||||
void addr_setType( CommsAddrRec* addr, CommsConnType type );
|
||||
void addr_addType( CommsAddrRec* addr, CommsConnType type );
|
||||
|
|
|
@ -528,7 +528,7 @@ engine_findMove( EngineCtxt* engine, const ModelCtxt* model,
|
|||
newMove->nTiles = 0;
|
||||
canMove = XP_FALSE;
|
||||
}
|
||||
result = XP_TRUE;
|
||||
XP_ASSERT( result );
|
||||
}
|
||||
|
||||
util_engineStopping( engine->util );
|
||||
|
|
|
@ -338,6 +338,34 @@ game_saveSucceeded( const XWGame* game, XP_U16 saveToken )
|
|||
}
|
||||
}
|
||||
|
||||
XP_Bool
|
||||
game_receiveMessage( XWGame* game, XWStreamCtxt* stream, CommsAddrRec* retAddr )
|
||||
{
|
||||
ServerCtxt* server = game->server;
|
||||
CommsMsgState commsState;
|
||||
XP_Bool result = comms_checkIncomingStream( game->comms, stream, retAddr,
|
||||
&commsState );
|
||||
if ( result ) {
|
||||
(void)server_do( server );
|
||||
|
||||
result = server_receiveMessage( server, stream );
|
||||
}
|
||||
comms_msgProcessed( game->comms, &commsState, !result );
|
||||
|
||||
if ( result ) {
|
||||
/* in case MORE work's pending. Multiple calls are required in at
|
||||
least one case, where I'm a host handling client registration *AND*
|
||||
I'm a robot. Only one server_do and I'll never make that first
|
||||
robot move. That's because comms can't detect a duplicate initial
|
||||
packet (in validateInitialMessage()). */
|
||||
for ( int ii = 0; ii < 5; ++ii ) {
|
||||
(void)server_do( server );
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
game_getState( const XWGame* game, GameStateInfo* gsi )
|
||||
{
|
||||
|
|
|
@ -82,6 +82,10 @@ void game_saveNewGame( MPFORMAL const CurGameInfo* gi, XW_UtilCtxt* util,
|
|||
void game_saveToStream( const XWGame* game, const CurGameInfo* gi,
|
||||
XWStreamCtxt* stream, XP_U16 saveToken );
|
||||
void game_saveSucceeded( const XWGame* game, XP_U16 saveToken );
|
||||
|
||||
XP_Bool game_receiveMessage( XWGame* game, XWStreamCtxt* stream,
|
||||
CommsAddrRec* retAddr );
|
||||
|
||||
void game_dispose( XWGame* game );
|
||||
|
||||
void game_getState( const XWGame* game, GameStateInfo* gsi );
|
||||
|
|
|
@ -61,6 +61,13 @@ nli_setDevID( NetLaunchInfo* nli, XP_U32 devID )
|
|||
types_addType( &nli->_conTypes, COMMS_CONN_RELAY );
|
||||
}
|
||||
|
||||
void
|
||||
nli_setInviteID( NetLaunchInfo* nli, const XP_UCHAR* inviteID )
|
||||
{
|
||||
nli->inviteID[0] = '\0';
|
||||
XP_STRCAT( nli->inviteID, inviteID );
|
||||
}
|
||||
|
||||
void
|
||||
nli_saveToStream( const NetLaunchInfo* nli, XWStreamCtxt* stream )
|
||||
{
|
||||
|
|
|
@ -76,6 +76,7 @@ void nli_saveToStream( const NetLaunchInfo* invit, XWStreamCtxt* stream );
|
|||
void nli_makeAddrRec( const NetLaunchInfo* invit, CommsAddrRec* addr );
|
||||
|
||||
void nli_setDevID( NetLaunchInfo* invit, XP_U32 devID );
|
||||
void nli_setInviteID( NetLaunchInfo* invit, const XP_UCHAR* inviteID );
|
||||
|
||||
|
||||
#endif
|
||||
|
|
127
xwords4/common/xwlist.c
Normal file
|
@ -0,0 +1,127 @@
|
|||
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
|
||||
/*
|
||||
* Copyright 2009 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 "xwlist.h"
|
||||
|
||||
#define MAX_HERE 16
|
||||
|
||||
typedef struct XWList {
|
||||
XP_U16 len;
|
||||
XP_U16 size;
|
||||
elem* list;
|
||||
MPSLOT
|
||||
} XWList;
|
||||
|
||||
XWList*
|
||||
mk_list(MPFORMAL XP_U16 XP_UNUSED(sizeHint))
|
||||
{
|
||||
XWList* list = XP_CALLOC( mpool, sizeof(*list));
|
||||
MPASSIGN( list->mpool, mpool);
|
||||
return list;
|
||||
}
|
||||
|
||||
void
|
||||
list_append( XWList* self, elem one )
|
||||
{
|
||||
if ( self->size == 0 ) { /* initial case */
|
||||
self->size = 2;
|
||||
self->list = XP_MALLOC( self->mpool, self->size * sizeof(self->list[0]) );
|
||||
}
|
||||
if ( self->len == self->size ) { /* need to grow? */
|
||||
self->size *= 2;
|
||||
self->list = XP_REALLOC( self->mpool, self->list, self->size * sizeof(self->list[0]) );
|
||||
}
|
||||
|
||||
self->list[self->len++] = one;
|
||||
XP_LOGF( "%s(): put %p at position %d (size: %d)", __func__, one, self->len-1, self->size );
|
||||
}
|
||||
|
||||
XP_U16
|
||||
list_get_len( const XWList* list )
|
||||
{
|
||||
return list->len;
|
||||
}
|
||||
|
||||
void
|
||||
list_remove_front( XWList* self, elem* out, XP_U16* countp )
|
||||
{
|
||||
const XP_U16 nMoved = XP_MIN( *countp, self->len );
|
||||
XP_MEMCPY( out, self->list, nMoved * sizeof(out[0]) );
|
||||
*countp = nMoved;
|
||||
|
||||
// Now copy the survivors down
|
||||
self->len -= nMoved;
|
||||
XP_MEMMOVE( &self->list[0], &self->list[nMoved], self->len * sizeof(self->list[0]));
|
||||
}
|
||||
|
||||
void
|
||||
list_remove_back(XWList* XP_UNUSED(self), elem* XP_UNUSED(here), XP_U16* XP_UNUSED(count))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
list_free( XWList* self, destructor proc, void* closure )
|
||||
{
|
||||
if ( !!proc ) {
|
||||
for ( XP_U16 ii = 0; ii < self->len; ++ii ) {
|
||||
(*proc)(self->list[ii], closure);
|
||||
}
|
||||
}
|
||||
|
||||
if ( !!self->list ) {
|
||||
XP_FREE( self->mpool, self->list );
|
||||
}
|
||||
XP_FREE( self->mpool, self );
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
static void
|
||||
dest(elem elem, void* XP_UNUSED(closure))
|
||||
{
|
||||
XP_LOGF( "%s(%p)", __func__, elem);
|
||||
}
|
||||
|
||||
void
|
||||
list_test_lists(MPFORMAL_NOCOMMA)
|
||||
{
|
||||
XWList* list = mk_list( mpool, 16 );
|
||||
for ( char* ii = 0; ii < (char*)100; ++ii ) {
|
||||
(void)list_append( list, ii );
|
||||
}
|
||||
|
||||
XP_ASSERT( list_get_len(list) == 100 );
|
||||
|
||||
char* prev = 0;
|
||||
while ( 0 < list_get_len( list ) ) {
|
||||
elem here;
|
||||
XP_U16 count = 1;
|
||||
list_remove_front( list, &here, &count );
|
||||
XP_LOGF( "%s(): got here: %p", __func__, here );
|
||||
XP_ASSERT( count == 1 );
|
||||
XP_ASSERT( prev++ == here );
|
||||
}
|
||||
|
||||
for ( char* ii = 0; ii < (char*)10; ++ii ) {
|
||||
(void)list_append( list, ii );
|
||||
}
|
||||
|
||||
list_free( list, dest, NULL );
|
||||
}
|
||||
#endif
|
44
xwords4/common/xwlist.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* -*-mode: C; fill-column: 78; c-basic-offset: 4; -*- */
|
||||
/*
|
||||
* Copyright 2017 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 _XWLIST_H_
|
||||
#define _XWLIST_H_
|
||||
|
||||
#include "comtypes.h"
|
||||
#include "mempool.h"
|
||||
|
||||
#include "xptypes.h"
|
||||
|
||||
typedef void* elem;
|
||||
typedef struct XWList XWList;
|
||||
typedef void (*destructor)(elem one, void* closure);
|
||||
|
||||
XWList* mk_list(MPFORMAL XP_U16 sizeHint);
|
||||
void list_free(XWList* list, destructor proc, void* closure);
|
||||
|
||||
void list_append(XWList* list, elem one);
|
||||
XP_U16 list_get_len(const XWList* list);
|
||||
void list_remove_front(XWList* list, elem* here, XP_U16* count);
|
||||
void list_remove_back(XWList* list, elem* here, XP_U16* count);
|
||||
|
||||
#ifdef DEBUG
|
||||
void list_test_lists(MPFORMAL_NOCOMMA);
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -130,6 +130,7 @@ DEFINES += -DCOMMS_XPORT_FLAGSPROC
|
|||
DEFINES += -DINITIAL_CLIENT_VERS=3
|
||||
DEFINES += -DCOMMON_LAYOUT
|
||||
DEFINES += -DNATIVE_NLI
|
||||
# DEFINES += -DRELAY_VIA_HTTP
|
||||
|
||||
# MAX_ROWS controls STREAM_VERS_BIGBOARD and with it move hashing
|
||||
DEFINES += -DMAX_ROWS=32
|
||||
|
@ -226,7 +227,7 @@ OBJ = \
|
|||
$(BUILD_PLAT_DIR)/relaycon.o \
|
||||
$(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS)
|
||||
|
||||
LIBS = -lm -luuid $(GPROFFLAG)
|
||||
LIBS = -lm -lpthread -luuid -lcurl -ljson-c $(GPROFFLAG)
|
||||
ifdef USE_SQLITE
|
||||
LIBS += -lsqlite3
|
||||
DEFINES += -DUSE_SQLITE
|
||||
|
@ -242,7 +243,7 @@ endif
|
|||
ifneq (,$(findstring DPLATFORM_GTK,$(DEFINES)))
|
||||
LIBS += `pkg-config --libs gtk+-3.0`
|
||||
CFLAGS += `pkg-config --cflags gtk+-3.0`
|
||||
# CFLAGS += -DGDK_DISABLE_DEPRECATED
|
||||
CFLAGS += -DGDK_DISABLE_DEPRECATED
|
||||
POINTER_SUPPORT = -DPOINTER_SUPPORT
|
||||
endif
|
||||
|
||||
|
@ -264,6 +265,8 @@ REQUIRED_DEBS = gcc libgtk-3-dev \
|
|||
libncursesw5-dev \
|
||||
uuid-dev \
|
||||
libsqlite3-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libjson-c-dev \
|
||||
|
||||
.PHONY: debcheck debs_install
|
||||
|
||||
|
|
|
@ -279,35 +279,57 @@ curses_util_userError( XW_UtilCtxt* uc, UtilErrID id )
|
|||
}
|
||||
} /* curses_util_userError */
|
||||
|
||||
static gint
|
||||
ask_move( gpointer data )
|
||||
{
|
||||
CursesAppGlobals* globals = (CursesAppGlobals*)data;
|
||||
CommonGlobals* cGlobals = &globals->cGlobals;
|
||||
const char* answers[] = {"Ok", "Cancel", NULL};
|
||||
|
||||
if (0 == cursesask(globals, cGlobals->question, VSIZE(answers)-1, answers) ) {
|
||||
BoardCtxt* board = cGlobals->game.board;
|
||||
if ( board_commitTurn( board, XP_TRUE, XP_TRUE, NULL ) ) {
|
||||
board_draw( board );
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* this needs to change!!! */
|
||||
static void
|
||||
curses_util_notifyMove( XW_UtilCtxt* uc, XWStreamCtxt* stream )
|
||||
{
|
||||
CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure;
|
||||
char* question;
|
||||
const char* answers[3] = {NULL};
|
||||
short numAnswers = 0;
|
||||
XP_Bool freeMe = XP_FALSE;
|
||||
|
||||
question = strFromStream( stream );
|
||||
freeMe = XP_TRUE;
|
||||
answers[numAnswers++] = "Cancel";
|
||||
answers[numAnswers++] = "Ok";
|
||||
|
||||
// result = okIndex ==
|
||||
cursesask( globals, question, numAnswers, answers );
|
||||
|
||||
if ( freeMe ) {
|
||||
free( question );
|
||||
}
|
||||
CommonGlobals* cGlobals = &globals->cGlobals;
|
||||
XP_U16 len = stream_getSize( stream );
|
||||
XP_ASSERT( len <= VSIZE(cGlobals->question) );
|
||||
stream_getBytes( stream, cGlobals->question, len );
|
||||
(void)g_idle_add( ask_move, globals );
|
||||
} /* curses_util_userQuery */
|
||||
|
||||
static gint
|
||||
ask_trade( gpointer data )
|
||||
{
|
||||
CursesAppGlobals* globals = (CursesAppGlobals*)data;
|
||||
CommonGlobals* cGlobals = &globals->cGlobals;
|
||||
|
||||
const char* buttons[] = { "Ok", "Cancel" };
|
||||
if (0 == cursesask( globals, cGlobals->question, VSIZE(buttons), buttons ) ) {
|
||||
BoardCtxt* board = cGlobals->game.board;
|
||||
if ( board_commitTurn( board, XP_TRUE, XP_TRUE, NULL ) ) {
|
||||
board_draw( board );
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
curses_util_notifyTrade( XW_UtilCtxt* uc, const XP_UCHAR** tiles, XP_U16 nTiles )
|
||||
{
|
||||
CursesAppGlobals* globals = (CursesAppGlobals*)uc->closure;
|
||||
formatConfirmTrade( &globals->cGlobals, tiles, nTiles );
|
||||
/* const char* buttons[] = { "Cancel", "Ok" }; */
|
||||
/* cursesask( globals, question, VSIZE(buttons), buttons ); */
|
||||
(void)g_idle_add( ask_trade, globals );
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1001,7 +1023,7 @@ curses_socket_added( void* closure, int newSock, GIOFunc func )
|
|||
/* XP_ASSERT( !globals->cGlobals.relaySocket ); */
|
||||
/* globals->cGlobals.relaySocket = newSock; */
|
||||
#endif
|
||||
} /* curses_socket_changed */
|
||||
} /* curses_socket_added */
|
||||
|
||||
static void
|
||||
curses_onGameSaved( void* closure, sqlite3_int64 rowid,
|
||||
|
@ -1591,6 +1613,27 @@ relay_sendNoConn_curses( const XP_U8* msg, XP_U16 len,
|
|||
return storeNoConnMsg( &globals->cGlobals, msg, len, relayID );
|
||||
} /* relay_sendNoConn_curses */
|
||||
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
static void
|
||||
onJoined( void* closure, const XP_UCHAR* connname, XWHostID hid )
|
||||
{
|
||||
LOG_FUNC();
|
||||
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
|
||||
CommsCtxt* comms = globals->cGlobals.game.comms;
|
||||
comms_gameJoined( comms, connname, hid );
|
||||
}
|
||||
|
||||
static void
|
||||
relay_requestJoin_curses( void* closure, const XP_UCHAR* devID, const XP_UCHAR* room,
|
||||
XP_U16 nPlayersHere, XP_U16 nPlayersTotal,
|
||||
XP_U16 seed, XP_U16 lang )
|
||||
{
|
||||
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
|
||||
relaycon_join( globals->cGlobals.params, devID, room, nPlayersHere, nPlayersTotal,
|
||||
seed, lang, onJoined, globals );
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
relay_status_curses( void* closure, CommsRelayState state )
|
||||
{
|
||||
|
@ -1659,6 +1702,7 @@ static void
|
|||
cursesGotBuf( void* closure, const CommsAddrRec* addr,
|
||||
const XP_U8* buf, XP_U16 len )
|
||||
{
|
||||
LOG_FUNC();
|
||||
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
|
||||
XP_U32 clientToken;
|
||||
XP_ASSERT( sizeof(clientToken) < len );
|
||||
|
@ -1676,6 +1720,19 @@ cursesGotBuf( void* closure, const CommsAddrRec* addr,
|
|||
XP_LOGF( "%s: dropping packet; meant for a different device",
|
||||
__func__ );
|
||||
}
|
||||
LOG_RETURN_VOID();
|
||||
}
|
||||
|
||||
static void
|
||||
cursesGotForRow( void* closure, const CommsAddrRec* from,
|
||||
sqlite3_int64 rowid, const XP_U8* buf,
|
||||
XP_U16 len )
|
||||
{
|
||||
LOG_FUNC();
|
||||
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
|
||||
XP_ASSERT( globals->cGlobals.selRow == rowid );
|
||||
gameGotBuf( &globals->cGlobals, XP_TRUE, buf, len, from );
|
||||
LOG_RETURN_VOID();
|
||||
}
|
||||
|
||||
static gint
|
||||
|
@ -1913,6 +1970,10 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
|
|||
.rconnd = relay_connd_curses,
|
||||
.rerror = relay_error_curses,
|
||||
.sendNoConn = relay_sendNoConn_curses,
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
.requestJoin = relay_requestJoin_curses,
|
||||
#endif
|
||||
|
||||
# ifdef COMMS_XPORT_FLAGSPROC
|
||||
.getFlags = curses_getFlags,
|
||||
# endif
|
||||
|
@ -1949,6 +2010,7 @@ cursesmain( XP_Bool isServer, LaunchParams* params )
|
|||
if ( params->useUdp ) {
|
||||
RelayConnProcs procs = {
|
||||
.msgReceived = cursesGotBuf,
|
||||
.msgForRow = cursesGotForRow,
|
||||
.msgNoticeReceived = cursesNoticeRcvd,
|
||||
.devIDReceived = cursesDevIDReceived,
|
||||
.msgErrorMsg = cursesErrorMsgRcvd,
|
||||
|
|
|
@ -71,6 +71,7 @@ struct CursesAppGlobals {
|
|||
gchar* lastErr;
|
||||
|
||||
XP_U16 nChatsSent;
|
||||
XP_U16 nextQueryTimeSecs;
|
||||
|
||||
union {
|
||||
struct {
|
||||
|
|
|
@ -52,6 +52,7 @@ openGamesDB( const char* dbName )
|
|||
",inviteInfo BLOB"
|
||||
",room VARCHAR(32)"
|
||||
",connvia VARCHAR(32)"
|
||||
",relayid VARCHAR(32)"
|
||||
",ended INT(1)"
|
||||
",turn INT(2)"
|
||||
",local INT(1)"
|
||||
|
@ -128,13 +129,14 @@ writeBlobColumnData( const XP_U8* data, gsize len, XP_U16 strVersion, sqlite3* p
|
|||
assertPrintResult( pDb, result, SQLITE_OK );
|
||||
result = sqlite3_blob_close( blob );
|
||||
assertPrintResult( pDb, result, SQLITE_OK );
|
||||
|
||||
if ( !!stmt ) {
|
||||
sqlite3_finalize( stmt );
|
||||
}
|
||||
|
||||
LOG_RETURNF( "%lld", curRow );
|
||||
return curRow;
|
||||
}
|
||||
} /* writeBlobColumnData */
|
||||
|
||||
static sqlite3_int64
|
||||
writeBlobColumnStream( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 curRow,
|
||||
|
@ -199,11 +201,12 @@ addSnapshot( CommonGlobals* cGlobals )
|
|||
void
|
||||
summarize( CommonGlobals* cGlobals )
|
||||
{
|
||||
XP_S16 nMoves = model_getNMoves( cGlobals->game.model );
|
||||
XP_Bool gameOver = server_getGameIsOver( cGlobals->game.server );
|
||||
const XWGame* game = &cGlobals->game;
|
||||
XP_S16 nMoves = model_getNMoves( game->model );
|
||||
XP_Bool gameOver = server_getGameIsOver( game->server );
|
||||
XP_Bool isLocal;
|
||||
XP_S16 turn = server_getCurrentTurn( cGlobals->game.server, &isLocal );
|
||||
XP_U32 lastMoveTime = server_getLastMoveTime( cGlobals->game.server );
|
||||
XP_S16 turn = server_getCurrentTurn( game->server, &isLocal );
|
||||
XP_U32 lastMoveTime = server_getLastMoveTime( game->server );
|
||||
XP_U16 seed = 0;
|
||||
XP_S16 nMissing = 0;
|
||||
XP_U16 nTotal = cGlobals->gi->nPlayers;
|
||||
|
@ -214,10 +217,11 @@ summarize( CommonGlobals* cGlobals )
|
|||
|
||||
// gchar* connvia = "local";
|
||||
gchar connvia[128] = {0};
|
||||
XP_UCHAR relayID[32] = {0};
|
||||
|
||||
if ( !!cGlobals->game.comms ) {
|
||||
nMissing = server_getMissingPlayers( cGlobals->game.server );
|
||||
comms_getAddr( cGlobals->game.comms, &addr );
|
||||
if ( !!game->comms ) {
|
||||
nMissing = server_getMissingPlayers( game->server );
|
||||
comms_getAddr( game->comms, &addr );
|
||||
CommsConnType typ;
|
||||
for ( XP_U32 st = 0; addr_iter( &addr, &typ, &st ); ) {
|
||||
if ( !!connvia[0] ) {
|
||||
|
@ -242,18 +246,21 @@ summarize( CommonGlobals* cGlobals )
|
|||
break;
|
||||
}
|
||||
}
|
||||
seed = comms_getChannelSeed( cGlobals->game.comms );
|
||||
seed = comms_getChannelSeed( game->comms );
|
||||
XP_U16 len = VSIZE(relayID);
|
||||
(void)comms_getRelayID( game->comms, relayID, &len );
|
||||
} else {
|
||||
strcat( connvia, "local" );
|
||||
}
|
||||
|
||||
const char* fmt = "UPDATE games "
|
||||
" SET room='%s', ended=%d, turn=%d, local=%d, ntotal=%d, nmissing=%d, "
|
||||
" nmoves=%d, seed=%d, gameid=%d, connvia='%s', lastMoveTime=%d"
|
||||
" SET room='%s', ended=%d, turn=%d, local=%d, ntotal=%d, "
|
||||
" nmissing=%d, nmoves=%d, seed=%d, gameid=%d, connvia='%s', "
|
||||
" relayid='%s', lastMoveTime=%d"
|
||||
" WHERE rowid=%lld";
|
||||
XP_UCHAR buf[256];
|
||||
snprintf( buf, sizeof(buf), fmt, room, gameOver?1:0, turn, isLocal?1:0,
|
||||
nTotal, nMissing, nMoves, seed, gameID, connvia, lastMoveTime,
|
||||
nTotal, nMissing, nMoves, seed, gameID, connvia, relayID, lastMoveTime,
|
||||
cGlobals->selRow );
|
||||
XP_LOGF( "query: %s", buf );
|
||||
sqlite3_stmt* stmt = NULL;
|
||||
|
@ -305,12 +312,46 @@ listGames( sqlite3* pDb )
|
|||
return list;
|
||||
}
|
||||
|
||||
GHashTable*
|
||||
getRelayIDsToRowsMap( sqlite3* pDb )
|
||||
{
|
||||
GHashTable* table = g_hash_table_new( g_str_hash, g_str_equal );
|
||||
sqlite3_stmt *ppStmt;
|
||||
int result = sqlite3_prepare_v2( pDb, "SELECT relayid, rowid FROM games "
|
||||
"where NOT relayid = ''", -1, &ppStmt, NULL );
|
||||
assertPrintResult( pDb, result, SQLITE_OK );
|
||||
while ( result == SQLITE_OK && NULL != ppStmt ) {
|
||||
switch( sqlite3_step( ppStmt ) ) {
|
||||
case SQLITE_ROW: /* have data */
|
||||
{
|
||||
XP_UCHAR relayID[32];
|
||||
getColumnText( ppStmt, 0, relayID, VSIZE(relayID) );
|
||||
gpointer key = g_strdup( relayID );
|
||||
sqlite3_int64* value = g_malloc( sizeof( value ) );
|
||||
*value = sqlite3_column_int64( ppStmt, 1 );
|
||||
g_hash_table_insert( table, key, value );
|
||||
/* XP_LOGF( "%s(): added map %s => %lld", __func__, (char*)key, *value ); */
|
||||
}
|
||||
break;
|
||||
case SQLITE_DONE:
|
||||
sqlite3_finalize( ppStmt );
|
||||
ppStmt = NULL;
|
||||
break;
|
||||
default:
|
||||
XP_ASSERT( 0 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
XP_Bool
|
||||
getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
|
||||
{
|
||||
XP_Bool success = XP_FALSE;
|
||||
const char* fmt = "SELECT room, ended, turn, local, nmoves, ntotal, nmissing, "
|
||||
"seed, connvia, gameid, lastMoveTime, snap "
|
||||
"seed, connvia, gameid, lastMoveTime, relayid, snap "
|
||||
"FROM games WHERE rowid = %lld";
|
||||
XP_UCHAR query[256];
|
||||
snprintf( query, sizeof(query), fmt, rowid );
|
||||
|
@ -321,25 +362,28 @@ getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
|
|||
result = sqlite3_step( ppStmt );
|
||||
if ( SQLITE_ROW == result ) {
|
||||
success = XP_TRUE;
|
||||
getColumnText( ppStmt, 0, gib->room, sizeof(gib->room) );
|
||||
gib->gameOver = 1 == sqlite3_column_int( ppStmt, 1 );
|
||||
gib->turn = sqlite3_column_int( ppStmt, 2 );
|
||||
gib->turnLocal = 1 == sqlite3_column_int( ppStmt, 3 );
|
||||
gib->nMoves = sqlite3_column_int( ppStmt, 4 );
|
||||
gib->nTotal = sqlite3_column_int( ppStmt, 5 );
|
||||
gib->nMissing = sqlite3_column_int( ppStmt, 6 );
|
||||
gib->seed = sqlite3_column_int( ppStmt, 7 );
|
||||
getColumnText( ppStmt, 8, gib->conn, sizeof(gib->conn) );
|
||||
gib->gameID = sqlite3_column_int( ppStmt, 9 );
|
||||
gib->lastMoveTime = sqlite3_column_int( ppStmt, 10 );
|
||||
int col = 0;
|
||||
getColumnText( ppStmt, col++, gib->room, sizeof(gib->room) );
|
||||
gib->gameOver = 1 == sqlite3_column_int( ppStmt, col++ );
|
||||
gib->turn = sqlite3_column_int( ppStmt, col++ );
|
||||
gib->turnLocal = 1 == sqlite3_column_int( ppStmt, col++ );
|
||||
gib->nMoves = sqlite3_column_int( ppStmt, col++ );
|
||||
gib->nTotal = sqlite3_column_int( ppStmt, col++ );
|
||||
gib->nMissing = sqlite3_column_int( ppStmt, col++ );
|
||||
gib->seed = sqlite3_column_int( ppStmt, col++ );
|
||||
getColumnText( ppStmt, col++, gib->conn, sizeof(gib->conn) );
|
||||
gib->gameID = sqlite3_column_int( ppStmt, col++ );
|
||||
gib->lastMoveTime = sqlite3_column_int( ppStmt, col++ );
|
||||
getColumnText( ppStmt, col++, gib->relayID, sizeof(gib->relayID) );
|
||||
snprintf( gib->name, sizeof(gib->name), "Game %lld", rowid );
|
||||
|
||||
#ifdef PLATFORM_GTK
|
||||
/* Load the snapshot */
|
||||
GdkPixbuf* snap = NULL;
|
||||
const XP_U8* ptr = sqlite3_column_blob( ppStmt, 11 );
|
||||
int snapCol = col++;
|
||||
const XP_U8* ptr = sqlite3_column_blob( ppStmt, snapCol );
|
||||
if ( !!ptr ) {
|
||||
int size = sqlite3_column_bytes( ppStmt, 11 );
|
||||
int size = sqlite3_column_bytes( ppStmt, snapCol );
|
||||
/* Skip the version that's written in */
|
||||
ptr += sizeof(XP_U16); size -= sizeof(XP_U16);
|
||||
GInputStream* istr = g_memory_input_stream_new_from_data( ptr, size, NULL );
|
||||
|
|
|
@ -31,6 +31,7 @@ typedef struct _GameInfo {
|
|||
XP_UCHAR name[128];
|
||||
XP_UCHAR room[128];
|
||||
XP_UCHAR conn[128];
|
||||
XP_UCHAR relayID[32];
|
||||
#ifdef PLATFORM_GTK
|
||||
GdkPixbuf* snap;
|
||||
#endif
|
||||
|
@ -55,6 +56,9 @@ void summarize( CommonGlobals* cGlobals );
|
|||
|
||||
/* Return GSList whose data is (ptrs to) rowids */
|
||||
GSList* listGames( sqlite3* dbp );
|
||||
/* Mapping of relayID -> rowid */
|
||||
GHashTable* getRelayIDsToRowsMap( sqlite3* pDb );
|
||||
|
||||
XP_Bool getGameInfo( sqlite3* dbp, sqlite3_int64 rowid, GameInfo* gib );
|
||||
void getRowsForGameID( sqlite3* dbp, XP_U32 gameID, sqlite3_int64* rowids,
|
||||
int* nRowIDs );
|
||||
|
|
|
@ -341,6 +341,8 @@ relay_connd_gtk( void* closure, XP_UCHAR* const room,
|
|||
char buf[256];
|
||||
|
||||
if ( allHere ) {
|
||||
/* disable for now. Seeing this too often */
|
||||
skip = XP_TRUE;
|
||||
snprintf( buf, sizeof(buf),
|
||||
"All expected players have joined in %s. Play!", room );
|
||||
} else {
|
||||
|
@ -428,13 +430,57 @@ relay_sendNoConn_gtk( const XP_U8* msg, XP_U16 len,
|
|||
return success;
|
||||
} /* relay_sendNoConn_gtk */
|
||||
|
||||
static void
|
||||
tryConnectToServer(CommonGlobals* cGlobals)
|
||||
{
|
||||
LaunchParams* params = cGlobals->params;
|
||||
XWStreamCtxt* stream =
|
||||
mem_stream_make( cGlobals->util->mpool, params->vtMgr,
|
||||
cGlobals, CHANNEL_NONE,
|
||||
sendOnClose );
|
||||
(void)server_initClientConnection( cGlobals->game.server,
|
||||
stream );
|
||||
}
|
||||
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
static void
|
||||
onJoined( void* closure, const XP_UCHAR* connname, XWHostID hid )
|
||||
{
|
||||
GtkGameGlobals* globals = (GtkGameGlobals*)closure;
|
||||
XWGame* game = &globals->cGlobals.game;
|
||||
CommsCtxt* comms = game->comms;
|
||||
comms_gameJoined( comms, connname, hid );
|
||||
if ( hid > 1 ) {
|
||||
globals->cGlobals.gi->serverRole = SERVER_ISCLIENT;
|
||||
server_reset( game->server, game->comms );
|
||||
tryConnectToServer( &globals->cGlobals );
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
relay_requestJoin_gtk( void* closure, const XP_UCHAR* devID, const XP_UCHAR* room,
|
||||
XP_U16 nPlayersHere, XP_U16 nPlayersTotal,
|
||||
XP_U16 seed, XP_U16 lang )
|
||||
{
|
||||
GtkGameGlobals* globals = (GtkGameGlobals*)closure;
|
||||
LaunchParams* params = globals->cGlobals.params;
|
||||
relaycon_join( params, devID, room, nPlayersHere, nPlayersTotal, seed, lang,
|
||||
onJoined, globals );
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef COMMS_XPORT_FLAGSPROC
|
||||
static XP_U32
|
||||
gtk_getFlags( void* closure )
|
||||
{
|
||||
GtkGameGlobals* globals = (GtkGameGlobals*)closure;
|
||||
# ifdef RELAY_VIA_HTTP
|
||||
XP_USE( globals );
|
||||
return COMMS_XPORT_FLAGS_HASNOCONN;
|
||||
# else
|
||||
return (!!globals->draw) ? COMMS_XPORT_FLAGS_NONE
|
||||
: COMMS_XPORT_FLAGS_HASNOCONN;
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -454,6 +500,9 @@ setTransportProcs( TransportProcs* procs, GtkGameGlobals* globals )
|
|||
procs->rconnd = relay_connd_gtk;
|
||||
procs->rerror = relay_error_gtk;
|
||||
procs->sendNoConn = relay_sendNoConn_gtk;
|
||||
# ifdef RELAY_VIA_HTTP
|
||||
procs->requestJoin = relay_requestJoin_gtk;
|
||||
# endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -663,12 +712,7 @@ createOrLoadObjects( GtkGameGlobals* globals )
|
|||
} else {
|
||||
DeviceRole serverRole = cGlobals->gi->serverRole;
|
||||
if ( serverRole == SERVER_ISCLIENT ) {
|
||||
XWStreamCtxt* stream =
|
||||
mem_stream_make( MEMPOOL params->vtMgr,
|
||||
cGlobals, CHANNEL_NONE,
|
||||
sendOnClose );
|
||||
(void)server_initClientConnection( cGlobals->game.server,
|
||||
stream );
|
||||
tryConnectToServer( cGlobals );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -1014,12 +1058,7 @@ new_game_impl( GtkGameGlobals* globals, XP_Bool fireConnDlg )
|
|||
}
|
||||
|
||||
if ( isClient ) {
|
||||
XWStreamCtxt* stream =
|
||||
mem_stream_make( MEMPOOL cGlobals->params->vtMgr,
|
||||
cGlobals, CHANNEL_NONE,
|
||||
sendOnClose );
|
||||
(void)server_initClientConnection( cGlobals->game.server,
|
||||
stream );
|
||||
tryConnectToServer( cGlobals );
|
||||
}
|
||||
#endif
|
||||
(void)server_do( cGlobals->game.server ); /* assign tiles, etc. */
|
||||
|
@ -1175,6 +1214,7 @@ handle_memstats( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
|
|||
stream_destroy( stream );
|
||||
|
||||
} /* handle_memstats */
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef XWFEATURE_ACTIVERECT
|
||||
|
@ -1199,15 +1239,15 @@ frame_active( GtkWidget* XP_UNUSED(widget), GtkGameGlobals* globals )
|
|||
}
|
||||
#endif
|
||||
|
||||
static GtkWidget*
|
||||
GtkWidget*
|
||||
createAddItem( GtkWidget* parent, gchar* label,
|
||||
GCallback handlerFunc, GtkGameGlobals* globals )
|
||||
GCallback handlerFunc, gpointer closure )
|
||||
{
|
||||
GtkWidget* item = gtk_menu_item_new_with_label( label );
|
||||
|
||||
if ( handlerFunc != NULL ) {
|
||||
g_signal_connect( item, "activate", G_CALLBACK(handlerFunc),
|
||||
globals );
|
||||
closure );
|
||||
}
|
||||
|
||||
gtk_menu_shell_append( GTK_MENU_SHELL(parent), item );
|
||||
|
@ -1302,7 +1342,7 @@ static void
|
|||
disenable_buttons( GtkGameGlobals* globals )
|
||||
{
|
||||
XP_U16 nPending = server_getPendingRegs( globals->cGlobals.game.server );
|
||||
if ( !globals->invite_button && 0 < nPending ) {
|
||||
if ( !globals->invite_button && 0 < nPending && !!globals->buttons_hbox ) {
|
||||
globals->invite_button =
|
||||
addButton( globals->buttons_hbox, "Invite",
|
||||
G_CALLBACK(handle_invite_button), globals );
|
||||
|
@ -1600,6 +1640,9 @@ send_invites( CommonGlobals* cGlobals, XP_U16 nPlayers,
|
|||
|
||||
NetLaunchInfo nli = {0};
|
||||
nli_init( &nli, cGlobals->gi, &addr, nPlayers, forceChannel );
|
||||
XP_UCHAR buf[32];
|
||||
snprintf( buf, sizeof(buf), "%X", makeRandomInt() );
|
||||
nli_setInviteID( &nli, buf );
|
||||
nli_setDevID( &nli, linux_getDevIDRelay( cGlobals->params ) );
|
||||
|
||||
#ifdef DEBUG
|
||||
|
|
|
@ -46,6 +46,10 @@ typedef struct GtkDrawCtx {
|
|||
/* GdkDrawable* pixmap; */
|
||||
GtkWidget* drawing_area;
|
||||
cairo_surface_t* surface;
|
||||
#ifdef GDK_AVAILABLE_IN_3_22
|
||||
GdkDrawingContext* dc;
|
||||
#endif
|
||||
|
||||
struct GtkGameGlobals* globals;
|
||||
|
||||
#ifdef USE_CAIRO
|
||||
|
@ -187,6 +191,10 @@ XP_Bool loadGameNoDraw( GtkGameGlobals* globals, LaunchParams* params,
|
|||
sqlite3_int64 rowid );
|
||||
void destroy_board_window( GtkWidget* widget, GtkGameGlobals* globals );
|
||||
|
||||
GtkWidget* makeAddSubmenu( GtkWidget* menubar, gchar* label );
|
||||
GtkWidget* createAddItem( GtkWidget* parent, gchar* label,
|
||||
GCallback handlerFunc, gpointer closure );
|
||||
|
||||
#endif /* PLATFORM_GTK */
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
|
||||
/* -*- compile-command: "make MEMDEBUG=TRUE -j5"; -*- */
|
||||
/*
|
||||
* Copyright 1997-2011 by Eric House (xwords@eehouse.org). All rights
|
||||
* Copyright 1997 - 2017 by Eric House (xwords@eehouse.org). All rights
|
||||
* reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -86,7 +86,14 @@ initCairo( GtkDrawCtx* dctx )
|
|||
if ( !!dctx->surface ) {
|
||||
cairo = cairo_create( dctx->surface );
|
||||
} else if ( !!dctx->drawing_area ) {
|
||||
#ifdef GDK_AVAILABLE_IN_3_22
|
||||
GdkWindow* window = gtk_widget_get_window( dctx->drawing_area );
|
||||
const cairo_region_t* region = gdk_window_get_visible_region( window );
|
||||
dctx->dc = gdk_window_begin_draw_frame( window, region );
|
||||
cairo = gdk_drawing_context_get_cairo_context( dctx->dc );
|
||||
#else
|
||||
cairo = gdk_cairo_create( gtk_widget_get_window(dctx->drawing_area) );
|
||||
#endif
|
||||
} else {
|
||||
XP_ASSERT( 0 );
|
||||
}
|
||||
|
@ -108,7 +115,12 @@ destroyCairo( GtkDrawCtx* dctx )
|
|||
{
|
||||
/* XP_LOGF( "%s(dctx=%p)", __func__, dctx ); */
|
||||
XP_ASSERT( !!dctx->_cairo );
|
||||
cairo_destroy(dctx->_cairo);
|
||||
#ifdef GDK_AVAILABLE_IN_3_22
|
||||
GdkWindow* window = gtk_widget_get_window( dctx->drawing_area );
|
||||
gdk_window_end_draw_frame( window, dctx->dc );
|
||||
#else
|
||||
cairo_destroy( dctx->_cairo );
|
||||
#endif
|
||||
dctx->_cairo = NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ findOpenGame( const GtkAppGlobals* apg, sqlite3_int64 rowid )
|
|||
}
|
||||
|
||||
enum { ROW_ITEM, ROW_THUMB, NAME_ITEM, ROOM_ITEM, GAMEID_ITEM, SEED_ITEM,
|
||||
CONN_ITEM, OVER_ITEM, TURN_ITEM, LOCAL_ITEM, NMOVES_ITEM, NTOTAL_ITEM,
|
||||
CONN_ITEM, RELAYID_ITEM, OVER_ITEM, TURN_ITEM, LOCAL_ITEM, NMOVES_ITEM, NTOTAL_ITEM,
|
||||
MISSING_ITEM, LASTTURN_ITEM, N_ITEMS };
|
||||
|
||||
static void
|
||||
|
@ -167,6 +167,7 @@ init_games_list( GtkAppGlobals* apg )
|
|||
addTextColumn( list, "GameID", GAMEID_ITEM );
|
||||
addTextColumn( list, "Seed", SEED_ITEM );
|
||||
addTextColumn( list, "Conn. via", CONN_ITEM );
|
||||
addTextColumn( list, "RelayID", RELAYID_ITEM );
|
||||
addTextColumn( list, "Ended", OVER_ITEM );
|
||||
addTextColumn( list, "Turn", TURN_ITEM );
|
||||
addTextColumn( list, "Local", LOCAL_ITEM );
|
||||
|
@ -183,6 +184,7 @@ init_games_list( GtkAppGlobals* apg )
|
|||
G_TYPE_INT, /* GAMEID_ITEM */
|
||||
G_TYPE_INT, /* SEED_ITEM */
|
||||
G_TYPE_STRING, /* CONN_ITEM */
|
||||
G_TYPE_STRING, /*RELAYID_ITEM */
|
||||
G_TYPE_BOOLEAN, /* OVER_ITEM */
|
||||
G_TYPE_INT, /* TURN_ITEM */
|
||||
G_TYPE_STRING, /* LOCAL_ITEM */
|
||||
|
@ -239,6 +241,7 @@ add_to_list( GtkWidget* list, sqlite3_int64 rowid, XP_Bool isNew,
|
|||
GAMEID_ITEM, gib->gameID,
|
||||
SEED_ITEM, gib->seed,
|
||||
CONN_ITEM, gib->conn,
|
||||
RELAYID_ITEM, gib->relayID,
|
||||
TURN_ITEM, gib->turn,
|
||||
OVER_ITEM, gib->gameOver,
|
||||
LOCAL_ITEM, localString,
|
||||
|
@ -506,6 +509,13 @@ trySetWinConfig( GtkAppGlobals* apg )
|
|||
gtk_window_move (GTK_WINDOW(apg->window), xx, yy );
|
||||
}
|
||||
|
||||
static void
|
||||
handle_movescheck( GtkWidget* XP_UNUSED(widget), GtkAppGlobals* apg )
|
||||
{
|
||||
LaunchParams* params = apg->params;
|
||||
relaycon_checkMsgs( params );
|
||||
}
|
||||
|
||||
static void
|
||||
makeGamesWindow( GtkAppGlobals* apg )
|
||||
{
|
||||
|
@ -529,6 +539,17 @@ makeGamesWindow( GtkAppGlobals* apg )
|
|||
GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_container_add( GTK_CONTAINER(swin), vbox );
|
||||
gtk_widget_show( vbox );
|
||||
|
||||
// add menubar here
|
||||
GtkWidget* menubar = gtk_menu_bar_new();
|
||||
GtkWidget* netMenu = makeAddSubmenu( menubar, "Network" );
|
||||
if ( params->useHTTP ) {
|
||||
(void)createAddItem( netMenu, "Check for moves",
|
||||
(GCallback)handle_movescheck, apg );
|
||||
}
|
||||
gtk_widget_show( menubar );
|
||||
gtk_box_pack_start( GTK_BOX(vbox), menubar, FALSE, TRUE, 0 );
|
||||
|
||||
GtkWidget* list = init_games_list( apg );
|
||||
gtk_container_add( GTK_CONTAINER(vbox), list );
|
||||
|
||||
|
@ -693,6 +714,17 @@ gtkGotBuf( void* closure, const CommsAddrRec* from,
|
|||
XP_USE( seed );
|
||||
}
|
||||
|
||||
static void
|
||||
gtkGotMsgForRow( void* closure, const CommsAddrRec* from,
|
||||
sqlite3_int64 rowid, const XP_U8* buf, XP_U16 len )
|
||||
{
|
||||
XP_LOGF( "%s(): got msg of len %d for row %lld", __func__, len, rowid );
|
||||
GtkAppGlobals* apg = (GtkAppGlobals*)closure;
|
||||
// LaunchParams* params = apg->params;
|
||||
(void)feedBufferGTK( apg, rowid, buf, len, from );
|
||||
LOG_RETURN_VOID();
|
||||
}
|
||||
|
||||
static gint
|
||||
requestMsgs( gpointer data )
|
||||
{
|
||||
|
@ -847,6 +879,7 @@ gtkmain( LaunchParams* params )
|
|||
if ( params->useUdp ) {
|
||||
RelayConnProcs procs = {
|
||||
.msgReceived = gtkGotBuf,
|
||||
.msgForRow = gtkGotMsgForRow,
|
||||
.msgNoticeReceived = gtkNoticeRcvd,
|
||||
.devIDReceived = gtkDevIDReceived,
|
||||
.msgErrorMsg = gtkErrorMsgRcvd,
|
||||
|
|
|
@ -634,6 +634,8 @@ typedef enum {
|
|||
,CMD_CHAT
|
||||
,CMD_USEUDP
|
||||
,CMD_NOUDP
|
||||
,CMD_USEHTTP
|
||||
,CMD_NOHTTPAUTO
|
||||
,CMD_DROPSENDRELAY
|
||||
,CMD_DROPRCVRELAY
|
||||
,CMD_DROPSENDSMS
|
||||
|
@ -752,6 +754,8 @@ static CmdInfoRec CmdInfoRecs[] = {
|
|||
,{ CMD_CHAT, true, "send-chat", "send a chat every <n> seconds" }
|
||||
,{ CMD_USEUDP, false, "use-udp", "connect to relay new-style, via udp not tcp (on by default)" }
|
||||
,{ CMD_NOUDP, false, "no-use-udp", "connect to relay old-style, via tcp not udp" }
|
||||
,{ CMD_USEHTTP, false, "use-http", "use relay's new http interfaces rather than sockets" }
|
||||
,{ CMD_NOHTTPAUTO, false, "no-http-auto", "When http's on, don't periodically connect to relay (manual only)" }
|
||||
|
||||
,{ 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" }
|
||||
|
@ -973,6 +977,7 @@ linux_setupDevidParams( LaunchParams* params )
|
|||
static int
|
||||
linux_init_relay_socket( CommonGlobals* cGlobals, const CommsAddrRec* addrRec )
|
||||
{
|
||||
XP_ASSERT( !cGlobals->params->useHTTP );
|
||||
struct sockaddr_in to_sock;
|
||||
struct hostent* host;
|
||||
int sock = cGlobals->relaySocket;
|
||||
|
@ -1174,6 +1179,7 @@ linux_relay_send( CommonGlobals* cGlobals, const XP_U8* buf, XP_U16 buflen,
|
|||
result = relaycon_send( cGlobals->params, buf, buflen,
|
||||
clientToken, addrRec );
|
||||
} else {
|
||||
XP_ASSERT( !cGlobals->params->useHTTP );
|
||||
int sock = cGlobals->relaySocket;
|
||||
|
||||
if ( sock == -1 ) {
|
||||
|
@ -1552,8 +1558,8 @@ linuxChangeRoles( CommonGlobals* cGlobals )
|
|||
}
|
||||
#endif
|
||||
|
||||
static unsigned int
|
||||
defaultRandomSeed()
|
||||
unsigned int
|
||||
makeRandomInt()
|
||||
{
|
||||
/* use kernel device rather than time() so can run multiple times/second
|
||||
without getting the same results. */
|
||||
|
@ -2028,7 +2034,7 @@ main( int argc, char** argv )
|
|||
XP_Bool isServer = XP_FALSE;
|
||||
// char* portNum = NULL;
|
||||
// char* hostName = "localhost";
|
||||
unsigned int seed = defaultRandomSeed();
|
||||
unsigned int seed = makeRandomInt();
|
||||
LaunchParams mainParams;
|
||||
XP_U16 nPlayerDicts = 0;
|
||||
XP_U16 robotCount = 0;
|
||||
|
@ -2284,6 +2290,7 @@ main( int argc, char** argv )
|
|||
break;
|
||||
case CMD_PLAYERNAME:
|
||||
index = mainParams.pgi.nPlayers++;
|
||||
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||
++mainParams.nLocalPlayers;
|
||||
mainParams.pgi.players[index].robotIQ = 0; /* means human */
|
||||
mainParams.pgi.players[index].isLocal = XP_TRUE;
|
||||
|
@ -2292,6 +2299,7 @@ main( int argc, char** argv )
|
|||
break;
|
||||
case CMD_REMOTEPLAYER:
|
||||
index = mainParams.pgi.nPlayers++;
|
||||
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||
mainParams.pgi.players[index].isLocal = XP_FALSE;
|
||||
++mainParams.info.serverInfo.nRemotePlayers;
|
||||
break;
|
||||
|
@ -2302,6 +2310,7 @@ main( int argc, char** argv )
|
|||
case CMD_ROBOTNAME:
|
||||
++robotCount;
|
||||
index = mainParams.pgi.nPlayers++;
|
||||
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||
++mainParams.nLocalPlayers;
|
||||
mainParams.pgi.players[index].robotIQ = 1; /* real smart by default */
|
||||
mainParams.pgi.players[index].isLocal = XP_TRUE;
|
||||
|
@ -2398,6 +2407,12 @@ main( int argc, char** argv )
|
|||
case CMD_NOUDP:
|
||||
mainParams.useUdp = false;
|
||||
break;
|
||||
case CMD_USEHTTP:
|
||||
mainParams.useHTTP = true;
|
||||
break;
|
||||
case CMD_NOHTTPAUTO:
|
||||
mainParams.noHTTPAuto = true;
|
||||
break;
|
||||
|
||||
case CMD_DROPSENDRELAY:
|
||||
mainParams.commsDisableds[COMMS_CONN_RELAY][1] = XP_TRUE;
|
||||
|
@ -2487,10 +2502,10 @@ main( int argc, char** argv )
|
|||
mainParams.dictDirs = g_slist_append( mainParams.dictDirs, "./" );
|
||||
}
|
||||
|
||||
if ( isServer ) {
|
||||
if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) {
|
||||
mainParams.pgi.serverRole = SERVER_STANDALONE;
|
||||
} else {
|
||||
if ( mainParams.info.serverInfo.nRemotePlayers == 0 ) {
|
||||
mainParams.pgi.serverRole = SERVER_STANDALONE;
|
||||
} else if ( isServer ) {
|
||||
if ( mainParams.info.serverInfo.nRemotePlayers > 0 ) {
|
||||
mainParams.pgi.serverRole = SERVER_ISSERVER;
|
||||
}
|
||||
} else {
|
||||
|
@ -2646,7 +2661,8 @@ main( int argc, char** argv )
|
|||
if ( mainParams.useCurses ) {
|
||||
if ( mainParams.needsNewGame ) {
|
||||
/* curses doesn't have newgame dialog */
|
||||
usage( argv[0], "game params required for curses version" );
|
||||
usage( argv[0], "game params required for curses version, e.g. --name Eric --room MyRoom"
|
||||
" --remote-player --dict-dir ../ --game-dict CollegeEng_2to8.xwd");
|
||||
} else {
|
||||
#if defined PLATFORM_NCURSES
|
||||
cursesmain( isServer, &mainParams );
|
||||
|
|
|
@ -111,6 +111,8 @@ const XP_UCHAR* linux_getDevID( LaunchParams* params, DevIDType* typ );
|
|||
void linux_doInitialReg( LaunchParams* params, XP_Bool idIsNew );
|
||||
XP_Bool linux_setupDevidParams( LaunchParams* params );
|
||||
|
||||
unsigned int makeRandomInt();
|
||||
|
||||
/* void initParams( LaunchParams* params ); */
|
||||
/* void freeParams( LaunchParams* params ); */
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
void
|
||||
linux_debugf( const char* format, ... )
|
||||
{
|
||||
char buf[1000];
|
||||
char buf[1024*8];
|
||||
va_list ap;
|
||||
struct tm* timp;
|
||||
struct timeval tv;
|
||||
|
@ -50,14 +50,17 @@ linux_debugf( const char* format, ... )
|
|||
gettimeofday( &tv, &tz );
|
||||
timp = localtime( &tv.tv_sec );
|
||||
|
||||
snprintf( buf, sizeof(buf), "<%d>%.2d:%.2d:%.2d:", getpid(),
|
||||
timp->tm_hour, timp->tm_min, timp->tm_sec );
|
||||
size_t len = snprintf( buf, sizeof(buf), "<%d:%lx>%.2d:%.2d:%.2d:", getpid(),
|
||||
pthread_self(), timp->tm_hour, timp->tm_min, timp->tm_sec );
|
||||
XP_ASSERT( len < sizeof(buf) );
|
||||
|
||||
va_start(ap, format);
|
||||
|
||||
vsprintf(buf+strlen(buf), format, ap);
|
||||
|
||||
len = vsprintf(buf+strlen(buf), format, ap);
|
||||
va_end(ap);
|
||||
|
||||
if ( len >= sizeof(buf) ) {
|
||||
buf[sizeof(buf)-1] = '\0';
|
||||
}
|
||||
|
||||
fprintf( stderr, "%s\n", buf );
|
||||
}
|
||||
|
|
|
@ -105,6 +105,8 @@ typedef struct LaunchParams {
|
|||
XP_Bool closeStdin;
|
||||
XP_Bool useCurses;
|
||||
XP_Bool useUdp;
|
||||
XP_Bool useHTTP;
|
||||
XP_Bool noHTTPAuto;
|
||||
XP_U16 splitPackets;
|
||||
XP_U16 chatsInterval; /* 0 means disabled */
|
||||
XP_U16 askTimeout;
|
||||
|
|
|
@ -20,12 +20,29 @@
|
|||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <curl/curl.h>
|
||||
#include <json-c/json.h>
|
||||
|
||||
|
||||
#include "relaycon.h"
|
||||
#include "linuxmain.h"
|
||||
#include "comtypes.h"
|
||||
#include "gamesdb.h"
|
||||
|
||||
#define MAX_MOVE_CHECK_MS ((XP_U16)(1000 * 60 * 60 * 24))
|
||||
#define RELAY_API_PROTO "http"
|
||||
|
||||
typedef struct _RelayConStorage {
|
||||
pthread_t mainThread;
|
||||
guint moveCheckerID;
|
||||
XP_U32 nextMoveCheckMS;
|
||||
pthread_cond_t relayCondVar;
|
||||
pthread_mutex_t relayMutex;
|
||||
GSList* relayTaskList;
|
||||
|
||||
pthread_mutex_t gotDataMutex;
|
||||
GSList* gotDataTaskList;
|
||||
|
||||
int socket;
|
||||
RelayConnProcs procs;
|
||||
void* procsClosure;
|
||||
|
@ -33,6 +50,8 @@ typedef struct _RelayConStorage {
|
|||
uint32_t nextID;
|
||||
XWPDevProto proto;
|
||||
LaunchParams* params;
|
||||
XP_UCHAR host[64];
|
||||
int nextTaskID;
|
||||
} RelayConStorage;
|
||||
|
||||
typedef struct _MsgHeader {
|
||||
|
@ -41,10 +60,16 @@ typedef struct _MsgHeader {
|
|||
} MsgHeader;
|
||||
|
||||
static RelayConStorage* getStorage( LaunchParams* params );
|
||||
static XP_Bool onMainThread( RelayConStorage* storage );
|
||||
static XP_U32 hostNameToIP( const XP_UCHAR* name );
|
||||
static gboolean relaycon_receive( GIOChannel *source, GIOCondition condition,
|
||||
gpointer data );
|
||||
static ssize_t sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len );
|
||||
static void schedule_next_check( RelayConStorage* storage );
|
||||
static void reset_schedule_check_interval( RelayConStorage* storage );
|
||||
static void checkForMovesOnce( RelayConStorage* storage );
|
||||
static gboolean gotDataTimer(gpointer user_data);
|
||||
|
||||
static ssize_t sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len, float timeoutSecs );
|
||||
static size_t addVLIStr( XP_U8* buf, size_t len, const XP_UCHAR* str );
|
||||
static void getNetString( const XP_U8** ptr, XP_U16 len, XP_UCHAR* buf );
|
||||
static XP_U16 getNetShort( const XP_U8** ptr );
|
||||
|
@ -59,7 +84,160 @@ static size_t writeBytes( XP_U8* buf, size_t len, const XP_U8* bytes,
|
|||
static size_t writeVLI( XP_U8* out, uint32_t nn );
|
||||
static size_t un2vli( int nn, uint8_t* buf );
|
||||
static bool vli2un( const uint8_t** inp, uint32_t* outp );
|
||||
#ifdef DEBUG
|
||||
static const char* msgToStr( XWRelayReg msg );
|
||||
#endif
|
||||
|
||||
static void* relayThread( void* arg );
|
||||
|
||||
typedef struct _WriteState {
|
||||
gchar* ptr;
|
||||
size_t curSize;
|
||||
} WriteState;
|
||||
|
||||
typedef enum {
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
JOIN,
|
||||
#endif
|
||||
POST, QUERY, } TaskType;
|
||||
|
||||
typedef struct _RelayTask {
|
||||
TaskType typ;
|
||||
int id;
|
||||
RelayConStorage* storage;
|
||||
WriteState ws;
|
||||
XP_U32 ctime;
|
||||
union {
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
struct {
|
||||
XP_U16 lang;
|
||||
XP_U16 nHere;
|
||||
XP_U16 nTotal;
|
||||
XP_U16 seed;
|
||||
XP_UCHAR devID[64];
|
||||
XP_UCHAR room[MAX_INVITE_LEN + 1];
|
||||
OnJoinedProc proc;
|
||||
void* closure;
|
||||
} join;
|
||||
#endif
|
||||
struct {
|
||||
XP_U8* msgbuf;
|
||||
XP_U16 len;
|
||||
float timeoutSecs;
|
||||
} post;
|
||||
struct {
|
||||
GHashTable* map;
|
||||
} query;
|
||||
} u;
|
||||
} RelayTask;
|
||||
|
||||
static RelayTask* makeRelayTask( RelayConStorage* storage, TaskType typ );
|
||||
static void freeRelayTask(RelayTask* task);
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
static void handleJoin( RelayTask* task );
|
||||
#endif
|
||||
static void handlePost( RelayTask* task );
|
||||
static void handleQuery( RelayTask* task );
|
||||
static void addToGotData( RelayTask* task );
|
||||
static RelayTask* getFromGotData( RelayConStorage* storage );
|
||||
|
||||
static size_t
|
||||
write_callback(void *contents, size_t size, size_t nmemb, void* data)
|
||||
{
|
||||
WriteState* ws = (WriteState*)data;
|
||||
|
||||
if ( !ws->ptr ) {
|
||||
ws->ptr = g_malloc0(1);
|
||||
ws->curSize = 1L;
|
||||
}
|
||||
|
||||
XP_LOGF( "%s(size=%ld, nmemb=%ld)", __func__, size, nmemb );
|
||||
size_t oldLen = ws->curSize;
|
||||
const size_t newLength = size * nmemb;
|
||||
XP_ASSERT( (oldLen + newLength) > 0 );
|
||||
ws->ptr = g_realloc( ws->ptr, oldLen + newLength );
|
||||
memcpy( ws->ptr + oldLen - 1, contents, newLength );
|
||||
ws->ptr[oldLen + newLength - 1] = '\0';
|
||||
// XP_LOGF( "%s() => %ld: (passed: \"%s\")", __func__, result, *strp );
|
||||
return newLength;
|
||||
}
|
||||
|
||||
static gchar*
|
||||
mkJsonParams( CURL* curl, va_list ap )
|
||||
{
|
||||
json_object* params = json_object_new_object();
|
||||
for ( ; ; ) {
|
||||
const char* name = va_arg(ap, const char*);
|
||||
if ( !name ) {
|
||||
break;
|
||||
}
|
||||
json_object* param = va_arg(ap, json_object*);
|
||||
XP_ASSERT( !!param );
|
||||
|
||||
json_object_object_add( params, name, param );
|
||||
// XP_LOGF( "%s: adding param (with name %s): %s", __func__, name, json_object_get_string(param) );
|
||||
}
|
||||
|
||||
const char* asStr = json_object_get_string( params );
|
||||
char* curl_params = curl_easy_escape( curl, asStr, strlen(asStr) );
|
||||
gchar* result = g_strdup_printf( "params=%s", curl_params );
|
||||
XP_LOGF( "%s: adding: params=%s (%s)", __func__, asStr, curl_params );
|
||||
curl_free( curl_params );
|
||||
json_object_put( params );
|
||||
return result;
|
||||
}
|
||||
|
||||
/* relay.py's methods all take one json object param "param" So we wrap
|
||||
everything else in that then send it. */
|
||||
static XP_Bool
|
||||
runWitCurl( RelayTask* task, const gchar* proc, ...)
|
||||
{
|
||||
CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
XP_ASSERT(res == CURLE_OK);
|
||||
CURL* curl = curl_easy_init();
|
||||
|
||||
char url[128];
|
||||
snprintf( url, sizeof(url), "%s://%s/xw4/relay.py/%s",
|
||||
RELAY_API_PROTO, task->storage->host, proc );
|
||||
curl_easy_setopt( curl, CURLOPT_URL, url );
|
||||
curl_easy_setopt( curl, CURLOPT_POST, 1L );
|
||||
|
||||
va_list ap;
|
||||
va_start( ap, proc );
|
||||
gchar* params = mkJsonParams( curl, ap );
|
||||
va_end( ap );
|
||||
|
||||
curl_easy_setopt( curl, CURLOPT_POSTFIELDS, params );
|
||||
curl_easy_setopt( curl, CURLOPT_POSTFIELDSIZE, (long)strlen(params) );
|
||||
|
||||
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, write_callback );
|
||||
curl_easy_setopt( curl, CURLOPT_WRITEDATA, &task->ws );
|
||||
// curl_easy_setopt( curl, CURLOPT_VERBOSE, 1L );
|
||||
|
||||
res = curl_easy_perform(curl);
|
||||
XP_Bool success = res == CURLE_OK;
|
||||
XP_LOGF( "%s(): curl_easy_perform(%s) => %d", __func__, proc, res );
|
||||
/* Check for errors */
|
||||
if ( ! success ) {
|
||||
XP_LOGF( "curl_easy_perform() failed: %s", curl_easy_strerror(res));
|
||||
} else {
|
||||
XP_LOGF( "%s(): got for %s: \"%s\"", __func__, proc, task->ws.ptr );
|
||||
}
|
||||
/* always cleanup */
|
||||
curl_easy_cleanup(curl);
|
||||
curl_global_cleanup();
|
||||
g_free( params );
|
||||
return success;
|
||||
}
|
||||
|
||||
void
|
||||
relaycon_checkMsgs( LaunchParams* params )
|
||||
{
|
||||
LOG_FUNC();
|
||||
RelayConStorage* storage = getStorage( params );
|
||||
XP_ASSERT( onMainThread(storage) );
|
||||
checkForMovesOnce( storage );
|
||||
}
|
||||
|
||||
void
|
||||
relaycon_init( LaunchParams* params, const RelayConnProcs* procs,
|
||||
|
@ -70,17 +248,36 @@ relaycon_init( LaunchParams* params, const RelayConnProcs* procs,
|
|||
XP_MEMCPY( &storage->procs, procs, sizeof(storage->procs) );
|
||||
storage->procsClosure = procsClosure;
|
||||
|
||||
storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
|
||||
(*procs->socketAdded)( storage, storage->socket, relaycon_receive );
|
||||
if ( params->useHTTP ) {
|
||||
storage->mainThread = pthread_self();
|
||||
pthread_mutex_init( &storage->relayMutex, NULL );
|
||||
pthread_cond_init( &storage->relayCondVar, NULL );
|
||||
pthread_t thread;
|
||||
(void)pthread_create( &thread, NULL, relayThread, storage );
|
||||
pthread_detach( thread );
|
||||
|
||||
XP_MEMSET( &storage->saddr, 0, sizeof(storage->saddr) );
|
||||
storage->saddr.sin_family = PF_INET;
|
||||
storage->saddr.sin_addr.s_addr = htonl( hostNameToIP(host) );
|
||||
storage->saddr.sin_port = htons(port);
|
||||
pthread_mutex_init( &storage->gotDataMutex, NULL );
|
||||
g_timeout_add( 50, gotDataTimer, storage );
|
||||
|
||||
XP_ASSERT( XP_STRLEN(host) < VSIZE(storage->host) );
|
||||
XP_MEMCPY( storage->host, host, XP_STRLEN(host) + 1 );
|
||||
} else {
|
||||
storage->socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
|
||||
(*procs->socketAdded)( storage, storage->socket, relaycon_receive );
|
||||
|
||||
XP_MEMSET( &storage->saddr, 0, sizeof(storage->saddr) );
|
||||
storage->saddr.sin_family = PF_INET;
|
||||
storage->saddr.sin_addr.s_addr = htonl( hostNameToIP(host) );
|
||||
storage->saddr.sin_port = htons(port);
|
||||
|
||||
}
|
||||
storage->params = params;
|
||||
|
||||
storage->proto = XWPDEV_PROTO_VERSION_1;
|
||||
|
||||
if ( params->useHTTP ) {
|
||||
schedule_next_check( storage );
|
||||
}
|
||||
}
|
||||
|
||||
/* Send existing relay-assigned rDevID to relay, or empty string if we have
|
||||
|
@ -109,7 +306,7 @@ relaycon_reg( LaunchParams* params, const XP_UCHAR* rDevID,
|
|||
indx += addVLIStr( &tmpbuf[indx], sizeof(tmpbuf) - indx, "linux box" );
|
||||
indx += addVLIStr( &tmpbuf[indx], sizeof(tmpbuf) - indx, "linux version" );
|
||||
|
||||
sendIt( storage, tmpbuf, indx );
|
||||
sendIt( storage, tmpbuf, indx, 0.5 );
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -146,7 +343,7 @@ relaycon_invite( LaunchParams* params, XP_U32 destDevID,
|
|||
indx += writeBytes( &tmpbuf[indx], sizeof(tmpbuf) - indx, ptr, len );
|
||||
stream_destroy( stream );
|
||||
|
||||
sendIt( storage, tmpbuf, indx );
|
||||
sendIt( storage, tmpbuf, indx, 0.5 );
|
||||
LOG_RETURN_VOID();
|
||||
}
|
||||
|
||||
|
@ -163,7 +360,7 @@ relaycon_send( LaunchParams* params, const XP_U8* buf, XP_U16 buflen,
|
|||
indx += writeHeader( storage, tmpbuf, XWPDEV_MSG );
|
||||
indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, gameToken );
|
||||
indx += writeBytes( &tmpbuf[indx], sizeof(tmpbuf) - indx, buf, buflen );
|
||||
nSent = sendIt( storage, tmpbuf, indx );
|
||||
nSent = sendIt( storage, tmpbuf, indx, 0.5 );
|
||||
if ( nSent > buflen ) {
|
||||
nSent = buflen;
|
||||
}
|
||||
|
@ -191,7 +388,7 @@ relaycon_sendnoconn( LaunchParams* params, const XP_U8* buf, XP_U16 buflen,
|
|||
(const XP_U8*)relayID, idLen );
|
||||
tmpbuf[indx++] = '\n';
|
||||
indx += writeBytes( &tmpbuf[indx], sizeof(tmpbuf) - indx, buf, buflen );
|
||||
nSent = sendIt( storage, tmpbuf, indx );
|
||||
nSent = sendIt( storage, tmpbuf, indx, 0.5 );
|
||||
if ( nSent > buflen ) {
|
||||
nSent = buflen;
|
||||
}
|
||||
|
@ -210,7 +407,7 @@ relaycon_requestMsgs( LaunchParams* params, const XP_UCHAR* devID )
|
|||
indx += writeHeader( storage, tmpbuf, XWPDEV_RQSTMSGS );
|
||||
indx += addVLIStr( &tmpbuf[indx], sizeof(tmpbuf) - indx, devID );
|
||||
|
||||
sendIt( storage, tmpbuf, indx );
|
||||
sendIt( storage, tmpbuf, indx, 0.5 );
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -225,9 +422,170 @@ relaycon_deleted( LaunchParams* params, const XP_UCHAR* devID,
|
|||
indx += writeDevID( &tmpbuf[indx], sizeof(tmpbuf) - indx, devID );
|
||||
indx += writeLong( &tmpbuf[indx], sizeof(tmpbuf) - indx, gameToken );
|
||||
|
||||
sendIt( storage, tmpbuf, indx );
|
||||
sendIt( storage, tmpbuf, indx, 0.1 );
|
||||
}
|
||||
|
||||
static XP_Bool
|
||||
onMainThread( RelayConStorage* storage )
|
||||
{
|
||||
return storage->mainThread = pthread_self();
|
||||
}
|
||||
|
||||
static const gchar*
|
||||
taskName( const RelayTask* task )
|
||||
{
|
||||
const char* str;
|
||||
# define CASE_STR(c) case c: str = #c; break
|
||||
switch (task->typ) {
|
||||
CASE_STR(POST);
|
||||
CASE_STR(QUERY);
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
CASE_STR(JOIN);
|
||||
#endif
|
||||
default: XP_ASSERT(0);
|
||||
str = NULL;
|
||||
}
|
||||
#undef CASE_STR
|
||||
return str;
|
||||
}
|
||||
|
||||
static gchar*
|
||||
listTasks( GSList* tasks )
|
||||
{
|
||||
XP_U32 now = (XP_U32)time(NULL);
|
||||
gchar* names[1 + g_slist_length(tasks)];
|
||||
int len = g_slist_length(tasks);
|
||||
names[len] = NULL;
|
||||
for ( int ii = 0; !!tasks; ++ii ) {
|
||||
RelayTask* task = (RelayTask*)tasks->data;
|
||||
names[ii] = g_strdup_printf( "{%s:id:%d;age:%ds}", taskName(task),
|
||||
task->id, now - task->ctime );
|
||||
tasks = tasks->next;
|
||||
}
|
||||
|
||||
gchar* result = g_strjoinv( ",", names );
|
||||
for ( int ii = 0; ii < len; ++ii ) {
|
||||
g_free( names[ii] );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void*
|
||||
relayThread( void* arg )
|
||||
{
|
||||
LOG_FUNC();
|
||||
RelayConStorage* storage = (RelayConStorage*)arg;
|
||||
for ( ; ; ) {
|
||||
pthread_mutex_lock( &storage->relayMutex );
|
||||
while ( !storage->relayTaskList ) {
|
||||
pthread_cond_wait( &storage->relayCondVar, &storage->relayMutex );
|
||||
}
|
||||
|
||||
int len = g_slist_length( storage->relayTaskList );
|
||||
gchar* strs = listTasks( storage->relayTaskList );
|
||||
GSList* head = storage->relayTaskList;
|
||||
storage->relayTaskList = g_slist_remove_link( storage->relayTaskList,
|
||||
storage->relayTaskList );
|
||||
RelayTask* task = head->data;
|
||||
g_slist_free( head );
|
||||
|
||||
|
||||
pthread_mutex_unlock( &storage->relayMutex );
|
||||
|
||||
XP_LOGF( "%s(): processing first of %d (%s)", __func__, len, strs );
|
||||
g_free( strs );
|
||||
|
||||
switch ( task->typ ) {
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
case JOIN:
|
||||
handleJoin( task );
|
||||
break;
|
||||
#endif
|
||||
case POST:
|
||||
handlePost( task );
|
||||
break;
|
||||
case QUERY:
|
||||
handleQuery( task );
|
||||
break;
|
||||
default:
|
||||
XP_ASSERT(0);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static XP_Bool
|
||||
didCombine( const RelayTask* one, const RelayTask* two )
|
||||
{
|
||||
/* For now.... */
|
||||
XP_Bool result = one->typ == QUERY && two->typ == QUERY;
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
addTask( RelayConStorage* storage, RelayTask* task )
|
||||
{
|
||||
pthread_mutex_lock( &storage->relayMutex );
|
||||
|
||||
/* Let's see if the current last task is the same. */
|
||||
GSList* last = g_slist_last( storage->relayTaskList );
|
||||
if ( !!last && didCombine( last->data, task ) ) {
|
||||
freeRelayTask( task );
|
||||
} else {
|
||||
storage->relayTaskList = g_slist_append( storage->relayTaskList, task );
|
||||
}
|
||||
gchar* strs = listTasks( storage->relayTaskList );
|
||||
pthread_cond_signal( &storage->relayCondVar );
|
||||
pthread_mutex_unlock( &storage->relayMutex );
|
||||
XP_LOGF( "%s(): task list now: %s", __func__, strs );
|
||||
g_free( strs );
|
||||
}
|
||||
|
||||
static RelayTask*
|
||||
makeRelayTask( RelayConStorage* storage, TaskType typ )
|
||||
{
|
||||
XP_ASSERT( onMainThread(storage) );
|
||||
RelayTask* task = (RelayTask*)g_malloc0(sizeof(*task));
|
||||
task->typ = typ;
|
||||
task->id = ++storage->nextTaskID;
|
||||
task->ctime = (XP_U32)time(NULL);
|
||||
task->storage = storage;
|
||||
return task;
|
||||
}
|
||||
|
||||
static void
|
||||
freeRelayTask( RelayTask* task )
|
||||
{
|
||||
GSList faker = { .next = NULL, .data = task };
|
||||
gchar* str = listTasks(&faker);
|
||||
XP_LOGF( "%s(): deleting %s", __func__, str );
|
||||
g_free( str );
|
||||
g_free( task->ws.ptr );
|
||||
g_free( task );
|
||||
}
|
||||
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
void
|
||||
relaycon_join( LaunchParams* params, const XP_UCHAR* devID, const XP_UCHAR* room,
|
||||
XP_U16 nPlayersHere, XP_U16 nPlayersTotal, XP_U16 seed, XP_U16 lang,
|
||||
OnJoinedProc proc, void* closure )
|
||||
{
|
||||
LOG_FUNC();
|
||||
RelayConStorage* storage = getStorage( params );
|
||||
XP_ASSERT( onMainThread(storage) );
|
||||
RelayTask* task = makeRelayTask( storage, JOIN );
|
||||
task->u.join.nHere = nPlayersHere;
|
||||
XP_STRNCPY( task->u.join.devID, devID, sizeof(task->u.join.devID) );
|
||||
XP_STRNCPY( task->u.join.room, room, sizeof(task->u.join.room) );
|
||||
task->u.join.nTotal = nPlayersTotal;
|
||||
task->u.join.lang = lang;
|
||||
task->u.join.seed = seed;
|
||||
task->u.join.proc = proc;
|
||||
task->u.join.closure = closure;
|
||||
addTask( storage, task );
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
sendAckIf( RelayConStorage* storage, const MsgHeader* header )
|
||||
{
|
||||
|
@ -235,40 +593,22 @@ sendAckIf( RelayConStorage* storage, const MsgHeader* header )
|
|||
XP_U8 tmpbuf[16];
|
||||
int indx = writeHeader( storage, tmpbuf, XWPDEV_ACK );
|
||||
indx += writeVLI( &tmpbuf[indx], header->packetID );
|
||||
sendIt( storage, tmpbuf, indx );
|
||||
sendIt( storage, tmpbuf, indx, 0.1 );
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
relaycon_receive( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpointer data )
|
||||
static gboolean
|
||||
process( RelayConStorage* storage, XP_U8* buf, ssize_t nRead )
|
||||
{
|
||||
XP_ASSERT( 0 != (G_IO_IN & condition) ); /* FIX ME */
|
||||
RelayConStorage* storage = (RelayConStorage*)data;
|
||||
XP_U8 buf[512];
|
||||
struct sockaddr_in from;
|
||||
socklen_t fromlen = sizeof(from);
|
||||
|
||||
int socket = g_io_channel_unix_get_fd( source );
|
||||
XP_LOGF( "%s: calling recvfrom on socket %d", __func__, socket );
|
||||
|
||||
ssize_t nRead = recvfrom( socket, buf, sizeof(buf), 0, /* flags */
|
||||
(struct sockaddr*)&from, &fromlen );
|
||||
|
||||
gchar* b64 = g_base64_encode( (const guchar*)buf,
|
||||
((0 <= nRead)? nRead : 0) );
|
||||
XP_LOGF( "%s: read %zd bytes ('%s')", __func__, nRead, b64 );
|
||||
g_free( b64 );
|
||||
#ifdef COMMS_CHECKSUM
|
||||
gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, buf, nRead );
|
||||
XP_LOGF( "%s: read %zd bytes ('%s')(sum=%s)", __func__, nRead, b64, sum );
|
||||
g_free( sum );
|
||||
#endif
|
||||
if ( 0 <= nRead ) {
|
||||
const XP_U8* ptr = buf;
|
||||
const XP_U8* end = buf + nRead;
|
||||
MsgHeader header;
|
||||
if ( readHeader( &ptr, &header ) ) {
|
||||
sendAckIf( storage, &header );
|
||||
|
||||
XP_LOGF( "%s(): got %s", __func__, msgToStr(header.cmd) );
|
||||
|
||||
switch( header.cmd ) {
|
||||
case XWPDEV_REGRSP: {
|
||||
uint32_t len;
|
||||
|
@ -318,7 +658,7 @@ relaycon_receive( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpo
|
|||
assert( 0 );
|
||||
}
|
||||
XP_USE( packetID );
|
||||
XP_LOGF( "got ack for packetID %d", packetID );
|
||||
XP_LOGF( "%s(): got ack for packetID %d", __func__, packetID );
|
||||
break;
|
||||
}
|
||||
case XWPDEV_ALERT: {
|
||||
|
@ -366,9 +706,55 @@ relaycon_receive( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpo
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
relaycon_receive( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpointer data )
|
||||
{
|
||||
XP_ASSERT( 0 != (G_IO_IN & condition) ); /* FIX ME */
|
||||
RelayConStorage* storage = (RelayConStorage*)data;
|
||||
XP_ASSERT( !storage->params->useHTTP );
|
||||
XP_U8 buf[512];
|
||||
struct sockaddr_in from;
|
||||
socklen_t fromlen = sizeof(from);
|
||||
|
||||
int socket = g_io_channel_unix_get_fd( source );
|
||||
XP_LOGF( "%s: calling recvfrom on socket %d", __func__, socket );
|
||||
|
||||
ssize_t nRead = recvfrom( socket, buf, sizeof(buf), 0, /* flags */
|
||||
(struct sockaddr*)&from, &fromlen );
|
||||
|
||||
gchar* b64 = g_base64_encode( (const guchar*)buf,
|
||||
((0 <= nRead)? nRead : 0) );
|
||||
XP_LOGF( "%s: read %zd bytes ('%s')", __func__, nRead, b64 );
|
||||
#ifdef COMMS_CHECKSUM
|
||||
gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, buf, nRead );
|
||||
XP_LOGF( "%s: read %zd bytes ('%s')(sum=%s)", __func__, nRead, b64, sum );
|
||||
g_free( sum );
|
||||
#endif
|
||||
g_free( b64 );
|
||||
return process( storage, buf, nRead );
|
||||
}
|
||||
|
||||
void
|
||||
relaycon_cleanup( LaunchParams* params )
|
||||
{
|
||||
RelayConStorage* storage = (RelayConStorage*)params->relayConStorage;
|
||||
if ( storage->params->useHTTP ) {
|
||||
pthread_mutex_lock( &storage->relayMutex );
|
||||
int nRelayTasks = g_slist_length( storage->relayTaskList );
|
||||
gchar* taskStrs = listTasks( storage->relayTaskList );
|
||||
pthread_mutex_unlock( &storage->relayMutex );
|
||||
|
||||
pthread_mutex_lock( &storage->gotDataMutex );
|
||||
int nDataTasks = g_slist_length( storage->gotDataTaskList );
|
||||
gchar* gotStrs = listTasks( storage->gotDataTaskList );
|
||||
pthread_mutex_unlock( &storage->gotDataMutex );
|
||||
|
||||
XP_LOGF( "%s(): sends pending: %d (%s); data tasks pending: %d (%s)", __func__,
|
||||
nRelayTasks, gotStrs, nDataTasks, taskStrs );
|
||||
|
||||
g_free( gotStrs );
|
||||
g_free( taskStrs );
|
||||
}
|
||||
XP_FREEP( params->mpool, ¶ms->relayConStorage );
|
||||
}
|
||||
|
||||
|
@ -402,12 +788,322 @@ hostNameToIP( const XP_UCHAR* name )
|
|||
return ip;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len )
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
static void
|
||||
onGotJoinData( RelayTask* task )
|
||||
{
|
||||
ssize_t nSent = sendto( storage->socket, msgbuf, len, 0, /* flags */
|
||||
(struct sockaddr*)&storage->saddr,
|
||||
sizeof(storage->saddr) );
|
||||
LOG_FUNC();
|
||||
RelayConStorage* storage = task->storage;
|
||||
XP_ASSERT( onMainThread(storage) );
|
||||
if ( !!task->ws.ptr ) {
|
||||
XP_LOGF( "%s(): got json? %s", __func__, task->ws.ptr );
|
||||
json_object* reply = json_tokener_parse( task->ws.ptr );
|
||||
json_object* jConnname = NULL;
|
||||
json_object* jHID = NULL;
|
||||
if ( json_object_object_get_ex( reply, "connname", &jConnname )
|
||||
&& json_object_object_get_ex( reply, "hid", &jHID ) ) {
|
||||
const char* connname = json_object_get_string( jConnname );
|
||||
XWHostID hid = json_object_get_int( jHID );
|
||||
(*task->u.join.proc)( task->u.join.closure, connname, hid );
|
||||
}
|
||||
json_object_put( jConnname );
|
||||
json_object_put( jHID );
|
||||
}
|
||||
freeRelayTask( task );
|
||||
}
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
onGotPostData( RelayTask* task )
|
||||
{
|
||||
RelayConStorage* storage = task->storage;
|
||||
/* Now pull any data from the reply */
|
||||
// got "{"status": "ok", "dataLen": 14, "data": "AYQDiDAyMUEzQ0MyADw=", "err": "none"}"
|
||||
if ( !!task->ws.ptr ) {
|
||||
json_object* reply = json_tokener_parse( task->ws.ptr );
|
||||
json_object* replyData;
|
||||
if ( json_object_object_get_ex( reply, "data", &replyData ) && !!replyData ) {
|
||||
const int len = json_object_array_length(replyData);
|
||||
for ( int ii = 0; ii < len; ++ii ) {
|
||||
json_object* datum = json_object_array_get_idx( replyData, ii );
|
||||
const char* str = json_object_get_string( datum );
|
||||
gsize out_len;
|
||||
guchar* buf = g_base64_decode( (const gchar*)str, &out_len );
|
||||
process( storage, buf, out_len );
|
||||
g_free( buf );
|
||||
}
|
||||
(void)json_object_put( replyData );
|
||||
}
|
||||
(void)json_object_put( reply );
|
||||
}
|
||||
|
||||
g_free( task->u.post.msgbuf );
|
||||
|
||||
freeRelayTask( task );
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
static void
|
||||
handleJoin( RelayTask* task )
|
||||
{
|
||||
LOG_FUNC();
|
||||
runWitCurl( task, "join",
|
||||
"devID", json_object_new_string( task->u.join.devID ),
|
||||
"room", json_object_new_string( task->u.join.room ),
|
||||
"seed", json_object_new_int( task->u.join.seed ),
|
||||
"lang", json_object_new_int( task->u.join.lang ),
|
||||
"nInGame", json_object_new_int( task->u.join.nTotal ),
|
||||
"nHere", json_object_new_int( task->u.join.nHere ),
|
||||
NULL );
|
||||
addToGotData( task );
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
handlePost( RelayTask* task )
|
||||
{
|
||||
XP_LOGF( "%s(task.post.len=%d)", __func__, task->u.post.len );
|
||||
XP_ASSERT( !onMainThread(task->storage) );
|
||||
char* data = g_base64_encode( task->u.post.msgbuf, task->u.post.len );
|
||||
struct json_object* jstr = json_object_new_string(data);
|
||||
g_free( data );
|
||||
|
||||
/* The protocol takes an array of messages so they can be combined. Do
|
||||
that soon. */
|
||||
json_object* dataArr = json_object_new_array();
|
||||
json_object_array_add( dataArr, jstr);
|
||||
|
||||
json_object* jTimeout = json_object_new_double( task->u.post.timeoutSecs );
|
||||
runWitCurl( task, "post", "data", dataArr, "timeoutSecs", jTimeout, NULL );
|
||||
|
||||
// Put the data on the main thread for processing
|
||||
addToGotData( task );
|
||||
} /* handlePost */
|
||||
|
||||
static ssize_t
|
||||
post( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len, float timeout )
|
||||
{
|
||||
XP_LOGF( "%s(len=%d)", __func__, len );
|
||||
RelayTask* task = makeRelayTask( storage, POST );
|
||||
task->u.post.msgbuf = g_malloc(len);
|
||||
task->u.post.timeoutSecs = timeout;
|
||||
XP_MEMCPY( task->u.post.msgbuf, msgbuf, len );
|
||||
task->u.post.len = len;
|
||||
addTask( storage, task );
|
||||
return len;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
onGotQueryData( RelayTask* task )
|
||||
{
|
||||
RelayConStorage* storage = task->storage;
|
||||
XP_Bool foundAny = false;
|
||||
if ( !!task->ws.ptr ) {
|
||||
json_object* reply = json_tokener_parse( task->ws.ptr );
|
||||
if ( !!reply ) {
|
||||
CommsAddrRec addr = {0};
|
||||
addr_addType( &addr, COMMS_CONN_RELAY );
|
||||
|
||||
GList* ids = g_hash_table_get_keys( task->u.query.map );
|
||||
const char* xxx = ids->data;
|
||||
|
||||
json_object* jMsgs;
|
||||
if ( json_object_object_get_ex( reply, "msgs", &jMsgs ) ) {
|
||||
/* Currently there's an array of arrays for each relayID (value) */
|
||||
XP_LOGF( "%s: got result of len %d", __func__, json_object_object_length(jMsgs) );
|
||||
XP_ASSERT( json_object_object_length(jMsgs) <= 1 );
|
||||
json_object_object_foreach(jMsgs, relayID, arrOfArrOfMoves) {
|
||||
XP_ASSERT( 0 == strcmp( relayID, xxx ) );
|
||||
int len1 = json_object_array_length( arrOfArrOfMoves );
|
||||
if ( len1 > 0 ) {
|
||||
sqlite3_int64 rowid = *(sqlite3_int64*)g_hash_table_lookup( task->u.query.map, relayID );
|
||||
XP_LOGF( "%s(): got row %lld for relayID %s", __func__, rowid, relayID );
|
||||
for ( int ii = 0; ii < len1; ++ii ) {
|
||||
json_object* forGameArray = json_object_array_get_idx( arrOfArrOfMoves, ii );
|
||||
int len2 = json_object_array_length( forGameArray );
|
||||
for ( int jj = 0; jj < len2; ++jj ) {
|
||||
json_object* oneMove = json_object_array_get_idx( forGameArray, jj );
|
||||
const char* asStr = json_object_get_string( oneMove );
|
||||
gsize out_len;
|
||||
guchar* buf = g_base64_decode( asStr, &out_len );
|
||||
(*storage->procs.msgForRow)( storage->procsClosure, &addr,
|
||||
rowid, buf, out_len );
|
||||
g_free(buf);
|
||||
foundAny = XP_TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
json_object_put( jMsgs );
|
||||
}
|
||||
json_object_put( reply );
|
||||
}
|
||||
}
|
||||
|
||||
if ( foundAny ) {
|
||||
/* Reschedule. If we got anything this time, check again sooner! */
|
||||
reset_schedule_check_interval( storage );
|
||||
}
|
||||
schedule_next_check( storage );
|
||||
|
||||
g_hash_table_destroy( task->u.query.map );
|
||||
freeRelayTask(task);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
handleQuery( RelayTask* task )
|
||||
{
|
||||
XP_ASSERT( !onMainThread(task->storage) );
|
||||
|
||||
if ( g_hash_table_size( task->u.query.map ) > 0 ) {
|
||||
GList* ids = g_hash_table_get_keys( task->u.query.map );
|
||||
|
||||
json_object* jIds = json_object_new_array();
|
||||
for ( GList* iter = ids; !!iter; iter = iter->next ) {
|
||||
json_object* idstr = json_object_new_string( iter->data );
|
||||
json_object_array_add(jIds, idstr);
|
||||
XP_ASSERT( !iter->next ); /* for curses case there should be only one */
|
||||
}
|
||||
g_list_free( ids );
|
||||
|
||||
runWitCurl( task, "query", "ids", jIds, NULL );
|
||||
}
|
||||
/* Put processing back on the main thread */
|
||||
addToGotData( task );
|
||||
} /* handleQuery */
|
||||
|
||||
static void
|
||||
checkForMovesOnce( RelayConStorage* storage )
|
||||
{
|
||||
LOG_FUNC();
|
||||
XP_ASSERT( onMainThread(storage) );
|
||||
|
||||
RelayTask* task = makeRelayTask( storage, QUERY );
|
||||
sqlite3* dbp = storage->params->pDb;
|
||||
task->u.query.map = getRelayIDsToRowsMap( dbp );
|
||||
addTask( storage, task );
|
||||
}
|
||||
|
||||
static gboolean
|
||||
checkForMoves( gpointer user_data )
|
||||
{
|
||||
RelayConStorage* storage = (RelayConStorage*)user_data;
|
||||
checkForMovesOnce( storage );
|
||||
schedule_next_check( storage );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gotDataTimer(gpointer user_data)
|
||||
{
|
||||
RelayConStorage* storage = (RelayConStorage*)user_data;
|
||||
assert( onMainThread(storage) );
|
||||
|
||||
for ( ; ; ) {
|
||||
RelayTask* task = getFromGotData( storage );
|
||||
|
||||
if ( !task ) {
|
||||
break;
|
||||
} else {
|
||||
switch ( task->typ ) {
|
||||
#ifdef RELAY_VIA_HTTP
|
||||
case JOIN:
|
||||
onGotJoinData( task );
|
||||
break;
|
||||
#endif
|
||||
case POST:
|
||||
onGotPostData( task );
|
||||
break;
|
||||
case QUERY:
|
||||
onGotQueryData( task );
|
||||
break;
|
||||
default:
|
||||
XP_ASSERT(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
addToGotData( RelayTask* task )
|
||||
{
|
||||
RelayConStorage* storage = task->storage;
|
||||
pthread_mutex_lock( &storage->gotDataMutex );
|
||||
storage->gotDataTaskList = g_slist_append( storage->gotDataTaskList, task );
|
||||
XP_LOGF( "%s(): added id %d; len now %d", __func__, task->id,
|
||||
g_slist_length(storage->gotDataTaskList) );
|
||||
pthread_mutex_unlock( &storage->gotDataMutex );
|
||||
}
|
||||
|
||||
static RelayTask*
|
||||
getFromGotData( RelayConStorage* storage )
|
||||
{
|
||||
RelayTask* task = NULL;
|
||||
XP_ASSERT( onMainThread(storage) );
|
||||
pthread_mutex_lock( &storage->gotDataMutex );
|
||||
int len = g_slist_length( storage->gotDataTaskList );
|
||||
// XP_LOGF( "%s(): before: len: %d", __func__, len );
|
||||
if ( len > 0 ) {
|
||||
GSList* head = storage->gotDataTaskList;
|
||||
storage->gotDataTaskList
|
||||
= g_slist_remove_link( storage->gotDataTaskList,
|
||||
storage->gotDataTaskList );
|
||||
task = head->data;
|
||||
g_slist_free( head );
|
||||
XP_LOGF( "%s(): got task id %d", __func__, task->id );
|
||||
}
|
||||
// XP_LOGF( "%s(): len now %d", __func__, g_slist_length(storage->gotDataTaskList) );
|
||||
pthread_mutex_unlock( &storage->gotDataMutex );
|
||||
return task;
|
||||
}
|
||||
|
||||
static void
|
||||
reset_schedule_check_interval( RelayConStorage* storage )
|
||||
{
|
||||
XP_ASSERT( onMainThread(storage) );
|
||||
storage->nextMoveCheckMS = 500;
|
||||
}
|
||||
|
||||
static void
|
||||
schedule_next_check( RelayConStorage* storage )
|
||||
{
|
||||
XP_ASSERT( onMainThread(storage) );
|
||||
XP_ASSERT( !storage->params->noHTTPAuto );
|
||||
if ( !storage->params->noHTTPAuto ) {
|
||||
if ( storage->moveCheckerID != 0 ) {
|
||||
g_source_remove( storage->moveCheckerID );
|
||||
storage->moveCheckerID = 0;
|
||||
}
|
||||
|
||||
storage->nextMoveCheckMS *= 2;
|
||||
if ( storage->nextMoveCheckMS > MAX_MOVE_CHECK_MS ) {
|
||||
storage->nextMoveCheckMS = MAX_MOVE_CHECK_MS;
|
||||
} else if ( storage->nextMoveCheckMS == 0 ) {
|
||||
storage->nextMoveCheckMS = 1000;
|
||||
}
|
||||
|
||||
storage->moveCheckerID = g_timeout_add( storage->nextMoveCheckMS,
|
||||
checkForMoves, storage );
|
||||
XP_ASSERT( storage->moveCheckerID != 0 );
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sendIt( RelayConStorage* storage, const XP_U8* msgbuf, XP_U16 len, float timeoutSecs )
|
||||
{
|
||||
ssize_t nSent;
|
||||
if ( storage->params->useHTTP ) {
|
||||
nSent = post( storage, msgbuf, len, timeoutSecs );
|
||||
} else {
|
||||
nSent = sendto( storage->socket, msgbuf, len, 0, /* flags */
|
||||
(struct sockaddr*)&storage->saddr,
|
||||
sizeof(storage->saddr) );
|
||||
}
|
||||
#ifdef COMMS_CHECKSUM
|
||||
gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, msgbuf, len );
|
||||
XP_LOGF( "%s: sent %d bytes with sum %s", __func__, len, sum );
|
||||
|
@ -601,3 +1297,33 @@ vli2un( const uint8_t** inp, uint32_t* outp )
|
|||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
static const char*
|
||||
msgToStr( XWRelayReg msg )
|
||||
{
|
||||
const char* str;
|
||||
# define CASE_STR(c) case c: str = #c; break
|
||||
switch( msg ) {
|
||||
CASE_STR(XWPDEV_UNAVAIL);
|
||||
CASE_STR(XWPDEV_REG);
|
||||
CASE_STR(XWPDEV_REGRSP);
|
||||
CASE_STR(XWPDEV_INVITE);
|
||||
CASE_STR(XWPDEV_KEEPALIVE);
|
||||
CASE_STR(XWPDEV_HAVEMSGS);
|
||||
CASE_STR(XWPDEV_RQSTMSGS);
|
||||
CASE_STR(XWPDEV_MSG);
|
||||
CASE_STR(XWPDEV_MSGNOCONN);
|
||||
CASE_STR(XWPDEV_MSGRSP);
|
||||
CASE_STR(XWPDEV_BADREG);
|
||||
CASE_STR(XWPDEV_ALERT); // should not receive this....
|
||||
CASE_STR(XWPDEV_ACK);
|
||||
CASE_STR(XWPDEV_DELGAME);
|
||||
default:
|
||||
str = "<unknown>";
|
||||
break;
|
||||
}
|
||||
# undef CASE_STR
|
||||
return str;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
typedef struct _Procs {
|
||||
void (*msgReceived)( void* closure, const CommsAddrRec* from,
|
||||
const XP_U8* buf, XP_U16 len );
|
||||
void (*msgForRow)( void* closure, const CommsAddrRec* from,
|
||||
sqlite3_int64 rowid, const XP_U8* buf, XP_U16 len );
|
||||
void (*msgNoticeReceived)( void* closure );
|
||||
void (*devIDReceived)( void* closure, const XP_UCHAR* devID,
|
||||
XP_U16 maxInterval );
|
||||
|
@ -56,4 +58,14 @@ void relaycon_cleanup( LaunchParams* params );
|
|||
|
||||
XP_U32 makeClientToken( sqlite3_int64 rowid, XP_U16 seed );
|
||||
void rowidFromToken( XP_U32 clientToken, sqlite3_int64* rowid, XP_U16* seed );
|
||||
|
||||
void relaycon_checkMsgs( LaunchParams* params );
|
||||
|
||||
# ifdef RELAY_VIA_HTTP
|
||||
typedef void (*OnJoinedProc)( void* closure, const XP_UCHAR* connname, XWHostID hid );
|
||||
void relaycon_join( LaunchParams* params, const XP_UCHAR* devID, const XP_UCHAR* room,
|
||||
XP_U16 nPlayersHere, XP_U16 nPlayersTotal, XP_U16 seed,
|
||||
XP_U16 lang, OnJoinedProc proc, void* closure );
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
|
1052
xwords4/linux/scripts/discon_ok2.py
Executable file
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
set -u -e
|
||||
|
||||
LOGDIR=$(basename $0)_logs
|
||||
LOGDIR=./$(basename $0)_logs
|
||||
APP_NEW=""
|
||||
DO_CLEAN=""
|
||||
APP_NEW_PARAMS=""
|
||||
|
@ -17,9 +17,9 @@ SAVE_GOOD=""
|
|||
MINDEVS=""
|
||||
MAXDEVS=""
|
||||
ONEPER=""
|
||||
RESIGN_RATIO=""
|
||||
RESIGN_PCT=0
|
||||
DROP_N=""
|
||||
MINRUN=2
|
||||
MINRUN=2 # seconds
|
||||
ONE_PER_ROOM="" # don't run more than one device at a time per room
|
||||
USE_GTK=""
|
||||
UNDO_PCT=0
|
||||
|
@ -31,6 +31,7 @@ NAMES=(UNUSED Brynn Ariela Kati Eric)
|
|||
SEND_CHAT=''
|
||||
CORE_COUNT=$(ls core.* 2>/dev/null | wc -l)
|
||||
DUP_PACKETS=''
|
||||
HTTP_PCT=0
|
||||
|
||||
declare -A PIDS
|
||||
declare -A APPS
|
||||
|
@ -43,7 +44,7 @@ declare -A LOGS
|
|||
declare -A MINEND
|
||||
declare -A ROOM_PIDS
|
||||
declare -a APPS_OLD=()
|
||||
declare -a DICTS= # wants to be =() too?
|
||||
declare -a DICTS=() # wants to be =() too?
|
||||
declare -A CHECKED_ROOMS
|
||||
|
||||
function cleanup() {
|
||||
|
@ -194,9 +195,6 @@ build_cmds() {
|
|||
for NLOCALS in ${LOCALS[@]}; do
|
||||
DEV=$((DEV + 1))
|
||||
FILE="${LOGDIR}/GAME_${GAME}_${DEV}.sql3"
|
||||
if [ $((RANDOM % 100)) -lt $UDP_PCT_START ]; then
|
||||
FILE="$FILE --use-udp"
|
||||
fi
|
||||
LOG=${LOGDIR}/${GAME}_${DEV}_LOG.txt
|
||||
> $LOG # clear the log
|
||||
|
||||
|
@ -219,7 +217,13 @@ build_cmds() {
|
|||
PARAMS="$PARAMS --game-dict $DICT --relay-port $PORT --host $HOST "
|
||||
PARAMS="$PARAMS --slow-robot 1:3 --skip-confirm"
|
||||
PARAMS="$PARAMS --db $FILE"
|
||||
if [ $((RANDOM % 100)) -lt $UDP_PCT_START ]; then
|
||||
PARAMS="$PARAMS --use-udp"
|
||||
fi
|
||||
PARAMS="$PARAMS --drop-nth-packet $DROP_N $PLAT_PARMS"
|
||||
if [ $((${RANDOM}%100)) -lt $HTTP_PCT ]; then
|
||||
PARAMS="$PARAMS --use-http"
|
||||
fi
|
||||
# PARAMS="$PARAMS --split-packets 2"
|
||||
if [ -n "$SEND_CHAT" ]; then
|
||||
PARAMS="$PARAMS --send-chat $SEND_CHAT"
|
||||
|
@ -304,6 +308,21 @@ launch() {
|
|||
# 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
|
||||
|
@ -353,11 +372,11 @@ kill_from_log() {
|
|||
}
|
||||
|
||||
maybe_resign() {
|
||||
if [ "$RESIGN_RATIO" -gt 0 ]; then
|
||||
if [ "$RESIGN_PCT" -gt 0 ]; then
|
||||
KEY=$1
|
||||
LOG=${LOGS[$KEY]}
|
||||
if grep -aq XWRELAY_ALLHERE $LOG; then
|
||||
if [ 0 -eq $(($RANDOM % $RESIGN_RATIO)) ]; 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
|
||||
|
@ -419,6 +438,7 @@ check_game() {
|
|||
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 ""
|
||||
|
@ -458,10 +478,8 @@ update_ldevid() {
|
|||
if [ $RNUM -lt 30 ]; then # upgrade or first run
|
||||
CMD="--ldevid LINUX_TEST_$(printf %.5d ${KEY})_"
|
||||
fi
|
||||
else
|
||||
if [ $RNUM -lt 10 ]; then
|
||||
CMD="${CMD}x" # give it a new local ID
|
||||
fi
|
||||
elif [ $RNUM -lt 10 ]; then
|
||||
CMD="${CMD}x" # give it a new local ID
|
||||
fi
|
||||
ARGS_DEVID[$KEY]="$CMD"
|
||||
fi
|
||||
|
@ -508,7 +526,8 @@ run_cmds() {
|
|||
local KEYS=( ${!ARGS[*]} )
|
||||
KEY=${KEYS[$INDX]}
|
||||
ROOM=${ROOMS[$KEY]}
|
||||
if [ 0 -eq ${PIDS[$KEY]} ]; then
|
||||
PID=${PIDS[$KEY]}
|
||||
if [ 0 -eq ${PID} ]; then
|
||||
if [ -n "$ONE_PER_ROOM" -a 0 -ne ${ROOM_PIDS[$ROOM]} ]; then
|
||||
continue
|
||||
fi
|
||||
|
@ -522,10 +541,12 @@ run_cmds() {
|
|||
ROOM_PIDS[$ROOM]=$PID
|
||||
MINEND[$KEY]=$(($NOW + $MINRUN))
|
||||
else
|
||||
PID=${PIDS[$KEY]}
|
||||
if [ -d /proc/$PID ]; then
|
||||
SLEEP=$((${MINEND[$KEY]} - $NOW))
|
||||
[ $SLEEP -gt 0 ] && sleep $SLEEP
|
||||
if [ $SLEEP -gt 0 ]; then
|
||||
sleep 1
|
||||
continue
|
||||
fi
|
||||
kill $PID || /bin/true
|
||||
wait $PID
|
||||
fi
|
||||
|
@ -594,6 +615,7 @@ function getArg() {
|
|||
function usage() {
|
||||
[ $# -gt 0 ] && echo "Error: $1" >&2
|
||||
echo "Usage: $(basename $0) \\" >&2
|
||||
echo " [--log-root] # default: . \\" >&2
|
||||
echo " [--dup-packets] # send all packets twice \\" >&2
|
||||
echo " [--clean-start] \\" >&2
|
||||
echo " [--game-dict <path/to/dict>]* \\" >&2
|
||||
|
@ -601,6 +623,7 @@ function usage() {
|
|||
echo " [--host <hostname>] \\" >&2
|
||||
echo " [--max-devs <int>] \\" >&2
|
||||
echo " [--min-devs <int>] \\" >&2
|
||||
echo " [--min-run <int>] # run each at least this long \\" >&2
|
||||
echo " [--new-app <path/to/app] \\" >&2
|
||||
echo " [--new-app-args [arg*]] # passed only to new app \\" >&2
|
||||
echo " [--num-games <int>] \\" >&2
|
||||
|
@ -608,12 +631,14 @@ function usage() {
|
|||
echo " [--old-app <path/to/app]* \\" >&2
|
||||
echo " [--one-per] # force one player per device \\" >&2
|
||||
echo " [--port <int>] \\" >&2
|
||||
echo " [--resign-ratio <0 <= n <=1000 > \\" >&2
|
||||
echo " [--resign-pct <0 <= n <=100 > \\" >&2
|
||||
echo " [--no-timeout] # run until all games done \\" >&2
|
||||
echo " [--seed <int>] \\" >&2
|
||||
echo " [--send-chat <interval-in-seconds> \\" >&2
|
||||
echo " [--udp-incr <pct>] \\" >&2
|
||||
echo " [--udp-start <pct>] # default: $UDP_PCT_START \\" >&2
|
||||
echo " [--undo-pct <int>] \\" >&2
|
||||
echo " [--http-pct <0 <= n <=100>] \\" >&2
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
@ -647,6 +672,11 @@ while [ "$#" -gt 0 ]; do
|
|||
APPS_OLD[${#APPS_OLD[@]}]=$(getArg $*)
|
||||
shift
|
||||
;;
|
||||
--log-root)
|
||||
[ -d $2 ] || usage "$1: no such directory $2"
|
||||
LOGDIR=$2/$(basename $0)_logs
|
||||
shift
|
||||
;;
|
||||
--dup-packets)
|
||||
DUP_PACKETS=1
|
||||
;;
|
||||
|
@ -671,6 +701,11 @@ while [ "$#" -gt 0 ]; do
|
|||
MAXDEVS=$(getArg $*)
|
||||
shift
|
||||
;;
|
||||
--min-run)
|
||||
MINRUN=$(getArg $*)
|
||||
[ $MINRUN -ge 2 -a $MINRUN -le 60 ] || usage "$1: n must be 2 <= n <= 60"
|
||||
shift
|
||||
;;
|
||||
--one-per)
|
||||
ONEPER=TRUE
|
||||
;;
|
||||
|
@ -690,14 +725,23 @@ while [ "$#" -gt 0 ]; do
|
|||
UNDO_PCT=$(getArg $*)
|
||||
shift
|
||||
;;
|
||||
--http-pct)
|
||||
HTTP_PCT=$(getArg $*)
|
||||
[ $HTTP_PCT -ge 0 -a $HTTP_PCT -le 100 ] || usage "$1: n must be 0 <= n <= 100"
|
||||
shift
|
||||
;;
|
||||
--send-chat)
|
||||
SEND_CHAT=$(getArg $*)
|
||||
shift
|
||||
;;
|
||||
--resign-ratio)
|
||||
RESIGN_RATIO=$(getArg $*)
|
||||
--resign-pct)
|
||||
RESIGN_PCT=$(getArg $*)
|
||||
[ $RESIGN_PCT -ge 0 -a $RESIGN_PCT -le 100 ] || usage "$1: n must be 0 <= n <= 100"
|
||||
shift
|
||||
;;
|
||||
--no-timeout)
|
||||
TIMEOUT=0x7FFFFFFF
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
;;
|
||||
|
@ -709,7 +753,7 @@ done
|
|||
|
||||
# Assign defaults
|
||||
#[ 0 -eq ${#DICTS[@]} ] && DICTS=(dict.xwd)
|
||||
[ 0 -eq ${#DICTS} ] && DICTS=(dict.xwd)
|
||||
[ 0 -eq ${#DICTS} ] && DICTS=(CollegeEng_2to8.xwd)
|
||||
[ -z "$APP_NEW" ] && APP_NEW=./obj_linux_memdbg/xwords
|
||||
[ -z "$MINDEVS" ] && MINDEVS=2
|
||||
[ -z "$MAXDEVS" ] && MAXDEVS=4
|
||||
|
@ -719,7 +763,7 @@ done
|
|||
[ -z "$PORT" ] && PORT=10997
|
||||
[ -z "$TIMEOUT" ] && TIMEOUT=$((NGAMES*60+500))
|
||||
[ -z "$SAVE_GOOD" ] && SAVE_GOOD=YES
|
||||
[ -z "$RESIGN_RATIO" -a "$NGAMES" -gt 1 ] && RESIGN_RATIO=1000 || RESIGN_RATIO=0
|
||||
# [ -z "$RESIGN_PCT" -a "$NGAMES" -gt 1 ] && RESIGN_RATIO=1000 || RESIGN_RATIO=0
|
||||
[ -z "$DROP_N" ] && DROP_N=0
|
||||
[ -z "$USE_GTK" ] && USE_GTK=FALSE
|
||||
[ -z "$UPGRADE_ODDS" ] && UPGRADE_ODDS=10
|
||||
|
@ -747,7 +791,8 @@ for FILE in $(ls $LOGDIR/*.{xwg,txt} 2>/dev/null); do
|
|||
done
|
||||
|
||||
if [ -z "$RESUME" -a -d $LOGDIR ]; then
|
||||
mv $LOGDIR /tmp/${LOGDIR}_$$
|
||||
NEWNAME="$(basename $LOGDIR)_$$"
|
||||
(cd $(dirname $LOGDIR) && mv $(basename $LOGDIR) /tmp/${NEWNAME})
|
||||
fi
|
||||
mkdir -p $LOGDIR
|
||||
|
||||
|
@ -759,7 +804,7 @@ DEADDIR=$LOGDIR/dead
|
|||
mkdir -p $DEADDIR
|
||||
|
||||
for VAR in NGAMES NROOMS USE_GTK TIMEOUT HOST PORT SAVE_GOOD \
|
||||
MINDEVS MAXDEVS ONEPER RESIGN_RATIO DROP_N ALL_VIA_RQ SEED \
|
||||
MINDEVS MAXDEVS ONEPER RESIGN_PCT DROP_N ALL_VIA_RQ SEED \
|
||||
APP_NEW; do
|
||||
echo "$VAR:" $(eval "echo \$${VAR}") 1>&2
|
||||
done
|
||||
|
|
100
xwords4/linux/scripts/list-message-flow.py
Executable file
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import getopt, re, sys
|
||||
import json, psycopg2
|
||||
|
||||
"""
|
||||
|
||||
I want to understand why some messages linger on the database so
|
||||
long. So given one or more logfiles that track a linux client's
|
||||
interaction, look at what it sends and receives and compare that with
|
||||
what's in the relay's msgs table.
|
||||
|
||||
"""
|
||||
|
||||
DEVID_PAT = re.compile('.*linux_getDevIDRelay => (\d+)$')
|
||||
QUERY_GOT_PAT = re.compile('.*>(\d+:\d+:\d+):runWitCurl\(\): got for query: \"({.*})\"$')
|
||||
# <26828:7f03b7fff700>07:47:20:runWitCurl(): got for post: "{"data": ["AR03ggcAH2gwBwESbnVja3k6NTlmYTFjZmM6MTEw", "AR43ggcAH2gwDQBvAgEAAAAAvdAAAAAAAAAAAJIGUGxheWVyGg==", "AYALgw=="], "err": "timeout"}"
|
||||
POST_GOT_PAT = re.compile('.*>(\d+:\d+:\d+):runWitCurl\(\): got for post: \"({.*})\"$')
|
||||
def usage(msg = None):
|
||||
if msg: sys.stderr.write('ERROR:' + msg + '\n')
|
||||
sys.stderr.write('usage: ' + sys.argv[0] + ': (-l logfile)+ \n')
|
||||
sys.exit(1)
|
||||
|
||||
def parseLog(log, data):
|
||||
devIDs = []
|
||||
msgMap = {}
|
||||
for line in open(log):
|
||||
line = line.strip()
|
||||
aMatch = DEVID_PAT.match(line)
|
||||
if aMatch:
|
||||
devID = int(aMatch.group(1))
|
||||
if devID and (len(devIDs) == 0 or devIDs[-1] != devID):
|
||||
devIDs.append(devID)
|
||||
|
||||
aMatch = QUERY_GOT_PAT.match(line)
|
||||
if aMatch:
|
||||
rtime = aMatch.group(1)
|
||||
jobj = json.loads(aMatch.group(2))
|
||||
for relayID in jobj:
|
||||
msgs = jobj[relayID]
|
||||
for msgarr in msgs:
|
||||
for msg in msgarr:
|
||||
if not msg in msgMap: msgMap[msg] = []
|
||||
msgMap[msg].append({'rtime' : rtime,})
|
||||
if len(msgMap[msg]) > 1: print('big case')
|
||||
|
||||
aMatch = POST_GOT_PAT.match(line)
|
||||
if aMatch:
|
||||
jobj = json.loads(aMatch.group(2))
|
||||
for datum in jobj['data']:
|
||||
data.add(datum)
|
||||
|
||||
return devIDs, msgMap
|
||||
|
||||
def fetchMsgs(devIDs, msgMaps, data):
|
||||
foundCount = 0
|
||||
notFoundCount = 0
|
||||
|
||||
con = psycopg2.connect(database='xwgames')
|
||||
cur = con.cursor()
|
||||
query = "SELECT ctime, stime, stime-ctime as age, msg64 FROM msgs WHERE devid in (%s) order by ctime" \
|
||||
% (','.join([str(id) for id in devIDs]))
|
||||
# print(query)
|
||||
cur.execute(query)
|
||||
for row in cur:
|
||||
msg64 = row[3]
|
||||
for msgMap in msgMaps:
|
||||
if msg64 in msgMap:
|
||||
print('added:', row[0], 'sent:', row[1], 'received:', msgMap[msg64][0]['rtime'], 'age:', row[2])
|
||||
if msg64 in data:
|
||||
foundCount += 1
|
||||
else:
|
||||
notFoundCount += 1
|
||||
print('found:', foundCount, 'not found:', notFoundCount);
|
||||
|
||||
|
||||
def main():
|
||||
logs = []
|
||||
opts, args = getopt.getopt(sys.argv[1:], "l:")
|
||||
for option, value in opts:
|
||||
if option == '-l': logs.append(value)
|
||||
else: usage("unknown option" + option)
|
||||
|
||||
if len(logs) == 0: usage('at least one -l requried')
|
||||
|
||||
msgMaps = []
|
||||
devIDs = set()
|
||||
data = set()
|
||||
for log in logs:
|
||||
ids, msgMap = parseLog(log, data)
|
||||
msgMaps.append(msgMap)
|
||||
for id in ids: devIDs.add(id)
|
||||
|
||||
print(msgMaps)
|
||||
print(devIDs)
|
||||
fetchMsgs(devIDs, msgMaps, data)
|
||||
|
||||
##############################################################################
|
||||
if __name__ == '__main__':
|
||||
main()
|
82
xwords4/linux/scripts/start-pair.sh
Executable file
|
@ -0,0 +1,82 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e -u
|
||||
|
||||
IN_SEQ=''
|
||||
HTTP='--use-http'
|
||||
CURSES='--curses'
|
||||
SLEEP_SEC=10000
|
||||
|
||||
usage() {
|
||||
[ $# -gt 0 ] && echo "ERROR: $1"
|
||||
echo "usage: $0 --in-sequence|--at-once [--no-use-http] [--gtk]"
|
||||
cat <<EOF
|
||||
|
||||
Starts a pair of devices meant to get into the same game. Verification
|
||||
is by looking at the relay, usually with
|
||||
./relay/scripts/showinplay.sh. Both should have an 'A' in the ACK
|
||||
column.
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--in-sequence)
|
||||
IN_SEQ=1
|
||||
;;
|
||||
--at-once)
|
||||
IN_SEQ=0
|
||||
;;
|
||||
--no-use-http)
|
||||
HTTP=''
|
||||
;;
|
||||
--gtk)
|
||||
CURSES=''
|
||||
;;
|
||||
*)
|
||||
usage "unexpected param $1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[ -n "$IN_SEQ" ] || usage "missing required param"
|
||||
|
||||
DB_TMPLATE=_cursesdb_
|
||||
LOG_TMPLATE=_curseslog_
|
||||
ROOM_TMPLATE=cursesRoom
|
||||
|
||||
echo "delete from msgs;" | psql xwgames
|
||||
echo "delete from games where room like '$ROOM_TMPLATE%';" | psql xwgames
|
||||
|
||||
rm -f ${DB_TMPLATE}*.sqldb
|
||||
rm -f ${LOG_TMPLATE}*
|
||||
|
||||
PIDS=''
|
||||
for GAME in $(seq 1); do
|
||||
ROOM=${ROOM_TMPLATE}${GAME}
|
||||
for N in $(seq 2); do
|
||||
# for N in $(seq 1); do
|
||||
DB=$DB_TMPLATE${GAME}_${N}.sqldb
|
||||
LOG=$LOG_TMPLATE${GAME}_${N}.log
|
||||
exec ./obj_linux_memdbg/xwords --server $CURSES --remote-player --robot Player \
|
||||
--room $ROOM --game-dict dict.xwd $HTTP\
|
||||
--skip-confirm --db $DB --close-stdin --server \
|
||||
>/dev/null 2>>$LOG &
|
||||
PID=$!
|
||||
echo "launched $PID"
|
||||
if [ $IN_SEQ -eq 1 ]; then
|
||||
sleep 9
|
||||
kill $PID
|
||||
sleep 1
|
||||
elif [ $IN_SEQ -eq 0 ]; then
|
||||
PIDS="$PIDS $PID"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
[ -n "${PIDS}" ] && sleep $SLEEP_SEC
|
||||
for PID in $PIDS; do
|
||||
kill $PID
|
||||
done
|
61
xwords4/newrelay/nr.py
Executable file
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import json, shelve
|
||||
|
||||
"""This will be a prototype of a simple store-and-forward message
|
||||
passing server. Target clients are peer-to-peer apps like turn-based
|
||||
games and maybe chat apps. It's expected that they depend on the
|
||||
server for nothing but message passing and any group-formation that it
|
||||
depends on (e.g three devices agreeing to participate in a single game
|
||||
of Fish, a process that gets them a token that can be used to address
|
||||
messages that are part of that game.) Clients can use this server as
|
||||
one of several means of communicating, depending on it to deliver
|
||||
messages e.g. when the devices are out of range of bluetooth.
|
||||
|
||||
"""
|
||||
|
||||
# register: a device is meant to call this once to get from the server
|
||||
# an identifier that will identify it from then on. Other APIs will
|
||||
# require this identifier.
|
||||
#
|
||||
# @param clientID: a String the client can optionally provide to link
|
||||
# this registration to an earlier one. For example, if a client app
|
||||
# wants state to survive a hard reset of the device but there are IDs
|
||||
# like serial numbers or a user's email address that will survive that
|
||||
# process, such an id could be used.
|
||||
def register(req, clientID = None):
|
||||
shelf = openShelf()
|
||||
obj = {'deviceID' : shelf['nextID']}
|
||||
shelf['nextID'] += 1
|
||||
shelf.close()
|
||||
return json.dumps(obj)
|
||||
|
||||
# Associate attributes with a device that can be used for indirectly
|
||||
# related purposes. The one I have in mind is GCM (Google Cloud
|
||||
# Messaging), where the device provides a server an ID that the server
|
||||
# can use to ask google's servers to forward a push message to the
|
||||
# device.
|
||||
def setAttr(req, deviceID, attrKey, attrValue):
|
||||
pass
|
||||
|
||||
# joinRoom: called when a device wants to start a new game to which
|
||||
# other devices will also connect. Returns a gameID that internally
|
||||
# refers to the game joined and the device's position in it.
|
||||
# @param
|
||||
def joinRoom(deviceID, room, lang, nTotal, nHere = 1, position = 0):
|
||||
pass
|
||||
|
||||
def forward(req, deviceID, msg, roomID, positions):
|
||||
pass
|
||||
|
||||
def openShelf():
|
||||
shelf = shelve.open("/tmp/nr.shelf")
|
||||
if not 'nextID' in shelf: shelf['nextID'] = 0;
|
||||
return shelf
|
||||
|
||||
def main():
|
||||
pass
|
||||
|
||||
##############################################################################
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -42,7 +42,7 @@ SRC = \
|
|||
|
||||
# STATIC ?= -static
|
||||
GITINFO = gitversion.txt
|
||||
HASH=$(shell git describe)
|
||||
HASH=$(shell git rev-parse --verify HEAD)
|
||||
|
||||
OBJ = $(patsubst %.cpp,obj/%.o,$(SRC))
|
||||
#LDFLAGS += -pthread -g -lmcheck $(STATIC)
|
||||
|
@ -67,10 +67,11 @@ endif
|
|||
|
||||
# turn on semaphore debugging
|
||||
# CPPFLAGS += -DDEBUG_LOCKS
|
||||
# CPPFLAGS += -DLOG_POLL
|
||||
|
||||
memdebug all: xwrelay rq
|
||||
|
||||
REQUIRED_DEBS = libpq-dev g++ \
|
||||
REQUIRED_DEBS = libpq-dev g++ libglib2.0-dev postgresql \
|
||||
|
||||
.PHONY: debcheck debs_install
|
||||
|
||||
|
|
|
@ -20,13 +20,16 @@
|
|||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "addrinfo.h"
|
||||
#include "xwrelay_priv.h"
|
||||
#include "tpool.h"
|
||||
#include "udpager.h"
|
||||
#include "mlock.h"
|
||||
|
||||
// static uint32_t s_prevCreated = 0L;
|
||||
|
||||
|
@ -68,7 +71,7 @@ AddrInfo::equals( const AddrInfo& other ) const
|
|||
if ( isTCP() ) {
|
||||
equal = m_socket == other.m_socket;
|
||||
if ( equal && created() != other.created() ) {
|
||||
logf( XW_LOGINFO, "%s: rejecting on time mismatch (%lx vs %lx)",
|
||||
logf( XW_LOGINFO, "%s(): rejecting on time mismatch (%lx vs %lx)",
|
||||
__func__, created(), other.created() );
|
||||
equal = false;
|
||||
}
|
||||
|
@ -82,3 +85,40 @@ AddrInfo::equals( const AddrInfo& other ) const
|
|||
return equal;
|
||||
}
|
||||
|
||||
static pthread_mutex_t s_refMutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static map<int, int > s_socketRefs;
|
||||
|
||||
void AddrInfo::ref() const
|
||||
{
|
||||
// logf( XW_LOGVERBOSE0, "%s(socket=%d)", __func__, m_socket );
|
||||
MutexLock ml( &s_refMutex );
|
||||
++s_socketRefs[m_socket];
|
||||
printRefMap();
|
||||
}
|
||||
|
||||
void
|
||||
AddrInfo::unref() const
|
||||
{
|
||||
// logf( XW_LOGVERBOSE0, "%s(socket=%d)", __func__, m_socket );
|
||||
|
||||
MutexLock ml( &s_refMutex );
|
||||
assert( s_socketRefs[m_socket] > 0 );
|
||||
--s_socketRefs[m_socket];
|
||||
if ( s_socketRefs[m_socket] == 0 ) {
|
||||
XWThreadPool::GetTPool()->CloseSocket( this );
|
||||
}
|
||||
printRefMap();
|
||||
}
|
||||
|
||||
/* private, and assumes have mutex */
|
||||
void
|
||||
AddrInfo::printRefMap() const
|
||||
{
|
||||
/* for ( map<int,int>::const_iterator iter = s_socketRefs.begin(); */
|
||||
/* iter != s_socketRefs.end(); ++iter ) { */
|
||||
/* int count = iter->second; */
|
||||
/* if ( count > 0 ) { */
|
||||
/* logf( XW_LOGVERBOSE0, "socket: %d; count: %d", iter->first, count ); */
|
||||
/* } */
|
||||
/* } */
|
||||
}
|
||||
|
|
|
@ -81,12 +81,18 @@ class AddrInfo {
|
|||
|
||||
bool equals( const AddrInfo& other ) const;
|
||||
|
||||
/* refcount the underlying socket (doesn't modify instance) */
|
||||
void ref() const;
|
||||
void unref() const;
|
||||
int getref() const;
|
||||
|
||||
private:
|
||||
void construct( int sock, const AddrUnion* saddr, bool isTCP );
|
||||
void init( int sock, ClientToken clientToken, const AddrUnion* saddr ) {
|
||||
construct( sock, saddr, false );
|
||||
m_clientToken = clientToken;
|
||||
}
|
||||
void printRefMap() const;
|
||||
|
||||
// AddrInfo& operator=(const AddrInfo&); // Prevent assignment
|
||||
int m_socket;
|
||||
|
|
|
@ -84,12 +84,13 @@ RelayConfigs::GetValueFor( const char* key, time_t* value )
|
|||
bool
|
||||
RelayConfigs::GetValueFor( const char* key, char* buf, int len )
|
||||
{
|
||||
MutexLock ml( &m_values_mutex );
|
||||
pthread_mutex_lock( &m_values_mutex );
|
||||
map<const char*,const char*>::const_iterator iter = m_values.find(key);
|
||||
bool found = iter != m_values.end();
|
||||
if ( found ) {
|
||||
snprintf( buf, len, "%s", iter->second );
|
||||
}
|
||||
pthread_mutex_unlock( &m_values_mutex );
|
||||
return found;
|
||||
}
|
||||
|
||||
|
@ -125,7 +126,7 @@ RelayConfigs::GetValueFor( const char* key, vector<int>& ints )
|
|||
void
|
||||
RelayConfigs::SetValueFor( const char* key, const char* value )
|
||||
{
|
||||
MutexLock ml( &m_values_mutex );
|
||||
pthread_mutex_lock( &m_values_mutex );
|
||||
|
||||
/* Remove any entry already there */
|
||||
map<const char*,const char*>::iterator iter = m_values.find(key);
|
||||
|
@ -136,6 +137,7 @@ RelayConfigs::SetValueFor( const char* key, const char* value )
|
|||
pair<map<const char*,const char*>::iterator,bool> result =
|
||||
m_values.insert( pair<const char*,const char*>(strdup(key),strdup(value) ) );
|
||||
assert( result.second );
|
||||
pthread_mutex_unlock( &m_values_mutex );
|
||||
}
|
||||
|
||||
ino_t
|
||||
|
|
|
@ -875,13 +875,13 @@ putNetShort( uint8_t** bufpp, unsigned short s )
|
|||
*bufpp += sizeof(s);
|
||||
}
|
||||
|
||||
void
|
||||
int
|
||||
CookieRef::store_message( HostID dest, const uint8_t* buf,
|
||||
unsigned int len )
|
||||
{
|
||||
logf( XW_LOGVERBOSE0, "%s: storing msg size %d for dest %d", __func__,
|
||||
len, dest );
|
||||
DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len );
|
||||
return DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len );
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1044,6 +1044,7 @@ CookieRef::postCheckAllHere()
|
|||
void
|
||||
CookieRef::postDropDevice( HostID hostID )
|
||||
{
|
||||
logf( XW_LOGINFO, "%s(hostID=%d)", __func__, hostID );
|
||||
CRefEvent evt( XWE_ACKTIMEOUT );
|
||||
evt.u.ack.srcID = hostID;
|
||||
m_eventQueue.push_back( evt );
|
||||
|
@ -1192,21 +1193,16 @@ CookieRef::sendAnyStored( const CRefEvent* evt )
|
|||
}
|
||||
|
||||
typedef struct _StoreData {
|
||||
string connName;
|
||||
HostID dest;
|
||||
uint8_t* buf;
|
||||
int buflen;
|
||||
int msgID;
|
||||
} StoreData;
|
||||
|
||||
void
|
||||
CookieRef::storeNoAck( bool acked, uint32_t packetID, void* data )
|
||||
{
|
||||
StoreData* sdata = (StoreData*)data;
|
||||
if ( !acked ) {
|
||||
DBMgr::Get()->StoreMessage( sdata->connName.c_str(), sdata->dest,
|
||||
sdata->buf, sdata->buflen );
|
||||
if ( acked ) {
|
||||
DBMgr::Get()->RemoveStoredMessages( &sdata->msgID, 1 );
|
||||
}
|
||||
free( sdata->buf );
|
||||
delete sdata;
|
||||
}
|
||||
|
||||
|
@ -1237,17 +1233,13 @@ CookieRef::forward_or_store( const CRefEvent* evt )
|
|||
}
|
||||
|
||||
uint32_t packetID = 0;
|
||||
int msgID = store_message( dest, buf, buflen );
|
||||
if ( (NULL == destAddr)
|
||||
|| !send_with_length( destAddr, dest, buf, buflen, true,
|
||||
&packetID ) ) {
|
||||
store_message( dest, buf, buflen );
|
||||
} else if ( 0 != packetID ) { // sent via UDP
|
||||
} else if ( 0 != msgID && 0 != packetID ) { // sent via UDP
|
||||
StoreData* data = new StoreData;
|
||||
data->connName = m_connName;
|
||||
data->dest = dest;
|
||||
data->buf = (uint8_t*)malloc( buflen );
|
||||
memcpy( data->buf, buf, buflen );
|
||||
data->buflen = buflen;
|
||||
data->msgID = msgID;
|
||||
UDPAckTrack::setOnAck( storeNoAck, packetID, data );
|
||||
}
|
||||
|
||||
|
@ -1376,20 +1368,16 @@ CookieRef::sendAllHere( bool initial )
|
|||
through the vector each time. */
|
||||
HostID dest;
|
||||
for ( dest = 1; dest <= m_nPlayersSought; ++dest ) {
|
||||
bool sent = false;
|
||||
*idLoc = dest; /* write in this target's hostId */
|
||||
|
||||
{
|
||||
RWReadLock rrl( &m_socketsRWLock );
|
||||
HostRec* hr = m_sockets[dest-1];
|
||||
if ( !!hr ) {
|
||||
sent = send_with_length( &hr->m_addr, dest, buf,
|
||||
bufp-buf, true );
|
||||
(void)send_with_length( &hr->m_addr, dest, buf, bufp-buf, true );
|
||||
}
|
||||
}
|
||||
if ( !sent ) {
|
||||
store_message( dest, buf, bufp-buf );
|
||||
}
|
||||
(void)store_message( dest, buf, bufp-buf );
|
||||
}
|
||||
} /* sendAllHere */
|
||||
|
||||
|
|
|
@ -275,8 +275,7 @@ class CookieRef {
|
|||
|
||||
bool notInUse(void) { return m_cid == 0; }
|
||||
|
||||
void store_message( HostID dest, const uint8_t* buf,
|
||||
unsigned int len );
|
||||
int store_message( HostID dest, const uint8_t* buf, unsigned int len );
|
||||
void send_stored_messages( HostID dest, const AddrInfo* addr );
|
||||
|
||||
void printSeeds( const char* caller );
|
||||
|
|
|
@ -337,7 +337,7 @@ CRefMgr::getMakeCookieRef( const char* connName, const char* cookie,
|
|||
} /* getMakeCookieRef */
|
||||
|
||||
CidInfo*
|
||||
CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead )
|
||||
CRefMgr::getMakeCookieRef( const char* const connName, HostID hid, bool* isDead )
|
||||
{
|
||||
CookieRef* cref = NULL;
|
||||
CidInfo* cinfo = NULL;
|
||||
|
@ -347,7 +347,7 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead )
|
|||
int nAlreadyHere = 0;
|
||||
|
||||
for ( ; ; ) { /* for: see comment above */
|
||||
CookieID cid = m_db->FindGame( connName, curCookie, sizeof(curCookie),
|
||||
CookieID cid = m_db->FindGame( connName, hid, curCookie, sizeof(curCookie),
|
||||
&curLangCode, &nPlayersT, &nAlreadyHere,
|
||||
isDead );
|
||||
if ( 0 != cid ) { /* already open */
|
||||
|
@ -375,6 +375,48 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead )
|
|||
return cinfo;
|
||||
}
|
||||
|
||||
CidInfo*
|
||||
CRefMgr::getMakeCookieRef( const AddrInfo::ClientToken clientToken, HostID srcID )
|
||||
{
|
||||
CookieRef* cref = NULL;
|
||||
CidInfo* cinfo = NULL;
|
||||
char curCookie[MAX_INVITE_LEN+1];
|
||||
int curLangCode;
|
||||
int nPlayersT = 0;
|
||||
int nAlreadyHere = 0;
|
||||
|
||||
for ( ; ; ) { /* for: see comment above */
|
||||
char connName[MAX_CONNNAME_LEN+1] = {0};
|
||||
CookieID cid = m_db->FindGame( clientToken, srcID,
|
||||
connName, sizeof(connName),
|
||||
curCookie, sizeof(curCookie),
|
||||
&curLangCode, &nPlayersT, &nAlreadyHere );
|
||||
// &seed );
|
||||
if ( 0 != cid ) { /* already open */
|
||||
cinfo = m_cidlock->Claim( cid );
|
||||
if ( NULL == cinfo->GetRef() ) {
|
||||
m_cidlock->Relinquish( cinfo, true );
|
||||
continue;
|
||||
}
|
||||
} else if ( nPlayersT == 0 ) { /* wasn't in the DB */
|
||||
/* do nothing; insufficient info to fake it */
|
||||
} else {
|
||||
cinfo = m_cidlock->Claim();
|
||||
if ( !m_db->AddCID( connName, cinfo->GetCid() ) ) {
|
||||
m_cidlock->Relinquish( cinfo, true );
|
||||
continue;
|
||||
}
|
||||
logf( XW_LOGINFO, "%s(): added cid???", __func__ );
|
||||
cref = AddNew( curCookie, connName, cinfo->GetCid(), curLangCode,
|
||||
nPlayersT, nAlreadyHere );
|
||||
cinfo->SetRef( cref );
|
||||
}
|
||||
break;
|
||||
}
|
||||
logf( XW_LOGINFO, "%s() => %p", __func__, cinfo );
|
||||
return cinfo;
|
||||
}
|
||||
|
||||
void
|
||||
CRefMgr::RemoveSocketRefs( const AddrInfo* addr )
|
||||
{
|
||||
|
@ -672,13 +714,13 @@ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid,
|
|||
}
|
||||
|
||||
/* ConnName case -- must exist (unless DB record's been removed */
|
||||
SafeCref::SafeCref( const char* const connName )
|
||||
SafeCref::SafeCref( const char* const connName, HostID hid )
|
||||
: m_cinfo( NULL )
|
||||
, m_mgr( CRefMgr::Get() )
|
||||
, m_isValid( false )
|
||||
{
|
||||
bool isDead = false;
|
||||
CidInfo* cinfo = m_mgr->getMakeCookieRef( connName, &isDead );
|
||||
CidInfo* cinfo = m_mgr->getMakeCookieRef( connName, hid, &isDead );
|
||||
if ( NULL != cinfo && NULL != cinfo->GetRef() ) {
|
||||
assert( cinfo->GetCid() == cinfo->GetRef()->GetCid() );
|
||||
m_locked = cinfo->GetRef()->Lock();
|
||||
|
@ -722,6 +764,19 @@ SafeCref::SafeCref( const AddrInfo* addr )
|
|||
}
|
||||
}
|
||||
|
||||
SafeCref::SafeCref( const AddrInfo::ClientToken clientToken, HostID srcID )
|
||||
: m_cinfo( NULL )
|
||||
, m_mgr( CRefMgr::Get() )
|
||||
, m_isValid( false )
|
||||
{
|
||||
CidInfo* cinfo = m_mgr->getMakeCookieRef( clientToken, srcID );
|
||||
if ( NULL != cinfo && NULL != cinfo->GetRef() ) {
|
||||
m_locked = cinfo->GetRef()->Lock();
|
||||
m_cinfo = cinfo;
|
||||
m_isValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
SafeCref::~SafeCref()
|
||||
{
|
||||
if ( m_cinfo != NULL ) {
|
||||
|
|
|
@ -128,7 +128,8 @@ class CRefMgr {
|
|||
int nPlayersS, int seed, int langCode,
|
||||
bool isPublic, bool* isDead );
|
||||
|
||||
CidInfo* getMakeCookieRef( const char* const connName, bool* isDead );
|
||||
CidInfo* getMakeCookieRef( const char* const connName, HostID hid, bool* isDead );
|
||||
CidInfo* getMakeCookieRef( const AddrInfo::ClientToken clientToken, HostID srcID );
|
||||
|
||||
CidInfo* getCookieRef( CookieID cid, bool failOk = false );
|
||||
CidInfo* getCookieRef( const AddrInfo* addr );
|
||||
|
@ -179,9 +180,10 @@ class SafeCref {
|
|||
const AddrInfo* addr, int clientVersion, DevID* devID,
|
||||
int nPlayersH, int nPlayersS, unsigned short gameSeed,
|
||||
int clientIndx, int langCode, bool wantsPublic, bool makePublic );
|
||||
SafeCref( const char* const connName );
|
||||
SafeCref( const char* const connName, HostID hid );
|
||||
SafeCref( CookieID cid, bool failOk = false );
|
||||
SafeCref( const AddrInfo* addr );
|
||||
SafeCref( const AddrInfo::ClientToken clientToken, HostID srcID );
|
||||
/* SafeCref( CookieRef* cref ); */
|
||||
~SafeCref();
|
||||
|
||||
|
|
|
@ -70,20 +70,6 @@ DBMgr::DBMgr()
|
|||
|
||||
pthread_mutex_init( &m_haveNoMessagesMutex, NULL );
|
||||
|
||||
/* Now figure out what the largest cid currently is. There must be a way
|
||||
to get postgres to do this for me.... */
|
||||
/* const char* query = "SELECT cid FROM games ORDER BY cid DESC LIMIT 1"; */
|
||||
/* PGresult* result = PQexec( m_pgconn, query ); */
|
||||
/* if ( 0 == PQntuples( result ) ) { */
|
||||
/* m_nextCID = 1; */
|
||||
/* } else { */
|
||||
/* char* value = PQgetvalue( result, 0, 0 ); */
|
||||
/* m_nextCID = 1 + atoi( value ); */
|
||||
/* } */
|
||||
/* PQclear(result); */
|
||||
/* logf( XW_LOGINFO, "%s: m_nextCID=%d", __func__, m_nextCID ); */
|
||||
|
||||
// I've seen rand returning the same series several times....
|
||||
srand( time( NULL ) );
|
||||
}
|
||||
|
||||
|
@ -107,7 +93,7 @@ DBMgr::AddNew( const char* cookie, const char* connName, CookieID cid,
|
|||
qb.appendQueryf( "INSERT INTO " GAMES_TABLE
|
||||
" (cid, room, connName, nTotal, lang, pub)"
|
||||
" VALUES( $$, $$, $$, $$, $$, $$ )" )
|
||||
.appendParam(cid)
|
||||
.appendParam(cid)
|
||||
.appendParam(cookie)
|
||||
.appendParam(connName)
|
||||
.appendParam(nPlayersT)
|
||||
|
@ -136,7 +122,7 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
|||
{
|
||||
bool found = false;
|
||||
|
||||
const char* fmt = "SELECT cid, room, lang, nPerDevice, dead FROM "
|
||||
const char* fmt = "SELECT cid, room, lang, dead FROM "
|
||||
GAMES_TABLE " WHERE connName = '%s' AND nTotal = %d "
|
||||
"AND %d = seeds[%d] AND 'A' = ack[%d] "
|
||||
;
|
||||
|
@ -148,10 +134,11 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
|||
assert( 1 >= PQntuples( result ) );
|
||||
found = 1 == PQntuples( result );
|
||||
if ( found ) {
|
||||
*cidp = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
||||
*langP = atoi( PQgetvalue( result, 0, 2 ) );
|
||||
*isDead = 't' == PQgetvalue( result, 0, 4 )[0];
|
||||
int col = 0;
|
||||
*cidp = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
*langP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*isDead = 't' == PQgetvalue( result, 0, col++ )[0];
|
||||
}
|
||||
PQclear( result );
|
||||
|
||||
|
@ -160,28 +147,29 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
|||
} /* FindGameFor */
|
||||
|
||||
CookieID
|
||||
DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen,
|
||||
DBMgr::FindGame( const char* connName, HostID hid, char* roomBuf, int roomBufLen,
|
||||
int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead )
|
||||
{
|
||||
CookieID cid = 0;
|
||||
|
||||
const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice, dead FROM "
|
||||
const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice[%d], dead FROM "
|
||||
GAMES_TABLE " WHERE connName = '%s'"
|
||||
// " LIMIT 1"
|
||||
;
|
||||
StrWPF query;
|
||||
query.catf( fmt, connName );
|
||||
query.catf( fmt, hid, connName );
|
||||
logf( XW_LOGINFO, "query: %s", query.c_str() );
|
||||
|
||||
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||
assert( 1 >= PQntuples( result ) );
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
cid = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
||||
*langP = atoi( PQgetvalue( result, 0, 2 ) );
|
||||
*nPlayersTP = atoi( PQgetvalue( result, 0, 3 ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, 4 ) );
|
||||
*isDead = 't' == PQgetvalue( result, 0, 5 )[0];
|
||||
int col = 0;
|
||||
cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( roomBuf, roomBufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
*langP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersTP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*isDead = 't' == PQgetvalue( result, 0, col++ )[0];
|
||||
}
|
||||
PQclear( result );
|
||||
|
||||
|
@ -189,6 +177,40 @@ DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen,
|
|||
return cid;
|
||||
} /* FindGame */
|
||||
|
||||
CookieID
|
||||
DBMgr::FindGame( const AddrInfo::ClientToken clientToken, HostID hid,
|
||||
char* connNameBuf, int connNameBufLen,
|
||||
char* roomBuf, int roomBufLen,
|
||||
int* langP, int* nPlayersTP, int* nPlayersHP )
|
||||
{
|
||||
CookieID cid = 0;
|
||||
const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice[%d], connname FROM "
|
||||
GAMES_TABLE " WHERE tokens[%d] = %d and NOT dead";
|
||||
// " LIMIT 1"
|
||||
;
|
||||
StrWPF query;
|
||||
query.catf( fmt, hid, hid, clientToken );
|
||||
logf( XW_LOGINFO, "query: %s", query.c_str() );
|
||||
|
||||
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
int col = 0;
|
||||
cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
// room
|
||||
snprintf( roomBuf, roomBufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
// lang
|
||||
*langP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersTP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( connNameBuf, connNameBufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
}
|
||||
PQclear( result );
|
||||
|
||||
logf( XW_LOGINFO, "%s(ct=%d,hid=%d) => %d (connname=%s)", __func__, clientToken,
|
||||
hid, cid, connNameBuf );
|
||||
return cid;
|
||||
}
|
||||
|
||||
bool
|
||||
DBMgr::FindPlayer( DevIDRelay relayID, AddrInfo::ClientToken token,
|
||||
string& connName, HostID* hidp, unsigned short* seed )
|
||||
|
@ -294,11 +316,13 @@ DBMgr::SeenSeed( const char* cookie, unsigned short seed,
|
|||
NULL, NULL, 0 );
|
||||
bool found = 1 == PQntuples( result );
|
||||
if ( found ) {
|
||||
*cid = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
*nPlayersHP = here_less_seed( PQgetvalue( result, 0, 2 ),
|
||||
atoi( PQgetvalue( result, 0, 3 ) ),
|
||||
seed );
|
||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
||||
int col = 0;
|
||||
*cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
|
||||
const char* seeds = PQgetvalue( result, 0, col++ );
|
||||
int perDeviceSum = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersHP = here_less_seed( seeds, perDeviceSum, seed );
|
||||
}
|
||||
PQclear( result );
|
||||
logf( XW_LOGINFO, "%s(%4X)=>%s", __func__, seed, found?"true":"false" );
|
||||
|
@ -333,9 +357,10 @@ DBMgr::FindOpen( const char* cookie, int lang, int nPlayersT, int nPlayersH,
|
|||
NULL, NULL, 0 );
|
||||
CookieID cid = 0;
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
cid = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, 2 ) );
|
||||
int col = 0;
|
||||
cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
/* cid may be 0, but should use game anyway */
|
||||
}
|
||||
PQclear( result );
|
||||
|
@ -699,9 +724,11 @@ DBMgr::RecordSent( const int* msgIDs, int nMsgIDs )
|
|||
if ( PGRES_TUPLES_OK == PQresultStatus( result ) ) {
|
||||
int ntuples = PQntuples( result );
|
||||
for ( int ii = 0; ii < ntuples; ++ii ) {
|
||||
RecordSent( PQgetvalue( result, ii, 0 ),
|
||||
atoi( PQgetvalue( result, ii, 1 ) ),
|
||||
atoi( PQgetvalue( result, ii, 2 ) ) );
|
||||
int col = 0;
|
||||
const char* const connName = PQgetvalue( result, ii, col++ );
|
||||
HostID hid = atoi( PQgetvalue( result, ii, col++ ) );
|
||||
int nBytes = atoi( PQgetvalue( result, ii, col++ ) );
|
||||
RecordSent( connName, hid, nBytes );
|
||||
}
|
||||
}
|
||||
PQclear( result );
|
||||
|
@ -1014,15 +1041,16 @@ DBMgr::CountStoredMessages( DevIDRelay relayID )
|
|||
return getCountWhere( MSGS_TABLE, test );
|
||||
}
|
||||
|
||||
void
|
||||
int
|
||||
DBMgr::StoreMessage( DevIDRelay destDevID, const uint8_t* const buf,
|
||||
int len )
|
||||
{
|
||||
int msgID = 0;
|
||||
clearHasNoMessages( destDevID );
|
||||
|
||||
size_t newLen;
|
||||
const char* fmt = "INSERT INTO " MSGS_TABLE " "
|
||||
"(devid, %s, msglen) VALUES(%d, %s'%s', %d)";
|
||||
"(devid, %s, msglen) VALUES(%d, %s'%s', %d) RETURNING id";
|
||||
|
||||
StrWPF query;
|
||||
if ( m_useB64 ) {
|
||||
|
@ -1038,13 +1066,20 @@ DBMgr::StoreMessage( DevIDRelay destDevID, const uint8_t* const buf,
|
|||
}
|
||||
|
||||
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
|
||||
execSql( query );
|
||||
|
||||
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
msgID = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
}
|
||||
PQclear( result );
|
||||
return msgID;
|
||||
}
|
||||
|
||||
void
|
||||
int
|
||||
DBMgr::StoreMessage( const char* const connName, int destHid,
|
||||
const uint8_t* buf, int len )
|
||||
{
|
||||
int msgID = 0;
|
||||
clearHasNoMessages( connName, destHid );
|
||||
|
||||
DevIDRelay devID = getDevID( connName, destHid );
|
||||
|
@ -1074,7 +1109,7 @@ DBMgr::StoreMessage( const char* const connName, int destHid,
|
|||
#ifdef HAVE_STIME
|
||||
" AND stime='epoch'"
|
||||
#endif
|
||||
" );", connName, destHid, b64 );
|
||||
" )", connName, destHid, b64 );
|
||||
g_free( b64 );
|
||||
} else {
|
||||
uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf,
|
||||
|
@ -1085,9 +1120,17 @@ DBMgr::StoreMessage( const char* const connName, int destHid,
|
|||
"E", bytes, len );
|
||||
PQfreemem( bytes );
|
||||
}
|
||||
query.catf(" RETURNING id;");
|
||||
|
||||
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
|
||||
execSql( query );
|
||||
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
msgID = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
} else {
|
||||
logf( XW_LOGINFO, "Not stored; duplicate?" );
|
||||
}
|
||||
PQclear( result );
|
||||
return msgID;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -75,9 +75,13 @@ class DBMgr {
|
|||
bool FindRelayIDFor( const char* connName, HostID hid, unsigned short seed,
|
||||
const DevID* host, DevIDRelay* devID );
|
||||
|
||||
CookieID FindGame( const char* connName, char* cookieBuf, int bufLen,
|
||||
CookieID FindGame( const char* connName, HostID hid, char* cookieBuf, int bufLen,
|
||||
int* langP, int* nPlayersTP, int* nPlayersHP,
|
||||
bool* isDead );
|
||||
CookieID FindGame( const AddrInfo::ClientToken clientToken, HostID hid,
|
||||
char* connNameBuf, int connNameBufLen,
|
||||
char* cookieBuf, int cookieBufLen,
|
||||
int* langP, int* nPlayersTP, int* nPlayersHP );
|
||||
|
||||
bool FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
||||
unsigned short seed, HostID hid,
|
||||
|
@ -137,10 +141,10 @@ class DBMgr {
|
|||
/* message storage -- different DB */
|
||||
int CountStoredMessages( const char* const connName );
|
||||
int CountStoredMessages( DevIDRelay relayID );
|
||||
void StoreMessage( DevIDRelay destRelayID, const uint8_t* const buf,
|
||||
int len );
|
||||
void StoreMessage( const char* const connName, int destHid,
|
||||
const uint8_t* const buf, int len );
|
||||
int StoreMessage( DevIDRelay destRelayID, const uint8_t* const buf,
|
||||
int len );
|
||||
int StoreMessage( const char* const connName, int destHid,
|
||||
const uint8_t* const buf, int len );
|
||||
void GetStoredMessages( DevIDRelay relayID, vector<MsgInfo>& msgs );
|
||||
void GetStoredMessages( const char* const connName, HostID hid,
|
||||
vector<DBMgr::MsgInfo>& msgs );
|
||||
|
@ -171,6 +175,7 @@ class DBMgr {
|
|||
int clientVersion, const char* const model,
|
||||
const char* const osVers, DevIDRelay relayID );
|
||||
|
||||
|
||||
PGconn* getThreadConn( void );
|
||||
void clearThreadConn();
|
||||
|
||||
|
|
255
xwords4/relay/scripts/relay.py
Executable file
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import base64, json, mod_python, socket, struct, sys
|
||||
import psycopg2, random
|
||||
|
||||
PROTOCOL_VERSION = 0
|
||||
PRX_DEVICE_GONE = 3
|
||||
PRX_GET_MSGS = 4
|
||||
|
||||
# try:
|
||||
# from mod_python import apache
|
||||
# apacheAvailable = True
|
||||
# except ImportError:
|
||||
# apacheAvailable = False
|
||||
|
||||
# Joining a game. Basic idea is you have stuff to match on (room,
|
||||
# number in game, language) and when somebody wants to join you add to
|
||||
# an existing matching game if there's space otherwise create a new
|
||||
# one. Problems are the unreliablity of transport: if you give a space
|
||||
# and the device doesn't get the message you can't hold it forever. So
|
||||
# device provides a seed that holds the space. If it asks again for a
|
||||
# space with the same seed it gets the same space. If it never asks
|
||||
# again (app deleted, say), the space needs eventually to be given to
|
||||
# somebody else. I think that's done by adding a timestamp array and
|
||||
# treating the space as available if TIME has expired. Need to think
|
||||
# about this: what if app fails to ACK for TIME, then returns with
|
||||
# seed to find it given away. Let's do a 30 minute reservation for
|
||||
# now? [Note: much of this is PENDING]
|
||||
|
||||
def join(req, devID, room, seed, hid = 0, lang = 1, nInGame = 2, nHere = 1, inviteID = None):
|
||||
assert hid <= 4
|
||||
seed = int(seed)
|
||||
assert seed != 0
|
||||
nInGame = int(nInGame)
|
||||
nHere = int(nHere)
|
||||
assert nHere <= nInGame
|
||||
assert nInGame <= 4
|
||||
|
||||
devID = int(devID, 16)
|
||||
|
||||
connname = None
|
||||
logs = [] # for debugging
|
||||
# logs.append('vers: ' + platform.python_version())
|
||||
|
||||
con = psycopg2.connect(database='xwgames')
|
||||
cur = con.cursor()
|
||||
# cur.execute('LOCK TABLE games IN ACCESS EXCLUSIVE MODE')
|
||||
|
||||
# First see if there's a game with a space for me. Must match on
|
||||
# room, lang and size. Must have room OR must have already given a
|
||||
# spot for a seed equal to mine, in which case I get it
|
||||
# back. Assumption is I didn't ack in time.
|
||||
|
||||
query = "SELECT connname, seeds, nperdevice FROM games "
|
||||
query += "WHERE lang = %s AND nTotal = %s AND room = %s "
|
||||
query += "AND (njoined + %s <= ntotal OR %s = ANY(seeds)) "
|
||||
query += "LIMIT 1"
|
||||
cur.execute( query, (lang, nInGame, room, nHere, seed))
|
||||
for row in cur:
|
||||
(connname, seeds, nperdevice) = row
|
||||
print('found', connname, seeds, nperdevice)
|
||||
break # should be only one!
|
||||
|
||||
# If we've found a match, we either need to UPDATE or, if the
|
||||
# seeds match, remind the caller of where he belongs. If a hid's
|
||||
# been specified, we honor it by updating if the slot's available;
|
||||
# otherwise a new game has to be created.
|
||||
if connname:
|
||||
if seed in seeds and nHere == nperdevice[seeds.index(seed)]:
|
||||
hid = seeds.index(seed) + 1
|
||||
print('resusing seed case; outta here!')
|
||||
else:
|
||||
if hid == 0:
|
||||
# Any gaps? Assign it
|
||||
if None in seeds:
|
||||
hid = seeds.index(None) + 1
|
||||
else:
|
||||
hid = len(seeds) + 1
|
||||
print('set hid to', hid, 'based on ', seeds)
|
||||
else:
|
||||
print('hid already', hid)
|
||||
query = "UPDATE games SET njoined = njoined + %s, "
|
||||
query += "devids[%d] = %%s, " % hid
|
||||
query += "seeds[%d] = %%s, " % hid
|
||||
query += "jtimes[%d] = 'now', " % hid
|
||||
query += "nperdevice[%d] = %%s " % hid
|
||||
query += "WHERE connname = %s "
|
||||
print(query)
|
||||
params = (nHere, devID, seed, nHere, connname)
|
||||
cur.execute(query, params)
|
||||
|
||||
# If nothing was found, add a new game and add me. Honor my hid
|
||||
# preference if specified
|
||||
if not connname:
|
||||
# This requires python3, which likely requires mod_wsgi
|
||||
# ts = datetime.datetime.utcnow().timestamp()
|
||||
# connname = '%s:%d:1' % (xwconfig.k_HOSTNAME, int(ts * 1000))
|
||||
connname = '%s:%d:1' % (xwconfig.k_HOSTNAME, random.randint(0, 10000000000))
|
||||
useHid = hid == 0 and 1 or hid
|
||||
print('not found case; inserting using hid:', useHid)
|
||||
query = "INSERT INTO games (connname, room, lang, ntotal, njoined, " + \
|
||||
"devids[%d], seeds[%d], jtimes[%d], nperdevice[%d]) " % (4 * (useHid,))
|
||||
query += "VALUES (%s, %s, %s, %s, %s, %s, %s, 'now', %s) "
|
||||
query += "RETURNING connname, array_length(seeds,1); "
|
||||
cur.execute(query, (connname, room, lang, nInGame, nHere, devID, seed, nHere))
|
||||
for row in cur:
|
||||
connname, gothid = row
|
||||
break
|
||||
if hid == 0: hid = gothid
|
||||
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
result = {'connname': connname, 'hid' : hid, 'log' : ':'.join(logs)}
|
||||
|
||||
return json.dumps(result)
|
||||
|
||||
def kill(req, params):
|
||||
print(params)
|
||||
params = json.loads(params)
|
||||
count = len(params)
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('127.0.0.1', 10998))
|
||||
|
||||
header = struct.Struct('!BBh')
|
||||
strLens = 0
|
||||
for ii in range(count):
|
||||
strLens += len(params[ii]['relayID']) + 1
|
||||
size = header.size + (2*count) + strLens
|
||||
sock.send(struct.Struct('!h').pack(size))
|
||||
sock.send(header.pack(PROTOCOL_VERSION, PRX_DEVICE_GONE, count))
|
||||
|
||||
for ii in range(count):
|
||||
elem = params[ii]
|
||||
asBytes = bytes(elem['relayID'])
|
||||
sock.send(struct.Struct('!H%dsc' % (len(asBytes))).pack(elem['seed'], asBytes, '\n'))
|
||||
sock.close()
|
||||
|
||||
result = {'err': 0}
|
||||
return json.dumps(result)
|
||||
|
||||
# winds up in handle_udp_packet() in xwrelay.cpp
|
||||
def post(req, params):
|
||||
err = 'none'
|
||||
params = json.loads(params)
|
||||
data = params['data']
|
||||
timeoutSecs = 'timeoutSecs' in params and params['timeoutSecs'] or 1.0
|
||||
binData = [base64.b64decode(datum) for datum in data]
|
||||
|
||||
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
udpSock.settimeout(float(timeoutSecs)) # seconds
|
||||
addr = ("127.0.0.1", 10997)
|
||||
for binDatum in binData:
|
||||
udpSock.sendto(binDatum, addr)
|
||||
|
||||
responses = []
|
||||
while True:
|
||||
try:
|
||||
data, server = udpSock.recvfrom(1024)
|
||||
responses.append(base64.b64encode(data))
|
||||
except socket.timeout:
|
||||
#If data is not received back from server, print it has timed out
|
||||
err = 'timeout'
|
||||
break
|
||||
|
||||
result = {'err' : err, 'data' : responses}
|
||||
return json.dumps(result)
|
||||
|
||||
def query(req, params):
|
||||
print('params', params)
|
||||
params = json.loads(params)
|
||||
ids = params['ids']
|
||||
# timeoutSecs = 'timeoutSecs' in params and float(params['timeoutSecs']) or 2.0
|
||||
|
||||
idsLen = 0
|
||||
for id in ids: idsLen += len(id)
|
||||
|
||||
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# tcpSock.settimeout(timeoutSecs)
|
||||
tcpSock.connect(('127.0.0.1', 10998))
|
||||
|
||||
lenShort = 2 + idsLen + len(ids) + 2
|
||||
print(lenShort, PROTOCOL_VERSION, PRX_GET_MSGS, len(ids))
|
||||
header = struct.Struct('!hBBh')
|
||||
assert header.size == 6
|
||||
tcpSock.send(header.pack(lenShort, PROTOCOL_VERSION, PRX_GET_MSGS, len(ids)))
|
||||
|
||||
for id in ids: tcpSock.send(id + '\n')
|
||||
|
||||
result = {'ids':ids}
|
||||
try:
|
||||
msgsLists = {}
|
||||
shortUnpacker = struct.Struct('!H')
|
||||
resLen, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) # not getting all bytes
|
||||
nameCount, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size))
|
||||
resLen -= shortUnpacker.size
|
||||
print('resLen:', resLen, 'nameCount:', nameCount)
|
||||
if nameCount == len(ids) and resLen > 0:
|
||||
print('nameCount', nameCount)
|
||||
for ii in range(nameCount):
|
||||
perGame = []
|
||||
countsThisGame, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) # problem
|
||||
print('countsThisGame:', countsThisGame)
|
||||
for jj in range(countsThisGame):
|
||||
msgLen, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size))
|
||||
print('msgLen:', msgLen)
|
||||
msgs = []
|
||||
if msgLen > 0:
|
||||
msg = tcpSock.recv(msgLen)
|
||||
print('msg len:', len(msg))
|
||||
msg = base64.b64encode(msg)
|
||||
msgs.append(msg)
|
||||
perGame.append(msgs)
|
||||
msgsLists[ids[ii]] = perGame
|
||||
|
||||
result['msgs'] = msgsLists
|
||||
except:
|
||||
# Anything but a timeout should mean we abort/send nothing
|
||||
result['err'] = 'hit exception'
|
||||
None
|
||||
|
||||
return json.dumps(result)
|
||||
|
||||
def main():
|
||||
result = None
|
||||
if len(sys.argv) > 1:
|
||||
cmd = sys.argv[1]
|
||||
args = sys.argv[2:]
|
||||
if cmd == 'query' and len(args) > 0:
|
||||
result = query(None, json.dumps({'ids':args}))
|
||||
elif cmd == 'post':
|
||||
# Params = { 'data' : 'V2VkIE9jdCAxOCAwNjowNDo0OCBQRFQgMjAxNwo=' }
|
||||
# params = json.dumps(params)
|
||||
# print(post(None, params))
|
||||
pass
|
||||
elif cmd == 'join':
|
||||
if len(args) == 6:
|
||||
result = join(None, 1, args[0], int(args[1]), int(args[2]), int(args[3]), int(args[4]), int(args[5]))
|
||||
elif cmd == 'kill':
|
||||
result = kill( None, json.dumps([{'relayID': args[0], 'seed':int(args[1])}]) )
|
||||
|
||||
if result:
|
||||
print '->', result
|
||||
else:
|
||||
print 'USAGE: query [connname/hid]*'
|
||||
print ' join <roomName> <seed> <hid> <lang> <nTotal> <nHere>'
|
||||
print ' query [connname/hid]*'
|
||||
# print ' post '
|
||||
print ' kill <relayID> <seed>'
|
||||
print ' join <roomName> <seed> <hid> <lang> <nTotal> <nHere>'
|
||||
|
||||
##############################################################################
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -51,16 +51,18 @@ fi
|
|||
|
||||
echo -n "Device (pid) count: $(pidof xwords | wc | awk '{print $2}')"
|
||||
echo "; relay pid[s]: $(pidof xwrelay)"
|
||||
echo "Row count:" $(psql -t xwgames -c "select count(*) FROM games $QUERY;")
|
||||
echo -n "Row count:" $(psql -t xwgames -c "select count(*) FROM games $QUERY;")
|
||||
echo "; Relay sockets: $(for PID in $(pidof xwrelay); do ls /proc/$PID/fd; done | sort -un | tr '\n' ' ')"
|
||||
|
||||
# Games
|
||||
echo "SELECT dead as d,connname,cid,room,lang as lg,clntVers as cv ,ntotal as t,nperdevice as nPerDev,nsents as snts, seeds,devids,tokens,ack, mtimes "\
|
||||
echo "SELECT dead as d,connname,cid,room,lang as lg,clntVers as cv ,ntotal as t,nperdevice as npd,nsents as snts, seeds,devids,tokens,ack, mtimes "\
|
||||
"FROM games $QUERY ORDER BY NOT dead, ctime DESC LIMIT $LIMIT;" \
|
||||
| psql xwgames
|
||||
|
||||
# Messages
|
||||
echo "SELECT * "\
|
||||
"FROM msgs WHERE connname IN (SELECT connname from games $QUERY) "\
|
||||
echo "Unack'd msgs count:" $(psql -t xwgames -c "select count(*) FROM msgs where stime = 'epoch' AND connname IN (SELECT connname from games $QUERY);")
|
||||
echo "SELECT id,connName,hid as h,token,ctime,stime,devid,msg64 "\
|
||||
"FROM msgs WHERE stime = 'epoch' AND connname IN (SELECT connname from games $QUERY) "\
|
||||
"ORDER BY ctime DESC, connname LIMIT $LIMIT;" \
|
||||
| psql xwgames
|
||||
|
||||
|
|
|
@ -119,13 +119,17 @@ XWThreadPool::Stop()
|
|||
void
|
||||
XWThreadPool::AddSocket( SockType stype, QueueCallback proc, const AddrInfo* from )
|
||||
{
|
||||
from->ref();
|
||||
|
||||
int sock = from->getSocket();
|
||||
logf( XW_LOGVERBOSE0, "%s(sock=%d, isTCP=%d)", __func__, sock, from->isTCP() );
|
||||
SockInfo si = { .m_type = stype,
|
||||
.m_proc = proc,
|
||||
.m_addr = *from
|
||||
};
|
||||
{
|
||||
int sock = from->getSocket();
|
||||
RWWriteLock ml( &m_activeSocketsRWLock );
|
||||
SockInfo si;
|
||||
si.m_type = stype;
|
||||
si.m_proc = proc;
|
||||
si.m_addr = *from;
|
||||
assert( m_activeSockets.find( sock ) == m_activeSockets.end() );
|
||||
m_activeSockets.insert( pair<int, SockInfo>( sock, si ) );
|
||||
}
|
||||
interrupt_poll();
|
||||
|
@ -158,13 +162,14 @@ XWThreadPool::RemoveSocket( const AddrInfo* addr )
|
|||
|
||||
size_t prevSize = m_activeSockets.size();
|
||||
|
||||
map<int, SockInfo>::iterator iter = m_activeSockets.find( addr->getSocket() );
|
||||
int sock = addr->getSocket();
|
||||
map<int, SockInfo>::iterator iter = m_activeSockets.find( sock );
|
||||
if ( m_activeSockets.end() != iter && iter->second.m_addr.equals( *addr ) ) {
|
||||
m_activeSockets.erase( iter );
|
||||
found = true;
|
||||
}
|
||||
logf( XW_LOGINFO, "%s: AFTER: %d sockets active (was %d)", __func__,
|
||||
m_activeSockets.size(), prevSize );
|
||||
logf( XW_LOGINFO, "%s(): AFTER closing %d: %d sockets active (was %d)", __func__,
|
||||
sock, m_activeSockets.size(), prevSize );
|
||||
}
|
||||
return found;
|
||||
} /* RemoveSocket */
|
||||
|
@ -184,8 +189,14 @@ XWThreadPool::CloseSocket( const AddrInfo* addr )
|
|||
++iter;
|
||||
}
|
||||
}
|
||||
logf( XW_LOGINFO, "CLOSING socket %d", addr->getSocket() );
|
||||
close( addr->getSocket() );
|
||||
int sock = addr->getSocket();
|
||||
int err = close( sock );
|
||||
if ( 0 != err ) {
|
||||
logf( XW_LOGERROR, "%s(): close(socket=%d) => %d/%s", __func__,
|
||||
sock, errno, strerror(errno) );
|
||||
} else {
|
||||
logf( XW_LOGINFO, "%s(): close(socket=%d) succeeded", __func__, sock );
|
||||
}
|
||||
|
||||
/* We always need to interrupt the poll because the socket we're closing
|
||||
will be in the list being listened to. That or we need to drop sockets
|
||||
|
@ -198,7 +209,7 @@ XWThreadPool::CloseSocket( const AddrInfo* addr )
|
|||
void
|
||||
XWThreadPool::EnqueueKill( const AddrInfo* addr, const char* const why )
|
||||
{
|
||||
logf( XW_LOGINFO, "%s(%d) reason: %s", __func__, addr->getSocket(), why );
|
||||
logf( XW_LOGINFO, "%s(socket = %d) reason: %s", __func__, addr->getSocket(), why );
|
||||
if ( addr->isTCP() ) {
|
||||
SockInfo si;
|
||||
si.m_type = STYPE_UNKNOWN;
|
||||
|
@ -265,7 +276,6 @@ XWThreadPool::real_tpool_main( ThreadInfo* tip )
|
|||
|
||||
if ( gotOne ) {
|
||||
sock = pr.m_info.m_addr.getSocket();
|
||||
logf( XW_LOGINFO, "worker thread got socket %d from queue", socket );
|
||||
switch ( pr.m_act ) {
|
||||
case Q_READ:
|
||||
assert( 0 );
|
||||
|
@ -275,8 +285,9 @@ XWThreadPool::real_tpool_main( ThreadInfo* tip )
|
|||
// }
|
||||
break;
|
||||
case Q_KILL:
|
||||
logf( XW_LOGINFO, "worker thread got socket %d from queue (to close it)", sock );
|
||||
(*m_kFunc)( &pr.m_info.m_addr );
|
||||
CloseSocket( &pr.m_info.m_addr );
|
||||
pr.m_info.m_addr.unref();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
@ -392,35 +403,40 @@ XWThreadPool::real_listener()
|
|||
curfd = 1;
|
||||
|
||||
int ii;
|
||||
for ( ii = 0; ii < nSockets && nEvents > 0; ++ii ) {
|
||||
for ( ii = 0; ii < nSockets && nEvents > 0; ++ii, ++curfd ) {
|
||||
|
||||
if ( fds[curfd].revents != 0 ) {
|
||||
// int socket = fds[curfd].fd;
|
||||
SockInfo* sinfo = &sinfos[curfd];
|
||||
const AddrInfo* addr = &sinfo->m_addr;
|
||||
|
||||
assert( fds[curfd].fd == addr->getSocket() );
|
||||
int sock = addr->getSocket();
|
||||
assert( fds[curfd].fd == sock );
|
||||
if ( !SocketFound( addr ) ) {
|
||||
logf( XW_LOGINFO, "%s(): dropping socket %d: not found",
|
||||
__func__, addr->getSocket() );
|
||||
/* no further processing if it's been removed while
|
||||
we've been sleeping in poll */
|
||||
we've been sleeping in poll. BUT: shouldn't curfd
|
||||
be incremented?? */
|
||||
--nEvents;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 0 != (fds[curfd].revents & (POLLIN | POLLPRI)) ) {
|
||||
if ( !UdpQueue::get()->handle( addr, sinfo->m_proc ) ) {
|
||||
// This is likely wrong!!! return of 0 means
|
||||
// remote closed, not error.
|
||||
RemoveSocket( addr );
|
||||
EnqueueKill( addr, "bad packet" );
|
||||
EnqueueKill( addr, "got EOF" );
|
||||
}
|
||||
} else {
|
||||
logf( XW_LOGERROR, "odd revents: %x",
|
||||
fds[curfd].revents );
|
||||
logf( XW_LOGERROR, "%s(): odd revents: %x; bad socket %d",
|
||||
__func__, fds[curfd].revents, sock );
|
||||
RemoveSocket( addr );
|
||||
EnqueueKill( addr, "error/hup in poll()" );
|
||||
EnqueueKill( addr, "error/hup in poll()" );
|
||||
}
|
||||
--nEvents;
|
||||
}
|
||||
++curfd;
|
||||
}
|
||||
assert( nEvents == 0 );
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ static UdpQueue* s_instance = NULL;
|
|||
|
||||
|
||||
void
|
||||
UdpThreadClosure::logStats()
|
||||
PacketThreadClosure::logStats()
|
||||
{
|
||||
time_t now = time( NULL );
|
||||
if ( 1 < now - m_created ) {
|
||||
|
@ -48,6 +48,7 @@ PartialPacket::stillGood() const
|
|||
bool
|
||||
PartialPacket::readAtMost( int len )
|
||||
{
|
||||
assert( len > 0 );
|
||||
bool success = false;
|
||||
uint8_t tmp[len];
|
||||
ssize_t nRead = recv( m_sock, tmp, len, 0 );
|
||||
|
@ -57,10 +58,12 @@ PartialPacket::readAtMost( int len )
|
|||
logf( XW_LOGERROR, "%s(len=%d, socket=%d): recv failed: %d (%s)", __func__,
|
||||
len, m_sock, m_errno, strerror(m_errno) );
|
||||
}
|
||||
} else if ( 0 == nRead ) { // remote socket closed
|
||||
logf( XW_LOGINFO, "%s: remote closed (socket=%d)", __func__, m_sock );
|
||||
} else if ( 0 == nRead ) { // remote socket half-closed
|
||||
logf( XW_LOGINFO, "%s(): remote closed (socket=%d)", __func__, m_sock );
|
||||
m_errno = -1; // so stillGood will fail
|
||||
} else {
|
||||
// logf( XW_LOGVERBOSE0, "%s(): read %d bytes on socket %d", __func__,
|
||||
// nRead, m_sock );
|
||||
m_errno = 0;
|
||||
success = len == nRead;
|
||||
int curSize = m_buf.size();
|
||||
|
@ -100,7 +103,11 @@ UdpQueue::get()
|
|||
return s_instance;
|
||||
}
|
||||
|
||||
// return false if socket should no longer be used
|
||||
// If we're already assembling data from this socket, continue. Otherwise
|
||||
// create a new parital packet and store data there. If we wind up with a
|
||||
// complete packet, dispatch it and delete since the data's been delivered.
|
||||
//
|
||||
// Return false if socket should no longer be used.
|
||||
bool
|
||||
UdpQueue::handle( const AddrInfo* addr, QueueCallback cb )
|
||||
{
|
||||
|
@ -145,6 +152,7 @@ UdpQueue::handle( const AddrInfo* addr, QueueCallback cb )
|
|||
}
|
||||
|
||||
success = success && (NULL == packet || packet->stillGood());
|
||||
logf( XW_LOGVERBOSE0, "%s(sock=%d) => %d", __func__, sock, success );
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -152,17 +160,21 @@ void
|
|||
UdpQueue::handle( const AddrInfo* addr, const uint8_t* buf, int len,
|
||||
QueueCallback cb )
|
||||
{
|
||||
UdpThreadClosure* utc = new UdpThreadClosure( addr, buf, len, cb );
|
||||
// addr->ref();
|
||||
PacketThreadClosure* ptc = new PacketThreadClosure( addr, buf, len, cb );
|
||||
MutexLock ml( &m_queueMutex );
|
||||
int id = ++m_nextID;
|
||||
utc->setID( id );
|
||||
logf( XW_LOGINFO, "%s: enqueuing packet %d (socket %d, len %d)",
|
||||
ptc->setID( id );
|
||||
logf( XW_LOGINFO, "%s(): enqueuing packet %d (socket %d, len %d)",
|
||||
__func__, id, addr->getSocket(), len );
|
||||
m_queue.push_back( utc );
|
||||
m_queue.push_back( ptc );
|
||||
|
||||
pthread_cond_signal( &m_queueCondVar );
|
||||
}
|
||||
|
||||
// Remove any PartialPacket record with the same socket/fd. This makes sense
|
||||
// when the socket's being reused or when we have just dealt with a single
|
||||
// packet and might be getting more.
|
||||
void
|
||||
UdpQueue::newSocket_locked( int sock )
|
||||
{
|
||||
|
@ -194,25 +206,26 @@ UdpQueue::thread_main()
|
|||
while ( m_queue.size() == 0 ) {
|
||||
pthread_cond_wait( &m_queueCondVar, &m_queueMutex );
|
||||
}
|
||||
UdpThreadClosure* utc = m_queue.front();
|
||||
PacketThreadClosure* ptc = m_queue.front();
|
||||
m_queue.pop_front();
|
||||
|
||||
pthread_mutex_unlock( &m_queueMutex );
|
||||
|
||||
utc->noteDequeued();
|
||||
ptc->noteDequeued();
|
||||
|
||||
time_t age = utc->ageInSeconds();
|
||||
time_t age = ptc->ageInSeconds();
|
||||
if ( 30 > age ) {
|
||||
logf( XW_LOGINFO, "%s: dispatching packet %d (socket %d); "
|
||||
"%d seconds old", __func__, utc->getID(),
|
||||
utc->addr()->getSocket(), age );
|
||||
(*utc->cb())( utc );
|
||||
utc->logStats();
|
||||
"%d seconds old", __func__, ptc->getID(),
|
||||
ptc->addr()->getSocket(), age );
|
||||
(*ptc->cb())( ptc );
|
||||
ptc->logStats();
|
||||
} else {
|
||||
logf( XW_LOGINFO, "%s: dropping packet %d; it's %d seconds old!",
|
||||
__func__, age );
|
||||
}
|
||||
delete utc;
|
||||
// ptc->addr()->unref();
|
||||
delete ptc;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -30,13 +30,13 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
class UdpThreadClosure;
|
||||
class PacketThreadClosure;
|
||||
|
||||
typedef void (*QueueCallback)( UdpThreadClosure* closure );
|
||||
typedef void (*QueueCallback)( PacketThreadClosure* closure );
|
||||
|
||||
class UdpThreadClosure {
|
||||
class PacketThreadClosure {
|
||||
public:
|
||||
UdpThreadClosure( const AddrInfo* addr, const uint8_t* buf,
|
||||
PacketThreadClosure( const AddrInfo* addr, const uint8_t* buf,
|
||||
int len, QueueCallback cb )
|
||||
: m_buf(new uint8_t[len])
|
||||
, m_len(len)
|
||||
|
@ -44,10 +44,14 @@ public:
|
|||
, m_cb(cb)
|
||||
, m_created(time( NULL ))
|
||||
{
|
||||
memcpy( m_buf, buf, len );
|
||||
memcpy( m_buf, buf, len );
|
||||
m_addr.ref();
|
||||
}
|
||||
|
||||
~UdpThreadClosure() { delete[] m_buf; }
|
||||
~PacketThreadClosure() {
|
||||
m_addr.unref();
|
||||
delete[] m_buf;
|
||||
}
|
||||
|
||||
const uint8_t* buf() const { return m_buf; }
|
||||
int len() const { return m_len; }
|
||||
|
@ -109,8 +113,8 @@ class UdpQueue {
|
|||
pthread_mutex_t m_partialsMutex;
|
||||
pthread_mutex_t m_queueMutex;
|
||||
pthread_cond_t m_queueCondVar;
|
||||
deque<UdpThreadClosure*> m_queue;
|
||||
// map<int, vector<UdpThreadClosure*> > m_bySocket;
|
||||
deque<PacketThreadClosure*> m_queue;
|
||||
// map<int, vector<PacketThreadClosure*> > m_bySocket;
|
||||
int m_nextID;
|
||||
map<int, PartialPacket*> m_partialPackets;
|
||||
};
|
||||
|
|