Merge remote-tracking branch 'origin/android_translate' into android_translate

This commit is contained in:
Weblate 2018-01-08 22:20:10 +01:00
commit 0aa8874e4d
69 changed files with 2729 additions and 456 deletions

View file

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

View file

@ -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 &quot;Archive&quot; 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
&quot;Google Cloud Messaging&quot; which never worked on
f-droid installs anyway.</li>
</ul>
<p>(The full changelog

View file

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

View file

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

View file

@ -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() ) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
../../../../../../xw4d/java/org/eehouse/android/xw4/GCMStub.java

View 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

View 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

View 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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -71,6 +71,7 @@ struct CursesAppGlobals {
gchar* lastErr;
XP_U16 nChatsSent;
XP_U16 nextQueryTimeSecs;
union {
struct {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 ); */

View file

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

View file

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

View file

@ -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, &params->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

View file

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

View file

@ -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__':

View file

@ -1,7 +1,7 @@
#!/bin/bash
set -u -e
LOGDIR=$(basename $0)_logs
LOGDIR=./$(basename $0)_logs
APP_NEW=""
DO_CLEAN=""
APP_NEW_PARAMS=""
@ -17,9 +17,9 @@ SAVE_GOOD=""
MINDEVS=""
MAXDEVS=""
ONEPER=""
RESIGN_RATIO=""
RESIGN_PCT=0
DROP_N=""
MINRUN=2
MINRUN=2 # seconds
ONE_PER_ROOM="" # don't run more than one device at a time per room
USE_GTK=""
UNDO_PCT=0
@ -31,6 +31,7 @@ NAMES=(UNUSED Brynn Ariela Kati Eric)
SEND_CHAT=''
CORE_COUNT=$(ls core.* 2>/dev/null | wc -l)
DUP_PACKETS=''
HTTP_PCT=0
declare -A PIDS
declare -A APPS
@ -43,7 +44,7 @@ declare -A LOGS
declare -A MINEND
declare -A ROOM_PIDS
declare -a APPS_OLD=()
declare -a DICTS= # wants to be =() too?
declare -a DICTS=() # wants to be =() too?
declare -A CHECKED_ROOMS
function cleanup() {
@ -194,9 +195,6 @@ build_cmds() {
for NLOCALS in ${LOCALS[@]}; do
DEV=$((DEV + 1))
FILE="${LOGDIR}/GAME_${GAME}_${DEV}.sql3"
if [ $((RANDOM % 100)) -lt $UDP_PCT_START ]; then
FILE="$FILE --use-udp"
fi
LOG=${LOGDIR}/${GAME}_${DEV}_LOG.txt
> $LOG # clear the log
@ -219,7 +217,13 @@ build_cmds() {
PARAMS="$PARAMS --game-dict $DICT --relay-port $PORT --host $HOST "
PARAMS="$PARAMS --slow-robot 1:3 --skip-confirm"
PARAMS="$PARAMS --db $FILE"
if [ $((RANDOM % 100)) -lt $UDP_PCT_START ]; then
PARAMS="$PARAMS --use-udp"
fi
PARAMS="$PARAMS --drop-nth-packet $DROP_N $PLAT_PARMS"
if [ $((${RANDOM}%100)) -lt $HTTP_PCT ]; then
PARAMS="$PARAMS --use-http"
fi
# PARAMS="$PARAMS --split-packets 2"
if [ -n "$SEND_CHAT" ]; then
PARAMS="$PARAMS --send-chat $SEND_CHAT"
@ -304,6 +308,21 @@ launch() {
# exec $CMD >/dev/null 2>>$LOG
# }
send_dead() {
ID=$1
DB=${FILES[$ID]}
while :; do
[ -f $DB ] || break # it's gone
RES=$(echo 'select relayid, seed from games limit 1;' | sqlite3 -separator ' ' $DB || /bin/true)
[ -n "$RES" ] && break
sleep 0.2
done
RELAYID=$(echo $RES | awk '{print $1}')
SEED=$(echo $RES | awk '{print $2}')
JSON="[{\"relayID\":\"$RELAYID\", \"seed\":$SEED}]"
curl -G --data-urlencode params="$JSON" http://$HOST/xw4/relay.py/kill >/dev/null 2>&1
}
close_device() {
ID=$1
MVTO=$2
@ -353,11 +372,11 @@ kill_from_log() {
}
maybe_resign() {
if [ "$RESIGN_RATIO" -gt 0 ]; then
if [ "$RESIGN_PCT" -gt 0 ]; then
KEY=$1
LOG=${LOGS[$KEY]}
if grep -aq XWRELAY_ALLHERE $LOG; then
if [ 0 -eq $(($RANDOM % $RESIGN_RATIO)) ]; then
if [ $((${RANDOM}%100)) -lt $RESIGN_PCT ]; then
echo "making $LOG $(connName $LOG) resign..."
kill_from_log $LOG && close_device $KEY $DEADDIR "resignation forced" || /bin/true
fi
@ -419,6 +438,7 @@ check_game() {
for ID in $OTHERS $KEY; do
echo -n "${ID}:${LOGS[$ID]}, "
kill_from_log ${LOGS[$ID]} || /bin/true
send_dead $ID
close_device $ID $DONEDIR "game over"
done
echo ""
@ -458,10 +478,8 @@ update_ldevid() {
if [ $RNUM -lt 30 ]; then # upgrade or first run
CMD="--ldevid LINUX_TEST_$(printf %.5d ${KEY})_"
fi
else
if [ $RNUM -lt 10 ]; then
CMD="${CMD}x" # give it a new local ID
fi
elif [ $RNUM -lt 10 ]; then
CMD="${CMD}x" # give it a new local ID
fi
ARGS_DEVID[$KEY]="$CMD"
fi
@ -508,7 +526,8 @@ run_cmds() {
local KEYS=( ${!ARGS[*]} )
KEY=${KEYS[$INDX]}
ROOM=${ROOMS[$KEY]}
if [ 0 -eq ${PIDS[$KEY]} ]; then
PID=${PIDS[$KEY]}
if [ 0 -eq ${PID} ]; then
if [ -n "$ONE_PER_ROOM" -a 0 -ne ${ROOM_PIDS[$ROOM]} ]; then
continue
fi
@ -522,10 +541,12 @@ run_cmds() {
ROOM_PIDS[$ROOM]=$PID
MINEND[$KEY]=$(($NOW + $MINRUN))
else
PID=${PIDS[$KEY]}
if [ -d /proc/$PID ]; then
SLEEP=$((${MINEND[$KEY]} - $NOW))
[ $SLEEP -gt 0 ] && sleep $SLEEP
if [ $SLEEP -gt 0 ]; then
sleep 1
continue
fi
kill $PID || /bin/true
wait $PID
fi
@ -594,6 +615,7 @@ function getArg() {
function usage() {
[ $# -gt 0 ] && echo "Error: $1" >&2
echo "Usage: $(basename $0) \\" >&2
echo " [--log-root] # default: . \\" >&2
echo " [--dup-packets] # send all packets twice \\" >&2
echo " [--clean-start] \\" >&2
echo " [--game-dict <path/to/dict>]* \\" >&2
@ -601,6 +623,7 @@ function usage() {
echo " [--host <hostname>] \\" >&2
echo " [--max-devs <int>] \\" >&2
echo " [--min-devs <int>] \\" >&2
echo " [--min-run <int>] # run each at least this long \\" >&2
echo " [--new-app <path/to/app] \\" >&2
echo " [--new-app-args [arg*]] # passed only to new app \\" >&2
echo " [--num-games <int>] \\" >&2
@ -608,12 +631,14 @@ function usage() {
echo " [--old-app <path/to/app]* \\" >&2
echo " [--one-per] # force one player per device \\" >&2
echo " [--port <int>] \\" >&2
echo " [--resign-ratio <0 <= n <=1000 > \\" >&2
echo " [--resign-pct <0 <= n <=100 > \\" >&2
echo " [--no-timeout] # run until all games done \\" >&2
echo " [--seed <int>] \\" >&2
echo " [--send-chat <interval-in-seconds> \\" >&2
echo " [--udp-incr <pct>] \\" >&2
echo " [--udp-start <pct>] # default: $UDP_PCT_START \\" >&2
echo " [--undo-pct <int>] \\" >&2
echo " [--http-pct <0 <= n <=100>] \\" >&2
exit 1
}
@ -647,6 +672,11 @@ while [ "$#" -gt 0 ]; do
APPS_OLD[${#APPS_OLD[@]}]=$(getArg $*)
shift
;;
--log-root)
[ -d $2 ] || usage "$1: no such directory $2"
LOGDIR=$2/$(basename $0)_logs
shift
;;
--dup-packets)
DUP_PACKETS=1
;;
@ -671,6 +701,11 @@ while [ "$#" -gt 0 ]; do
MAXDEVS=$(getArg $*)
shift
;;
--min-run)
MINRUN=$(getArg $*)
[ $MINRUN -ge 2 -a $MINRUN -le 60 ] || usage "$1: n must be 2 <= n <= 60"
shift
;;
--one-per)
ONEPER=TRUE
;;
@ -690,14 +725,23 @@ while [ "$#" -gt 0 ]; do
UNDO_PCT=$(getArg $*)
shift
;;
--http-pct)
HTTP_PCT=$(getArg $*)
[ $HTTP_PCT -ge 0 -a $HTTP_PCT -le 100 ] || usage "$1: n must be 0 <= n <= 100"
shift
;;
--send-chat)
SEND_CHAT=$(getArg $*)
shift
;;
--resign-ratio)
RESIGN_RATIO=$(getArg $*)
--resign-pct)
RESIGN_PCT=$(getArg $*)
[ $RESIGN_PCT -ge 0 -a $RESIGN_PCT -le 100 ] || usage "$1: n must be 0 <= n <= 100"
shift
;;
--no-timeout)
TIMEOUT=0x7FFFFFFF
;;
--help)
usage
;;
@ -709,7 +753,7 @@ done
# Assign defaults
#[ 0 -eq ${#DICTS[@]} ] && DICTS=(dict.xwd)
[ 0 -eq ${#DICTS} ] && DICTS=(dict.xwd)
[ 0 -eq ${#DICTS} ] && DICTS=(CollegeEng_2to8.xwd)
[ -z "$APP_NEW" ] && APP_NEW=./obj_linux_memdbg/xwords
[ -z "$MINDEVS" ] && MINDEVS=2
[ -z "$MAXDEVS" ] && MAXDEVS=4
@ -719,7 +763,7 @@ done
[ -z "$PORT" ] && PORT=10997
[ -z "$TIMEOUT" ] && TIMEOUT=$((NGAMES*60+500))
[ -z "$SAVE_GOOD" ] && SAVE_GOOD=YES
[ -z "$RESIGN_RATIO" -a "$NGAMES" -gt 1 ] && RESIGN_RATIO=1000 || RESIGN_RATIO=0
# [ -z "$RESIGN_PCT" -a "$NGAMES" -gt 1 ] && RESIGN_RATIO=1000 || RESIGN_RATIO=0
[ -z "$DROP_N" ] && DROP_N=0
[ -z "$USE_GTK" ] && USE_GTK=FALSE
[ -z "$UPGRADE_ODDS" ] && UPGRADE_ODDS=10
@ -747,7 +791,8 @@ for FILE in $(ls $LOGDIR/*.{xwg,txt} 2>/dev/null); do
done
if [ -z "$RESUME" -a -d $LOGDIR ]; then
mv $LOGDIR /tmp/${LOGDIR}_$$
NEWNAME="$(basename $LOGDIR)_$$"
(cd $(dirname $LOGDIR) && mv $(basename $LOGDIR) /tmp/${NEWNAME})
fi
mkdir -p $LOGDIR
@ -759,7 +804,7 @@ DEADDIR=$LOGDIR/dead
mkdir -p $DEADDIR
for VAR in NGAMES NROOMS USE_GTK TIMEOUT HOST PORT SAVE_GOOD \
MINDEVS MAXDEVS ONEPER RESIGN_RATIO DROP_N ALL_VIA_RQ SEED \
MINDEVS MAXDEVS ONEPER RESIGN_PCT DROP_N ALL_VIA_RQ SEED \
APP_NEW; do
echo "$VAR:" $(eval "echo \$${VAR}") 1>&2
done

View 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()

View 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
View 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()

View file

@ -67,6 +67,7 @@ endif
# turn on semaphore debugging
# CPPFLAGS += -DDEBUG_LOCKS
# CPPFLAGS += -DLOG_POLL
memdebug all: xwrelay rq

View file

@ -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 ); */
/* } */
/* } */
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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