mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-07 05:24:46 +01:00
Merge branch 'android_branch' into android_translate
This commit is contained in:
commit
3216ef8a5c
76 changed files with 2163 additions and 432 deletions
40
.travis.yml
Normal file
40
.travis.yml
Normal file
|
@ -0,0 +1,40 @@
|
|||
language: android
|
||||
jdk: oraclejdk8
|
||||
env:
|
||||
global:
|
||||
- ANDROID_TARGET=android-15
|
||||
- ANDROID_ABI=armeabi-v7a,x86
|
||||
- secure: d8PwteM+xp1IRU3QkvmHtxh+1Ta9n/kl/SJ3EZa3iColVVXY1etzjY3cKrEGKKMJuI4be30kPzvNw9/BVTawDpnU9/NtWqykJ8QHXNWnZIvUQ/kxHBS1DbcstmcYU9gvR83EFb8BT+Y9frpNfMcZDlSvBpEGqDQEPmxiDzSmjdUmJJQWStncxL9pE+lCdM6lHBgtfYoMMiqCQF/DxkQisjyUVF4mbTGuT9JOOWjVsTGPA7ehzsWDHoJ3p2ai8UKHAYucUWZcTt4rkq9l35ExvgKd3L8luk8U3X3Fk9yzVhPJC56T0XNbNrsQ2W7/7oGRv6EQFV3aKDZimJ7CVjBcEjZmPxeUVvCsMW8XB41ZvYcy6xsjF96oyjn1gb0r/2mZbTaWP0izSTwMYZ5vFNKUamDtRZgrneD0lfvXgfTzirrCU7FqO2RH7ZK5PQpSgSoZxKsKyeyFPEa2ihivc95rz1MS6mamle9wrIlSAgEGcaZMIYvKiOnCLk7CZCKuwm2dhYPgzCHW3PUopay59BBwMsSqWpxsiHEr5jYGpb0pHGbzPTJNUpg1LNQX5eMQOMlEt7rfpoC7JG24hR9vxl4Yf9LhxYlSwUiPy7TYHdbA0kUS68skfzxU6+ekWZF2QFM+L4vWCYmEHDy7n+I0df+PavycgNW989ROlAKhQjtMyqM=
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- platform-tools
|
||||
- build-tools-23.0.3
|
||||
- android-23
|
||||
before_script:
|
||||
- export TERM=dumb
|
||||
- curl -L http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin -O
|
||||
- chmod u+x android-ndk-r10e-linux-x86_64.bin
|
||||
- "./android-ndk-r10e-linux-x86_64.bin > /dev/null"
|
||||
- rm android-ndk-r10e-linux-x86_64.bin
|
||||
- export ANDROID_NDK_HOME=`pwd`/android-ndk-r10e
|
||||
- export LOCAL_ANDROID_NDK_HOME="$ANDROID_NDK_HOME"
|
||||
- export LOCAL_ANDROID_NDK_HOST_PLATFORM="linux-x86_64"
|
||||
- export PATH=$PATH:${ANDROID_NDK_HOME}
|
||||
- cd xwords4/android/
|
||||
before_install:
|
||||
- openssl aes-256-cbc -K $encrypted_8436f2891714_key -iv $encrypted_8436f2891714_iv
|
||||
-in id_rsa_uploader.enc -out /tmp/id_rsa_uploader -d
|
||||
- chmod 600 \/tmp\/id_rsa_uploader
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get install -y python-lxml imagemagick
|
||||
script:
|
||||
- "./gradlew -PuseCrashlytics assXw4dDeb"
|
||||
- scp -o "StrictHostKeyChecking no" -i /tmp/id_rsa_uploader -d app/build/outputs/apk/*.apk
|
||||
uploader@eehouse.org:XW4D_UPLOAD
|
||||
notifications:
|
||||
email:
|
||||
recipients:
|
||||
- xwords@eehouse.org
|
||||
on_success: always
|
||||
on_failure: always
|
BIN
id_rsa_uploader.enc
Normal file
BIN
id_rsa_uploader.enc
Normal file
Binary file not shown.
|
@ -1,7 +1,9 @@
|
|||
def INITIAL_CLIENT_VERS = 8
|
||||
def VERSION_CODE_BASE = 121
|
||||
def VERSION_NAME = '4.4.125'
|
||||
def VERSION_CODE_BASE = 126
|
||||
def VERSION_NAME = '4.4.130'
|
||||
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"
|
||||
|
||||
boolean forFDroid = hasProperty('forFDroid')
|
||||
|
||||
|
@ -37,8 +39,6 @@ android {
|
|||
applicationVariants.all { variant ->
|
||||
// renameArtifact(variant)
|
||||
// variant.buildConfigField "String", "FIELD_NAME", "\"my String\""
|
||||
def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID")
|
||||
variant.buildConfigField "String", "SENDER_ID", "\"$GCM_SENDER_ID\""
|
||||
variant.buildConfigField "String", "FABRIC_API_KEY", "\"$FABRIC_API_KEY\""
|
||||
|
||||
resValue "string", "git_rev", "$GITREV"
|
||||
|
@ -51,9 +51,6 @@ android {
|
|||
// FIX ME
|
||||
variant.buildConfigField "String", "STRINGS_HASH", "\"00000\""
|
||||
|
||||
def senderID = System.getenv("GCM_SENDER_ID")
|
||||
variant.buildConfigField "String", "GCM_SENDER_ID", "\"$senderID\""
|
||||
|
||||
variant.buildConfigField "short", "CLIENT_VERS_RELAY", "$INITIAL_CLIENT_VERS"
|
||||
|
||||
variant.buildConfigField "boolean", "FOR_FDROID", "$forFDroid"
|
||||
|
@ -61,6 +58,10 @@ android {
|
|||
|
||||
flavorDimensions "variant"//, "abi"
|
||||
productFlavors {
|
||||
all {
|
||||
buildConfigField "String", "BUILD_INFO_NAME", "\"${BUILD_INFO_NAME}\""
|
||||
}
|
||||
|
||||
xw4 {
|
||||
dimension "variant"
|
||||
applicationId "org.eehouse.android.xw4"
|
||||
|
@ -70,6 +71,8 @@ android {
|
|||
resValue "string", "invite_prefix", "/and/"
|
||||
buildConfigField "boolean", "WIDIR_ENABLED", "false"
|
||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
|
||||
|
||||
buildConfigField "String", "GCM_SENDER_ID", "\"$GCM_SENDER_ID\""
|
||||
}
|
||||
xw4d {
|
||||
dimension "variant"
|
||||
|
@ -81,6 +84,8 @@ android {
|
|||
resValue "string", "invite_prefix", "/anddbg/"
|
||||
buildConfigField "boolean", "WIDIR_ENABLED", "true"
|
||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true"
|
||||
|
||||
buildConfigField "String", "GCM_SENDER_ID", "\"\""
|
||||
}
|
||||
|
||||
// WARNING: "all" breaks things. Seems to be a keyword. Need
|
||||
|
@ -254,9 +259,14 @@ afterEvaluate {
|
|||
|
||||
task makeBuildAssets() {
|
||||
def assetsDir = android.sourceSets.main.assets.srcDirs.toArray()[0]
|
||||
String path = new File(assetsDir, 'build-info.txt').getAbsolutePath()
|
||||
File file = new File(path);
|
||||
file.write("git: ${GITREV}\n");
|
||||
String path = new File(assetsDir, BUILD_INFO_NAME).getAbsolutePath()
|
||||
String out = "git: ${GITREV}\n"
|
||||
|
||||
String diff = "git diff".execute().text.trim()
|
||||
if (diff) {
|
||||
out += "\n" + diff
|
||||
}
|
||||
new File(path).write(out)
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
|
|
|
@ -34,13 +34,7 @@
|
|||
/>
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="false" />
|
||||
|
||||
<!-- GCM stuff -->
|
||||
<permission android:name="${APP_ID}.permission.C2D_MESSAGE"
|
||||
android:protectionLevel="signature" />
|
||||
<uses-permission android:name="${APP_ID}.permission.C2D_MESSAGE" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
<application android:icon="@drawable/icon48x48"
|
||||
|
@ -208,16 +202,5 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="com.google.android.gcm.GCMBroadcastReceiver"
|
||||
android:permission="com.google.android.c2dm.permission.SEND" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
|
||||
<category android:name="${APP_ID}" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".GCMIntentService" />
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -13,10 +13,9 @@
|
|||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>CrossWords 4.4.125 release</h2>
|
||||
<h2>CrossWords 4.4.130 release</h2>
|
||||
|
||||
<p>This release fixes a problem inviting to new networked games, and
|
||||
with title bars on some Samsung devices.</p>
|
||||
<p>This release makes a couple of small UI tweaks.</p>
|
||||
|
||||
<div id="survey">
|
||||
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
|
||||
|
@ -26,10 +25,12 @@
|
|||
|
||||
<h3>New with this release</h3>
|
||||
<ul>
|
||||
<li>Fix delays bringing up the Invite dialog for new games</li>
|
||||
<li>Explicitly specify application "theme" to fix a Samsung
|
||||
"upgrade" turning the titlebar white and so making menu
|
||||
icons disappear</li>
|
||||
<li>Offer to "Archive" finished games</li>
|
||||
<li>Make tap on thumbnail toggle whether game's selected rather
|
||||
than open it. (Tap to the right still opens)</li>
|
||||
<li>Bug fix: don't allow duplicate group names</li>
|
||||
<li>Fix battery-hogging behavior on non-Google-play
|
||||
installs</li>
|
||||
</ul>
|
||||
|
||||
<p>(The full changelog
|
||||
|
|
|
@ -550,7 +550,7 @@ public class BTService extends XWService {
|
|||
} else {
|
||||
short len = is.readShort();
|
||||
byte[] nliData = new byte[len];
|
||||
is.read( nliData );
|
||||
is.readFully( nliData );
|
||||
nli = XwJNI.nliFromStream( nliData );
|
||||
}
|
||||
|
||||
|
@ -573,26 +573,20 @@ public class BTService extends XWService {
|
|||
int gameID = dis.readInt();
|
||||
switch ( cmd ) {
|
||||
case MESG_SEND:
|
||||
short len = dis.readShort();
|
||||
byte[] buffer = new byte[len];
|
||||
int nRead = dis.read( buffer, 0, len );
|
||||
if ( nRead == len ) {
|
||||
BluetoothDevice host = socket.getRemoteDevice();
|
||||
addAddr( host );
|
||||
byte[] buffer = new byte[dis.readShort()];
|
||||
dis.readFully( buffer );
|
||||
BluetoothDevice host = socket.getRemoteDevice();
|
||||
addAddr( host );
|
||||
|
||||
CommsAddrRec addr = new CommsAddrRec( host.getName(),
|
||||
host.getAddress() );
|
||||
ReceiveResult rslt
|
||||
= BTService.this.receiveMessage( BTService.this,
|
||||
gameID, m_btMsgSink,
|
||||
buffer, addr );
|
||||
CommsAddrRec addr = new CommsAddrRec( host.getName(),
|
||||
host.getAddress() );
|
||||
ReceiveResult rslt
|
||||
= BTService.this.receiveMessage( BTService.this,
|
||||
gameID, m_btMsgSink,
|
||||
buffer, addr );
|
||||
|
||||
result = rslt == ReceiveResult.GAME_GONE ?
|
||||
BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
|
||||
} else {
|
||||
Log.e( TAG, "receiveMessage(): read only %d of %d bytes",
|
||||
nRead, len );
|
||||
}
|
||||
result = rslt == ReceiveResult.GAME_GONE ?
|
||||
BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
|
||||
break;
|
||||
case MESG_GAMEGONE:
|
||||
postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
|
||||
|
|
|
@ -188,10 +188,8 @@ public class BiDiSockWrap {
|
|||
DataInputStream inStream
|
||||
= new DataInputStream( mSocket.getInputStream() );
|
||||
while ( mRunThreads ) {
|
||||
short len = inStream.readShort();
|
||||
Log.d( TAG, "got len: %d", len );
|
||||
byte[] packet = new byte[len];
|
||||
inStream.read( packet );
|
||||
byte[] packet = new byte[inStream.readShort()];
|
||||
inStream.readFully( packet );
|
||||
mIface.gotPacket( BiDiSockWrap.this, packet );
|
||||
}
|
||||
} catch( IOException ioe ) {
|
||||
|
|
|
@ -322,26 +322,35 @@ public class BoardCanvas extends Canvas implements DrawCtx {
|
|||
}
|
||||
}
|
||||
|
||||
public void drawTimer( Rect rect, int player, int secondsLeft )
|
||||
public void drawTimer( Rect rect, final int player,
|
||||
int secondsLeft )
|
||||
{
|
||||
if ( null != m_jniThread &&
|
||||
(m_lastSecsLeft != secondsLeft || m_lastTimerPlayer != player) ) {
|
||||
m_lastSecsLeft = secondsLeft;
|
||||
m_lastTimerPlayer = player;
|
||||
if ( m_lastSecsLeft != secondsLeft || m_lastTimerPlayer != player ) {
|
||||
final Rect rectCopy = new Rect(rect);
|
||||
final int secondsLeftCopy = secondsLeft;
|
||||
m_activity.runOnUiThread( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if ( null != m_jniThread ) {
|
||||
m_lastSecsLeft = secondsLeftCopy;
|
||||
m_lastTimerPlayer = player;
|
||||
|
||||
String negSign = secondsLeft < 0? "-":"";
|
||||
secondsLeft = Math.abs( secondsLeft );
|
||||
String time = String.format( "%s%d:%02d", negSign, secondsLeft/60,
|
||||
secondsLeft%60 );
|
||||
String negSign = secondsLeftCopy < 0? "-":"";
|
||||
int secondsLeft = Math.abs( secondsLeftCopy );
|
||||
String time =
|
||||
String.format( "%s%d:%02d", negSign,
|
||||
secondsLeft/60, secondsLeft%60 );
|
||||
|
||||
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
|
||||
m_fillPaint.setColor( m_playerColors[player] );
|
||||
fillRectOther( rectCopy, CommonPrefs.COLOR_BACKGRND );
|
||||
m_fillPaint.setColor( m_playerColors[player] );
|
||||
|
||||
Rect shorter = new Rect( rect );
|
||||
shorter.inset( 0, shorter.height() / 5 );
|
||||
drawCentered( time, shorter, null );
|
||||
rectCopy.inset( 0, rectCopy.height() / 5 );
|
||||
drawCentered( time, rectCopy, null );
|
||||
|
||||
m_jniThread.handle( JNIThread.JNICmd.CMD_DRAW );
|
||||
m_jniThread.handle( JNIThread.JNICmd.CMD_DRAW );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -104,7 +104,6 @@ public class BoardDelegate extends DelegateBase
|
|||
private Button m_exchCancelButton;
|
||||
private SentInvitesInfo m_sentInfo;
|
||||
private Perms23.PermCbck m_permCbck;
|
||||
private ArrayList<String> m_pendingChats;
|
||||
|
||||
private CommsConnTypeSet m_connTypes = null;
|
||||
private String[] m_missingDevs;
|
||||
|
@ -205,6 +204,25 @@ 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 ) {
|
||||
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();
|
||||
}
|
||||
};
|
||||
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 )
|
||||
|
@ -553,8 +571,6 @@ public class BoardDelegate extends DelegateBase
|
|||
m_isFirstLaunch = null == savedInstanceState;
|
||||
getBundledData( savedInstanceState );
|
||||
|
||||
m_pendingChats = new ArrayList<String>();
|
||||
|
||||
m_utils = new BoardUtilCtxt();
|
||||
m_timers = new TimerRunnable[4]; // needs to be in sync with
|
||||
// XWTimerReason
|
||||
|
@ -843,7 +859,7 @@ public class BoardDelegate extends DelegateBase
|
|||
Utils.setItemVisible( menu, R.id.board_menu_game_invites, enable );
|
||||
|
||||
enable = XWPrefs.getStudyEnabled( m_activity );
|
||||
Utils.setItemVisible( menu, R.id.games_menu_study, enable );
|
||||
Utils.setItemVisible( menu, R.id.board_menu_study, enable );
|
||||
|
||||
return true;
|
||||
} // onPrepareOptionsMenu
|
||||
|
@ -913,7 +929,7 @@ public class BoardDelegate extends DelegateBase
|
|||
case R.id.board_menu_tray:
|
||||
cmd = JNICmd.CMD_TOGGLE_TRAY;
|
||||
break;
|
||||
case R.id.games_menu_study:
|
||||
case R.id.board_menu_study:
|
||||
StudyListDelegate.launchOrAlert( getDelegator(), m_gi.dictLang, this );
|
||||
break;
|
||||
case R.id.board_menu_game_netstats:
|
||||
|
@ -1095,6 +1111,12 @@ public class BoardDelegate extends DelegateBase
|
|||
makeOkOnlyBuilder( R.string.after_restart ).show();
|
||||
break;
|
||||
|
||||
case ARCHIVE_ACTION:
|
||||
String archiveName = (String)params[0];
|
||||
long archiveGroup = (Long)params[1];
|
||||
archiveAndClose( archiveName, archiveGroup );
|
||||
break;
|
||||
|
||||
case ENABLE_SMS_DO:
|
||||
post( new Runnable() {
|
||||
public void run() {
|
||||
|
@ -2144,7 +2166,6 @@ public class BoardDelegate extends DelegateBase
|
|||
|
||||
if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) {
|
||||
warnIfNoTransport();
|
||||
trySendChats();
|
||||
tickle( isStart );
|
||||
tryInvites();
|
||||
}
|
||||
|
@ -2407,15 +2428,6 @@ public class BoardDelegate extends DelegateBase
|
|||
}
|
||||
}
|
||||
|
||||
private void trySendChats()
|
||||
{
|
||||
Iterator<String> iter = m_pendingChats.iterator();
|
||||
while ( iter.hasNext() ) {
|
||||
handleViaThread( JNICmd.CMD_SENDCHAT, iter.next() );
|
||||
}
|
||||
m_pendingChats.clear();
|
||||
}
|
||||
|
||||
private void tryInvites()
|
||||
{
|
||||
if ( 0 < m_mySIS.nMissing && m_summary.hasRematchInfo() ) {
|
||||
|
@ -2588,6 +2600,16 @@ public class BoardDelegate extends DelegateBase
|
|||
return wordsArray;
|
||||
}
|
||||
|
||||
private void archiveAndClose( String archiveName, long groupID )
|
||||
{
|
||||
if ( DBUtils.GROUPID_UNSPEC == groupID ) {
|
||||
groupID = DBUtils.addGroup( m_activity, archiveName );
|
||||
}
|
||||
DBUtils.moveGame( m_activity, m_rowid, groupID );
|
||||
waitCloseGame( false );
|
||||
finish();
|
||||
}
|
||||
|
||||
// For now, supported if standalone or either BT or SMS used for transport
|
||||
private boolean rematchSupported( boolean showMulti )
|
||||
{
|
||||
|
|
|
@ -158,8 +158,7 @@ public class BoardView extends View implements BoardHandler, SyncedDraw {
|
|||
|
||||
if ( null != m_dims ) {
|
||||
if ( BoardContainer.getIsPortrait() != (m_dims.height > m_dims.width) ) {
|
||||
// square possible; will break above!
|
||||
Assert.assertTrue( m_dims.height != m_dims.width );
|
||||
// square possible; will break above! No. tested by forceing square
|
||||
Log.d( TAG, "onMeasure: discarding m_dims" );
|
||||
if ( ++m_dimsTossCount < 4 ) {
|
||||
m_dims = null;
|
||||
|
|
|
@ -52,9 +52,6 @@ public class ConnStatusHandler {
|
|||
public Handler getHandler();
|
||||
}
|
||||
|
||||
private static final int GREEN = 0xFF00AF00;
|
||||
private static final int RED = 0xFFAF0000;
|
||||
private static final int BLACK = 0xFF000000;
|
||||
private static final int SUCCESS_IN = 0;
|
||||
private static final int SUCCESS_OUT = 1;
|
||||
private static final int SHOW_SUCCESS_INTERVAL = 1000;
|
||||
|
@ -340,7 +337,7 @@ public class ConnStatusHandler {
|
|||
boolean isIn )
|
||||
{
|
||||
enabled = enabled && null != newestSuccess( connTypes, isIn );
|
||||
s_fillPaint.setColor( enabled ? GREEN : RED );
|
||||
s_fillPaint.setColor( enabled ? XWApp.GREEN : XWApp.RED );
|
||||
canvas.drawRect( rect, s_fillPaint );
|
||||
}
|
||||
|
||||
|
|
|
@ -81,7 +81,9 @@ public class DBUtils {
|
|||
private static long s_cachedRowID = ROWID_NOTFOUND;
|
||||
private static byte[] s_cachedBytes = null;
|
||||
|
||||
public static enum GameChangeType { GAME_CHANGED, GAME_CREATED, GAME_DELETED };
|
||||
public static enum GameChangeType { GAME_CHANGED, GAME_CREATED,
|
||||
GAME_DELETED, GAME_MOVED,
|
||||
};
|
||||
|
||||
public static interface DBChangeListener {
|
||||
public void gameSaved( long rowid, GameChangeType change );
|
||||
|
@ -1616,21 +1618,34 @@ public class DBUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
// ORDER BY clause that governs display of games in main GamesList view
|
||||
private static final String s_getGroupGamesOrderBy =
|
||||
TextUtils.join(",", new String[] {
|
||||
// Ended games at bottom
|
||||
DBHelper.GAME_OVER,
|
||||
// games with unread chat messages at top
|
||||
"(" + DBHelper.HASMSGS + " & " + GameSummary.MSG_FLAGS_CHAT + ") IS NOT 0 DESC",
|
||||
// Games not yet connected at top
|
||||
DBHelper.TURN + " is -1 DESC",
|
||||
// Games where it's a local player's turn at top
|
||||
DBHelper.TURN_LOCAL + " DESC",
|
||||
// finally, sort by timestamp of last-made move
|
||||
DBHelper.LASTMOVE,
|
||||
});
|
||||
|
||||
public static long[] getGroupGames( Context context, long groupID )
|
||||
{
|
||||
long[] result = null;
|
||||
initDB( context );
|
||||
String[] columns = { ROW_ID };
|
||||
String[] columns = { ROW_ID, DBHelper.HASMSGS };
|
||||
String selection = String.format( "%s=%d", DBHelper.GROUPID, groupID );
|
||||
String orderBy = String.format( "%s,%s DESC,%s", DBHelper.GAME_OVER,
|
||||
DBHelper.TURN_LOCAL, DBHelper.LASTMOVE );
|
||||
synchronized( s_dbHelper ) {
|
||||
Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
|
||||
selection, // selection
|
||||
null, // args
|
||||
null, // groupBy
|
||||
null, // having
|
||||
orderBy
|
||||
s_getGroupGamesOrderBy
|
||||
);
|
||||
int index = cursor.getColumnIndex( ROW_ID );
|
||||
result = new long[ cursor.getCount() ];
|
||||
|
@ -1688,6 +1703,29 @@ public class DBUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static long getGroup( Context context, String name )
|
||||
{
|
||||
long result = GROUPID_UNSPEC;
|
||||
String[] columns = { ROW_ID };
|
||||
String selection = DBHelper.GROUPNAME + " = ?";
|
||||
String[] selArgs = { name };
|
||||
|
||||
initDB( context );
|
||||
synchronized( s_dbHelper ) {
|
||||
Cursor cursor = s_db.query( DBHelper.TABLE_NAME_GROUPS, columns,
|
||||
selection, selArgs,
|
||||
null, // groupBy
|
||||
null, // having
|
||||
null // orderby
|
||||
);
|
||||
if ( cursor.moveToNext() ) {
|
||||
result = cursor.getLong( cursor.getColumnIndex( ROW_ID ) );
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long addGroup( Context context, String name )
|
||||
{
|
||||
long rowid = GROUPID_UNSPEC;
|
||||
|
@ -1746,13 +1784,14 @@ public class DBUtils {
|
|||
}
|
||||
|
||||
// Change group id of a game
|
||||
public static void moveGame( Context context, long gameid, long groupID )
|
||||
public static void moveGame( Context context, long rowid, long groupID )
|
||||
{
|
||||
Assert.assertTrue( GROUPID_UNSPEC != groupID );
|
||||
ContentValues values = new ContentValues();
|
||||
values.put( DBHelper.GROUPID, groupID );
|
||||
updateRow( context, DBHelper.TABLE_NAME_SUM, gameid, values );
|
||||
updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
|
||||
invalGroupsCache();
|
||||
notifyListeners( rowid, GameChangeType.GAME_MOVED );
|
||||
}
|
||||
|
||||
private static String getChatHistoryStr( Context context, long rowid )
|
||||
|
|
|
@ -163,6 +163,7 @@ public class DelegateBase implements DlgClickNotify,
|
|||
}
|
||||
if ( this != result ) {
|
||||
Log.d( TAG, "%s.curThis() => " + result, this.toString() );
|
||||
Assert.fail();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ public class DlgDelegate {
|
|||
TRAY_PICKED,
|
||||
INVITE_INFO,
|
||||
DISABLE_DUALPANE,
|
||||
ARCHIVE_ACTION,
|
||||
|
||||
// Dict Browser
|
||||
FINISH_ACTION,
|
||||
|
|
|
@ -22,7 +22,6 @@ package org.eehouse.android.xw4;
|
|||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
|
@ -214,13 +213,13 @@ public class ExpiringDelegate {
|
|||
int offset = 0;
|
||||
int count = s_points.length;
|
||||
if ( 0 < redWidth ) {
|
||||
s_paint.setColor( Color.RED );
|
||||
s_paint.setColor( XWApp.RED );
|
||||
canvas.drawLines( s_points, offset, count / 2, s_paint );
|
||||
count /= 2;
|
||||
offset += count;
|
||||
}
|
||||
if ( redWidth < width ) {
|
||||
s_paint.setColor( Color.GREEN );
|
||||
s_paint.setColor( XWApp.GREEN );
|
||||
}
|
||||
canvas.drawLines( s_points, offset, count, s_paint );
|
||||
}
|
||||
|
@ -256,7 +255,7 @@ public class ExpiringDelegate {
|
|||
|
||||
Paint paint = new Paint();
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setColor( Color.RED );
|
||||
paint.setColor( XWApp.RED );
|
||||
canvas.drawRect( 0, 0, pct, 1, paint );
|
||||
paint.setColor( Utils.TURN_COLOR );
|
||||
canvas.drawRect( pct, 0, 100, 1, paint );
|
||||
|
|
|
@ -64,7 +64,7 @@ public class GameListItem extends LinearLayout
|
|||
private LinearLayout m_list;
|
||||
private TextView m_state;
|
||||
private TextView m_modTime;
|
||||
private ImageView m_marker;
|
||||
private ImageView m_gameTypeImage;
|
||||
private TextView m_role;
|
||||
|
||||
private boolean m_expanded, m_haveTurn, m_haveTurnLocal;
|
||||
|
@ -90,16 +90,6 @@ public class GameListItem extends LinearLayout
|
|||
m_lastMoveTime = 0;
|
||||
m_loadingCount = 0;
|
||||
m_dsdel = new DrawSelDelegate( this );
|
||||
|
||||
setOnClickListener( new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( View v ) {
|
||||
// if selected, just un-select
|
||||
if ( null != m_summary ) {
|
||||
m_cb.itemClicked( GameListItem.this, m_summary );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
public GameSummary getSummary()
|
||||
|
@ -174,13 +164,32 @@ public class GameListItem extends LinearLayout
|
|||
}
|
||||
|
||||
// View.OnClickListener interface
|
||||
public void onClick( View view ) {
|
||||
m_expanded = !m_expanded;
|
||||
DBUtils.setExpanded( m_rowid, m_expanded );
|
||||
public void onClick( View view )
|
||||
{
|
||||
int id = view.getId();
|
||||
switch ( id ) {
|
||||
case R.id.expander:
|
||||
m_expanded = !m_expanded;
|
||||
DBUtils.setExpanded( m_rowid, m_expanded );
|
||||
|
||||
makeThumbnailIf( m_expanded );
|
||||
makeThumbnailIf( m_expanded );
|
||||
|
||||
showHide();
|
||||
showHide();
|
||||
break;
|
||||
|
||||
case R.id.view_loaded:
|
||||
toggleSelected();
|
||||
break;
|
||||
|
||||
case R.id.right_side:
|
||||
if ( null != m_summary ) {
|
||||
m_cb.itemClicked( GameListItem.this, m_summary );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Assert.assertFalse(BuildConfig.DEBUG);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void findViews()
|
||||
|
@ -191,12 +200,15 @@ public class GameListItem extends LinearLayout
|
|||
m_expandButton.setOnClickListener( this );
|
||||
m_viewUnloaded = (TextView)findViewById( R.id.view_unloaded );
|
||||
m_viewLoaded = findViewById( R.id.view_loaded );
|
||||
m_viewLoaded.setOnClickListener( this );
|
||||
m_list = (LinearLayout)findViewById( R.id.player_list );
|
||||
m_state = (TextView)findViewById( R.id.state );
|
||||
m_modTime = (TextView)findViewById( R.id.modtime );
|
||||
m_marker = (ImageView)findViewById( R.id.msg_marker );
|
||||
m_gameTypeImage = (ImageView)findViewById( R.id.game_type_marker );
|
||||
m_thumb = (ImageView)findViewById( R.id.thumbnail );
|
||||
m_role = (TextView)findViewById( R.id.role );
|
||||
|
||||
findViewById( R.id.right_side ).setOnClickListener( this );
|
||||
}
|
||||
|
||||
private void setLoaded( boolean loaded )
|
||||
|
@ -316,19 +328,20 @@ public class GameListItem extends LinearLayout
|
|||
|
||||
int iconID = summary.isMultiGame() ?
|
||||
R.drawable.multigame__gen : R.drawable.sologame__gen;
|
||||
m_marker.setImageResource( iconID );
|
||||
m_marker.setOnClickListener( new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick( View view ) {
|
||||
toggleSelected();
|
||||
}
|
||||
} );
|
||||
m_gameTypeImage.setImageResource( iconID );
|
||||
|
||||
boolean hasChat = summary.isMultiGame();
|
||||
if ( hasChat ) {
|
||||
int flags = DBUtils.getMsgFlags( m_context, m_rowid );
|
||||
hasChat = 0 != (flags & GameSummary.MSG_FLAGS_CHAT);
|
||||
}
|
||||
findViewById( R.id.has_chat_marker )
|
||||
.setVisibility( hasChat ? View.VISIBLE : View.GONE );
|
||||
|
||||
String roleSummary = summary.summarizeRole( m_context, m_rowid );
|
||||
m_role.setVisibility( null == roleSummary ? View.GONE : View.VISIBLE );
|
||||
if ( null != roleSummary ) {
|
||||
m_role.setText( roleSummary );
|
||||
} else {
|
||||
m_role.setVisibility( View.GONE );
|
||||
}
|
||||
|
||||
update( expanded, summary.lastMoveTime, haveATurn,
|
||||
|
@ -420,6 +433,7 @@ public class GameListItem extends LinearLayout
|
|||
// }
|
||||
// GameListAdapter.ClickHandler interface
|
||||
|
||||
@Override
|
||||
public void longClicked()
|
||||
{
|
||||
toggleSelected();
|
||||
|
|
|
@ -1196,7 +1196,7 @@ public class GameUtils {
|
|||
for ( CommsConnType typ : conTypes ) {
|
||||
switch ( typ ) {
|
||||
case COMMS_CONN_RELAY:
|
||||
tellRelayDied( context, summary, informNow );
|
||||
// see below
|
||||
break;
|
||||
case COMMS_CONN_BT:
|
||||
BTService.gameDied( context, addr.bt_btAddr, gameID );
|
||||
|
@ -1210,6 +1210,14 @@ public class GameUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// comms doesn't have a relay address for us until the game's
|
||||
// in play (all devices registered, at least.) To enable
|
||||
// deleting on relay half-games that we created but nobody
|
||||
// joined, special-case this one.
|
||||
if ( summary.inRelayGame() ) {
|
||||
tellRelayDied( context, summary, informNow );
|
||||
}
|
||||
|
||||
gamePtr.release();
|
||||
}
|
||||
|
|
|
@ -564,6 +564,7 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
private static final int[] DEBUG_ITEMS = {
|
||||
// R.id.games_menu_loaddb,
|
||||
R.id.games_menu_storedb,
|
||||
R.id.games_menu_writegit,
|
||||
};
|
||||
private static final int[] NOSEL_ITEMS = {
|
||||
R.id.games_menu_newgroup,
|
||||
|
@ -754,9 +755,18 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
lstnr = new OnClickListener() {
|
||||
public void onClick( DialogInterface dlg, int item ) {
|
||||
String name = namer.getName();
|
||||
DBUtils.addGroup( m_activity, name );
|
||||
mkListAdapter();
|
||||
showNewGroupIf();
|
||||
long hasName = DBUtils.getGroup( m_activity, name );
|
||||
if ( DBUtils.GROUPID_UNSPEC == hasName ) {
|
||||
DBUtils.addGroup( m_activity, name );
|
||||
mkListAdapter();
|
||||
showNewGroupIf();
|
||||
} else {
|
||||
String msg = LocUtils
|
||||
.getString( m_activity,
|
||||
R.string.duplicate_group_name_fmt,
|
||||
name );
|
||||
makeOkOnlyBuilder( msg ).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
lstnr2 = new OnClickListener() {
|
||||
|
@ -1059,8 +1069,6 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
invalidateOptionsMenuIf();
|
||||
setTitle();
|
||||
}
|
||||
|
||||
mkListAdapter();
|
||||
}
|
||||
|
||||
public void invalidateOptionsMenuIf()
|
||||
|
@ -1132,6 +1140,9 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
mkListAdapter();
|
||||
setSelGame( rowid );
|
||||
break;
|
||||
case GAME_MOVED:
|
||||
mkListAdapter();
|
||||
break;
|
||||
default:
|
||||
Assert.fail();
|
||||
break;
|
||||
|
@ -1539,10 +1550,10 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
GameUtils.resendAllIf( m_activity, null, true, true );
|
||||
break;
|
||||
case R.id.games_menu_newgame_solo:
|
||||
handleNewGame( true );
|
||||
handleNewGameButton( true );
|
||||
break;
|
||||
case R.id.games_menu_newgame_net:
|
||||
handleNewGame( false );
|
||||
handleNewGameButton( false );
|
||||
break;
|
||||
|
||||
case R.id.games_menu_newgroup:
|
||||
|
@ -1597,6 +1608,10 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
Action.STORAGE_CONFIRMED, itemID );
|
||||
break;
|
||||
|
||||
case R.id.games_menu_writegit:
|
||||
Utils.gitInfoToClip( m_activity );
|
||||
break;
|
||||
|
||||
default:
|
||||
handled = handleSelGamesItem( itemID, selRowIDs )
|
||||
|| handleSelGroupsItem( itemID, getSelGroupIDs() );
|
||||
|
|
|
@ -184,7 +184,7 @@ public class NetUtils {
|
|||
short len = dis.readShort();
|
||||
if ( len > 0 ) {
|
||||
byte[] packet = new byte[len];
|
||||
dis.read( packet );
|
||||
dis.readFully( packet );
|
||||
msgs[ii][jj] = packet;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ public class RefreshNamesTask extends AsyncTask<Void, Void, String[]> {
|
|||
// Can't figure out how to read a null-terminated string
|
||||
// from DataInputStream so parse it myself.
|
||||
byte[] bytes = new byte[len];
|
||||
dis.read( bytes );
|
||||
dis.readFully( bytes );
|
||||
|
||||
int index = -1;
|
||||
for ( int ii = 0; ii < nRooms; ++ii ) {
|
||||
|
|
|
@ -736,7 +736,7 @@ public class RelayService extends XWService
|
|||
case XWPDEV_MSG:
|
||||
int token = dis.readInt();
|
||||
byte[] msg = new byte[dis.available()];
|
||||
dis.read( msg );
|
||||
dis.readFully( msg );
|
||||
postData( this, token, msg );
|
||||
|
||||
// game-related packets only count
|
||||
|
@ -756,9 +756,8 @@ public class RelayService extends XWService
|
|||
resetBackoff = true;
|
||||
intent = getIntentTo( this, MsgCmds.GOT_INVITE );
|
||||
int srcDevID = dis.readInt();
|
||||
short len = dis.readShort();
|
||||
byte[] nliData = new byte[len];
|
||||
dis.read( nliData );
|
||||
byte[] nliData = new byte[dis.readShort()];
|
||||
dis.readFully( nliData );
|
||||
NetLaunchInfo nli = XwJNI.nliFromStream( nliData );
|
||||
intent.putExtra( INVITE_FROM, srcDevID );
|
||||
String asStr = nli.toString();
|
||||
|
@ -995,9 +994,8 @@ public class RelayService extends XWService
|
|||
private String getVLIString( DataInputStream dis )
|
||||
throws java.io.IOException
|
||||
{
|
||||
int len = vli2un( dis );
|
||||
byte[] tmp = new byte[len];
|
||||
dis.read( tmp );
|
||||
byte[] tmp = new byte[vli2un( dis )];
|
||||
dis.readFully( tmp );
|
||||
String result = new String( tmp );
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -522,7 +522,7 @@ public class SMSService extends XWService {
|
|||
case DATA:
|
||||
int gameID = dis.readInt();
|
||||
byte[] rest = new byte[dis.available()];
|
||||
dis.read( rest );
|
||||
dis.readFully( rest );
|
||||
if ( feedMessage( gameID, rest, new CommsAddrRec( phone ) ) ) {
|
||||
SMSResendReceiver.resetTimer( this );
|
||||
}
|
||||
|
@ -618,7 +618,7 @@ public class SMSService extends XWService {
|
|||
} else {
|
||||
SMS_CMD cmd = SMS_CMD.values()[dis.readByte()];
|
||||
byte[] rest = new byte[dis.available()];
|
||||
dis.read( rest );
|
||||
dis.readFully( rest );
|
||||
receive( cmd, rest, senderPhone );
|
||||
success = true;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public class StudyListDelegate extends ListDelegateBase
|
|||
implements OnItemSelectedListener, SelectableItem,
|
||||
View.OnLongClickListener, View.OnClickListener,
|
||||
DBUtils.StudyListListener {
|
||||
private static final String TAG = StudyListDelegate.class.getSimpleName();
|
||||
|
||||
protected static final int NO_LANG = -1;
|
||||
|
||||
|
@ -220,7 +221,8 @@ public class StudyListDelegate extends ListDelegateBase
|
|||
showToast( msg );
|
||||
break;
|
||||
default:
|
||||
Assert.assertFalse( BuildConfig.DEBUG );
|
||||
Log.d( TAG, "not handling: %s", action );
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
return handled;
|
||||
|
|
|
@ -32,7 +32,9 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.text.ClipboardManager;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.media.Ringtone;
|
||||
|
@ -55,9 +57,12 @@ import android.widget.Toast;
|
|||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
@ -187,6 +192,33 @@ public class Utils {
|
|||
context.startActivity( Intent.createChooser( intent, chooserMsg ) );
|
||||
}
|
||||
|
||||
static void gitInfoToClip( Context context )
|
||||
{
|
||||
StringBuilder sb;
|
||||
try {
|
||||
InputStream is = context.getAssets().open( BuildConfig.BUILD_INFO_NAME,
|
||||
AssetManager.ACCESS_BUFFER );
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
sb = new StringBuilder();
|
||||
for ( ; ; ) {
|
||||
String line = reader.readLine();
|
||||
if ( null == line ) {
|
||||
break;
|
||||
}
|
||||
sb.append( line ).append( "\n" );
|
||||
}
|
||||
reader.close();
|
||||
} catch ( Exception ex ) {
|
||||
sb = null;
|
||||
}
|
||||
|
||||
if ( null != sb ) {
|
||||
ClipboardManager clipboard = (ClipboardManager)
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboard.setText( sb.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
public static void postNotification( Context context, Intent intent,
|
||||
int titleID, int bodyID, int id )
|
||||
{
|
||||
|
|
|
@ -36,7 +36,6 @@ public class XWApp extends Application {
|
|||
private static final String TAG = XWApp.class.getSimpleName();
|
||||
|
||||
public static final boolean BTSUPPORTED = true;
|
||||
public static final boolean GCMSUPPORTED = true;
|
||||
public static final boolean ATTACH_SUPPORTED = false;
|
||||
public static final boolean LOG_LIFECYLE = false;
|
||||
public static final boolean DEBUG_EXP_TIMERS = false;
|
||||
|
@ -53,6 +52,9 @@ public class XWApp extends Application {
|
|||
public static final int MAX_TRAY_TILES = 7; // comtypes.h
|
||||
public static final int SEL_COLOR = Color.argb( 0xFF, 0x09, 0x70, 0x93 );
|
||||
|
||||
public static final int GREEN = 0xFF00AF00;
|
||||
public static final int RED = 0xFFAF0000;
|
||||
|
||||
private static UUID s_UUID = null;
|
||||
private static Boolean s_onEmulator = null;
|
||||
private static Context s_context = null;
|
||||
|
|
1
xwords4/android/app/src/main/res/.gitignore
vendored
1
xwords4/android/app/src/main/res/.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
values-??/strings.xml
|
||||
**/*__gen.png
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
<EditText android:id="@+id/chat_edit"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="false"
|
||||
android:inputType="textCapSentences|textMultiLine"
|
||||
android:layout_weight="1"
|
||||
android:scrollHorizontally="false"
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
android:layout_width="fill_parent"
|
||||
android:layout_marginLeft="30dip"
|
||||
android:layout_marginRight="30dip"
|
||||
android:autoText="false"
|
||||
android:capitalize="words"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:maxLength="32"
|
||||
android:inputType="textCapWords"
|
||||
android:selectAllOnFocus="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
<EditText android:id="@+id/word_edit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:layout_weight="1"
|
||||
android:hint="@string/word_search_hint"
|
||||
android:capitalize="characters"
|
||||
|
|
|
@ -138,9 +138,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:scrollHorizontally="false"
|
||||
android:autoText="false"
|
||||
android:capitalize="none"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:selectAllOnFocus="true"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
android:maxLength="31"
|
||||
|
|
|
@ -31,13 +31,25 @@
|
|||
android:visibility="gone"
|
||||
>
|
||||
|
||||
<ImageView android:id="@+id/msg_marker"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="center_vertical|center_horizontal"
|
||||
android:paddingLeft="8dip"
|
||||
android:paddingRight="8dip"
|
||||
/>
|
||||
<RelativeLayout android:id="@+id/game_view_container"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="center_vertical|center_horizontal"
|
||||
android:paddingLeft="8dip"
|
||||
android:paddingRight="8dip"
|
||||
>
|
||||
<ImageView android:id="@+id/game_type_marker"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:src="@drawable/multigame__gen"
|
||||
/>
|
||||
<ImageView android:id="@+id/has_chat_marker"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/green_chat__gen"
|
||||
android:layout_alignParentBottom="true"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageView android:id="@+id/thumbnail"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -49,9 +61,12 @@
|
|||
|
||||
<!-- this layout is vertical, holds everything but the status
|
||||
icon[s] (plural later) -->
|
||||
<LinearLayout android:orientation="vertical"
|
||||
<LinearLayout android:id="@+id/right_side"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:clickable="true"
|
||||
android:longClickable="true"
|
||||
>
|
||||
|
||||
<!-- This is the game name and expander -->
|
||||
|
@ -66,7 +81,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
android:layout_marginLeft="30dip"
|
||||
android:layout_marginRight="30dip"
|
||||
android:autoText="false"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:selectAllOnFocus="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
@ -37,7 +38,8 @@
|
|||
android:layout_marginLeft="30dip"
|
||||
android:layout_marginRight="30dip"
|
||||
android:autoText="false"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:selectAllOnFocus="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
android:layout_marginLeft="30dip"
|
||||
android:layout_marginRight="30dip"
|
||||
android:autoText="false"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:selectAllOnFocus="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
@ -37,7 +38,8 @@
|
|||
android:layout_marginLeft="30dip"
|
||||
android:layout_marginRight="30dip"
|
||||
android:autoText="false"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:selectAllOnFocus="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
|
||||
<TextView android:id="@+id/text_item2"
|
||||
|
@ -39,7 +39,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="right"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
<TextView android:id="@+id/english_view"
|
||||
style="@style/evenly_spaced_horizontal"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
<TextView android:id="@+id/xlated_view"
|
||||
style="@style/evenly_spaced_horizontal"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
|
||||
</org.eehouse.android.xw4.loc.LocListItem>
|
||||
|
|
|
@ -37,7 +37,8 @@
|
|||
<EditText android:id="@+id/loc_search_field"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:scrollHorizontally="true"
|
||||
android:autoText="false"
|
||||
android:capitalize="none"
|
||||
android:maxLines="1"
|
||||
android:inputType="textCapWords"
|
||||
android:maxLength="32"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:selectAllOnFocus="true"
|
||||
/>
|
||||
|
|
|
@ -51,11 +51,11 @@
|
|||
android:layout_marginLeft="20dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:scrollHorizontally="true"
|
||||
android:autoText="false"
|
||||
android:capitalize="words"
|
||||
android:selectAllOnFocus="true"
|
||||
android:gravity="fill_horizontal"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:maxLength="32"
|
||||
android:inputType="textCapWords"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
||||
|
@ -101,11 +101,9 @@
|
|||
android:layout_marginLeft="20dip"
|
||||
android:layout_marginRight="20dip"
|
||||
android:scrollHorizontally="true"
|
||||
android:autoText="false"
|
||||
android:capitalize="none"
|
||||
android:gravity="fill_horizontal"
|
||||
android:password="true"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:inputType="textPassword"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
>
|
||||
<TextView android:id="@+id/item_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:gravity="left"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:gravity="left"
|
||||
/>
|
||||
<TextView android:id="@+id/item_score"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:gravity="right"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:gravity="right"
|
||||
/>
|
||||
</org.eehouse.android.xw4.ExpiringLinearLayout>
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
android:layout_marginLeft="30dip"
|
||||
android:layout_marginRight="30dip"
|
||||
android:autoText="false"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:inputType="text"
|
||||
android:selectAllOnFocus="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
/>
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
</menu>
|
||||
</item>
|
||||
|
||||
<item android:id="@+id/games_menu_study"
|
||||
<item android:id="@+id/board_menu_study"
|
||||
android:title="@string/gamel_menu_study"
|
||||
/>
|
||||
|
||||
|
|
|
@ -120,5 +120,8 @@
|
|||
<item android:id="@+id/games_menu_loaddb"
|
||||
android:title="@string/gamel_menu_loaddb"
|
||||
/>
|
||||
<item android:id="@+id/games_menu_writegit"
|
||||
android:title="@string/gamel_menu_writegit"
|
||||
/>
|
||||
|
||||
</menu>
|
||||
|
|
4
xwords4/android/app/src/main/res/values-v11/themes.xml
Normal file
4
xwords4/android/app/src/main/res/values-v11/themes.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="android:Theme.Holo"/>
|
||||
</resources>
|
4
xwords4/android/app/src/main/res/values-v21/themes.xml
Normal file
4
xwords4/android/app/src/main/res/values-v21/themes.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="android:Theme.Material"/>
|
||||
</resources>
|
|
@ -111,6 +111,7 @@
|
|||
<string name="key_notagain_trading">key_notagain_trading</string>
|
||||
<string name="key_notagain_hidenewgamebuttons">key_notagain_hidenewgamebuttons</string>
|
||||
<string name="key_na_lookup">key_na_lookup</string>
|
||||
<string name="key_na_archive">key_na_archive</string>
|
||||
<string name="key_na_browse">key_na_browse</string>
|
||||
<string name="key_na_browseall">key_na_browseall</string>
|
||||
<string name="key_na_values">key_na_values</string>
|
||||
|
|
|
@ -1743,6 +1743,14 @@
|
|||
<string name="not_again_lookup">This button lets you look up,
|
||||
online, the words just played.</string>
|
||||
|
||||
<string name="not_again_archive">Archiving uses a special group
|
||||
called \"Archive\" to store finished games you want to keep. And,
|
||||
since deleting an entire archive is easy, archiving is also a
|
||||
great way to mark games for deletion – if that\'s what you prefer
|
||||
to do.\n\n(Deleting the Archive group is safe because it will be
|
||||
created anew when needed.)
|
||||
</string>
|
||||
|
||||
<!-- -->
|
||||
<string name="button_move">Move</string>
|
||||
<string name="button_newgroup">New group</string>
|
||||
|
@ -2123,7 +2131,7 @@
|
|||
<string name="newgroup_label">Name your new group:</string>
|
||||
|
||||
<string name="list_group_delete">Delete group</string>
|
||||
<string name="list_group_rename">Rename</string>
|
||||
<string name="list_group_rename">Rename group</string>
|
||||
<string name="list_group_default">Put new games here</string>
|
||||
<string name="list_group_moveup">Move up</string>
|
||||
<string name="list_group_movedown">Move down</string>
|
||||
|
@ -2158,6 +2166,10 @@
|
|||
game with the same players and parameters as the one that
|
||||
just ended. -->
|
||||
<string name="button_rematch">Rematch</string>
|
||||
<string name="button_archive">Archive\u200C</string>
|
||||
<string name="group_name_archive">Archive</string>
|
||||
|
||||
<string name="duplicate_group_name_fmt">The group \"%1$s\" already exists.</string>
|
||||
|
||||
<string name="button_reconnect">Reconnect</string>
|
||||
|
||||
|
@ -2419,11 +2431,11 @@
|
|||
|
||||
<string name="set_pref">Hide buttons</string>
|
||||
|
||||
<string name="not_again_hidenewgamebuttons">These two buttons do
|
||||
the same thing as the first two items in this window\'s Action Bar
|
||||
(or menu). If you like you can hide the buttons to make more games
|
||||
visible.\n\n(If you later want to unhide them go to the Appearance
|
||||
section of App settings).
|
||||
<string name="not_again_hidenewgamebuttons">The two buttons at the
|
||||
bottom of this screen and the first two items in its Action Bar
|
||||
(or menu) do the same thing. If you like you can hide the buttons
|
||||
to make more games visible.\n\n(If you later want to unhide the
|
||||
buttons go to the Appearance section of App settings).
|
||||
</string>
|
||||
|
||||
<string name="waiting_title">Waiting for players</string>
|
||||
|
@ -2491,6 +2503,7 @@
|
|||
<string name="name_dict_fmt">%1$s/%2$s</string>
|
||||
<string name="gamel_menu_storedb">Write games to SD card</string>
|
||||
<string name="gamel_menu_loaddb">Load games from SD card</string>
|
||||
<string name="gamel_menu_writegit">Copy git info to clipboard</string>
|
||||
<string name="enable_dupes_title">Accept duplicate invites</string>
|
||||
<string name="xlations_locale">Fake locale for translation</string>
|
||||
<string name="enable_dupes_summary">Accept invitations more than once</string>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="android:Theme.Material"/>
|
||||
|
||||
<style name="config_separator">
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
|
|
4
xwords4/android/app/src/main/res/values/themes.xml
Normal file
4
xwords4/android/app/src/main/res/values/themes.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="AppTheme" parent="android:Theme"/>
|
||||
</resources>
|
|
@ -16,12 +16,18 @@
|
|||
android:title="@string/pref_human_name"
|
||||
android:capitalize="words"
|
||||
android:defaultValue=""
|
||||
android:maxLines="1"
|
||||
android:maxLength="32"
|
||||
android:inputType="text"
|
||||
/>
|
||||
<org.eehouse.android.xw4.XWEditTextPreference
|
||||
android:key="@string/key_robot_name"
|
||||
android:title="@string/robot_label"
|
||||
android:capitalize="words"
|
||||
android:defaultValue="@string/button_default_robot"
|
||||
android:maxLines="1"
|
||||
android:maxLength="32"
|
||||
android:inputType="text"
|
||||
/>
|
||||
</PreferenceScreen>
|
||||
|
||||
|
|
26
xwords4/android/app/src/xw4/AndroidManifest.xml
Normal file
26
xwords4/android/app/src/xw4/AndroidManifest.xml
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.eehouse.android.xw4"
|
||||
>
|
||||
|
||||
<!-- GCM stuff -->
|
||||
<permission android:name="${APP_ID}.permission.C2D_MESSAGE"
|
||||
android:protectionLevel="signature" />
|
||||
<uses-permission android:name="${APP_ID}.permission.C2D_MESSAGE" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application>
|
||||
<receiver android:name="com.google.android.gcm.GCMBroadcastReceiver"
|
||||
android:permission="com.google.android.c2dm.permission.SEND" >
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
|
||||
<category android:name="${APP_ID}" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".GCMIntentService" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -29,12 +29,15 @@ import com.google.android.gcm.GCMRegistrar;
|
|||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
public class GCMIntentService extends GCMBaseIntentService {
|
||||
private static final String TAG = GCMIntentService.class.getSimpleName();
|
||||
|
||||
public GCMIntentService()
|
||||
{
|
||||
super( BuildConfig.GCM_SENDER_ID );
|
||||
Assert.assertTrue( BuildConfig.GCM_SENDER_ID.length() > 0 );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -120,20 +123,22 @@ public class GCMIntentService extends GCMBaseIntentService {
|
|||
|
||||
public static void init( Application app )
|
||||
{
|
||||
int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK );
|
||||
if ( 8 <= sdkVersion && 0 < BuildConfig.GCM_SENDER_ID.length() ) {
|
||||
try {
|
||||
GCMRegistrar.checkDevice( app );
|
||||
// GCMRegistrar.checkManifest( app );
|
||||
String regId = DevID.getGCMDevID( app );
|
||||
if ( regId.equals("") ) {
|
||||
GCMRegistrar.register( app, BuildConfig.GCM_SENDER_ID );
|
||||
if ( 0 < BuildConfig.GCM_SENDER_ID.length() ) {
|
||||
int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK );
|
||||
if ( 8 <= sdkVersion ) {
|
||||
try {
|
||||
GCMRegistrar.checkDevice( app );
|
||||
// GCMRegistrar.checkManifest( app );
|
||||
String regId = DevID.getGCMDevID( app );
|
||||
if ( regId.equals("") ) {
|
||||
GCMRegistrar.register( app, BuildConfig.GCM_SENDER_ID );
|
||||
}
|
||||
} catch ( UnsupportedOperationException uoe ) {
|
||||
Log.w( TAG, "Device can't do GCM." );
|
||||
} catch ( Exception whatever ) {
|
||||
// funky devices could do anything
|
||||
Log.ex( TAG, whatever );
|
||||
}
|
||||
} catch ( UnsupportedOperationException uoe ) {
|
||||
Log.w( TAG, "Device can't do GCM." );
|
||||
} catch ( Exception whatever ) {
|
||||
// funky devices could do anything
|
||||
Log.ex( TAG, whatever );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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" );
|
||||
}
|
||||
}
|
BIN
xwords4/android/id_rsa_uploader.enc
Normal file
BIN
xwords4/android/id_rsa_uploader.enc
Normal file
Binary file not shown.
|
@ -27,12 +27,15 @@ getPackage() {
|
|||
echo $PACK
|
||||
}
|
||||
|
||||
# FIXME: not all options require a working directory, e.g. --apk
|
||||
WD=$(pwd)
|
||||
while :; do
|
||||
if [ -e ${WD}/AndroidManifest.xml -a -e ${WD}/build.xml ]; then
|
||||
break
|
||||
elif [ -e ${WD}/app/build.gradle ]; then
|
||||
break
|
||||
elif [ ${WD} = '/' ]; then
|
||||
usage "reached / without finding AndroidManifest.xml"
|
||||
usage "reached / without finding AndroidManifest.xml or build.gradle"
|
||||
else
|
||||
WD=$(cd $WD/.. && pwd)
|
||||
fi
|
||||
|
|
|
@ -10,7 +10,7 @@ function printHead() {
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="/xw4mobile.css" />
|
||||
<title>Crosswords Invite redirect</title>
|
||||
<title>CrossWords Invite redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="center">
|
||||
|
@ -51,32 +51,31 @@ function printAndroid() {
|
|||
print <<<EOF
|
||||
<div>
|
||||
<p>You'll have come here after clicking a link in an email or
|
||||
text inviting you to a Crosswords game. But you should not be seeing
|
||||
text inviting you to a CrossWords game. But you should not be seeing
|
||||
this page.</p>
|
||||
|
||||
<p>If you got this page on your device, it means either
|
||||
<ul>
|
||||
<li>The copy of Crosswords you have is NOT beta 56 or newer (dating from about Dec. 1, 2012).</li>
|
||||
<li> OR </li>
|
||||
<li> that your copy of Crosswords is new enough <em>BUT</em> that
|
||||
when you clicked on the link and were asked to choose between a
|
||||
browser and Crosswords you chose the browser.</li>
|
||||
<li>You don't have CrossWords installed</li>
|
||||
<li>OR</li>
|
||||
<li>that when you clicked on the link and were asked to choose between a
|
||||
browser and CrossWords you chose the browser.</li>
|
||||
</ul></p>
|
||||
|
||||
<p>In the first case, install the latest Crosswords,
|
||||
<p>In the first case, install the latest CrossWords,
|
||||
either <a href="market://search?q=pname:org.eehouse.android.xw4">via
|
||||
the Google Play store</a> or
|
||||
(sideloading) <a href="https://sourceforge.net/projects/xwords/files/xwords_Android/4.4%20beta%2056/XWords4-release_android_beta_56.apk/download">via
|
||||
(sideloading) <a href="https://sourceforge.net/projects/xwords/files/xwords_Android/4.4%20beta%20129/XWords4-release_android_beta_129.apk/download">via
|
||||
Sourceforge.net</a>. After the install is finished go back to the
|
||||
invite email (or text) and tap the link again.</p>
|
||||
|
||||
<p>In the second case, hit your browser's back button, click the
|
||||
link in your invite email (or text) again, and this time let
|
||||
Crosswords handle it.</p>
|
||||
CrossWords handle it.</p>
|
||||
|
||||
<p>(If you get tired of having to having to make that choice, Android
|
||||
will allow you to make Crosswords the default. If you do that
|
||||
Crosswords will be given control of all URLs that start with
|
||||
will allow you to make CrossWords the default. If you do that
|
||||
CrossWords will be given control of all URLs that start with
|
||||
"http://eehouse.org/and/" -- not all URLs of any type.)</p>
|
||||
|
||||
<p>Have fun. And as always, <a href="mailto:xwords@eehouse.org">let
|
||||
|
|
|
@ -19,3 +19,7 @@ $(IMG_DEST)/drawable-mdpi/%__gen.png: $(IMG_SRC)/%.svg
|
|||
|
||||
$(IMG_DEST)/drawable-hdpi/%__gen.png: $(IMG_SRC)/%.svg
|
||||
convert $(PARAMS) -scale 48x48 $< $@
|
||||
|
||||
# Build have-chat badge using R.color.dull_green
|
||||
$(IMG_DEST)/drawable/green_chat__gen.png: $(IMG_DEST)/drawable/stat_notify_chat.png
|
||||
convert -fill '#00AF00' -colorize 50% $< $@
|
||||
|
|
|
@ -66,20 +66,7 @@ k_filebase = "/var/www/html/"
|
|||
k_apkDir = "xw4/android/"
|
||||
k_shelfFile = k_filebase + 'xw4/info_shelf_2'
|
||||
k_urlbase = "http://eehouse.org"
|
||||
k_versions = { 'org.eehouse.android.xw4': {
|
||||
'version' : 91,
|
||||
k_AVERS : 91,
|
||||
k_URL : k_apkDir + 'XWords4-release_' + k_REL_REV + '.apk',
|
||||
},
|
||||
}
|
||||
|
||||
# k_versions_dbg = { 'org.eehouse.android.xw4': {
|
||||
# 'version' : 74,
|
||||
# k_AVERS : 74,
|
||||
# k_GVERS : k_DBG_REV,
|
||||
# k_URL : k_apkDir + 'XWords4-release_' + k_DBG_REV + '.apk',
|
||||
# },
|
||||
# }
|
||||
s_shelf = None
|
||||
|
||||
g_langs = {'English' : 'en',
|
||||
|
@ -127,7 +114,7 @@ def md5Checksums( sums, filePath ):
|
|||
if filePath in sums:
|
||||
result = sums[filePath]
|
||||
else:
|
||||
logging.debug( "opening %s" % (k_filebase + "and_wordlists/" + filePath))
|
||||
# logging.debug( "opening %s" % (k_filebase + "and_wordlists/" + filePath))
|
||||
try:
|
||||
file = open( k_filebase + "and_wordlists/" + filePath, 'rb' )
|
||||
md5 = hashlib.md5()
|
||||
|
@ -158,7 +145,7 @@ def openShelf():
|
|||
if not k_SUMS in s_shelf: s_shelf[k_SUMS] = {}
|
||||
if not k_COUNT in s_shelf: s_shelf[k_COUNT] = 0
|
||||
s_shelf[k_COUNT] += 1
|
||||
logging.debug( "Count now %d" % s_shelf[k_COUNT] )
|
||||
# logging.debug( "Count now %d" % s_shelf[k_COUNT] )
|
||||
|
||||
def closeShelf():
|
||||
global s_shelf
|
||||
|
@ -223,6 +210,34 @@ def getOrderedApks( path, appID, debug ):
|
|||
result = sorted(apkToCode.keys(), reverse=True, key=lambda file: (apkToCode[file], apkToMtime[file]))
|
||||
return result
|
||||
|
||||
# Given a version, find the apk that has the next highest version
|
||||
def getNextAfter(path, appID, curVers, debug):
|
||||
# print 'getNextAfter(', path, ')'
|
||||
apks = getOrderedApks(path, appID, debug)
|
||||
|
||||
map = {}
|
||||
max = 0
|
||||
for apk in apks:
|
||||
versionCode = getAAPTInfo(apk)['versionCode']
|
||||
if versionCode > curVers:
|
||||
map[versionCode] = apk
|
||||
if max < versionCode: max = versionCode
|
||||
|
||||
# print map
|
||||
|
||||
result = None
|
||||
if map:
|
||||
print 'looking between', curVers+1, 'and', max
|
||||
for nextVersion in range(curVers+1, max+1):
|
||||
if nextVersion in map:
|
||||
result = map[nextVersion]
|
||||
break
|
||||
|
||||
if result:
|
||||
print nextVersion, ':', result
|
||||
return result
|
||||
|
||||
# Returns '' for xw4, <variant> for anything else
|
||||
def getVariantDir( name ):
|
||||
result = ''
|
||||
splits = string.split( name, '.' )
|
||||
|
@ -271,10 +286,10 @@ def dictVersion( req, name, lang, md5sum ):
|
|||
closeShelf()
|
||||
return json.dumps( result )
|
||||
|
||||
def getApp( params, name ):
|
||||
def getApp( params, name = None, debug = False):
|
||||
result = None
|
||||
if k_NAME in params:
|
||||
name = params[k_NAME]
|
||||
if k_DEBUG in params: debug = params[k_DEBUG]
|
||||
if k_NAME in params: name = params[k_NAME]
|
||||
if name:
|
||||
variantDir = getVariantDir( name )
|
||||
# If we're a dev device, always push the latest
|
||||
|
@ -303,18 +318,21 @@ def getApp( params, name ):
|
|||
result = {k_URL: url}
|
||||
logging.debug( result )
|
||||
|
||||
elif k_GVERS in params:
|
||||
gvers = params[k_GVERS]
|
||||
elif k_AVERS in params:
|
||||
vers = params[k_AVERS]
|
||||
if k_INSTALLER in params: installer = params[k_INSTALLER]
|
||||
else: installer = ''
|
||||
|
||||
logging.debug( "name: %s; installer: %s; gvers: %s"
|
||||
% (name, installer, gvers) )
|
||||
if name in k_versions:
|
||||
if k_GVERS in versForName and not gvers == versForName[k_GVERS]:
|
||||
result = {k_URL: k_urlbase + '/' + versForName[k_URL]}
|
||||
else:
|
||||
logging.debug(name + " is up-to-date")
|
||||
% (name, installer, vers) )
|
||||
print "name: %s; installer: %s; vers: %s" % (name, installer, vers)
|
||||
dir = k_filebase + k_apkDir + 'rel/'
|
||||
apk = getNextAfter( dir, name, vers, debug )
|
||||
if apk:
|
||||
apk = apk[len(k_filebase):] # strip fs path
|
||||
result = {k_URL: k_urlbase + '/' + apk}
|
||||
else:
|
||||
logging.debug(name + " is up-to-date")
|
||||
else:
|
||||
logging.debug( 'Error: bad name ' + name )
|
||||
else:
|
||||
|
@ -542,15 +560,13 @@ def getUpdates( req, params ):
|
|||
result[k_DICTS] = dictsResult
|
||||
|
||||
# Let's not upgrade strings at the same time as we're upgrading the app
|
||||
if appResult:
|
||||
logging.debug( 'skipping xlation upgrade because app being updated' )
|
||||
elif k_XLATEINFO in asJson and k_NAME in asJson and k_STRINGSHASH in asJson:
|
||||
xlateResult = getXlate( asJson[k_XLATEINFO], asJson[k_NAME], asJson[k_STRINGSHASH] )
|
||||
if xlateResult:
|
||||
logging.debug( xlateResult )
|
||||
result[k_XLATEINFO] = xlateResult;
|
||||
else:
|
||||
logging.debug( "NOT FOUND xlate info" )
|
||||
# if appResult:
|
||||
# logging.debug( 'skipping xlation upgrade because app being updated' )
|
||||
# elif k_XLATEINFO in asJson and k_NAME in asJson and k_STRINGSHASH in asJson:
|
||||
# xlateResult = getXlate( asJson[k_XLATEINFO], asJson[k_NAME], asJson[k_STRINGSHASH] )
|
||||
# if xlateResult:
|
||||
# logging.debug( xlateResult )
|
||||
# result[k_XLATEINFO] = xlateResult;
|
||||
|
||||
result = json.dumps( result )
|
||||
# logging.debug( result )
|
||||
|
@ -564,7 +580,7 @@ def clearShelf():
|
|||
def usage(msg=None):
|
||||
if msg: print "ERROR:", msg
|
||||
print "usage:", sys.argv[0], '--get-sums [lang/dict]*'
|
||||
print ' | --test-get-app app <org.eehouse.app.name> avers gvers'
|
||||
print ' | --get-app --appID <org.something> --vers <avers> --gvers <gvers> [--debug]'
|
||||
print ' | --test-get-dicts name lang curSum'
|
||||
print ' | --list-apks [--path <path/to/apks>] [--debug] --appID org.something'
|
||||
print ' | --list-dicts'
|
||||
|
@ -574,8 +590,9 @@ def usage(msg=None):
|
|||
|
||||
def main():
|
||||
argc = len(sys.argv)
|
||||
if 1 >= argc: usage();
|
||||
if 1 >= argc: usage('too few args')
|
||||
arg = sys.argv[1]
|
||||
args = sys.argv[2:]
|
||||
if arg == '--clear-shelf':
|
||||
clearShelf()
|
||||
elif arg == '--list-dicts':
|
||||
|
@ -589,12 +606,24 @@ def main():
|
|||
print arg, md5Checksums(dictSums, arg)
|
||||
s_shelf[k_SUMS] = dictSums
|
||||
closeShelf()
|
||||
elif arg == '--test-get-app':
|
||||
if not 4 == argc: usage()
|
||||
params = { k_NAME: sys.argv[2],
|
||||
k_GVERS: sys.argv[3],
|
||||
elif arg == '--get-app':
|
||||
appID = None
|
||||
vers = 0
|
||||
debug = False
|
||||
while len(args):
|
||||
arg = args.pop(0)
|
||||
if arg == '--appID': appID = args.pop(0)
|
||||
elif arg == '--vers': vers = int(args.pop(0))
|
||||
elif arg == '--debug': debug = True
|
||||
else: usage('unexpected arg: ' + arg)
|
||||
if not appID: usage('--appID required')
|
||||
elif not vers: usage('--vers required')
|
||||
params = { k_NAME: appID,
|
||||
k_AVERS: vers,
|
||||
k_DEBUG: debug,
|
||||
k_DEVOK: False, # FIX ME
|
||||
}
|
||||
print getApp( params, sys.argv[2] )
|
||||
print getApp( params )
|
||||
elif arg == '--test-get-dicts':
|
||||
if not 5 == argc: usage()
|
||||
params = { k_NAME: sys.argv[2],
|
||||
|
@ -607,7 +636,6 @@ def main():
|
|||
path = ""
|
||||
debug = False
|
||||
appID = ''
|
||||
args = sys.argv[2:]
|
||||
while len(args):
|
||||
arg = args.pop(0)
|
||||
if arg == '--appID': appID = args.pop(0)
|
||||
|
|
|
@ -43,3 +43,8 @@ for SVG in img_src/*.svg; do
|
|||
fi
|
||||
done
|
||||
done
|
||||
|
||||
OTHER_IMAGES="app/src/main/res/drawable/green_chat__gen.png"
|
||||
for IMAGE in $OTHER_IMAGES; do
|
||||
make -f $(dirname $0)/images.mk $IMAGE >/dev/null 2>&1
|
||||
done
|
||||
|
|
|
@ -447,10 +447,13 @@ printDims( const BoardDims* dimsp )
|
|||
# define printDims( ldims )
|
||||
# endif
|
||||
|
||||
/* For debugging the special case of square board */
|
||||
// #define FORCE_SQUARE
|
||||
|
||||
void
|
||||
board_figureLayout( BoardCtxt* board, const CurGameInfo* gi,
|
||||
XP_U16 bLeft, XP_U16 bTop,
|
||||
const XP_U16 bWidth, const XP_U16 bHeight,
|
||||
XP_U16 bWidth, XP_U16 bHeight,
|
||||
XP_U16 colPctMax, XP_U16 scorePct, XP_U16 trayPct,
|
||||
XP_U16 scoreWidth, XP_U16 fontWidth, XP_U16 fontHt,
|
||||
XP_Bool squareTiles, BoardDims* dimsp )
|
||||
|
@ -465,6 +468,14 @@ board_figureLayout( BoardCtxt* board, const CurGameInfo* gi,
|
|||
XP_U16 wantHt;
|
||||
XP_U16 nToScroll;
|
||||
|
||||
#ifdef FORCE_SQUARE
|
||||
if ( bWidth > bHeight ) {
|
||||
bWidth = bHeight;
|
||||
} else {
|
||||
bHeight = bWidth;
|
||||
}
|
||||
#endif
|
||||
|
||||
ldims.left = bLeft;
|
||||
ldims.top = bTop;
|
||||
ldims.width = bWidth;
|
||||
|
@ -552,7 +563,13 @@ board_figureLayout( BoardCtxt* board, const CurGameInfo* gi,
|
|||
|
||||
ldims.boardHt = cellSize * nCells;
|
||||
ldims.trayTop = ldims.top + scoreHt + (cellSize * (nCells-nToScroll));
|
||||
ldims.height = heightUsed;
|
||||
ldims.height =
|
||||
#ifdef FORCE_SQUARE
|
||||
ldims.width
|
||||
#else
|
||||
heightUsed
|
||||
#endif
|
||||
;
|
||||
ldims.cellSize = cellSize;
|
||||
|
||||
if ( gi->timerEnabled ) {
|
||||
|
|
|
@ -1721,11 +1721,10 @@ cursesDevIDReceived( void* closure, const XP_UCHAR* devID,
|
|||
|
||||
/* If we already have one, make sure it's the same! Else store. */
|
||||
gchar buf[64];
|
||||
XP_Bool have = db_fetch( pDb, KEY_RDEVID, buf, sizeof(buf) );
|
||||
XP_Bool have = db_fetch( pDb, KEY_RDEVID, buf, sizeof(buf) )
|
||||
&& 0 == strcmp( buf, devID );
|
||||
if ( !have ) {
|
||||
db_store( pDb, KEY_RDEVID, devID );
|
||||
} else {
|
||||
XP_ASSERT( 0 == strcmp( buf, devID ) );
|
||||
}
|
||||
(void)g_timeout_add_seconds( maxInterval, keepalive_timer, globals );
|
||||
} else {
|
||||
|
|
|
@ -112,6 +112,12 @@ destroyCairo( GtkDrawCtx* dctx )
|
|||
dctx->_cairo = NULL;
|
||||
}
|
||||
|
||||
static XP_Bool
|
||||
haveCairo( const GtkDrawCtx* dctx )
|
||||
{
|
||||
return !!dctx->_cairo;
|
||||
}
|
||||
|
||||
static cairo_t*
|
||||
getCairo( const GtkDrawCtx* dctx )
|
||||
{
|
||||
|
@ -1231,15 +1237,20 @@ gtk_draw_drawTimer( DrawCtx* p_dctx, const XP_Rect* rInner,
|
|||
XP_U16 playerNum, XP_S16 secondsLeft )
|
||||
{
|
||||
GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
|
||||
XP_UCHAR buf[10];
|
||||
XP_Bool hadCairo = haveCairo( dctx );
|
||||
if ( hadCairo || initCairo( dctx ) ) {
|
||||
XP_UCHAR buf[10];
|
||||
|
||||
gtkFormatTimerText( buf, VSIZE(buf), secondsLeft );
|
||||
gtkFormatTimerText( buf, VSIZE(buf), secondsLeft );
|
||||
|
||||
/* gdk_gc_set_clip_rectangle( dctx->drawGC, (GdkRectangle*)rInner ); */
|
||||
gtkEraseRect( dctx, rInner );
|
||||
draw_string_at( dctx, NULL, buf, rInner->height-1,
|
||||
rInner, XP_GTK_JUST_CENTER,
|
||||
&dctx->playerColors[playerNum], NULL );
|
||||
gtkEraseRect( dctx, rInner );
|
||||
draw_string_at( dctx, NULL, buf, rInner->height-1,
|
||||
rInner, XP_GTK_JUST_CENTER,
|
||||
&dctx->playerColors[playerNum], NULL );
|
||||
if ( !hadCairo ) {
|
||||
destroyCairo( dctx );
|
||||
}
|
||||
}
|
||||
} /* gtk_draw_drawTimer */
|
||||
|
||||
#ifdef XWFEATURE_MINIWIN
|
||||
|
|
|
@ -1577,7 +1577,7 @@ parsePair( const char* optarg, XP_U16* min, XP_U16* max )
|
|||
} else {
|
||||
int intmin, intmax;
|
||||
if ( 2 == sscanf( optarg, "%d:%d", &intmin, &intmax ) ) {
|
||||
if ( intmin <= intmin ) {
|
||||
if ( intmin <= intmax ) {
|
||||
*min = intmin;
|
||||
*max = intmax;
|
||||
success = true;
|
||||
|
@ -2284,6 +2284,7 @@ main( int argc, char** argv )
|
|||
break;
|
||||
case CMD_PLAYERNAME:
|
||||
index = mainParams.pgi.nPlayers++;
|
||||
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||
++mainParams.nLocalPlayers;
|
||||
mainParams.pgi.players[index].robotIQ = 0; /* means human */
|
||||
mainParams.pgi.players[index].isLocal = XP_TRUE;
|
||||
|
@ -2292,6 +2293,7 @@ main( int argc, char** argv )
|
|||
break;
|
||||
case CMD_REMOTEPLAYER:
|
||||
index = mainParams.pgi.nPlayers++;
|
||||
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||
mainParams.pgi.players[index].isLocal = XP_FALSE;
|
||||
++mainParams.info.serverInfo.nRemotePlayers;
|
||||
break;
|
||||
|
@ -2302,6 +2304,7 @@ main( int argc, char** argv )
|
|||
case CMD_ROBOTNAME:
|
||||
++robotCount;
|
||||
index = mainParams.pgi.nPlayers++;
|
||||
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||
++mainParams.nLocalPlayers;
|
||||
mainParams.pgi.players[index].robotIQ = 1; /* real smart by default */
|
||||
mainParams.pgi.players[index].isLocal = XP_TRUE;
|
||||
|
|
|
@ -257,12 +257,13 @@ relaycon_receive( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpo
|
|||
gchar* b64 = g_base64_encode( (const guchar*)buf,
|
||||
((0 <= nRead)? nRead : 0) );
|
||||
XP_LOGF( "%s: read %zd bytes ('%s')", __func__, nRead, b64 );
|
||||
g_free( b64 );
|
||||
#ifdef COMMS_CHECKSUM
|
||||
gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, buf, nRead );
|
||||
XP_LOGF( "%s: read %zd bytes ('%s')(sum=%s)", __func__, nRead, b64, sum );
|
||||
g_free( sum );
|
||||
#endif
|
||||
g_free( b64 );
|
||||
|
||||
if ( 0 <= nRead ) {
|
||||
const XP_U8* ptr = buf;
|
||||
const XP_U8* end = buf + nRead;
|
||||
|
|
985
xwords4/linux/scripts/discon_ok2.py
Executable file
985
xwords4/linux/scripts/discon_ok2.py
Executable file
|
@ -0,0 +1,985 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import re, os, sys, getopt, shutil, threading, requests, json, glob
|
||||
import argparse, datetime, random, subprocess, time
|
||||
|
||||
# LOGDIR=./$(basename $0)_logs
|
||||
# APP_NEW=""
|
||||
# DO_CLEAN=""
|
||||
# APP_NEW_PARAMS=""
|
||||
# NGAMES = 1
|
||||
g_UDP_PCT_START = 100
|
||||
# UDP_PCT_INCR=10
|
||||
# UPGRADE_ODDS=""
|
||||
# NROOMS=""
|
||||
# HOST=""
|
||||
# PORT=""
|
||||
# TIMEOUT=""
|
||||
# SAVE_GOOD=""
|
||||
# MINDEVS=""
|
||||
# MAXDEVS=""
|
||||
# ONEPER=""
|
||||
# RESIGN_PCT=0
|
||||
g_DROP_N=0
|
||||
# MINRUN=2 # seconds
|
||||
# ONE_PER_ROOM="" # don't run more than one device at a time per room
|
||||
# USE_GTK=""
|
||||
# UNDO_PCT=0
|
||||
# ALL_VIA_RQ=${ALL_VIA_RQ:-FALSE}
|
||||
# SEED=""
|
||||
# BOARD_SIZES_OLD=(15)
|
||||
# BOARD_SIZES_NEW=(15)
|
||||
g_NAMES = [None, '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
|
||||
# declare -A NEW_ARGS
|
||||
# declare -a ARGS
|
||||
# declare -A ARGS_DEVID
|
||||
# declare -A ROOMS
|
||||
# declare -A FILES
|
||||
# declare -A LOGS
|
||||
# declare -A MINEND
|
||||
# ROOM_PIDS = {}
|
||||
# declare -a APPS_OLD=()
|
||||
# declare -a DICTS= # wants to be =() too?
|
||||
# declare -A CHECKED_ROOMS
|
||||
|
||||
# function cleanup() {
|
||||
# APP="$(basename $APP_NEW)"
|
||||
# while pidof $APP; do
|
||||
# echo "killing existing $APP instances..."
|
||||
# killall -9 $APP
|
||||
# sleep 1
|
||||
# done
|
||||
# echo "cleaning everything up...."
|
||||
# if [ -d $LOGDIR ]; then
|
||||
# mv $LOGDIR /tmp/${LOGDIR}_$$
|
||||
# fi
|
||||
# if [ -e $(dirname $0)/../../relay/xwrelay.log ]; then
|
||||
# mkdir -p /tmp/${LOGDIR}_$$
|
||||
# mv $(dirname $0)/../../relay/xwrelay.log /tmp/${LOGDIR}_$$
|
||||
# fi
|
||||
|
||||
# echo "DELETE FROM games WHERE room LIKE 'ROOM_%';" | psql -q -t xwgames
|
||||
# echo "DELETE FROM msgs WHERE NOT devid in (SELECT unnest(devids) from games);" | psql -q -t xwgames
|
||||
# }
|
||||
|
||||
# function connName() {
|
||||
# LOG=$1
|
||||
# grep -a 'got_connect_cmd: connName' $LOG | \
|
||||
# tail -n 1 | \
|
||||
# sed 's,^.*connName: \"\(.*\)\" (reconnect=.)$,\1,'
|
||||
# }
|
||||
|
||||
# function check_room() {
|
||||
# ROOM=$1
|
||||
# if [ -z ${CHECKED_ROOMS[$ROOM]:-""} ]; then
|
||||
# NUM=$(echo "SELECT COUNT(*) FROM games "\
|
||||
# "WHERE NOT dead "\
|
||||
# "AND ntotal!=sum_array(nperdevice) "\
|
||||
# "AND ntotal != -sum_array(nperdevice) "\
|
||||
# "AND room='$ROOM'" |
|
||||
# psql -q -t xwgames)
|
||||
# NUM=$((NUM+0))
|
||||
# if [ "$NUM" -gt 0 ]; then
|
||||
# echo "$ROOM in the DB has unconsummated games. Remove them."
|
||||
# exit 1
|
||||
# else
|
||||
# CHECKED_ROOMS[$ROOM]=1
|
||||
# fi
|
||||
# fi
|
||||
# }
|
||||
|
||||
# print_cmdline() {
|
||||
# local COUNTER=$1
|
||||
# local LOG=${LOGS[$COUNTER]}
|
||||
# echo -n "New cmdline: " >> $LOG
|
||||
# echo "${APPS[$COUNTER]} ${NEW_ARGS[$COUNTER]} ${ARGS[$COUNTER]}" >> $LOG
|
||||
# }
|
||||
|
||||
def pick_ndevs(args):
|
||||
RNUM = random.randint(0, 99)
|
||||
if RNUM > 90 and args.MAXDEVS >= 4:
|
||||
NDEVS = 4
|
||||
elif RNUM > 75 and args.MAXDEVS >= 3:
|
||||
NDEVS = 3
|
||||
else:
|
||||
NDEVS = 2
|
||||
if NDEVS < args.MINDEVS:
|
||||
NDEVS = args.MINDEVS
|
||||
return NDEVS
|
||||
|
||||
# # Given a device count, figure out how many local players per device.
|
||||
# # "1 1" would be a two-device game with 1 each. "1 2 1" a
|
||||
# # three-device game with four players total
|
||||
def figure_locals(args, NDEVS):
|
||||
NPLAYERS = pick_ndevs(args)
|
||||
if NPLAYERS < NDEVS: NPLAYERS = NDEVS
|
||||
|
||||
EXTRAS = 0
|
||||
if not args.ONEPER:
|
||||
EXTRAS = NPLAYERS - NDEVS
|
||||
|
||||
LOCALS = []
|
||||
for IGNORE in range(NDEVS):
|
||||
COUNT = 1
|
||||
if EXTRAS > 0:
|
||||
EXTRA = random.randint(0, EXTRAS)
|
||||
if EXTRA > 0:
|
||||
COUNT += EXTRA
|
||||
EXTRAS -= EXTRA
|
||||
LOCALS.append(COUNT)
|
||||
assert 0 < sum(LOCALS) <= 4
|
||||
return LOCALS
|
||||
|
||||
def player_params(args, NLOCALS, NPLAYERS, NAME_INDX):
|
||||
assert 0 < NPLAYERS <= 4
|
||||
NREMOTES = NPLAYERS - NLOCALS
|
||||
PARAMS = []
|
||||
while NLOCALS > 0 or NREMOTES > 0:
|
||||
if 0 == random.randint(0, 2) and 0 < NLOCALS:
|
||||
PARAMS += ['--robot', g_NAMES[NAME_INDX], '--robot-iq', str(random.randint(1,100))]
|
||||
NLOCALS -= 1
|
||||
NAME_INDX += 1
|
||||
elif 0 < NREMOTES:
|
||||
PARAMS += ['--remote-player']
|
||||
NREMOTES -= 1
|
||||
return PARAMS
|
||||
|
||||
def logReaderStub(dev): dev.logReaderMain()
|
||||
|
||||
class Device():
|
||||
sConnnameMap = {}
|
||||
sHasLDevIDMap = {}
|
||||
sConnNamePat = re.compile('.*got_connect_cmd: connName: "([^"]+)".*$')
|
||||
sGameOverPat = re.compile('.*\[unused tiles\].*')
|
||||
sTilesLeftPat = re.compile('.*pool_removeTiles: (\d+) tiles left in pool')
|
||||
sRelayIDPat = re.compile('.*UPDATE games.*seed=(\d+),.*relayid=\'([^\']+)\'.*')
|
||||
|
||||
def __init__(self, args, indx, app, params, room, db, log, nInGame):
|
||||
self.indx = indx
|
||||
self.args = args
|
||||
self.pid = 0
|
||||
self.gameOver = False
|
||||
self.app = app
|
||||
self.params = params
|
||||
self.room = room
|
||||
self.db = db
|
||||
self.logPath = log
|
||||
self.nInGame = nInGame
|
||||
# runtime stuff; init now
|
||||
self.proc = None
|
||||
self.connname = None
|
||||
self.devID = ''
|
||||
self.launchCount = 0
|
||||
self.allDone = False # when true, can be killed
|
||||
self.nTilesLeft = -1 # negative means don't know
|
||||
self.relayID = None
|
||||
self.relaySeed = 0
|
||||
|
||||
with open(self.logPath, "w") as log:
|
||||
log.write('New cmdline: ' + self.app + ' ' + (' '.join([str(p) for p in self.params])))
|
||||
log.write(os.linesep)
|
||||
|
||||
def logReaderMain(self):
|
||||
assert self and self.proc
|
||||
stdout, stderr = self.proc.communicate()
|
||||
# print('logReaderMain called; opening:', self.logPath, 'flag:', flag)
|
||||
nLines = 0
|
||||
with open(self.logPath, 'a') as log:
|
||||
for line in stderr.splitlines():
|
||||
nLines += 1
|
||||
log.write(line + os.linesep)
|
||||
|
||||
# check for connname
|
||||
if not self.connname:
|
||||
match = Device.sConnNamePat.match(line)
|
||||
if match:
|
||||
self.connname = match.group(1)
|
||||
if not self.connname in Device.sConnnameMap:
|
||||
Device.sConnnameMap[self.connname] = set()
|
||||
Device.sConnnameMap[self.connname].add(self)
|
||||
|
||||
# check for game over
|
||||
if not self.gameOver:
|
||||
match = Device.sGameOverPat.match(line)
|
||||
if match: self.gameOver = True
|
||||
|
||||
# Check every line for tiles left
|
||||
match = Device.sTilesLeftPat.match(line)
|
||||
if match: self.nTilesLeft = int(match.group(1))
|
||||
|
||||
if not self.relayID:
|
||||
match = Device.sRelayIDPat.match(line)
|
||||
if match:
|
||||
self.relaySeed = int(match.group(1))
|
||||
self.relayID = match.group(2)
|
||||
|
||||
# print('logReaderMain done, wrote lines:', nLines, 'to', self.logPath);
|
||||
|
||||
def launch(self):
|
||||
args = [self.app] + [str(p) for p in self.params]
|
||||
if self.devID: args.extend( ' '.split(self.devID))
|
||||
self.launchCount += 1
|
||||
# self.logStream = open(self.logPath, flag)
|
||||
self.proc = subprocess.Popen(args, stdout = subprocess.DEVNULL,
|
||||
stderr = subprocess.PIPE, universal_newlines = True)
|
||||
self.pid = self.proc.pid
|
||||
self.minEnd = datetime.datetime.now() + datetime.timedelta(seconds = self.args.MINRUN)
|
||||
|
||||
# Now start a thread to read stdio
|
||||
self.reader = threading.Thread(target = logReaderStub, args=(self,))
|
||||
self.reader.isDaemon = True
|
||||
self.reader.start()
|
||||
|
||||
def running(self):
|
||||
return self.proc and not self.proc.poll()
|
||||
|
||||
def minTimeExpired(self):
|
||||
assert self.proc
|
||||
return self.minEnd < datetime.datetime.now()
|
||||
|
||||
def kill(self):
|
||||
if self.proc.poll() is None:
|
||||
self.proc.terminate()
|
||||
self.proc.wait()
|
||||
assert self.proc.poll() is not None
|
||||
|
||||
self.reader.join()
|
||||
self.reader = None
|
||||
else:
|
||||
print('NOT killing')
|
||||
self.proc = None
|
||||
self.check_game()
|
||||
|
||||
def moveFiles(self):
|
||||
assert not self.running()
|
||||
shutil.move(self.logPath, self.args.LOGDIR + '/done')
|
||||
shutil.move(self.db, self.args.LOGDIR + '/done')
|
||||
|
||||
def send_dead(self):
|
||||
JSON = json.dumps([{'relayID': self.relayID, 'seed': self.relaySeed}])
|
||||
url = 'http://%s/xw4/relay.py/kill' % (self.args.HOST)
|
||||
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
|
||||
|
||||
def update_ldevid(self):
|
||||
if not self.app in Device.sHasLDevIDMap:
|
||||
hasLDevID = False
|
||||
proc = subprocess.Popen([self.app, '--help'], stderr=subprocess.PIPE)
|
||||
# output, err, = proc.communicate()
|
||||
for line in proc.stderr.readlines():
|
||||
if b'--ldevid' in line:
|
||||
hasLDevID = True
|
||||
break
|
||||
print('found --ldevid:', hasLDevID);
|
||||
Device.sHasLDevIDMap[self.app] = hasLDevID
|
||||
|
||||
if Device.sHasLDevIDMap[self.app]:
|
||||
RNUM = random.randint(0, 99)
|
||||
if not self.devID:
|
||||
if RNUM < 30:
|
||||
self.devID = '--ldevid LINUX_TEST_%.5d_' % (self.indx)
|
||||
elif RNUM < 10:
|
||||
self.devID += 'x'
|
||||
|
||||
def check_game(self):
|
||||
if self.gameOver and not self.allDone:
|
||||
allDone = False
|
||||
if len(Device.sConnnameMap[self.connname]) == self.nInGame:
|
||||
allDone = True
|
||||
for dev in Device.sConnnameMap[self.connname]:
|
||||
if dev == self: continue
|
||||
if not dev.gameOver:
|
||||
allDone = False
|
||||
break
|
||||
|
||||
if allDone:
|
||||
for dev in Device.sConnnameMap[self.connname]:
|
||||
dev.allDone = True
|
||||
|
||||
# print('Closing', self.connname, datetime.datetime.now())
|
||||
# for dev in Device.sConnnameMap[self.connname]:
|
||||
# dev.kill()
|
||||
# # kill_from_logs $OTHERS $KEY
|
||||
# for ID in $OTHERS $KEY; do
|
||||
# echo -n "${ID}:${LOGS[$ID]}, "
|
||||
# kill_from_log ${LOGS[$ID]} || /bin/true
|
||||
# send_dead $ID
|
||||
# close_device $ID $DONEDIR "game over"
|
||||
# done
|
||||
# echo ""
|
||||
# # XWRELAY_ERROR_DELETED may be old
|
||||
# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DELETED)' $LOG; then
|
||||
# echo "deleting $LOG $(connName $LOG) b/c another resigned"
|
||||
# kill_from_log $LOG || /bin/true
|
||||
# close_device $KEY $DEADDIR "other resigned"
|
||||
# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DEADGAME)' $LOG; then
|
||||
# echo "deleting $LOG $(connName $LOG) b/c another resigned"
|
||||
# kill_from_log $LOG || /bin/true
|
||||
# close_device $KEY $DEADDIR "other resigned"
|
||||
# else
|
||||
# maybe_resign $KEY
|
||||
# fi
|
||||
# }
|
||||
|
||||
|
||||
def build_cmds(args):
|
||||
devs = []
|
||||
COUNTER = 0
|
||||
PLAT_PARMS = []
|
||||
if not args.USE_GTK:
|
||||
PLAT_PARMS += ['--curses', '--close-stdin']
|
||||
|
||||
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)
|
||||
assert(len(LOCALS) == NDEVS)
|
||||
DICT = args.DICTS[GAME % len(args.DICTS)]
|
||||
# make one in three games public
|
||||
PUBLIC = []
|
||||
if random.randint(0, 3) == 0: PUBLIC = ['--make-public', '--join-public']
|
||||
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
|
||||
|
||||
# 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
|
||||
# NAPPS=$((1+${#APPS_OLD[*]}))
|
||||
# if [ 0 -lt $((RANDOM%$NAPPS)) ]; then
|
||||
# APPS[$COUNTER]=${APPS_OLD[$((RANDOM%${#APPS_OLD[*]}))]}
|
||||
# BOARD_SIZE="--board-size ${BOARD_SIZES_OLD[$((RANDOM%${#BOARD_SIZES_OLD[*]}))]}"
|
||||
# NEW_ARGS[$COUNTER]=""
|
||||
# fi
|
||||
# fi
|
||||
|
||||
PARAMS = player_params(args, NLOCALS, NPLAYERS, DEV)
|
||||
PARAMS += PLAT_PARMS
|
||||
PARAMS += BOARD_SIZE + ['--room', ROOM, '--trade-pct', args.TRADE_PCT, '--sort-tiles']
|
||||
if args.UNDO_PCT > 0:
|
||||
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]
|
||||
if random.randint(0,100) % 100 < g_UDP_PCT_START:
|
||||
PARAMS += ['--use-udp']
|
||||
|
||||
PARAMS += ['--drop-nth-packet', g_DROP_N]
|
||||
if random.randint(0, 100) < args.HTTP_PCT:
|
||||
PARAMS += ['--use-http']
|
||||
|
||||
PARAMS += ['--split-packets', '2']
|
||||
if args.SEND_CHAT:
|
||||
PARAMS += ['--send-chat', args.SEND_CHAT]
|
||||
|
||||
if args.DUP_PACKETS:
|
||||
PARAMS += ['--dup-packets']
|
||||
# PARAMS += ['--my-port', '1024']
|
||||
# PARAMS += ['--savefail-pct', 10]
|
||||
|
||||
# With the --seed param passed, games with more than 2
|
||||
# devices don't get going. No idea why. This param is NOT
|
||||
# passed in the old bash version of this script, so fixing
|
||||
# it isn't a priority.
|
||||
# PARAMS += ['--seed', args.SEED]
|
||||
PARAMS += PUBLIC
|
||||
if DEV > 1:
|
||||
PARAMS += ['--force-channel', DEV - 1]
|
||||
else:
|
||||
PARAMS += ['--server']
|
||||
|
||||
# print('PARAMS:', PARAMS)
|
||||
|
||||
dev = Device(args, COUNTER, args.APP_NEW, PARAMS, ROOM, FILE, LOG, len(LOCALS))
|
||||
dev.update_ldevid()
|
||||
devs.append(dev)
|
||||
|
||||
COUNTER += 1
|
||||
return devs
|
||||
|
||||
# read_resume_cmds() {
|
||||
# COUNTER=0
|
||||
# for LOG in $(ls $LOGDIR/*.txt); do
|
||||
# echo "need to parse cmd and deal with changes"
|
||||
# exit 1
|
||||
# CMD=$(head -n 1 $LOG)
|
||||
|
||||
# ARGS[$COUNTER]=$CMD
|
||||
# LOGS[$COUNTER]=$LOG
|
||||
# PIDS[$COUNTER]=0
|
||||
|
||||
# set $CMD
|
||||
# while [ $# -gt 0 ]; do
|
||||
# case $1 in
|
||||
# --file)
|
||||
# FILES[$COUNTER]=$2
|
||||
# shift
|
||||
# ;;
|
||||
# --room)
|
||||
# ROOMS[$COUNTER]=$2
|
||||
# shift
|
||||
# ;;
|
||||
# esac
|
||||
# shift
|
||||
# done
|
||||
# COUNTER=$((COUNTER+1))
|
||||
# done
|
||||
# ROOM_PIDS[$ROOM]=0
|
||||
# }
|
||||
|
||||
# launch() {
|
||||
# KEY=$1
|
||||
# LOG=${LOGS[$KEY]}
|
||||
# APP="${APPS[$KEY]}"
|
||||
# if [ -z "$APP" ]; then
|
||||
# echo "error: no app set"
|
||||
# exit 1
|
||||
# fi
|
||||
# PARAMS="${NEW_ARGS[$KEY]} ${ARGS[$KEY]} ${ARGS_DEVID[$KEY]}"
|
||||
# exec $APP $PARAMS >/dev/null 2>>$LOG
|
||||
# }
|
||||
|
||||
# # launch_via_rq() {
|
||||
# # KEY=$1
|
||||
# # RELAYID=$2
|
||||
# # PIPE=${PIPES[$KEY]}
|
||||
# # ../relay/rq -f $RELAYID -o $PIPE &
|
||||
# # CMD="${CMDS[$KEY]}"
|
||||
# # exec $CMD >/dev/null 2>>$LOG
|
||||
# # }
|
||||
|
||||
# send_dead() {
|
||||
# ID=$1
|
||||
# DB=${FILES[$ID]}
|
||||
# while :; do
|
||||
# [ -f $DB ] || break # it's gone
|
||||
# RES=$(echo 'select relayid, seed from games limit 1;' | sqlite3 -separator ' ' $DB || /bin/true)
|
||||
# [ -n "$RES" ] && break
|
||||
# sleep 0.2
|
||||
# done
|
||||
# RELAYID=$(echo $RES | awk '{print $1}')
|
||||
# SEED=$(echo $RES | awk '{print $2}')
|
||||
# JSON="[{\"relayID\":\"$RELAYID\", \"seed\":$SEED}]"
|
||||
# curl -G --data-urlencode params="$JSON" http://$HOST/xw4/relay.py/kill >/dev/null 2>&1
|
||||
# }
|
||||
|
||||
# close_device() {
|
||||
# ID=$1
|
||||
# MVTO=$2
|
||||
# REASON="$3"
|
||||
# PID=${PIDS[$ID]}
|
||||
# if [ $PID -ne 0 ]; then
|
||||
# kill ${PIDS[$ID]} 2>/dev/null
|
||||
# wait ${PIDS[$ID]}
|
||||
# ROOM=${ROOMS[$ID]}
|
||||
# [ ${ROOM_PIDS[$ROOM]} -eq $PID ] && ROOM_PIDS[$ROOM]=0
|
||||
# fi
|
||||
# unset PIDS[$ID]
|
||||
# unset ARGS[$ID]
|
||||
# echo "closing game: $REASON" >> ${LOGS[$ID]}
|
||||
# if [ -n "$MVTO" ]; then
|
||||
# [ -f "${FILES[$ID]}" ] && mv ${FILES[$ID]} $MVTO
|
||||
# mv ${LOGS[$ID]} $MVTO
|
||||
# else
|
||||
# rm -f ${FILES[$ID]}
|
||||
# rm -f ${LOGS[$ID]}
|
||||
# fi
|
||||
# unset FILES[$ID]
|
||||
# unset LOGS[$ID]
|
||||
# unset ROOMS[$ID]
|
||||
# unset APPS[$ID]
|
||||
# unset ARGS_DEVID[$ID]
|
||||
|
||||
# COUNT=${#ARGS[*]}
|
||||
# echo "$COUNT devices left playing..."
|
||||
# }
|
||||
|
||||
# OBITS=""
|
||||
|
||||
# kill_from_log() {
|
||||
# LOG=$1
|
||||
# RELAYID=$(./scripts/relayID.sh --long $LOG)
|
||||
# if [ -n "$RELAYID" ]; then
|
||||
# OBITS="$OBITS -d $RELAYID"
|
||||
# if [ 0 -eq $(($RANDOM%2)) ]; then
|
||||
# ../relay/rq -a $HOST $OBITS 2>/dev/null || /bin/true
|
||||
# OBITS=""
|
||||
# fi
|
||||
# return 0 # success
|
||||
# fi
|
||||
# echo "unable to send kill command for $LOG"
|
||||
# return 1
|
||||
# }
|
||||
|
||||
# maybe_resign() {
|
||||
# if [ "$RESIGN_PCT" -gt 0 ]; then
|
||||
# KEY=$1
|
||||
# LOG=${LOGS[$KEY]}
|
||||
# if grep -aq XWRELAY_ALLHERE $LOG; then
|
||||
# if [ $((${RANDOM}%100)) -lt $RESIGN_PCT ]; then
|
||||
# echo "making $LOG $(connName $LOG) resign..."
|
||||
# kill_from_log $LOG && close_device $KEY $DEADDIR "resignation forced" || /bin/true
|
||||
# fi
|
||||
# fi
|
||||
# fi
|
||||
# }
|
||||
|
||||
# try_upgrade() {
|
||||
# KEY=$1
|
||||
# if [ 0 -lt ${#APPS_OLD[@]} ]; then
|
||||
# if [ $APP_NEW != "${APPS[$KEY]}" ]; then
|
||||
# # one in five chance of upgrading
|
||||
# if [ 0 -eq $((RANDOM % UPGRADE_ODDS)) ]; then
|
||||
# APPS[$KEY]=$APP_NEW
|
||||
# NEW_ARGS[$KEY]="$APP_NEW_PARAMS"
|
||||
# print_cmdline $KEY
|
||||
# fi
|
||||
# fi
|
||||
# fi
|
||||
# }
|
||||
|
||||
# try_upgrade_upd() {
|
||||
# KEY=$1
|
||||
# CMD=${ARGS[$KEY]}
|
||||
# if [ "${CMD/--use-udp/}" = "${CMD}" ]; then
|
||||
# if [ $((RANDOM % 100)) -lt $UDP_PCT_INCR ]; then
|
||||
# ARGS[$KEY]="$CMD --use-udp"
|
||||
# echo -n "$(date +%r): "
|
||||
# echo "upgrading key $KEY to use UDP"
|
||||
# fi
|
||||
# fi
|
||||
# }
|
||||
|
||||
# check_game() {
|
||||
# KEY=$1
|
||||
# LOG=${LOGS[$KEY]}
|
||||
# CONNNAME="$(connName $LOG)"
|
||||
# OTHERS=""
|
||||
# if [ -n "$CONNNAME" ]; then
|
||||
# if grep -aq '\[unused tiles\]' $LOG ; then
|
||||
# for INDX in ${!LOGS[*]}; do
|
||||
# [ $INDX -eq $KEY ] && continue
|
||||
# ALOG=${LOGS[$INDX]}
|
||||
# CONNNAME2="$(connName $ALOG)"
|
||||
# if [ "$CONNNAME2" = "$CONNNAME" ]; then
|
||||
# if ! grep -aq '\[unused tiles\]' $ALOG; then
|
||||
# OTHERS=""
|
||||
# break
|
||||
# fi
|
||||
# OTHERS="$OTHERS $INDX"
|
||||
# fi
|
||||
# done
|
||||
# fi
|
||||
# fi
|
||||
|
||||
# if [ -n "$OTHERS" ]; then
|
||||
# echo -n "Closing $CONNNAME [$(date)]: "
|
||||
# # kill_from_logs $OTHERS $KEY
|
||||
# for ID in $OTHERS $KEY; do
|
||||
# echo -n "${ID}:${LOGS[$ID]}, "
|
||||
# kill_from_log ${LOGS[$ID]} || /bin/true
|
||||
# send_dead $ID
|
||||
# close_device $ID $DONEDIR "game over"
|
||||
# done
|
||||
# echo ""
|
||||
# # XWRELAY_ERROR_DELETED may be old
|
||||
# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DELETED)' $LOG; then
|
||||
# echo "deleting $LOG $(connName $LOG) b/c another resigned"
|
||||
# kill_from_log $LOG || /bin/true
|
||||
# close_device $KEY $DEADDIR "other resigned"
|
||||
# elif grep -aq 'relay_error_curses(XWRELAY_ERROR_DEADGAME)' $LOG; then
|
||||
# echo "deleting $LOG $(connName $LOG) b/c another resigned"
|
||||
# kill_from_log $LOG || /bin/true
|
||||
# close_device $KEY $DEADDIR "other resigned"
|
||||
# else
|
||||
# maybe_resign $KEY
|
||||
# fi
|
||||
# }
|
||||
|
||||
# increment_drop() {
|
||||
# KEY=$1
|
||||
# CMD=${ARGS[$KEY]}
|
||||
# if [ "$CMD" != "${CMD/drop-nth-packet//}" ]; then
|
||||
# DROP_N=$(echo $CMD | sed 's,^.*drop-nth-packet \(-*[0-9]*\) .*$,\1,')
|
||||
# if [ $DROP_N -gt 0 ]; then
|
||||
# NEXT_N=$((DROP_N+1))
|
||||
# ARGS[$KEY]=$(echo $CMD | sed "s,^\(.*drop-nth-packet \)$DROP_N\(.*\)$,\1$NEXT_N\2,")
|
||||
# fi
|
||||
# fi
|
||||
# }
|
||||
|
||||
def summarizeTileCounts(devs):
|
||||
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 countCores():
|
||||
return len(glob.glob1('/tmp',"core*"))
|
||||
|
||||
def run_cmds(args, devs):
|
||||
nCores = countCores()
|
||||
endTime = datetime.datetime.now() + datetime.timedelta(seconds = args.TIMEOUT)
|
||||
LOOPCOUNT = 0
|
||||
|
||||
while len(devs) > 0:
|
||||
if countCores() > nCores:
|
||||
print('core file count increased; exiting')
|
||||
break
|
||||
if datetime.datetime.now() > endTime:
|
||||
print('outta time; outta here')
|
||||
break
|
||||
|
||||
LOOPCOUNT += 1
|
||||
if 0 == LOOPCOUNT % 20: summarizeTileCounts(devs)
|
||||
|
||||
dev = random.choice(devs)
|
||||
if not dev.running():
|
||||
if dev.allDone:
|
||||
dev.moveFiles()
|
||||
dev.send_dead()
|
||||
devs.remove(dev)
|
||||
else:
|
||||
# if [ -n "$ONE_PER_ROOM" -a 0 -ne ${ROOM_PIDS[$ROOM]} ]; then
|
||||
# continue
|
||||
# fi
|
||||
# try_upgrade $KEY
|
||||
# try_upgrade_upd $KEY
|
||||
dev.launch()
|
||||
# PID=$!
|
||||
# # renice doesn't work on one of my machines...
|
||||
# renice -n 1 -p $PID >/dev/null 2>&1 || /bin/true
|
||||
# PIDS[$KEY]=$PID
|
||||
# ROOM_PIDS[$ROOM]=$PID
|
||||
# MINEND[$KEY]=$(($NOW + $MINRUN))
|
||||
elif not dev.minTimeExpired():
|
||||
# print('sleeping...')
|
||||
time.sleep(2)
|
||||
else:
|
||||
dev.kill()
|
||||
# if g_DROP_N >= 0: dev.increment_drop()
|
||||
# update_ldevid $KEY
|
||||
|
||||
|
||||
# if we get here via a break, kill any remaining games
|
||||
if devs:
|
||||
print('stopping %d remaining games' % (len(devs)))
|
||||
for dev in devs:
|
||||
if dev.running(): dev.kill()
|
||||
|
||||
# run_via_rq() {
|
||||
# # launch then kill all games to give chance to hook up
|
||||
# for KEY in ${!ARGS[*]}; do
|
||||
# echo "launching $KEY"
|
||||
# launch $KEY &
|
||||
# PID=$!
|
||||
# sleep 1
|
||||
# kill $PID
|
||||
# wait $PID
|
||||
# # add_pipe $KEY
|
||||
# done
|
||||
|
||||
# echo "now running via rq"
|
||||
# # then run them
|
||||
# while :; do
|
||||
# COUNT=${#ARGS[*]}
|
||||
# [ 0 -ge $COUNT ] && break
|
||||
|
||||
# INDX=$(($RANDOM%COUNT))
|
||||
# KEYS=( ${!ARGS[*]} )
|
||||
# KEY=${KEYS[$INDX]}
|
||||
# CMD=${ARGS[$KEY]}
|
||||
|
||||
# RELAYID=$(./scripts/relayID.sh --short ${LOGS[$KEY]})
|
||||
# MSG_COUNT=$(../relay/rq -a $HOST -m $RELAYID 2>/dev/null | sed 's,^.*-- ,,')
|
||||
# if [ $MSG_COUNT -gt 0 ]; then
|
||||
# launch $KEY &
|
||||
# PID=$!
|
||||
# sleep 2
|
||||
# kill $PID || /bin/true
|
||||
# wait $PID
|
||||
# fi
|
||||
# [ "$DROP_N" -ge 0 ] && increment_drop $KEY
|
||||
# check_game $KEY
|
||||
# done
|
||||
# } # run_via_rq
|
||||
|
||||
# function getArg() {
|
||||
# [ 1 -lt "$#" ] || usage "$1 requires an argument"
|
||||
# echo $2
|
||||
# }
|
||||
|
||||
def mkParser():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--send-chat', dest = 'SEND_CHAT', type = str, default = None,
|
||||
help = 'the message to send')
|
||||
|
||||
parser.add_argument('--app-new', dest = 'APP_NEW', default = './obj_linux_memdbg/xwords',
|
||||
help = 'the app we\'ll use')
|
||||
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('--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',
|
||||
help = 'run games using gtk instead of ncurses')
|
||||
# #
|
||||
# # echo " [--clean-start] \\" >&2
|
||||
parser.add_argument('--game-dict', dest = 'DICTS', action = 'append', default = [])
|
||||
# # echo " [--help] \\" >&2
|
||||
parser.add_argument('--host', dest = 'HOST', default = 'localhost',
|
||||
help = 'relay hostname')
|
||||
# # echo " [--max-devs <int>] \\" >&2
|
||||
parser.add_argument('--min-devs', dest = 'MINDEVS', type = int, default = 2,
|
||||
help = 'No game will have fewer devices than this')
|
||||
parser.add_argument('--max-devs', dest = 'MAXDEVS', type = int, default = 4,
|
||||
help = 'No game will have more devices than this')
|
||||
parser.add_argument('--min-run', dest = 'MINRUN', type = int, default = 2,
|
||||
help = 'Keep each run alive at least this many seconds')
|
||||
# # echo " [--new-app <path/to/app] \\" >&2
|
||||
# # echo " [--new-app-args [arg*]] # passed only to new app \\" >&2
|
||||
# # echo " [--num-rooms <int>] \\" >&2
|
||||
# # echo " [--old-app <path/to/app]* \\" >&2
|
||||
parser.add_argument('--one-per', dest = 'ONEPER', default = False,
|
||||
action = 'store_true', help = 'force one player per device')
|
||||
parser.add_argument('--port', dest = 'PORT', default = 10997, type = int, \
|
||||
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
|
||||
# # echo " [--udp-incr <pct>] \\" >&2
|
||||
# # echo " [--udp-start <pct>] # default: $UDP_PCT_START \\" >&2
|
||||
# # echo " [--undo-pct <int>] \\" >&2
|
||||
parser.add_argument('--http-pct', dest = 'HTTP_PCT', default = 0, type = int,
|
||||
help = 'pct of games to be using web api')
|
||||
|
||||
parser.add_argument('--undo-pct', dest = 'UNDO_PCT', default = 0, type = int)
|
||||
parser.add_argument('--trade-pct', dest = 'TRADE_PCT', default = 0, type = int)
|
||||
|
||||
return parser
|
||||
|
||||
# #######################################################
|
||||
# ##################### MAIN begins #####################
|
||||
# #######################################################
|
||||
|
||||
def parseArgs():
|
||||
args = mkParser().parse_args()
|
||||
assignDefaults(args)
|
||||
print(args)
|
||||
return args
|
||||
# print(options)
|
||||
|
||||
# while [ "$#" -gt 0 ]; do
|
||||
# case $1 in
|
||||
# --udp-start)
|
||||
# UDP_PCT_START=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --udp-incr)
|
||||
# UDP_PCT_INCR=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --clean-start)
|
||||
# DO_CLEAN=1
|
||||
# ;;
|
||||
# --num-games)
|
||||
# NGAMES=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --num-rooms)
|
||||
# NROOMS=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --old-app)
|
||||
# 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
|
||||
# ;;
|
||||
# --new-app)
|
||||
# APP_NEW=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --new-app-args)
|
||||
# APP_NEW_PARAMS="${2}"
|
||||
# echo "got $APP_NEW_PARAMS"
|
||||
# shift
|
||||
# ;;
|
||||
# --game-dict)
|
||||
# DICTS[${#DICTS[@]}]=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --min-devs)
|
||||
# MINDEVS=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --max-devs)
|
||||
# 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
|
||||
# ;;
|
||||
# --host)
|
||||
# HOST=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --port)
|
||||
# PORT=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --seed)
|
||||
# SEED=$(getArg $*)
|
||||
# shift
|
||||
# ;;
|
||||
# --undo-pct)
|
||||
# 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-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
|
||||
# ;;
|
||||
# *) usage "unrecognized option $1"
|
||||
# ;;
|
||||
# esac
|
||||
# shift
|
||||
# done
|
||||
|
||||
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
|
||||
if os.path.exists(args.LOGDIR):
|
||||
shutil.move(args.LOGDIR, '/tmp/' + args.LOGDIR + '_' + str(random.randint(0, 100000)))
|
||||
for d in ['', 'done', 'dead',]:
|
||||
os.mkdir(args.LOGDIR + '/' + d)
|
||||
# [ -z "$SAVE_GOOD" ] && SAVE_GOOD=YES
|
||||
# # [ -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
|
||||
# #$((NGAMES/50))
|
||||
# [ 0 -eq $UPGRADE_ODDS ] && UPGRADE_ODDS=1
|
||||
# [ -n "$SEED" ] && RANDOM=$SEED
|
||||
# [ -z "$ONEPER" -a $NROOMS -lt $NGAMES ] && usage "use --one-per if --num-rooms < --num-games"
|
||||
|
||||
# [ -n "$DO_CLEAN" ] && cleanup
|
||||
|
||||
# RESUME=""
|
||||
# for FILE in $(ls $LOGDIR/*.{xwg,txt} 2>/dev/null); do
|
||||
# if [ -e $FILE ]; then
|
||||
# echo "Unfinished games found in $LOGDIR; continue with them (or discard)?"
|
||||
# read -p "<yes/no> " ANSWER
|
||||
# case "$ANSWER" in
|
||||
# y|yes|Y|YES)
|
||||
# RESUME=1
|
||||
# ;;
|
||||
# *)
|
||||
# ;;
|
||||
# esac
|
||||
# fi
|
||||
# break
|
||||
# done
|
||||
|
||||
# if [ -z "$RESUME" -a -d $LOGDIR ]; then
|
||||
# NEWNAME="$(basename $LOGDIR)_$$"
|
||||
# (cd $(dirname $LOGDIR) && mv $(basename $LOGDIR) /tmp/${NEWNAME})
|
||||
# fi
|
||||
# mkdir -p $LOGDIR
|
||||
|
||||
# if [ "$SAVE_GOOD" = YES ]; then
|
||||
# DONEDIR=$LOGDIR/done
|
||||
# mkdir -p $DONEDIR
|
||||
# fi
|
||||
# DEADDIR=$LOGDIR/dead
|
||||
# mkdir -p $DEADDIR
|
||||
|
||||
# for VAR in NGAMES NROOMS USE_GTK TIMEOUT HOST PORT SAVE_GOOD \
|
||||
# MINDEVS MAXDEVS ONEPER RESIGN_PCT DROP_N ALL_VIA_RQ SEED \
|
||||
# APP_NEW; do
|
||||
# echo "$VAR:" $(eval "echo \$${VAR}") 1>&2
|
||||
# done
|
||||
# echo "DICTS: ${DICTS[*]}"
|
||||
# echo -n "APPS_OLD: "; [ xx = "${APPS_OLD[*]+xx}" ] && echo "${APPS_OLD[*]}" || echo ""
|
||||
|
||||
# echo "*********$0 starting: $(date)**************"
|
||||
# STARTTIME=$(date +%s)
|
||||
# [ -z "$RESUME" ] && build_cmds || read_resume_cmds
|
||||
# if [ TRUE = "$ALL_VIA_RQ" ]; then
|
||||
# run_via_rq
|
||||
# else
|
||||
# run_cmds
|
||||
# fi
|
||||
|
||||
# wait
|
||||
|
||||
# SECONDS=$(($(date +%s)-$STARTTIME))
|
||||
# HOURS=$((SECONDS/3600))
|
||||
# SECONDS=$((SECONDS%3600))
|
||||
# MINUTES=$((SECONDS/60))
|
||||
# SECONDS=$((SECONDS%60))
|
||||
# echo "*********$0 finished: $(date) (took $HOURS:$MINUTES:$SECONDS)**************"
|
||||
|
||||
def main():
|
||||
args = parseArgs()
|
||||
devs = build_cmds(args)
|
||||
run_cmds(args, devs)
|
||||
|
||||
##############################################################################
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -42,7 +42,7 @@ SRC = \
|
|||
|
||||
# STATIC ?= -static
|
||||
GITINFO = gitversion.txt
|
||||
HASH=$(shell git describe)
|
||||
HASH=$(shell git rev-parse --verify HEAD)
|
||||
|
||||
OBJ = $(patsubst %.cpp,obj/%.o,$(SRC))
|
||||
#LDFLAGS += -pthread -g -lmcheck $(STATIC)
|
||||
|
@ -70,6 +70,24 @@ endif
|
|||
|
||||
memdebug all: xwrelay rq
|
||||
|
||||
REQUIRED_DEBS = libpq-dev g++ libglib2.0-dev postgresql \
|
||||
|
||||
.PHONY: debcheck debs_install
|
||||
|
||||
debs_install:
|
||||
sudo apt-get install $(REQUIRED_DEBS)
|
||||
|
||||
debcheck:
|
||||
@if which dpkg; then \
|
||||
for DEB in $(REQUIRED_DEBS); do \
|
||||
if ! dpkg -l $$DEB >/dev/null 2>&1; then \
|
||||
echo "$$DEB not installed"; \
|
||||
echo "try running 'make debs_install'"; \
|
||||
break; \
|
||||
fi \
|
||||
done; \
|
||||
fi
|
||||
|
||||
# Manual config in order to place -lpq after the .obj files as
|
||||
# required by something Ubuntu did upgrading natty to oneiric
|
||||
xwrelay: $(OBJ)
|
||||
|
|
|
@ -875,13 +875,13 @@ putNetShort( uint8_t** bufpp, unsigned short s )
|
|||
*bufpp += sizeof(s);
|
||||
}
|
||||
|
||||
void
|
||||
int
|
||||
CookieRef::store_message( HostID dest, const uint8_t* buf,
|
||||
unsigned int len )
|
||||
{
|
||||
logf( XW_LOGVERBOSE0, "%s: storing msg size %d for dest %d", __func__,
|
||||
len, dest );
|
||||
DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len );
|
||||
return DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len );
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1044,6 +1044,7 @@ CookieRef::postCheckAllHere()
|
|||
void
|
||||
CookieRef::postDropDevice( HostID hostID )
|
||||
{
|
||||
logf( XW_LOGINFO, "%s(hostID=%d)", __func__, hostID );
|
||||
CRefEvent evt( XWE_ACKTIMEOUT );
|
||||
evt.u.ack.srcID = hostID;
|
||||
m_eventQueue.push_back( evt );
|
||||
|
@ -1192,21 +1193,16 @@ CookieRef::sendAnyStored( const CRefEvent* evt )
|
|||
}
|
||||
|
||||
typedef struct _StoreData {
|
||||
string connName;
|
||||
HostID dest;
|
||||
uint8_t* buf;
|
||||
int buflen;
|
||||
int msgID;
|
||||
} StoreData;
|
||||
|
||||
void
|
||||
CookieRef::storeNoAck( bool acked, uint32_t packetID, void* data )
|
||||
{
|
||||
StoreData* sdata = (StoreData*)data;
|
||||
if ( !acked ) {
|
||||
DBMgr::Get()->StoreMessage( sdata->connName.c_str(), sdata->dest,
|
||||
sdata->buf, sdata->buflen );
|
||||
if ( acked ) {
|
||||
DBMgr::Get()->RemoveStoredMessages( &sdata->msgID, 1 );
|
||||
}
|
||||
free( sdata->buf );
|
||||
delete sdata;
|
||||
}
|
||||
|
||||
|
@ -1237,17 +1233,13 @@ CookieRef::forward_or_store( const CRefEvent* evt )
|
|||
}
|
||||
|
||||
uint32_t packetID = 0;
|
||||
int msgID = store_message( dest, buf, buflen );
|
||||
if ( (NULL == destAddr)
|
||||
|| !send_with_length( destAddr, dest, buf, buflen, true,
|
||||
&packetID ) ) {
|
||||
store_message( dest, buf, buflen );
|
||||
} else if ( 0 != packetID ) { // sent via UDP
|
||||
} else if ( 0 != msgID && 0 != packetID ) { // sent via UDP
|
||||
StoreData* data = new StoreData;
|
||||
data->connName = m_connName;
|
||||
data->dest = dest;
|
||||
data->buf = (uint8_t*)malloc( buflen );
|
||||
memcpy( data->buf, buf, buflen );
|
||||
data->buflen = buflen;
|
||||
data->msgID = msgID;
|
||||
UDPAckTrack::setOnAck( storeNoAck, packetID, data );
|
||||
}
|
||||
|
||||
|
@ -1376,20 +1368,16 @@ CookieRef::sendAllHere( bool initial )
|
|||
through the vector each time. */
|
||||
HostID dest;
|
||||
for ( dest = 1; dest <= m_nPlayersSought; ++dest ) {
|
||||
bool sent = false;
|
||||
*idLoc = dest; /* write in this target's hostId */
|
||||
|
||||
{
|
||||
RWReadLock rrl( &m_socketsRWLock );
|
||||
HostRec* hr = m_sockets[dest-1];
|
||||
if ( !!hr ) {
|
||||
sent = send_with_length( &hr->m_addr, dest, buf,
|
||||
bufp-buf, true );
|
||||
(void)send_with_length( &hr->m_addr, dest, buf, bufp-buf, true );
|
||||
}
|
||||
}
|
||||
if ( !sent ) {
|
||||
store_message( dest, buf, bufp-buf );
|
||||
}
|
||||
(void)store_message( dest, buf, bufp-buf );
|
||||
}
|
||||
} /* sendAllHere */
|
||||
|
||||
|
|
|
@ -275,8 +275,7 @@ class CookieRef {
|
|||
|
||||
bool notInUse(void) { return m_cid == 0; }
|
||||
|
||||
void store_message( HostID dest, const uint8_t* buf,
|
||||
unsigned int len );
|
||||
int store_message( HostID dest, const uint8_t* buf, unsigned int len );
|
||||
void send_stored_messages( HostID dest, const AddrInfo* addr );
|
||||
|
||||
void printSeeds( const char* caller );
|
||||
|
|
|
@ -337,7 +337,7 @@ CRefMgr::getMakeCookieRef( const char* connName, const char* cookie,
|
|||
} /* getMakeCookieRef */
|
||||
|
||||
CidInfo*
|
||||
CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead )
|
||||
CRefMgr::getMakeCookieRef( const char* const connName, HostID hid, bool* isDead )
|
||||
{
|
||||
CookieRef* cref = NULL;
|
||||
CidInfo* cinfo = NULL;
|
||||
|
@ -347,7 +347,7 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead )
|
|||
int nAlreadyHere = 0;
|
||||
|
||||
for ( ; ; ) { /* for: see comment above */
|
||||
CookieID cid = m_db->FindGame( connName, curCookie, sizeof(curCookie),
|
||||
CookieID cid = m_db->FindGame( connName, hid, curCookie, sizeof(curCookie),
|
||||
&curLangCode, &nPlayersT, &nAlreadyHere,
|
||||
isDead );
|
||||
if ( 0 != cid ) { /* already open */
|
||||
|
@ -375,6 +375,48 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead )
|
|||
return cinfo;
|
||||
}
|
||||
|
||||
CidInfo*
|
||||
CRefMgr::getMakeCookieRef( const AddrInfo::ClientToken clientToken, HostID srcID )
|
||||
{
|
||||
CookieRef* cref = NULL;
|
||||
CidInfo* cinfo = NULL;
|
||||
char curCookie[MAX_INVITE_LEN+1];
|
||||
int curLangCode;
|
||||
int nPlayersT = 0;
|
||||
int nAlreadyHere = 0;
|
||||
|
||||
for ( ; ; ) { /* for: see comment above */
|
||||
char connName[MAX_CONNNAME_LEN+1] = {0};
|
||||
CookieID cid = m_db->FindGame( clientToken, srcID,
|
||||
connName, sizeof(connName),
|
||||
curCookie, sizeof(curCookie),
|
||||
&curLangCode, &nPlayersT, &nAlreadyHere );
|
||||
// &seed );
|
||||
if ( 0 != cid ) { /* already open */
|
||||
cinfo = m_cidlock->Claim( cid );
|
||||
if ( NULL == cinfo->GetRef() ) {
|
||||
m_cidlock->Relinquish( cinfo, true );
|
||||
continue;
|
||||
}
|
||||
} else if ( nPlayersT == 0 ) { /* wasn't in the DB */
|
||||
/* do nothing; insufficient info to fake it */
|
||||
} else {
|
||||
cinfo = m_cidlock->Claim();
|
||||
if ( !m_db->AddCID( connName, cinfo->GetCid() ) ) {
|
||||
m_cidlock->Relinquish( cinfo, true );
|
||||
continue;
|
||||
}
|
||||
logf( XW_LOGINFO, "%s(): added cid???", __func__ );
|
||||
cref = AddNew( curCookie, connName, cinfo->GetCid(), curLangCode,
|
||||
nPlayersT, nAlreadyHere );
|
||||
cinfo->SetRef( cref );
|
||||
}
|
||||
break;
|
||||
}
|
||||
logf( XW_LOGINFO, "%s() => %p", __func__, cinfo );
|
||||
return cinfo;
|
||||
}
|
||||
|
||||
void
|
||||
CRefMgr::RemoveSocketRefs( const AddrInfo* addr )
|
||||
{
|
||||
|
@ -649,10 +691,14 @@ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid,
|
|||
nPlayersS, gameSeed, langCode,
|
||||
wantsPublic || makePublic, &isDead );
|
||||
|
||||
/* If the reconnect doesn't check out, treat it as a connect */
|
||||
/* If the reconnect doesn't check out, treat it as a connect. But
|
||||
preserve the existing hid. If the DB was deleted it's important
|
||||
that devices keep their places (hids) */
|
||||
if ( NULL == cinfo ) {
|
||||
logf( XW_LOGINFO, "%s: taking a second crack", __func__ );
|
||||
m_hid = HOST_ID_NONE;
|
||||
logf( XW_LOGINFO, "%s: taking a second crack; (cur hid: %d)",
|
||||
__func__, hid );
|
||||
assert( m_hid == hid );
|
||||
// m_hid = HOST_ID_NONE; /* wrong; but why was I doing it? */
|
||||
cinfo = m_mgr->getMakeCookieRef( cookie, nPlayersH, nPlayersS,
|
||||
langCode, gameSeed, clientIndx,
|
||||
wantsPublic, makePublic, &m_seenSeed );
|
||||
|
@ -668,13 +714,13 @@ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid,
|
|||
}
|
||||
|
||||
/* ConnName case -- must exist (unless DB record's been removed */
|
||||
SafeCref::SafeCref( const char* const connName )
|
||||
SafeCref::SafeCref( const char* const connName, HostID hid )
|
||||
: m_cinfo( NULL )
|
||||
, m_mgr( CRefMgr::Get() )
|
||||
, m_isValid( false )
|
||||
{
|
||||
bool isDead = false;
|
||||
CidInfo* cinfo = m_mgr->getMakeCookieRef( connName, &isDead );
|
||||
CidInfo* cinfo = m_mgr->getMakeCookieRef( connName, hid, &isDead );
|
||||
if ( NULL != cinfo && NULL != cinfo->GetRef() ) {
|
||||
assert( cinfo->GetCid() == cinfo->GetRef()->GetCid() );
|
||||
m_locked = cinfo->GetRef()->Lock();
|
||||
|
@ -718,6 +764,19 @@ SafeCref::SafeCref( const AddrInfo* addr )
|
|||
}
|
||||
}
|
||||
|
||||
SafeCref::SafeCref( const AddrInfo::ClientToken clientToken, HostID srcID )
|
||||
: m_cinfo( NULL )
|
||||
, m_mgr( CRefMgr::Get() )
|
||||
, m_isValid( false )
|
||||
{
|
||||
CidInfo* cinfo = m_mgr->getMakeCookieRef( clientToken, srcID );
|
||||
if ( NULL != cinfo && NULL != cinfo->GetRef() ) {
|
||||
m_locked = cinfo->GetRef()->Lock();
|
||||
m_cinfo = cinfo;
|
||||
m_isValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
SafeCref::~SafeCref()
|
||||
{
|
||||
if ( m_cinfo != NULL ) {
|
||||
|
|
|
@ -128,7 +128,8 @@ class CRefMgr {
|
|||
int nPlayersS, int seed, int langCode,
|
||||
bool isPublic, bool* isDead );
|
||||
|
||||
CidInfo* getMakeCookieRef( const char* const connName, bool* isDead );
|
||||
CidInfo* getMakeCookieRef( const char* const connName, HostID hid, bool* isDead );
|
||||
CidInfo* getMakeCookieRef( const AddrInfo::ClientToken clientToken, HostID srcID );
|
||||
|
||||
CidInfo* getCookieRef( CookieID cid, bool failOk = false );
|
||||
CidInfo* getCookieRef( const AddrInfo* addr );
|
||||
|
@ -179,9 +180,10 @@ class SafeCref {
|
|||
const AddrInfo* addr, int clientVersion, DevID* devID,
|
||||
int nPlayersH, int nPlayersS, unsigned short gameSeed,
|
||||
int clientIndx, int langCode, bool wantsPublic, bool makePublic );
|
||||
SafeCref( const char* const connName );
|
||||
SafeCref( const char* const connName, HostID hid );
|
||||
SafeCref( CookieID cid, bool failOk = false );
|
||||
SafeCref( const AddrInfo* addr );
|
||||
SafeCref( const AddrInfo::ClientToken clientToken, HostID srcID );
|
||||
/* SafeCref( CookieRef* cref ); */
|
||||
~SafeCref();
|
||||
|
||||
|
|
|
@ -70,20 +70,6 @@ DBMgr::DBMgr()
|
|||
|
||||
pthread_mutex_init( &m_haveNoMessagesMutex, NULL );
|
||||
|
||||
/* Now figure out what the largest cid currently is. There must be a way
|
||||
to get postgres to do this for me.... */
|
||||
/* const char* query = "SELECT cid FROM games ORDER BY cid DESC LIMIT 1"; */
|
||||
/* PGresult* result = PQexec( m_pgconn, query ); */
|
||||
/* if ( 0 == PQntuples( result ) ) { */
|
||||
/* m_nextCID = 1; */
|
||||
/* } else { */
|
||||
/* char* value = PQgetvalue( result, 0, 0 ); */
|
||||
/* m_nextCID = 1 + atoi( value ); */
|
||||
/* } */
|
||||
/* PQclear(result); */
|
||||
/* logf( XW_LOGINFO, "%s: m_nextCID=%d", __func__, m_nextCID ); */
|
||||
|
||||
// I've seen rand returning the same series several times....
|
||||
srand( time( NULL ) );
|
||||
}
|
||||
|
||||
|
@ -107,7 +93,7 @@ DBMgr::AddNew( const char* cookie, const char* connName, CookieID cid,
|
|||
qb.appendQueryf( "INSERT INTO " GAMES_TABLE
|
||||
" (cid, room, connName, nTotal, lang, pub)"
|
||||
" VALUES( $$, $$, $$, $$, $$, $$ )" )
|
||||
.appendParam(cid)
|
||||
.appendParam(cid)
|
||||
.appendParam(cookie)
|
||||
.appendParam(connName)
|
||||
.appendParam(nPlayersT)
|
||||
|
@ -136,7 +122,7 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
|||
{
|
||||
bool found = false;
|
||||
|
||||
const char* fmt = "SELECT cid, room, lang, nPerDevice, dead FROM "
|
||||
const char* fmt = "SELECT cid, room, lang, dead FROM "
|
||||
GAMES_TABLE " WHERE connName = '%s' AND nTotal = %d "
|
||||
"AND %d = seeds[%d] AND 'A' = ack[%d] "
|
||||
;
|
||||
|
@ -148,10 +134,11 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
|||
assert( 1 >= PQntuples( result ) );
|
||||
found = 1 == PQntuples( result );
|
||||
if ( found ) {
|
||||
*cidp = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
||||
*langP = atoi( PQgetvalue( result, 0, 2 ) );
|
||||
*isDead = 't' == PQgetvalue( result, 0, 4 )[0];
|
||||
int col = 0;
|
||||
*cidp = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
*langP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*isDead = 't' == PQgetvalue( result, 0, col++ )[0];
|
||||
}
|
||||
PQclear( result );
|
||||
|
||||
|
@ -160,28 +147,29 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
|||
} /* FindGameFor */
|
||||
|
||||
CookieID
|
||||
DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen,
|
||||
DBMgr::FindGame( const char* connName, HostID hid, char* roomBuf, int roomBufLen,
|
||||
int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead )
|
||||
{
|
||||
CookieID cid = 0;
|
||||
|
||||
const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice, dead FROM "
|
||||
const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice[%d], dead FROM "
|
||||
GAMES_TABLE " WHERE connName = '%s'"
|
||||
// " LIMIT 1"
|
||||
;
|
||||
StrWPF query;
|
||||
query.catf( fmt, connName );
|
||||
query.catf( fmt, hid, connName );
|
||||
logf( XW_LOGINFO, "query: %s", query.c_str() );
|
||||
|
||||
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||
assert( 1 >= PQntuples( result ) );
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
cid = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
||||
*langP = atoi( PQgetvalue( result, 0, 2 ) );
|
||||
*nPlayersTP = atoi( PQgetvalue( result, 0, 3 ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, 4 ) );
|
||||
*isDead = 't' == PQgetvalue( result, 0, 5 )[0];
|
||||
int col = 0;
|
||||
cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( roomBuf, roomBufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
*langP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersTP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*isDead = 't' == PQgetvalue( result, 0, col++ )[0];
|
||||
}
|
||||
PQclear( result );
|
||||
|
||||
|
@ -189,6 +177,40 @@ DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen,
|
|||
return cid;
|
||||
} /* FindGame */
|
||||
|
||||
CookieID
|
||||
DBMgr::FindGame( const AddrInfo::ClientToken clientToken, HostID hid,
|
||||
char* connNameBuf, int connNameBufLen,
|
||||
char* roomBuf, int roomBufLen,
|
||||
int* langP, int* nPlayersTP, int* nPlayersHP )
|
||||
{
|
||||
CookieID cid = 0;
|
||||
const char* fmt = "SELECT cid, room, lang, nTotal, nPerDevice[%d], connname FROM "
|
||||
GAMES_TABLE " WHERE tokens[%d] = %d and NOT dead";
|
||||
// " LIMIT 1"
|
||||
;
|
||||
StrWPF query;
|
||||
query.catf( fmt, hid, hid, clientToken );
|
||||
logf( XW_LOGINFO, "query: %s", query.c_str() );
|
||||
|
||||
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
int col = 0;
|
||||
cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
// room
|
||||
snprintf( roomBuf, roomBufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
// lang
|
||||
*langP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersTP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( connNameBuf, connNameBufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
}
|
||||
PQclear( result );
|
||||
|
||||
logf( XW_LOGINFO, "%s(ct=%d,hid=%d) => %d (connname=%s)", __func__, clientToken,
|
||||
hid, cid, connNameBuf );
|
||||
return cid;
|
||||
}
|
||||
|
||||
bool
|
||||
DBMgr::FindPlayer( DevIDRelay relayID, AddrInfo::ClientToken token,
|
||||
string& connName, HostID* hidp, unsigned short* seed )
|
||||
|
@ -294,11 +316,13 @@ DBMgr::SeenSeed( const char* cookie, unsigned short seed,
|
|||
NULL, NULL, 0 );
|
||||
bool found = 1 == PQntuples( result );
|
||||
if ( found ) {
|
||||
*cid = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
*nPlayersHP = here_less_seed( PQgetvalue( result, 0, 2 ),
|
||||
atoi( PQgetvalue( result, 0, 3 ) ),
|
||||
seed );
|
||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
||||
int col = 0;
|
||||
*cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
|
||||
const char* seeds = PQgetvalue( result, 0, col++ );
|
||||
int perDeviceSum = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersHP = here_less_seed( seeds, perDeviceSum, seed );
|
||||
}
|
||||
PQclear( result );
|
||||
logf( XW_LOGINFO, "%s(%4X)=>%s", __func__, seed, found?"true":"false" );
|
||||
|
@ -333,9 +357,10 @@ DBMgr::FindOpen( const char* cookie, int lang, int nPlayersT, int nPlayersH,
|
|||
NULL, NULL, 0 );
|
||||
CookieID cid = 0;
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
cid = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, 2 ) );
|
||||
int col = 0;
|
||||
cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||
*nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||
/* cid may be 0, but should use game anyway */
|
||||
}
|
||||
PQclear( result );
|
||||
|
@ -699,9 +724,11 @@ DBMgr::RecordSent( const int* msgIDs, int nMsgIDs )
|
|||
if ( PGRES_TUPLES_OK == PQresultStatus( result ) ) {
|
||||
int ntuples = PQntuples( result );
|
||||
for ( int ii = 0; ii < ntuples; ++ii ) {
|
||||
RecordSent( PQgetvalue( result, ii, 0 ),
|
||||
atoi( PQgetvalue( result, ii, 1 ) ),
|
||||
atoi( PQgetvalue( result, ii, 2 ) ) );
|
||||
int col = 0;
|
||||
const char* const connName = PQgetvalue( result, ii, col++ );
|
||||
HostID hid = atoi( PQgetvalue( result, ii, col++ ) );
|
||||
int nBytes = atoi( PQgetvalue( result, ii, col++ ) );
|
||||
RecordSent( connName, hid, nBytes );
|
||||
}
|
||||
}
|
||||
PQclear( result );
|
||||
|
@ -1014,43 +1041,51 @@ DBMgr::CountStoredMessages( DevIDRelay relayID )
|
|||
return getCountWhere( MSGS_TABLE, test );
|
||||
}
|
||||
|
||||
void
|
||||
DBMgr::StoreMessage( DevIDRelay devID, const uint8_t* const buf,
|
||||
int
|
||||
DBMgr::StoreMessage( DevIDRelay destDevID, const uint8_t* const buf,
|
||||
int len )
|
||||
{
|
||||
clearHasNoMessages( devID );
|
||||
int msgID = 0;
|
||||
clearHasNoMessages( destDevID );
|
||||
|
||||
size_t newLen;
|
||||
const char* fmt = "INSERT INTO " MSGS_TABLE " "
|
||||
"(devid, %s, msglen) VALUES(%d, %s'%s', %d)";
|
||||
"(devid, %s, msglen) VALUES(%d, %s'%s', %d) RETURNING id";
|
||||
|
||||
StrWPF query;
|
||||
if ( m_useB64 ) {
|
||||
gchar* b64 = g_base64_encode( buf, len );
|
||||
query.catf( fmt, "msg64", devID, "", b64, len );
|
||||
query.catf( fmt, "msg64", destDevID, "", b64, len );
|
||||
g_free( b64 );
|
||||
} else {
|
||||
uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf,
|
||||
len, &newLen );
|
||||
assert( NULL != bytes );
|
||||
query.catf( fmt, "msg", devID, "E", bytes, len );
|
||||
query.catf( fmt, "msg", destDevID, "E", bytes, len );
|
||||
PQfreemem( bytes );
|
||||
}
|
||||
|
||||
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
|
||||
execSql( query );
|
||||
|
||||
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
msgID = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
}
|
||||
PQclear( result );
|
||||
return msgID;
|
||||
}
|
||||
|
||||
void
|
||||
DBMgr::StoreMessage( const char* const connName, int hid,
|
||||
int
|
||||
DBMgr::StoreMessage( const char* const connName, int destHid,
|
||||
const uint8_t* buf, int len )
|
||||
{
|
||||
clearHasNoMessages( connName, hid );
|
||||
int msgID = 0;
|
||||
clearHasNoMessages( connName, destHid );
|
||||
|
||||
DevIDRelay devID = getDevID( connName, hid );
|
||||
DevIDRelay devID = getDevID( connName, destHid );
|
||||
if ( DEVID_NONE == devID ) {
|
||||
logf( XW_LOGERROR, "%s: warning: devid not found for connName=%s, "
|
||||
"hid=%d", __func__, connName, hid );
|
||||
"hid=%d", __func__, connName, destHid );
|
||||
} else {
|
||||
clearHasNoMessages( devID );
|
||||
}
|
||||
|
@ -1066,7 +1101,7 @@ DBMgr::StoreMessage( const char* const connName, int hid,
|
|||
StrWPF query;
|
||||
if ( m_useB64 ) {
|
||||
gchar* b64 = g_base64_encode( buf, len );
|
||||
query.catf( fmt, "msg64", connName, hid, devID, hid, connName,
|
||||
query.catf( fmt, "msg64", connName, destHid, devID, destHid, connName,
|
||||
"", b64, len );
|
||||
|
||||
query.catf( " WHERE NOT EXISTS (SELECT 1 FROM " MSGS_TABLE
|
||||
|
@ -1074,20 +1109,28 @@ DBMgr::StoreMessage( const char* const connName, int hid,
|
|||
#ifdef HAVE_STIME
|
||||
" AND stime='epoch'"
|
||||
#endif
|
||||
" );", connName, hid, b64 );
|
||||
" )", connName, destHid, b64 );
|
||||
g_free( b64 );
|
||||
} else {
|
||||
uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf,
|
||||
len, &newLen );
|
||||
assert( NULL != bytes );
|
||||
|
||||
query.catf( fmt, "msg", connName, hid, devID, hid, connName,
|
||||
query.catf( fmt, "msg", connName, destHid, devID, destHid, connName,
|
||||
"E", bytes, len );
|
||||
PQfreemem( bytes );
|
||||
}
|
||||
query.catf(" RETURNING id;");
|
||||
|
||||
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
|
||||
execSql( query );
|
||||
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||
if ( 1 == PQntuples( result ) ) {
|
||||
msgID = atoi( PQgetvalue( result, 0, 0 ) );
|
||||
} else {
|
||||
logf( XW_LOGINFO, "Not stored; duplicate?" );
|
||||
}
|
||||
PQclear( result );
|
||||
return msgID;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -75,9 +75,13 @@ class DBMgr {
|
|||
bool FindRelayIDFor( const char* connName, HostID hid, unsigned short seed,
|
||||
const DevID* host, DevIDRelay* devID );
|
||||
|
||||
CookieID FindGame( const char* connName, char* cookieBuf, int bufLen,
|
||||
CookieID FindGame( const char* connName, HostID hid, char* cookieBuf, int bufLen,
|
||||
int* langP, int* nPlayersTP, int* nPlayersHP,
|
||||
bool* isDead );
|
||||
CookieID FindGame( const AddrInfo::ClientToken clientToken, HostID hid,
|
||||
char* connNameBuf, int connNameBufLen,
|
||||
char* cookieBuf, int cookieBufLen,
|
||||
int* langP, int* nPlayersTP, int* nPlayersHP );
|
||||
|
||||
bool FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
||||
unsigned short seed, HostID hid,
|
||||
|
@ -137,9 +141,10 @@ class DBMgr {
|
|||
/* message storage -- different DB */
|
||||
int CountStoredMessages( const char* const connName );
|
||||
int CountStoredMessages( DevIDRelay relayID );
|
||||
void StoreMessage( DevIDRelay relayID, const uint8_t* const buf, int len );
|
||||
void StoreMessage( const char* const connName, int hid,
|
||||
const uint8_t* const buf, int len );
|
||||
int StoreMessage( DevIDRelay destRelayID, const uint8_t* const buf,
|
||||
int len );
|
||||
int StoreMessage( const char* const connName, int destHid,
|
||||
const uint8_t* const buf, int len );
|
||||
void GetStoredMessages( DevIDRelay relayID, vector<MsgInfo>& msgs );
|
||||
void GetStoredMessages( const char* const connName, HostID hid,
|
||||
vector<DBMgr::MsgInfo>& msgs );
|
||||
|
@ -170,6 +175,7 @@ class DBMgr {
|
|||
int clientVersion, const char* const model,
|
||||
const char* const osVers, DevIDRelay relayID );
|
||||
|
||||
|
||||
PGconn* getThreadConn( void );
|
||||
void clearThreadConn();
|
||||
|
||||
|
|
250
xwords4/relay/scripts/relay.py
Executable file
250
xwords4/relay/scripts/relay.py
Executable file
|
@ -0,0 +1,250 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import base64, json, mod_python, socket, struct, sys
|
||||
import psycopg2, random
|
||||
|
||||
PROTOCOL_VERSION = 0
|
||||
PRX_DEVICE_GONE = 3
|
||||
PRX_GET_MSGS = 4
|
||||
|
||||
# try:
|
||||
# from mod_python import apache
|
||||
# apacheAvailable = True
|
||||
# except ImportError:
|
||||
# apacheAvailable = False
|
||||
|
||||
# Joining a game. Basic idea is you have stuff to match on (room,
|
||||
# number in game, language) and when somebody wants to join you add to
|
||||
# an existing matching game if there's space otherwise create a new
|
||||
# one. Problems are the unreliablity of transport: if you give a space
|
||||
# and the device doesn't get the message you can't hold it forever. So
|
||||
# device provides a seed that holds the space. If it asks again for a
|
||||
# space with the same seed it gets the same space. If it never asks
|
||||
# again (app deleted, say), the space needs eventually to be given to
|
||||
# somebody else. I think that's done by adding a timestamp array and
|
||||
# treating the space as available if TIME has expired. Need to think
|
||||
# about this: what if app fails to ACK for TIME, then returns with
|
||||
# seed to find it given away. Let's do a 30 minute reservation for
|
||||
# now? [Note: much of this is PENDING]
|
||||
|
||||
def join(req, devID, room, seed, hid = 0, lang = 1, nInGame = 2, nHere = 1, inviteID = None):
|
||||
assert hid <= 4
|
||||
seed = int(seed)
|
||||
assert seed != 0
|
||||
nInGame = int(nInGame)
|
||||
nHere = int(nHere)
|
||||
assert nHere <= nInGame
|
||||
assert nInGame <= 4
|
||||
|
||||
devID = int(devID, 16)
|
||||
|
||||
connname = None
|
||||
logs = [] # for debugging
|
||||
# logs.append('vers: ' + platform.python_version())
|
||||
|
||||
con = psycopg2.connect(database='xwgames')
|
||||
cur = con.cursor()
|
||||
# cur.execute('LOCK TABLE games IN ACCESS EXCLUSIVE MODE')
|
||||
|
||||
# First see if there's a game with a space for me. Must match on
|
||||
# room, lang and size. Must have room OR must have already given a
|
||||
# spot for a seed equal to mine, in which case I get it
|
||||
# back. Assumption is I didn't ack in time.
|
||||
|
||||
query = "SELECT connname, seeds, nperdevice FROM games "
|
||||
query += "WHERE lang = %s AND nTotal = %s AND room = %s "
|
||||
query += "AND (njoined + %s <= ntotal OR %s = ANY(seeds)) "
|
||||
query += "LIMIT 1"
|
||||
cur.execute( query, (lang, nInGame, room, nHere, seed))
|
||||
for row in cur:
|
||||
(connname, seeds, nperdevice) = row
|
||||
print('found', connname, seeds, nperdevice)
|
||||
break # should be only one!
|
||||
|
||||
# If we've found a match, we either need to UPDATE or, if the
|
||||
# seeds match, remind the caller of where he belongs. If a hid's
|
||||
# been specified, we honor it by updating if the slot's available;
|
||||
# otherwise a new game has to be created.
|
||||
if connname:
|
||||
if seed in seeds and nHere == nperdevice[seeds.index(seed)]:
|
||||
hid = seeds.index(seed) + 1
|
||||
print('resusing seed case; outta here!')
|
||||
else:
|
||||
if hid == 0:
|
||||
# Any gaps? Assign it
|
||||
if None in seeds:
|
||||
hid = seeds.index(None) + 1
|
||||
else:
|
||||
hid = len(seeds) + 1
|
||||
print('set hid to', hid, 'based on ', seeds)
|
||||
else:
|
||||
print('hid already', hid)
|
||||
query = "UPDATE games SET njoined = njoined + %s, "
|
||||
query += "devids[%d] = %%s, " % hid
|
||||
query += "seeds[%d] = %%s, " % hid
|
||||
query += "jtimes[%d] = 'now', " % hid
|
||||
query += "nperdevice[%d] = %%s " % hid
|
||||
query += "WHERE connname = %s "
|
||||
print(query)
|
||||
params = (nHere, devID, seed, nHere, connname)
|
||||
cur.execute(query, params)
|
||||
|
||||
# If nothing was found, add a new game and add me. Honor my hid
|
||||
# preference if specified
|
||||
if not connname:
|
||||
# This requires python3, which likely requires mod_wsgi
|
||||
# ts = datetime.datetime.utcnow().timestamp()
|
||||
# connname = '%s:%d:1' % (xwconfig.k_HOSTNAME, int(ts * 1000))
|
||||
connname = '%s:%d:1' % (xwconfig.k_HOSTNAME, random.randint(0, 10000000000))
|
||||
useHid = hid == 0 and 1 or hid
|
||||
print('not found case; inserting using hid:', useHid)
|
||||
query = "INSERT INTO games (connname, room, lang, ntotal, njoined, " + \
|
||||
"devids[%d], seeds[%d], jtimes[%d], nperdevice[%d]) " % (4 * (useHid,))
|
||||
query += "VALUES (%s, %s, %s, %s, %s, %s, %s, 'now', %s) "
|
||||
query += "RETURNING connname, array_length(seeds,1); "
|
||||
cur.execute(query, (connname, room, lang, nInGame, nHere, devID, seed, nHere))
|
||||
for row in cur:
|
||||
connname, gothid = row
|
||||
break
|
||||
if hid == 0: hid = gothid
|
||||
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
result = {'connname': connname, 'hid' : hid, 'log' : ':'.join(logs)}
|
||||
|
||||
return json.dumps(result)
|
||||
|
||||
def kill(req, params):
|
||||
print(params)
|
||||
params = json.loads(params)
|
||||
count = len(params)
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('127.0.0.1', 10998))
|
||||
|
||||
header = struct.Struct('!BBh')
|
||||
strLens = 0
|
||||
for ii in range(count):
|
||||
strLens += len(params[ii]['relayID']) + 1
|
||||
size = header.size + (2*count) + strLens
|
||||
sock.send(struct.Struct('!h').pack(size))
|
||||
sock.send(header.pack(PROTOCOL_VERSION, PRX_DEVICE_GONE, count))
|
||||
|
||||
for ii in range(count):
|
||||
elem = params[ii]
|
||||
asBytes = bytes(elem['relayID'])
|
||||
sock.send(struct.Struct('!H%dsc' % (len(asBytes))).pack(elem['seed'], asBytes, '\n'))
|
||||
sock.close()
|
||||
|
||||
result = {'err': 0}
|
||||
return json.dumps(result)
|
||||
|
||||
# winds up in handle_udp_packet() in xwrelay.cpp
|
||||
def post(req, params):
|
||||
err = 'none'
|
||||
params = json.loads(params)
|
||||
data = params['data']
|
||||
timeoutSecs = 'timeoutSecs' in params and params['timeoutSecs'] or 1.0
|
||||
binData = [base64.b64decode(datum) for datum in data]
|
||||
|
||||
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
udpSock.settimeout(float(timeoutSecs)) # seconds
|
||||
addr = ("127.0.0.1", 10997)
|
||||
for binDatum in binData:
|
||||
udpSock.sendto(binDatum, addr)
|
||||
|
||||
responses = []
|
||||
while True:
|
||||
try:
|
||||
data, server = udpSock.recvfrom(1024)
|
||||
responses.append(base64.b64encode(data))
|
||||
except socket.timeout:
|
||||
#If data is not received back from server, print it has timed out
|
||||
err = 'timeout'
|
||||
break
|
||||
|
||||
result = {'err' : err, 'data' : responses}
|
||||
return json.dumps(result)
|
||||
|
||||
def query(req, params):
|
||||
print('params', params)
|
||||
params = json.loads(params)
|
||||
ids = params['ids']
|
||||
timeoutSecs = 'timeoutSecs' in params and float(params['timeoutSecs']) or 2.0
|
||||
|
||||
idsLen = 0
|
||||
for id in ids: idsLen += len(id)
|
||||
|
||||
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
tcpSock.settimeout(timeoutSecs)
|
||||
tcpSock.connect(('127.0.0.1', 10998))
|
||||
|
||||
lenShort = 2 + idsLen + len(ids) + 2
|
||||
print(lenShort, PROTOCOL_VERSION, PRX_GET_MSGS, len(ids))
|
||||
header = struct.Struct('!hBBh')
|
||||
assert header.size == 6
|
||||
tcpSock.send(header.pack(lenShort, PROTOCOL_VERSION, PRX_GET_MSGS, len(ids)))
|
||||
|
||||
for id in ids: tcpSock.send(id + '\n')
|
||||
|
||||
msgsLists = {}
|
||||
try:
|
||||
shortUnpacker = struct.Struct('!H')
|
||||
resLen, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) # not getting all bytes
|
||||
nameCount, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size))
|
||||
resLen -= shortUnpacker.size
|
||||
print('resLen:', resLen, 'nameCount:', nameCount)
|
||||
if nameCount == len(ids) and resLen > 0:
|
||||
print('nameCount', nameCount)
|
||||
for ii in range(nameCount):
|
||||
perGame = []
|
||||
countsThisGame, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size)) # problem
|
||||
print('countsThisGame:', countsThisGame)
|
||||
for jj in range(countsThisGame):
|
||||
msgLen, = shortUnpacker.unpack(tcpSock.recv(shortUnpacker.size))
|
||||
print('msgLen:', msgLen)
|
||||
msgs = []
|
||||
if msgLen > 0:
|
||||
msg = tcpSock.recv(msgLen)
|
||||
print('msg len:', len(msg))
|
||||
msg = base64.b64encode(msg)
|
||||
msgs.append(msg)
|
||||
perGame.append(msgs)
|
||||
msgsLists[ids[ii]] = perGame
|
||||
except:
|
||||
None
|
||||
|
||||
return json.dumps(msgsLists)
|
||||
|
||||
def main():
|
||||
result = None
|
||||
if len(sys.argv) > 1:
|
||||
cmd = sys.argv[1]
|
||||
args = sys.argv[2:]
|
||||
if cmd == 'query' and len(args) > 0:
|
||||
result = query(None, json.dumps({'ids':args}))
|
||||
elif cmd == 'post':
|
||||
# Params = { 'data' : 'V2VkIE9jdCAxOCAwNjowNDo0OCBQRFQgMjAxNwo=' }
|
||||
# params = json.dumps(params)
|
||||
# print(post(None, params))
|
||||
pass
|
||||
elif cmd == 'join':
|
||||
if len(args) == 6:
|
||||
result = join(None, 1, args[0], int(args[1]), int(args[2]), int(args[3]), int(args[4]), int(args[5]))
|
||||
elif cmd == 'kill':
|
||||
result = kill( None, json.dumps([{'relayID': args[0], 'seed':int(args[1])}]) )
|
||||
|
||||
if result:
|
||||
print '->', result
|
||||
else:
|
||||
print 'USAGE: query [connname/hid]*'
|
||||
print ' join <roomName> <seed> <hid> <lang> <nTotal> <nHere>'
|
||||
print ' query [connname/hid]*'
|
||||
# print ' post '
|
||||
print ' kill <relayID> <seed>'
|
||||
print ' join <roomName> <seed> <hid> <lang> <nTotal> <nHere>'
|
||||
|
||||
##############################################################################
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -54,13 +54,14 @@ echo "; relay pid[s]: $(pidof xwrelay)"
|
|||
echo "Row count:" $(psql -t xwgames -c "select count(*) FROM games $QUERY;")
|
||||
|
||||
# Games
|
||||
echo "SELECT dead as d,connname,cid,room,lang as lg,clntVers as cv ,ntotal as t,nperdevice as nPerDev,nsents as snts, seeds,devids,tokens,ack, mtimes "\
|
||||
echo "SELECT dead as d,connname,cid,room,lang as lg,clntVers as cv ,ntotal as t,nperdevice as npd,nsents as snts, seeds,devids,tokens,ack, mtimes "\
|
||||
"FROM games $QUERY ORDER BY NOT dead, ctime DESC LIMIT $LIMIT;" \
|
||||
| psql xwgames
|
||||
|
||||
# Messages
|
||||
echo "SELECT * "\
|
||||
"FROM msgs WHERE connname IN (SELECT connname from games $QUERY) "\
|
||||
echo "Unack'd msgs count:" $(psql -t xwgames -c "select count(*) FROM msgs where stime = 'epoch' AND connname IN (SELECT connname from games $QUERY);")
|
||||
echo "SELECT id,connName,hid as h,token,ctime,stime,devid,msg64 "\
|
||||
"FROM msgs WHERE stime = 'epoch' AND connname IN (SELECT connname from games $QUERY) "\
|
||||
"ORDER BY ctime DESC, connname LIMIT $LIMIT;" \
|
||||
| psql xwgames
|
||||
|
||||
|
|
|
@ -550,18 +550,18 @@ assemble_packet( vector<uint8_t>& packet, uint32_t* packetIDP, XWRelayReg cmd,
|
|||
}
|
||||
|
||||
#ifdef LOG_UDP_PACKETS
|
||||
gsize size = 0;
|
||||
gint state = 0;
|
||||
gint save = 0;
|
||||
gchar out[1024];
|
||||
for ( unsigned int ii = 0; ii < iocount; ++ii ) {
|
||||
size += g_base64_encode_step( (const guchar*)vec[ii].iov_base,
|
||||
vec[ii].iov_len,
|
||||
FALSE, &out[size], &state, &save );
|
||||
}
|
||||
size += g_base64_encode_close( FALSE, &out[size], &state, &save );
|
||||
assert( size < sizeof(out) );
|
||||
out[size] = '\0';
|
||||
// gsize size = 0;
|
||||
// gint state = 0;
|
||||
// gint save = 0;
|
||||
// gchar out[1024];
|
||||
// for ( unsigned int ii = 0; ii < iocount; ++ii ) {
|
||||
// size += g_base64_encode_step( (const guchar*)vec[ii].iov_base,
|
||||
// vec[ii].iov_len,
|
||||
// FALSE, &out[size], &state, &save );
|
||||
// }
|
||||
// size += g_base64_encode_close( FALSE, &out[size], &state, &save );
|
||||
// assert( size < sizeof(out) );
|
||||
// out[size] = '\0';
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -640,8 +640,10 @@ send_via_udp_impl( int sock, const struct sockaddr* dest_addr,
|
|||
#ifdef LOG_UDP_PACKETS
|
||||
gchar* b64 = g_base64_encode( (uint8_t*)dest_addr,
|
||||
sizeof(*dest_addr) );
|
||||
gchar* out = g_base64_encode( packet.data(), packet.size() );
|
||||
logf( XW_LOGINFO, "%s()=>%d; addr='%s'; msg='%s'", __func__, nSent,
|
||||
b64, out );
|
||||
g_free( out );
|
||||
g_free( b64 );
|
||||
#else
|
||||
logf( XW_LOGINFO, "%s()=>%d", __func__, nSent );
|
||||
|
@ -760,15 +762,19 @@ send_havemsgs( const AddrInfo* addr )
|
|||
|
||||
class MsgClosure {
|
||||
public:
|
||||
MsgClosure( DevIDRelay devid, const vector<uint8_t>* packet,
|
||||
OnMsgAckProc proc, void* procClosure )
|
||||
MsgClosure( DevIDRelay dest, const vector<uint8_t>* packet,
|
||||
int msgID, OnMsgAckProc proc, void* procClosure )
|
||||
{
|
||||
m_devid = devid;
|
||||
assert(m_msgID != 0);
|
||||
m_destDevID = dest;
|
||||
m_packet = *packet;
|
||||
m_proc = proc;
|
||||
m_procClosure = procClosure;
|
||||
m_msgID = msgID;
|
||||
}
|
||||
DevIDRelay m_devid;
|
||||
int getMsgID() { return m_msgID; }
|
||||
int m_msgID;
|
||||
DevIDRelay m_destDevID;
|
||||
vector<uint8_t> m_packet;
|
||||
OnMsgAckProc m_proc;
|
||||
void* m_procClosure;
|
||||
|
@ -778,22 +784,29 @@ static void
|
|||
onPostedMsgAcked( bool acked, uint32_t packetID, void* data )
|
||||
{
|
||||
MsgClosure* mc = (MsgClosure*)data;
|
||||
if ( !acked ) {
|
||||
DBMgr::Get()->StoreMessage( mc->m_devid, mc->m_packet.data(),
|
||||
mc->m_packet.size() );
|
||||
int msgID = mc->getMsgID();
|
||||
if ( acked ) {
|
||||
DBMgr::Get()->RemoveStoredMessages( &msgID, 1 );
|
||||
} else {
|
||||
assert( msgID != 0 );
|
||||
// So we only store after ack fails? Change that!!!
|
||||
// DBMgr::Get()->StoreMessage( mc->m_destDevID, mc->m_packet.data(),
|
||||
// mc->m_packet.size() );
|
||||
}
|
||||
if ( NULL != mc->m_proc ) {
|
||||
(*mc->m_proc)( acked, mc->m_devid, packetID, mc->m_procClosure );
|
||||
(*mc->m_proc)( acked, mc->m_destDevID, packetID, mc->m_procClosure );
|
||||
}
|
||||
delete mc;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
post_or_store( DevIDRelay devid, vector<uint8_t>& packet, uint32_t packetID,
|
||||
post_or_store( DevIDRelay destDevID, vector<uint8_t>& packet, uint32_t packetID,
|
||||
OnMsgAckProc proc, void* procClosure )
|
||||
{
|
||||
const AddrInfo::AddrUnion* addru = DevMgr::Get()->get( devid );
|
||||
int msgID = DBMgr::Get()->StoreMessage( destDevID, packet.data(), packet.size() );
|
||||
|
||||
const AddrInfo::AddrUnion* addru = DevMgr::Get()->get( destDevID );
|
||||
bool canSendNow = !!addru;
|
||||
|
||||
bool sent = false;
|
||||
|
@ -804,21 +817,18 @@ post_or_store( DevIDRelay devid, vector<uint8_t>& packet, uint32_t packetID,
|
|||
if ( get_addr_info_if( &addr, &sock, &dest_addr ) ) {
|
||||
sent = 0 < send_packet_via_udp_impl( packet, sock, dest_addr );
|
||||
|
||||
if ( sent ) {
|
||||
MsgClosure* mc = new MsgClosure( devid, &packet,
|
||||
if ( sent && msgID != 0 ) {
|
||||
MsgClosure* mc = new MsgClosure( destDevID, &packet, msgID,
|
||||
proc, procClosure );
|
||||
UDPAckTrack::setOnAck( onPostedMsgAcked, packetID, (void*)mc );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !sent ) {
|
||||
DBMgr::Get()->StoreMessage( devid, packet.data(), packet.size() );
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool
|
||||
post_message( DevIDRelay devid, const char* message, OnMsgAckProc proc,
|
||||
post_message( DevIDRelay destDevID, const char* message, OnMsgAckProc proc,
|
||||
void* procClosure )
|
||||
{
|
||||
vector<uint8_t> packet;
|
||||
|
@ -830,7 +840,7 @@ post_message( DevIDRelay devid, const char* message, OnMsgAckProc proc,
|
|||
assemble_packet( packet, &packetID, XWPDEV_ALERT, lenbuf, lenlen,
|
||||
message, len, NULL );
|
||||
|
||||
return post_or_store( devid, packet, packetID, proc, procClosure );
|
||||
return post_or_store( destDevID, packet, packetID, proc, procClosure );
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -988,13 +998,13 @@ processReconnect( const uint8_t* bufp, int bufLen, const AddrInfo* addr )
|
|||
} /* processReconnect */
|
||||
|
||||
static bool
|
||||
processAck( const uint8_t* bufp, int bufLen, const AddrInfo* addr )
|
||||
processAck( const uint8_t* bufp, int bufLen, AddrInfo::ClientToken clientToken )
|
||||
{
|
||||
bool success = false;
|
||||
const uint8_t* end = bufp + bufLen;
|
||||
HostID srcID;
|
||||
if ( getNetByte( &bufp, end, &srcID ) ) {
|
||||
SafeCref scr( addr );
|
||||
SafeCref scr( clientToken, srcID );
|
||||
success = scr.HandleAck( srcID );
|
||||
}
|
||||
return success;
|
||||
|
@ -1084,7 +1094,8 @@ forwardMessage( const uint8_t* buf, int buflen, const AddrInfo* addr )
|
|||
} /* forwardMessage */
|
||||
|
||||
static bool
|
||||
processMessage( const uint8_t* buf, int bufLen, const AddrInfo* addr )
|
||||
processMessage( const uint8_t* buf, int bufLen, const AddrInfo* addr,
|
||||
AddrInfo::ClientToken clientToken )
|
||||
{
|
||||
bool success = false; /* default is failure */
|
||||
XWRELAY_Cmd cmd = *buf;
|
||||
|
@ -1099,7 +1110,11 @@ processMessage( const uint8_t* buf, int bufLen, const AddrInfo* addr )
|
|||
success = processReconnect( buf+1, bufLen-1, addr );
|
||||
break;
|
||||
case XWRELAY_ACK:
|
||||
success = processAck( buf+1, bufLen-1, addr );
|
||||
if ( clientToken != 0 ) {
|
||||
success = processAck( buf+1, bufLen-1, clientToken );
|
||||
} else {
|
||||
logf( XW_LOGERROR, "%s(): null client token", __func__ );
|
||||
}
|
||||
break;
|
||||
case XWRELAY_GAME_DISCONNECT:
|
||||
success = processDisconnect( buf+1, bufLen-1, addr );
|
||||
|
@ -1334,6 +1349,9 @@ handleMsgsMsg( const AddrInfo* addr, bool sendFull,
|
|||
logf( XW_LOGVERBOSE0, "%s: wrote %d bytes", __func__, nwritten );
|
||||
if ( sendFull && nwritten >= 0 && (size_t)nwritten == out.size() ) {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@ -1438,7 +1456,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const uint8_t* bufp,
|
|||
}
|
||||
unsigned short nMsgs;
|
||||
if ( getNetShort( &bufp, end, &nMsgs ) ) {
|
||||
SafeCref scr( connName );
|
||||
SafeCref scr( connName, hid );
|
||||
while ( scr.IsValid() && nMsgs-- > 0 ) {
|
||||
unsigned short len;
|
||||
if ( getNetShort( &bufp, end, &len ) ) {
|
||||
|
@ -1460,7 +1478,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const uint8_t* bufp,
|
|||
static void
|
||||
game_thread_proc( UdpThreadClosure* utc )
|
||||
{
|
||||
if ( !processMessage( utc->buf(), utc->len(), utc->addr() ) ) {
|
||||
if ( !processMessage( utc->buf(), utc->len(), utc->addr(), 0 ) ) {
|
||||
XWThreadPool::GetTPool()->CloseSocket( utc->addr() );
|
||||
}
|
||||
}
|
||||
|
@ -1528,7 +1546,7 @@ proxy_thread_proc( UdpThreadClosure* utc )
|
|||
sizeof( connName ), &hid ) ) {
|
||||
break;
|
||||
}
|
||||
SafeCref scr( connName );
|
||||
SafeCref scr( connName, hid );
|
||||
scr.DeviceGone( hid, seed );
|
||||
}
|
||||
}
|
||||
|
@ -1748,7 +1766,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
|||
clientToken = ntohl( clientToken );
|
||||
if ( AddrInfo::NULL_TOKEN != clientToken ) {
|
||||
AddrInfo addr( g_udpsock, clientToken, utc->saddr() );
|
||||
(void)processMessage( ptr, end - ptr, &addr );
|
||||
(void)processMessage( ptr, end - ptr, &addr, clientToken );
|
||||
} else {
|
||||
logf( XW_LOGERROR, "%s: dropping packet with token of 0",
|
||||
__func__ );
|
||||
|
@ -1766,7 +1784,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
|||
logf( XW_LOGERROR, "parse failed!!!" );
|
||||
break;
|
||||
}
|
||||
SafeCref scr( connName );
|
||||
SafeCref scr( connName, hid );
|
||||
if ( scr.IsValid() ) {
|
||||
AddrInfo addr( g_udpsock, clientToken, utc->saddr() );
|
||||
handlePutMessage( scr, hid, &addr, end - ptr, &ptr, end );
|
||||
|
@ -1833,7 +1851,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
|||
string connName;
|
||||
if ( DBMgr::Get()->FindPlayer( devID.asRelayID(), clientToken,
|
||||
connName, &hid, &seed ) ) {
|
||||
SafeCref scr( connName.c_str() );
|
||||
SafeCref scr( connName.c_str(), hid );
|
||||
scr.DeviceGone( hid, seed );
|
||||
}
|
||||
}
|
||||
|
@ -1980,7 +1998,7 @@ maint_str_loop( int udpsock, const char* str )
|
|||
} // maint_str_loop
|
||||
|
||||
static uint32_t
|
||||
getIPAddr( void )
|
||||
getUDPIPAddr( void )
|
||||
{
|
||||
uint32_t result = INADDR_ANY;
|
||||
char iface[16] = {0};
|
||||
|
@ -2215,7 +2233,7 @@ main( int argc, char** argv )
|
|||
struct sockaddr_in saddr;
|
||||
g_udpsock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
|
||||
saddr.sin_family = PF_INET;
|
||||
saddr.sin_addr.s_addr = getIPAddr();
|
||||
saddr.sin_addr.s_addr = getUDPIPAddr();
|
||||
saddr.sin_port = htons(udpport);
|
||||
int err = bind( g_udpsock, (struct sockaddr*)&saddr, sizeof(saddr) );
|
||||
if ( 0 == err ) {
|
||||
|
|
|
@ -14,7 +14,7 @@ LOGFILE=/tmp/xwrelay_log_$$.txt
|
|||
date > $LOGFILE
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 start | stop | restart | mkdb"
|
||||
echo "usage: $0 start | stop | restart | mkdb | debs_install"
|
||||
}
|
||||
|
||||
make_db() {
|
||||
|
@ -28,7 +28,7 @@ make_db() {
|
|||
exit 1
|
||||
fi
|
||||
createdb $DBNAME
|
||||
cat | psql $DBNAME --file - <<EOF
|
||||
cat <<-EOF | psql $DBNAME --file -
|
||||
create or replace function sum_array( DECIMAL [] )
|
||||
returns decimal
|
||||
as \$\$
|
||||
|
@ -40,7 +40,7 @@ from generate_series(
|
|||
\$\$ language sql immutable;
|
||||
EOF
|
||||
|
||||
cat | psql $DBNAME --file - <<EOF
|
||||
cat <<-EOF | psql $DBNAME --file -
|
||||
CREATE TABLE games (
|
||||
cid integer
|
||||
,room VARCHAR(32)
|
||||
|
@ -62,7 +62,7 @@ cid integer
|
|||
);
|
||||
EOF
|
||||
|
||||
cat | psql $DBNAME --file - <<EOF
|
||||
cat <<-EOF | psql $DBNAME --file -
|
||||
CREATE TABLE msgs (
|
||||
id SERIAL
|
||||
,connName VARCHAR(64)
|
||||
|
@ -78,7 +78,7 @@ id SERIAL
|
|||
);
|
||||
EOF
|
||||
|
||||
cat | psql $DBNAME --file - <<EOF
|
||||
cat <<-EOF | psql $DBNAME --file -
|
||||
CREATE TABLE devices (
|
||||
id INTEGER UNIQUE PRIMARY KEY
|
||||
,devTypes INTEGER[]
|
||||
|
@ -114,6 +114,10 @@ do_start() {
|
|||
fi
|
||||
}
|
||||
|
||||
install_debs() {
|
||||
sudo apt-get install postgresql-client postgresql
|
||||
}
|
||||
|
||||
case $1 in
|
||||
|
||||
stop)
|
||||
|
@ -149,6 +153,9 @@ case $1 in
|
|||
make_db
|
||||
;;
|
||||
|
||||
debs_install)
|
||||
install_debs
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 0
|
||||
|
|
|
@ -52,8 +52,8 @@ void send_havemsgs( const AddrInfo* addr );
|
|||
|
||||
typedef void (*OnMsgAckProc)( bool acked, DevIDRelay devid, uint32_t packetID,
|
||||
void* data );
|
||||
bool post_message( DevIDRelay devid, const char* message, OnMsgAckProc proc,
|
||||
void* data );
|
||||
bool post_message( DevIDRelay destDevID, const char* message,
|
||||
OnMsgAckProc proc, void* data );
|
||||
void post_upgrade( DevIDRelay devid );
|
||||
|
||||
time_t uptime(void);
|
||||
|
|
Loading…
Reference in a new issue