mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-04 23:02:02 +01:00
Merge remote-tracking branch 'origin/android_translate' into android_translate
This commit is contained in:
commit
0aa8874e4d
69 changed files with 2729 additions and 456 deletions
|
@ -1,6 +1,6 @@
|
|||
def INITIAL_CLIENT_VERS = 8
|
||||
def VERSION_CODE_BASE = 126
|
||||
def VERSION_NAME = '4.4.130'
|
||||
def VERSION_CODE_BASE = 127
|
||||
def VERSION_NAME = '4.4.131'
|
||||
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,7 +197,7 @@ 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'
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>CrossWords 4.4.130 release</h2>
|
||||
<h2>CrossWords 4.4.131 release</h2>
|
||||
|
||||
<p>This release makes a couple of small UI tweaks.</p>
|
||||
<p>An F-Droid-only release meeting new requirements</p>
|
||||
|
||||
<div id="survey">
|
||||
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
|
||||
|
@ -25,12 +25,10 @@
|
|||
|
||||
<h3>New with this release</h3>
|
||||
<ul>
|
||||
<li>Offer to "Archive" finished games</li>
|
||||
<li>Make tap on thumbnail toggle whether game's selected rather
|
||||
than open it. (Tap to the right still opens)</li>
|
||||
<li>Bug fix: don't allow duplicate group names</li>
|
||||
<li>Fix battery-hogging behavior on non-Google-play
|
||||
installs</li>
|
||||
<li>F-Droid has stiffened their prohibition against including
|
||||
proprietary Google components. This release complies by removing
|
||||
"Google Cloud Messaging" – which never worked on
|
||||
f-droid installs anyway.</li>
|
||||
</ul>
|
||||
|
||||
<p>(The full changelog
|
||||
|
|
|
@ -206,23 +206,16 @@ public class BoardDelegate extends DelegateBase
|
|||
ab.setNegativeButton( R.string.button_rematch, lstnr );
|
||||
|
||||
// If we're not already in the "archive" group, offer to move
|
||||
final String archiveName = LocUtils
|
||||
.getString( m_activity, R.string.group_name_archive );
|
||||
final long archiveGroup = DBUtils.getGroup( m_activity, archiveName );
|
||||
long curGroup = DBUtils.getGroupForGame( m_activity, m_rowid );
|
||||
if ( curGroup != archiveGroup ) {
|
||||
if ( !inArchiveGroup() ) {
|
||||
lstnr = new OnClickListener() {
|
||||
public void onClick( DialogInterface dlg,
|
||||
int whichButton ) {
|
||||
makeNotAgainBuilder( R.string.not_again_archive,
|
||||
R.string.key_na_archive,
|
||||
Action.ARCHIVE_ACTION )
|
||||
.setParams( archiveName, archiveGroup )
|
||||
.show();
|
||||
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 )
|
||||
|
@ -847,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 );
|
||||
|
@ -890,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;
|
||||
|
@ -1112,9 +1112,7 @@ public class BoardDelegate extends DelegateBase
|
|||
break;
|
||||
|
||||
case ARCHIVE_ACTION:
|
||||
String archiveName = (String)params[0];
|
||||
long archiveGroup = (Long)params[1];
|
||||
archiveAndClose( archiveName, archiveGroup );
|
||||
archiveAndClose();
|
||||
break;
|
||||
|
||||
case ENABLE_SMS_DO:
|
||||
|
@ -2600,12 +2598,33 @@ public class BoardDelegate extends DelegateBase
|
|||
return wordsArray;
|
||||
}
|
||||
|
||||
private void archiveAndClose( String archiveName, long groupID )
|
||||
private boolean inArchiveGroup()
|
||||
{
|
||||
if ( DBUtils.GROUPID_UNSPEC == groupID ) {
|
||||
groupID = DBUtils.addGroup( m_activity, archiveName );
|
||||
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, groupID );
|
||||
DBUtils.moveGame( m_activity, m_rowid, archiveGroupID );
|
||||
waitCloseGame( false );
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -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() ) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 ) {
|
||||
|
|
|
@ -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,16 @@ 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.atomic.AtomicInteger;
|
||||
|
||||
public class RelayService extends XWService
|
||||
implements NetStateCache.StateChangedIf {
|
||||
|
@ -60,6 +68,7 @@ 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;
|
||||
|
||||
// One day, in seconds. Probably should be configurable.
|
||||
private static final long MAX_KEEPALIVE_SECS = 24 * 60 * 60;
|
||||
|
@ -90,8 +99,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 Map<Integer, PacketData> s_packetsSentUDP = new HashMap<>();
|
||||
private static Map<Integer, PacketData> s_packetsSentWeb = new HashMap<>();
|
||||
private static AtomicInteger s_nextPacketID = new AtomicInteger();
|
||||
private static boolean s_gcmWorking = false;
|
||||
private static boolean s_registered = false;
|
||||
private static CommsAddrRec s_addr =
|
||||
|
@ -110,6 +120,8 @@ public class RelayService extends XWService
|
|||
private Runnable m_onInactivity;
|
||||
private int m_maxIntervalSeconds = 0;
|
||||
private long m_lastGamePacketReceived;
|
||||
// m_nativeNotWorking: set to true if too many acks missed?
|
||||
private boolean m_nativeNotWorking = false;
|
||||
private static DevIDType s_curType = DevIDType.ID_TYPE_NONE;
|
||||
private static long s_regStartTime = 0;
|
||||
|
||||
|
@ -160,7 +172,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;
|
||||
}
|
||||
|
||||
|
@ -403,7 +415,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 +461,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 +522,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() {
|
||||
|
@ -601,6 +613,15 @@ public class RelayService extends XWService
|
|||
}
|
||||
}
|
||||
|
||||
private boolean skipNativeSend()
|
||||
{
|
||||
boolean skip = m_nativeNotWorking;
|
||||
if ( ! skip ) {
|
||||
skip = XWPrefs.getSkipToWebAPI( RelayService.this );
|
||||
}
|
||||
return skip;
|
||||
}
|
||||
|
||||
private void startWriteThread()
|
||||
{
|
||||
if ( null == m_UDPWriteThread ) {
|
||||
|
@ -608,46 +629,38 @@ public class RelayService extends XWService
|
|||
public void run() {
|
||||
Log.i( TAG, "write thread starting" );
|
||||
for ( ; ; ) {
|
||||
PacketData outData;
|
||||
boolean exitNow = false;
|
||||
boolean useWeb = skipNativeSend();
|
||||
List<PacketData> dataListUDP = new ArrayList<>();
|
||||
List<PacketData> dataListWeb = new ArrayList<>();
|
||||
try {
|
||||
outData = m_queue.take();
|
||||
for ( PacketData outData = m_queue.take(); // blocks
|
||||
null != outData;
|
||||
outData = m_queue.poll() ) { // doesn't block
|
||||
if ( outData.isEOQ() ) {
|
||||
exitNow = true;
|
||||
break;
|
||||
}
|
||||
if ( useWeb || outData.getForWeb() ) {
|
||||
dataListWeb.add(outData);
|
||||
} else {
|
||||
dataListUDP.add(outData);
|
||||
}
|
||||
}
|
||||
} catch ( InterruptedException ie ) {
|
||||
Log.w( TAG, "write thread killed" );
|
||||
break;
|
||||
}
|
||||
if ( null == outData
|
||||
|| 0 == outData.getLength() ) {
|
||||
if ( exitNow ) {
|
||||
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" );
|
||||
}
|
||||
sendViaWeb( dataListWeb );
|
||||
sendViaUDP( dataListUDP );
|
||||
|
||||
resetExitTimer();
|
||||
ConnStatusHandler.showSuccessOut();
|
||||
}
|
||||
Log.i( TAG, "write thread exiting" );
|
||||
}
|
||||
|
@ -659,8 +672,138 @@ public class RelayService extends XWService
|
|||
}
|
||||
}
|
||||
|
||||
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( 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.isEOQ() );
|
||||
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);
|
||||
if ( null != result ) {
|
||||
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, s_packetsSentWeb ); // 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" );
|
||||
}
|
||||
} catch ( JSONException ex ) {
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
}
|
||||
}
|
||||
}
|
||||
return sentLen;
|
||||
}
|
||||
|
||||
private int sendViaUDP( List<PacketData> packets )
|
||||
{
|
||||
int sentLen = 0;
|
||||
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();
|
||||
noteSent( packet, s_packetsSentUDP );
|
||||
getOut = false;
|
||||
} 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" );
|
||||
}
|
||||
if ( getOut ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( sentLen > 0 ) {
|
||||
startAckTimer( packets );
|
||||
}
|
||||
|
||||
return sentLen;
|
||||
}
|
||||
|
||||
private void startAckTimer( final List<PacketData> packets )
|
||||
{
|
||||
Runnable ackTimer = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<PacketData> forResend = new ArrayList<>();
|
||||
Log.d( TAG, "ackTimer.run() called" );
|
||||
synchronized ( s_packetsSentUDP ) {
|
||||
for ( PacketData packet : packets ) {
|
||||
PacketData stillThere = s_packetsSentUDP.remove(packet.m_packetID);
|
||||
if ( stillThere != null ) {
|
||||
Log.d( TAG, "packed %d not yet acked; resending",
|
||||
stillThere.m_packetID );
|
||||
stillThere.setForWeb();
|
||||
forResend.add( stillThere );
|
||||
}
|
||||
}
|
||||
}
|
||||
m_queue.addAll( forResend );
|
||||
}
|
||||
};
|
||||
m_handler.postDelayed( ackTimer, 10 * 1000 );
|
||||
}
|
||||
|
||||
private void noteSent( PacketData packet, Map<Integer, PacketData> map )
|
||||
{
|
||||
int pid = packet.m_packetID;
|
||||
Log.d( TAG, "Sent [udp?] packet: cmd=%s, id=%d",
|
||||
packet.m_cmd.toString(), pid );
|
||||
if ( packet.m_cmd != XWRelayReg.XWPDEV_ACK ) {
|
||||
synchronized( map ) {
|
||||
map.put( pid, packet );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void noteSent( List<PacketData> packets, Map<Integer, PacketData> map )
|
||||
{
|
||||
for ( PacketData packet : packets ) {
|
||||
noteSent( packet, map );
|
||||
}
|
||||
}
|
||||
|
||||
private void stopUDPThreadsIf()
|
||||
{
|
||||
DbgUtils.assertOnUIThread();
|
||||
|
||||
if ( null != m_UDPWriteThread ) {
|
||||
// can't add null
|
||||
m_queue.add( new PacketData() );
|
||||
|
@ -687,7 +830,7 @@ public class RelayService extends XWService
|
|||
}
|
||||
|
||||
// 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 );
|
||||
|
@ -766,7 +909,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.
|
||||
|
@ -795,7 +938,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()
|
||||
|
@ -873,11 +1016,15 @@ public class RelayService extends XWService
|
|||
{
|
||||
ByteArrayOutputStream bas = new ByteArrayOutputStream();
|
||||
try {
|
||||
String devid = getDevID( null );
|
||||
DevIDType[] typp = new DevIDType[1];
|
||||
String devid = getDevID( typp );
|
||||
if ( null != devid ) {
|
||||
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 );
|
||||
|
@ -1080,6 +1227,7 @@ public class RelayService extends XWService
|
|||
@Override
|
||||
protected Void doInBackground( Void... ignored )
|
||||
{
|
||||
Assert.assertFalse( XWPrefs.getSkipToWebAPI( m_context ) );
|
||||
// format: total msg lenth: 2
|
||||
// number-of-relayIDs: 2
|
||||
// for-each-relayid: relayid + '\n': varies
|
||||
|
@ -1127,6 +1275,8 @@ public class RelayService extends XWService
|
|||
}
|
||||
// Now open a real socket, write size and proto, and
|
||||
// copy in the formatted buffer
|
||||
|
||||
Assert.assertFalse( XWPrefs.getSkipToWebAPI( m_context ) );
|
||||
Socket socket = NetUtils.makeProxySocket( m_context, 8000 );
|
||||
if ( null != socket ) {
|
||||
DataOutputStream outStream =
|
||||
|
@ -1203,23 +1353,31 @@ 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 static void noteAck( int packetID, boolean fromUDP )
|
||||
{
|
||||
synchronized( s_packetsSent ) {
|
||||
if ( s_packetsSent.contains( packetID ) ) {
|
||||
s_packetsSent.remove( packetID );
|
||||
PacketData packet;
|
||||
Map<Integer, PacketData> map = fromUDP ? s_packetsSentUDP : s_packetsSentWeb;
|
||||
synchronized( map ) {
|
||||
packet = map.remove( packetID );
|
||||
if ( packet != null ) {
|
||||
Log.d( TAG, "noteAck(fromUDP=%b): removed for id %d: %s",
|
||||
fromUDP, packetID, packet );
|
||||
} 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 ( Integer pkid : map.keySet() ) {
|
||||
pstrs.add( map.get(pkid).toString() );
|
||||
}
|
||||
Log.d( TAG, "noteAck(fromUDP=%b): Got ack for %d; there are %d unacked packets: %s",
|
||||
fromUDP, packetID, map.size(), TextUtils.join( ",", pstrs ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1245,7 +1403,7 @@ public class RelayService extends XWService
|
|||
registerWithRelay();
|
||||
} else {
|
||||
stopUDPThreadsIf();
|
||||
startFetchThreadIf();
|
||||
startFetchThreadIfNotUDP();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1394,18 +1552,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;
|
||||
}
|
||||
|
||||
|
@ -1419,14 +1578,37 @@ 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 boolean m_useWeb;
|
||||
|
||||
public PacketData() {
|
||||
m_bas = null;
|
||||
m_created = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public PacketData( ByteArrayOutputStream bas, XWRelayReg cmd )
|
||||
{
|
||||
this();
|
||||
m_bas = bas;
|
||||
m_cmd = cmd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format( "{cmd: %s; age: %d ms}", m_cmd,
|
||||
System.currentTimeMillis() - m_created );
|
||||
}
|
||||
|
||||
void setForWeb() { m_useWeb = true; }
|
||||
boolean getForWeb() { return m_useWeb; }
|
||||
|
||||
public boolean isEOQ() { return 0 == getLength(); }
|
||||
|
||||
public int getLength()
|
||||
{
|
||||
int result = 0;
|
||||
|
@ -1439,13 +1621,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()
|
||||
|
@ -1464,10 +1646,5 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -115,6 +115,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,6 +25,7 @@
|
|||
android:title="@string/board_menu_trade"
|
||||
android:alphabeticShortcut="T"
|
||||
android:showAsAction="ifRoom"
|
||||
android:icon="@drawable/trade__gen"
|
||||
/>
|
||||
</group>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
@ -150,6 +152,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-->
|
||||
|
||||
|
|
|
@ -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 -->
|
||||
|
@ -2486,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>
|
||||
|
@ -2525,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>
|
||||
|
|
|
@ -415,11 +415,29 @@
|
|||
<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"
|
||||
/>
|
||||
<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"
|
||||
|
@ -432,11 +450,6 @@
|
|||
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:defaultValue="false"
|
||||
/>
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen android:title="@string/pref_group_l10n_title"
|
||||
|
|
|
@ -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 "";
|
||||
}
|
||||
}
|
|
@ -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
|
81
xwords4/android/img_src/archive.svg
Normal file
81
xwords4/android/img_src/archive.svg
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?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 100 100"
|
||||
x="0px"
|
||||
y="0px"
|
||||
id="svg3396"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="archive.svg"
|
||||
width="100"
|
||||
height="100">
|
||||
<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="1016"
|
||||
id="namedview3412"
|
||||
showgrid="true"
|
||||
showborder="false"
|
||||
inkscape:zoom="6.616"
|
||||
inkscape:cx="26.571947"
|
||||
inkscape:cy="80.637848"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3396">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3510" />
|
||||
</sodipodi:namedview>
|
||||
<title
|
||||
id="title3398">01</title>
|
||||
<path
|
||||
d="m 83,26 -66,0 0,60 66,0 z m -6,54 -54,0 0,-48 54,0 z"
|
||||
id="path3400"
|
||||
inkscape:connector-curvature="0" />
|
||||
<polygon
|
||||
points="41.7,46.7 35.7,46.7 35.7,58.3 64.3,58.3 64.3,46.7 64.3,46.7 58.3,46.7 58.3,52.3 41.7,52.3 "
|
||||
id="polygon3402"
|
||||
transform="translate(0,-5)" />
|
||||
<rect
|
||||
x="25"
|
||||
y="15"
|
||||
width="50"
|
||||
height="6"
|
||||
id="rect3404" />
|
||||
<rect
|
||||
x="30"
|
||||
y="4"
|
||||
width="40"
|
||||
height="6"
|
||||
id="rect3406" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
54
xwords4/android/img_src/rematch.svg
Normal file
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
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 |
|
@ -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"
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -284,7 +284,6 @@
|
|||
<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_p2p"></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>
|
||||
|
@ -445,7 +444,6 @@
|
|||
<string name="download_failed">Nedlasting mislyktes</string>
|
||||
|
||||
<string name="default_loc">Lagre ordlister internt</string>
|
||||
<string name="default_loc_summary"></string>
|
||||
|
||||
<string name="download_path_title">Nedlastingsmappe</string>
|
||||
|
||||
|
@ -594,7 +592,6 @@
|
|||
<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="connection_via_label"></string>
|
||||
|
||||
<string name="set_pref">Skjul knapper</string>
|
||||
|
||||
|
@ -612,7 +609,6 @@
|
|||
<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="git_rev_title"></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>
|
||||
|
|
9
xwords4/android/scripts/adb-pull-apk.sh
Executable file
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:
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
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
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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -2401,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;
|
||||
|
@ -2490,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 {
|
||||
|
@ -2649,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,41 +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 );
|
||||
#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 );
|
||||
|
||||
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;
|
||||
|
@ -319,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: {
|
||||
|
@ -367,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 );
|
||||
}
|
||||
|
||||
|
@ -403,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 );
|
||||
|
@ -602,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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import re, os, sys, getopt, shutil, threading, requests, json, glob
|
||||
import argparse, datetime, random, subprocess, time
|
||||
import argparse, datetime, random, signal, subprocess, time
|
||||
|
||||
# LOGDIR=./$(basename $0)_logs
|
||||
# APP_NEW=""
|
||||
|
@ -161,7 +161,8 @@ class Device():
|
|||
sTilesLeftPat = re.compile('.*pool_removeTiles: (\d+) tiles left in pool')
|
||||
sRelayIDPat = re.compile('.*UPDATE games.*seed=(\d+),.*relayid=\'([^\']+)\'.*')
|
||||
|
||||
def __init__(self, args, indx, app, params, room, db, log, nInGame):
|
||||
def __init__(self, args, game, indx, app, params, room, db, log, nInGame):
|
||||
self.game = game
|
||||
self.indx = indx
|
||||
self.args = args
|
||||
self.pid = 0
|
||||
|
@ -178,7 +179,7 @@ class Device():
|
|||
self.devID = ''
|
||||
self.launchCount = 0
|
||||
self.allDone = False # when true, can be killed
|
||||
self.nTilesLeft = -1 # negative means don't know
|
||||
self.nTilesLeft = None
|
||||
self.relayID = None
|
||||
self.relaySeed = 0
|
||||
|
||||
|
@ -257,6 +258,12 @@ class Device():
|
|||
self.proc = None
|
||||
self.check_game()
|
||||
|
||||
def handleAllDone(self):
|
||||
if self.allDone:
|
||||
self.moveFiles()
|
||||
self.send_dead()
|
||||
return self.allDone
|
||||
|
||||
def moveFiles(self):
|
||||
assert not self.running()
|
||||
shutil.move(self.logPath, self.args.LOGDIR + '/done')
|
||||
|
@ -268,10 +275,9 @@ class Device():
|
|||
req = requests.get(url, params = {'params' : JSON})
|
||||
|
||||
def getTilesCount(self):
|
||||
result = None
|
||||
if self.nTilesLeft != -1:
|
||||
result = '%.2d:%.2d' % (self.indx, self.nTilesLeft)
|
||||
return result
|
||||
return {'index': self.indx, 'nTilesLeft': self.nTilesLeft,
|
||||
'launchCount': self.launchCount, 'game': self.game,
|
||||
}
|
||||
|
||||
def update_ldevid(self):
|
||||
if not self.app in Device.sHasLDevIDMap:
|
||||
|
@ -306,6 +312,7 @@ class Device():
|
|||
|
||||
if allDone:
|
||||
for dev in Device.sConnnameMap[self.connname]:
|
||||
assert self.game == dev.game
|
||||
dev.allDone = True
|
||||
|
||||
# print('Closing', self.connname, datetime.datetime.now())
|
||||
|
@ -343,7 +350,6 @@ def build_cmds(args):
|
|||
|
||||
for GAME in range(1, args.NGAMES + 1):
|
||||
ROOM = 'ROOM_%.3d' % (GAME % args.NROOMS)
|
||||
# check_room $ROOM
|
||||
NDEVS = pick_ndevs(args)
|
||||
LOCALS = figure_locals(args, NDEVS) # as array
|
||||
NPLAYERS = sum(LOCALS)
|
||||
|
@ -355,12 +361,9 @@ def build_cmds(args):
|
|||
DEV = 0
|
||||
for NLOCALS in LOCALS:
|
||||
DEV += 1
|
||||
FILE="%s/GAME_%d_%d.sql3" % (args.LOGDIR, GAME, DEV)
|
||||
LOG='%s/%d_%d_LOG.txt' % (args.LOGDIR, GAME, DEV)
|
||||
# os.system("rm -f $LOG") # clear the log
|
||||
DB = '{}/{:02d}_{:02d}_DB.sql3'.format(args.LOGDIR, GAME, DEV)
|
||||
LOG = '{}/{:02d}_{:02d}_LOG.txt'.format(args.LOGDIR, GAME, DEV)
|
||||
|
||||
# APPS[$COUNTER]="$APP_NEW"
|
||||
# NEW_ARGS[$COUNTER]="$APP_NEW_PARAMS"
|
||||
BOARD_SIZE = ['--board-size', '15']
|
||||
# if [ 0 -lt ${#APPS_OLD[@]} ]; then
|
||||
# # 50% chance of starting out with old app
|
||||
|
@ -379,7 +382,7 @@ def build_cmds(args):
|
|||
PARAMS += ['--undo-pct', args.UNDO_PCT]
|
||||
PARAMS += [ '--game-dict', DICT, '--relay-port', args.PORT, '--host', args.HOST]
|
||||
PARAMS += ['--slow-robot', '1:3', '--skip-confirm']
|
||||
PARAMS += ['--db', FILE]
|
||||
PARAMS += ['--db', DB]
|
||||
if random.randint(0,100) % 100 < g_UDP_PCT_START:
|
||||
PARAMS += ['--use-udp']
|
||||
|
||||
|
@ -409,7 +412,7 @@ def build_cmds(args):
|
|||
|
||||
# print('PARAMS:', PARAMS)
|
||||
|
||||
dev = Device(args, COUNTER, args.APP_NEW, PARAMS, ROOM, FILE, LOG, len(LOCALS))
|
||||
dev = Device(args, GAME, COUNTER, args.APP_NEW, PARAMS, ROOM, DB, LOG, len(LOCALS))
|
||||
dev.update_ldevid()
|
||||
devs.append(dev)
|
||||
|
||||
|
@ -627,22 +630,75 @@ def build_cmds(args):
|
|||
# fi
|
||||
# }
|
||||
|
||||
def summarizeTileCounts(devs):
|
||||
nDevs = len(devs)
|
||||
strs = [dev.getTilesCount() for dev in devs]
|
||||
strs = [s for s in strs if s]
|
||||
nWithTiles = len(strs)
|
||||
print('%s %d/%d %s' % (datetime.datetime.now().strftime("%H:%M:%S"), nDevs, nWithTiles, ' '.join(strs)))
|
||||
def summarizeTileCounts(devs, endTime, state):
|
||||
shouldGoOn = True
|
||||
data = [dev.getTilesCount() for dev in devs]
|
||||
nDevs = len(data)
|
||||
totalTiles = 0
|
||||
colWidth = max(2, len(str(nDevs)))
|
||||
headWidth = 0
|
||||
fmtData = [{'head' : 'dev', },
|
||||
{'head' : 'launches', },
|
||||
{'head' : 'tls left', },
|
||||
]
|
||||
for datum in fmtData:
|
||||
headWidth = max(headWidth, len(datum['head']))
|
||||
datum['data'] = []
|
||||
|
||||
# Group devices by game
|
||||
games = []
|
||||
prev = -1
|
||||
for datum in data:
|
||||
gameNo = datum['game']
|
||||
if gameNo != prev:
|
||||
games.append([])
|
||||
prev = gameNo
|
||||
games[-1].append('{:0{width}d}'.format(datum['index'], width=colWidth))
|
||||
fmtData[0]['data'] = ['+'.join(game) for game in games]
|
||||
|
||||
nLaunches = 0
|
||||
for datum in data:
|
||||
launchCount = datum['launchCount']
|
||||
nLaunches += launchCount
|
||||
fmtData[1]['data'].append('{:{width}d}'.format(launchCount, width=colWidth))
|
||||
|
||||
nTiles = datum['nTilesLeft']
|
||||
fmtData[2]['data'].append(nTiles is None and ('-' * colWidth) or '{:{width}d}'.format(nTiles, width=colWidth))
|
||||
if not nTiles is None: totalTiles += int(nTiles)
|
||||
|
||||
|
||||
print('')
|
||||
print('devs left: {}; tiles left: {}; total launches: {}; {}/{}'
|
||||
.format(nDevs, totalTiles, nLaunches, datetime.datetime.now(), endTime ))
|
||||
fmt = '{head:>%d} {data}' % headWidth
|
||||
for datum in fmtData: datum['data'] = ' '.join(datum['data'])
|
||||
for datum in fmtData:
|
||||
print(fmt.format(**datum))
|
||||
|
||||
# Now let's see if things are stuck: if the tile string hasn't
|
||||
# changed in two minutes bail. Note that the count of tiles left
|
||||
# isn't enough because it's zero for a long time as devices are
|
||||
# using up what's left in their trays and getting killed.
|
||||
now = datetime.datetime.now()
|
||||
tilesStr = fmtData[2]['data']
|
||||
if not 'tilesStr' in state or state['tilesStr'] != tilesStr:
|
||||
state['lastChange'] = now
|
||||
state['tilesStr'] = tilesStr
|
||||
|
||||
return now - state['lastChange'] < datetime.timedelta(minutes = 1)
|
||||
|
||||
def countCores():
|
||||
return len(glob.glob1('/tmp',"core*"))
|
||||
|
||||
gDone = False
|
||||
|
||||
def run_cmds(args, devs):
|
||||
nCores = countCores()
|
||||
endTime = datetime.datetime.now() + datetime.timedelta(seconds = args.TIMEOUT)
|
||||
endTime = datetime.datetime.now() + datetime.timedelta(minutes = args.TIMEOUT_MINS)
|
||||
LOOPCOUNT = 0
|
||||
printState = {}
|
||||
|
||||
while len(devs) > 0:
|
||||
while len(devs) > 0 and not gDone:
|
||||
if countCores() > nCores:
|
||||
print('core file count increased; exiting')
|
||||
break
|
||||
|
@ -651,13 +707,14 @@ def run_cmds(args, devs):
|
|||
break
|
||||
|
||||
LOOPCOUNT += 1
|
||||
if 0 == LOOPCOUNT % 20: summarizeTileCounts(devs)
|
||||
if 0 == LOOPCOUNT % 20:
|
||||
if not summarizeTileCounts(devs, endTime, printState):
|
||||
print('no change in too long; exiting')
|
||||
break
|
||||
|
||||
dev = random.choice(devs)
|
||||
if not dev.running():
|
||||
if dev.allDone:
|
||||
dev.moveFiles()
|
||||
dev.send_dead()
|
||||
if dev.handleAllDone():
|
||||
devs.remove(dev)
|
||||
else:
|
||||
# if [ -n "$ONE_PER_ROOM" -a 0 -ne ${ROOM_PIDS[$ROOM]} ]; then
|
||||
|
@ -674,9 +731,11 @@ def run_cmds(args, devs):
|
|||
# MINEND[$KEY]=$(($NOW + $MINRUN))
|
||||
elif not dev.minTimeExpired():
|
||||
# print('sleeping...')
|
||||
time.sleep(2)
|
||||
time.sleep(1.0)
|
||||
else:
|
||||
dev.kill()
|
||||
if dev.handleAllDone():
|
||||
devs.remove(dev)
|
||||
# if g_DROP_N >= 0: dev.increment_drop()
|
||||
# update_ldevid $KEY
|
||||
|
||||
|
@ -739,8 +798,8 @@ def mkParser():
|
|||
parser.add_argument('--num-games', dest = 'NGAMES', type = int, default = 1, help = 'number of games')
|
||||
parser.add_argument('--num-rooms', dest = 'NROOMS', type = int, default = 0,
|
||||
help = 'number of roooms (default to --num-games)')
|
||||
parser.add_argument('--no-timeout', dest = 'TIMEOUT', default = False, action = 'store_true',
|
||||
help = 'run forever (default proportional to number of games')
|
||||
parser.add_argument('--timeout-mins', dest = 'TIMEOUT_MINS', default = 10000, type = int,
|
||||
help = 'minutes after which to timeout')
|
||||
parser.add_argument('--log-root', dest='LOGROOT', default = '.', help = 'where logfiles go')
|
||||
parser.add_argument('--dup-packets', dest = 'DUP_PACKETS', default = False, help = 'send all packet twice')
|
||||
parser.add_argument('--use-gtk', dest = 'USE_GTK', default = False, action = 'store_true',
|
||||
|
@ -768,7 +827,6 @@ def mkParser():
|
|||
help = 'Port relay\'s on')
|
||||
parser.add_argument('--resign-pct', dest = 'RESIGN_PCT', default = 0, type = int, \
|
||||
help = 'Odds of resigning [0..100]')
|
||||
# # echo " [--no-timeout] # run until all games done \\" >&2
|
||||
parser.add_argument('--seed', type = int, dest = 'SEED',
|
||||
default = random.randint(1, 1000000000))
|
||||
# # echo " [--send-chat <interval-in-seconds> \\" >&2
|
||||
|
@ -900,7 +958,6 @@ def parseArgs():
|
|||
|
||||
def assignDefaults(args):
|
||||
if not args.NROOMS: args.NROOMS = args.NGAMES
|
||||
args.TIMEOUT = not args.TIMEOUT and (args.NGAMES * 60 + 500) or 100000000000
|
||||
if len(args.DICTS) == 0: args.DICTS.append('CollegeEng_2to8.xwd')
|
||||
args.LOGDIR = os.path.basename(sys.argv[0]) + '_logs'
|
||||
# Move an existing logdir aside
|
||||
|
@ -975,10 +1032,20 @@ def assignDefaults(args):
|
|||
# SECONDS=$((SECONDS%60))
|
||||
# echo "*********$0 finished: $(date) (took $HOURS:$MINUTES:$SECONDS)**************"
|
||||
|
||||
def termHandler(signum, frame):
|
||||
global gDone
|
||||
print('termHandler() called')
|
||||
gDone = True
|
||||
|
||||
def main():
|
||||
startTime = datetime.datetime.now()
|
||||
signal.signal(signal.SIGINT, termHandler)
|
||||
|
||||
args = parseArgs()
|
||||
devs = build_cmds(args)
|
||||
nDevs = len(devs)
|
||||
run_cmds(args, devs)
|
||||
print('{} finished; took {} for {} devices'.format(sys.argv[0], datetime.datetime.now() - startTime, nDevs))
|
||||
|
||||
##############################################################################
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -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
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
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
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()
|
|
@ -67,6 +67,7 @@ endif
|
|||
|
||||
# turn on semaphore debugging
|
||||
# CPPFLAGS += -DDEBUG_LOCKS
|
||||
# CPPFLAGS += -DLOG_POLL
|
||||
|
||||
memdebug all: xwrelay rq
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -171,13 +171,13 @@ 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
|
||||
# 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.settimeout(timeoutSecs)
|
||||
tcpSock.connect(('127.0.0.1', 10998))
|
||||
|
||||
lenShort = 2 + idsLen + len(ids) + 2
|
||||
|
@ -188,8 +188,9 @@ def query(req, params):
|
|||
|
||||
for id in ids: tcpSock.send(id + '\n')
|
||||
|
||||
msgsLists = {}
|
||||
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))
|
||||
|
@ -212,10 +213,14 @@ def query(req, params):
|
|||
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(msgsLists)
|
||||
return json.dumps(result)
|
||||
|
||||
def main():
|
||||
result = None
|
||||
|
|
|
@ -51,7 +51,8 @@ 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 npd,nsents as snts, seeds,devids,tokens,ack, mtimes "\
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -28,8 +28,12 @@ DEVICE_PORTS=10998
|
|||
|
||||
# Port for per-device UDP interface (experimental)
|
||||
UDP_PORT=10997
|
||||
# interface to listen on -- may get dup packets if not specified
|
||||
UDP_IFACE=eth0
|
||||
|
||||
# interface to listen on -- may get dup packets if not specified. BUT:
|
||||
# at least on Linode specifying this leads to an socket that can't be
|
||||
# reached from localhost, e.g. by python scripts, and local tests pass
|
||||
# fine without it. So the dup packets thing may no longer apply.
|
||||
# UDP_IFACE=eth0
|
||||
|
||||
# How long after we've read from an address before we assume it's
|
||||
# recycled. Also sent to clients as a suggested ping interval
|
||||
|
|
|
@ -124,8 +124,6 @@ logf( XW_LogLevel level, const char* format, ... )
|
|||
va_end(ap);
|
||||
#else
|
||||
FILE* where = NULL;
|
||||
struct tm* timp;
|
||||
struct timeval tv;
|
||||
bool useFile;
|
||||
char logFile[256];
|
||||
|
||||
|
@ -143,13 +141,14 @@ logf( XW_LogLevel level, const char* format, ... )
|
|||
|
||||
if ( !!where ) {
|
||||
static int tm_yday = 0;
|
||||
struct timeval tv;
|
||||
gettimeofday( &tv, NULL );
|
||||
struct tm result;
|
||||
timp = localtime_r( &tv.tv_sec, &result );
|
||||
struct tm* timp = localtime_r( &tv.tv_sec, &result );
|
||||
|
||||
char timeBuf[64];
|
||||
sprintf( timeBuf, "%.2d:%.2d:%.2d", timp->tm_hour,
|
||||
timp->tm_min, timp->tm_sec );
|
||||
sprintf( timeBuf, "%.2d:%.2d:%.2d.%03ld", timp->tm_hour,
|
||||
timp->tm_min, timp->tm_sec, tv.tv_usec / 1000 );
|
||||
|
||||
/* log the date once/day. This isn't threadsafe so may be
|
||||
repeated but that's harmless. */
|
||||
|
@ -1031,7 +1030,7 @@ processDisconnect( const uint8_t* bufp, int bufLen, const AddrInfo* addr )
|
|||
} /* processDisconnect */
|
||||
|
||||
static void
|
||||
killSocket( const AddrInfo* addr )
|
||||
rmSocketRefs( const AddrInfo* addr )
|
||||
{
|
||||
logf( XW_LOGINFO, "%s(addr.socket=%d)", __func__, addr->getSocket() );
|
||||
CRefMgr::Get()->RemoveSocketRefs( addr );
|
||||
|
@ -1304,14 +1303,17 @@ handleMsgsMsg( const AddrInfo* addr, bool sendFull,
|
|||
const uint8_t* bufp, const uint8_t* end )
|
||||
{
|
||||
unsigned short nameCount;
|
||||
int ii;
|
||||
if ( getNetShort( &bufp, end, &nameCount ) ) {
|
||||
assert( nameCount == 1 ); // Don't commit this!!!
|
||||
DBMgr* dbmgr = DBMgr::Get();
|
||||
vector<uint8_t> out(4); /* space for len and n_msgs */
|
||||
assert( out.size() == 4 );
|
||||
vector<int> msgIDs;
|
||||
for ( ii = 0; ii < nameCount && bufp < end; ++ii ) {
|
||||
|
||||
for ( int ii = 0; ii < nameCount; ++ii ) {
|
||||
if ( bufp >= end ) {
|
||||
logf( XW_LOGERROR, "%s(): ran off the end", __func__ );
|
||||
break;
|
||||
}
|
||||
// See NetUtils.java for reply format
|
||||
// message-length: 2
|
||||
// nameCount: 2
|
||||
|
@ -1329,6 +1331,7 @@ handleMsgsMsg( const AddrInfo* addr, bool sendFull,
|
|||
break;
|
||||
}
|
||||
|
||||
logf( XW_LOGVERBOSE0, "%s(): connName: %s", __func__, connName );
|
||||
dbmgr->RecordAddress( connName, hid, addr );
|
||||
|
||||
/* For each relayID, write the number of messages and then
|
||||
|
@ -1345,14 +1348,21 @@ handleMsgsMsg( const AddrInfo* addr, bool sendFull,
|
|||
memcpy( &out[0], &tmp, sizeof(tmp) );
|
||||
tmp = htons( nameCount );
|
||||
memcpy( &out[2], &tmp, sizeof(tmp) );
|
||||
ssize_t nwritten = write( addr->getSocket(), &out[0], out.size() );
|
||||
logf( XW_LOGVERBOSE0, "%s: wrote %d bytes", __func__, nwritten );
|
||||
if ( sendFull && nwritten >= 0 && (size_t)nwritten == out.size() ) {
|
||||
int sock = addr->getSocket();
|
||||
ssize_t nWritten = write( sock, &out[0], out.size() );
|
||||
if ( nWritten < 0 ) {
|
||||
logf( XW_LOGERROR, "%s(): write to socket %d failed: %d/%s", __func__,
|
||||
sock, errno, strerror(errno) );
|
||||
} else if ( sendFull && (size_t)nWritten == out.size() ) {
|
||||
logf( XW_LOGVERBOSE0, "%s(): wrote %d bytes to socket %d", __func__,
|
||||
nWritten, sock );
|
||||
dbmgr->RecordSent( &msgIDs[0], msgIDs.size() );
|
||||
// This is wrong: should be removed when ACK returns and not
|
||||
// before. But for some reason if I make that change apps wind up
|
||||
// stalling.
|
||||
dbmgr->RemoveStoredMessages( msgIDs );
|
||||
} else {
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
} // handleMsgsMsg
|
||||
|
@ -1476,23 +1486,24 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const uint8_t* bufp,
|
|||
} // handleProxyMsgs
|
||||
|
||||
static void
|
||||
game_thread_proc( UdpThreadClosure* utc )
|
||||
game_thread_proc( PacketThreadClosure* ptc )
|
||||
{
|
||||
if ( !processMessage( utc->buf(), utc->len(), utc->addr(), 0 ) ) {
|
||||
XWThreadPool::GetTPool()->CloseSocket( utc->addr() );
|
||||
logf( XW_LOGVERBOSE0, "%s()", __func__ );
|
||||
if ( !processMessage( ptc->buf(), ptc->len(), ptc->addr(), 0 ) ) {
|
||||
// XWThreadPool::GetTPool()->CloseSocket( ptc->addr() );
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
proxy_thread_proc( UdpThreadClosure* utc )
|
||||
proxy_thread_proc( PacketThreadClosure* ptc )
|
||||
{
|
||||
const int len = utc->len();
|
||||
const AddrInfo* addr = utc->addr();
|
||||
const int len = ptc->len();
|
||||
const AddrInfo* addr = ptc->addr();
|
||||
|
||||
if ( len > 0 ) {
|
||||
assert( addr->isTCP() );
|
||||
int sock = addr->getSocket();
|
||||
const uint8_t* bufp = utc->buf();
|
||||
const uint8_t* bufp = ptc->buf();
|
||||
const uint8_t* end = bufp + len;
|
||||
if ( (0 == *bufp++) ) { /* protocol */
|
||||
XWPRXYCMD cmd = (XWPRXYCMD)*bufp++;
|
||||
|
@ -1561,7 +1572,8 @@ proxy_thread_proc( UdpThreadClosure* utc )
|
|||
}
|
||||
}
|
||||
}
|
||||
XWThreadPool::GetTPool()->CloseSocket( addr );
|
||||
// Should I remove this, or make it into more of an unref() call?
|
||||
// XWThreadPool::GetTPool()->CloseSocket( addr );
|
||||
} // proxy_thread_proc
|
||||
|
||||
static size_t
|
||||
|
@ -1726,10 +1738,10 @@ ackPacketIf( const UDPHeader* header, const AddrInfo* addr )
|
|||
}
|
||||
|
||||
static void
|
||||
handle_udp_packet( UdpThreadClosure* utc )
|
||||
handle_udp_packet( PacketThreadClosure* ptc )
|
||||
{
|
||||
const uint8_t* ptr = utc->buf();
|
||||
const uint8_t* end = ptr + utc->len();
|
||||
const uint8_t* ptr = ptc->buf();
|
||||
const uint8_t* end = ptr + ptc->len();
|
||||
|
||||
UDPHeader header;
|
||||
if ( getHeader( &ptr, end, &header ) ) {
|
||||
|
@ -1752,7 +1764,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
|||
if ( 3 >= clientVers ) {
|
||||
checkAllAscii( model, "bad model" );
|
||||
}
|
||||
registerDevice( relayID, &devID, utc->addr(),
|
||||
registerDevice( relayID, &devID, ptc->addr(),
|
||||
clientVers, devDesc, model, osVers );
|
||||
}
|
||||
}
|
||||
|
@ -1765,7 +1777,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
|||
ptr += sizeof(clientToken);
|
||||
clientToken = ntohl( clientToken );
|
||||
if ( AddrInfo::NULL_TOKEN != clientToken ) {
|
||||
AddrInfo addr( g_udpsock, clientToken, utc->saddr() );
|
||||
AddrInfo addr( g_udpsock, clientToken, ptc->saddr() );
|
||||
(void)processMessage( ptr, end - ptr, &addr, clientToken );
|
||||
} else {
|
||||
logf( XW_LOGERROR, "%s: dropping packet with token of 0",
|
||||
|
@ -1786,7 +1798,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
|||
}
|
||||
SafeCref scr( connName, hid );
|
||||
if ( scr.IsValid() ) {
|
||||
AddrInfo addr( g_udpsock, clientToken, utc->saddr() );
|
||||
AddrInfo addr( g_udpsock, clientToken, ptc->saddr() );
|
||||
handlePutMessage( scr, hid, &addr, end - ptr, &ptr, end );
|
||||
assert( ptr == end ); // DON'T CHECK THIS IN!!!
|
||||
} else {
|
||||
|
@ -1821,7 +1833,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
|||
case XWPDEV_RQSTMSGS: {
|
||||
DevID devID( ID_TYPE_RELAY );
|
||||
if ( getVLIString( &ptr, end, devID.m_devIDString ) ) {
|
||||
const AddrInfo* addr = utc->addr();
|
||||
const AddrInfo* addr = ptc->addr();
|
||||
DevMgr::Get()->rememberDevice( devID.asRelayID(), addr );
|
||||
|
||||
if ( XWPDEV_RQSTMSGS == header.cmd ) {
|
||||
|
@ -1862,7 +1874,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
|||
}
|
||||
|
||||
// Do this after the device and address are registered
|
||||
ackPacketIf( &header, utc->addr() );
|
||||
ackPacketIf( &header, ptc->addr() );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2335,7 +2347,7 @@ main( int argc, char** argv )
|
|||
(void)sigaction( SIGINT, &act, NULL );
|
||||
|
||||
XWThreadPool* tPool = XWThreadPool::GetTPool();
|
||||
tPool->Setup( nWorkerThreads, killSocket );
|
||||
tPool->Setup( nWorkerThreads, rmSocketRefs );
|
||||
|
||||
/* set up select call */
|
||||
fd_set rfds;
|
||||
|
|
Loading…
Reference in a new issue