mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-02-04 20:46:28 +01:00
Merge remote-tracking branch 'origin/android_translate' into android_translate
This commit is contained in:
commit
6ffd4d4a37
77 changed files with 2168 additions and 437 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 INITIAL_CLIENT_VERS = 8
|
||||||
def VERSION_CODE_BASE = 121
|
def VERSION_CODE_BASE = 126
|
||||||
def VERSION_NAME = '4.4.125'
|
def VERSION_NAME = '4.4.130'
|
||||||
def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY")
|
def FABRIC_API_KEY = System.getenv("FABRIC_API_KEY")
|
||||||
|
def GCM_SENDER_ID = System.getenv("GCM_SENDER_ID")
|
||||||
|
def BUILD_INFO_NAME = "build-info.txt"
|
||||||
|
|
||||||
boolean forFDroid = hasProperty('forFDroid')
|
boolean forFDroid = hasProperty('forFDroid')
|
||||||
|
|
||||||
|
@ -37,8 +39,6 @@ android {
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
// renameArtifact(variant)
|
// renameArtifact(variant)
|
||||||
// variant.buildConfigField "String", "FIELD_NAME", "\"my String\""
|
// 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\""
|
variant.buildConfigField "String", "FABRIC_API_KEY", "\"$FABRIC_API_KEY\""
|
||||||
|
|
||||||
resValue "string", "git_rev", "$GITREV"
|
resValue "string", "git_rev", "$GITREV"
|
||||||
|
@ -51,9 +51,6 @@ android {
|
||||||
// FIX ME
|
// FIX ME
|
||||||
variant.buildConfigField "String", "STRINGS_HASH", "\"00000\""
|
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 "short", "CLIENT_VERS_RELAY", "$INITIAL_CLIENT_VERS"
|
||||||
|
|
||||||
variant.buildConfigField "boolean", "FOR_FDROID", "$forFDroid"
|
variant.buildConfigField "boolean", "FOR_FDROID", "$forFDroid"
|
||||||
|
@ -61,6 +58,10 @@ android {
|
||||||
|
|
||||||
flavorDimensions "variant"//, "abi"
|
flavorDimensions "variant"//, "abi"
|
||||||
productFlavors {
|
productFlavors {
|
||||||
|
all {
|
||||||
|
buildConfigField "String", "BUILD_INFO_NAME", "\"${BUILD_INFO_NAME}\""
|
||||||
|
}
|
||||||
|
|
||||||
xw4 {
|
xw4 {
|
||||||
dimension "variant"
|
dimension "variant"
|
||||||
applicationId "org.eehouse.android.xw4"
|
applicationId "org.eehouse.android.xw4"
|
||||||
|
@ -70,6 +71,8 @@ android {
|
||||||
resValue "string", "invite_prefix", "/and/"
|
resValue "string", "invite_prefix", "/and/"
|
||||||
buildConfigField "boolean", "WIDIR_ENABLED", "false"
|
buildConfigField "boolean", "WIDIR_ENABLED", "false"
|
||||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
|
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "false"
|
||||||
|
|
||||||
|
buildConfigField "String", "GCM_SENDER_ID", "\"$GCM_SENDER_ID\""
|
||||||
}
|
}
|
||||||
xw4d {
|
xw4d {
|
||||||
dimension "variant"
|
dimension "variant"
|
||||||
|
@ -81,6 +84,8 @@ android {
|
||||||
resValue "string", "invite_prefix", "/anddbg/"
|
resValue "string", "invite_prefix", "/anddbg/"
|
||||||
buildConfigField "boolean", "WIDIR_ENABLED", "true"
|
buildConfigField "boolean", "WIDIR_ENABLED", "true"
|
||||||
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true"
|
buildConfigField "boolean", "RELAYINVITE_SUPPORTED", "true"
|
||||||
|
|
||||||
|
buildConfigField "String", "GCM_SENDER_ID", "\"\""
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: "all" breaks things. Seems to be a keyword. Need
|
// WARNING: "all" breaks things. Seems to be a keyword. Need
|
||||||
|
@ -254,9 +259,14 @@ afterEvaluate {
|
||||||
|
|
||||||
task makeBuildAssets() {
|
task makeBuildAssets() {
|
||||||
def assetsDir = android.sourceSets.main.assets.srcDirs.toArray()[0]
|
def assetsDir = android.sourceSets.main.assets.srcDirs.toArray()[0]
|
||||||
String path = new File(assetsDir, 'build-info.txt').getAbsolutePath()
|
String path = new File(assetsDir, BUILD_INFO_NAME).getAbsolutePath()
|
||||||
File file = new File(path);
|
String out = "git: ${GITREV}\n"
|
||||||
file.write("git: ${GITREV}\n");
|
|
||||||
|
String diff = "git diff".execute().text.trim()
|
||||||
|
if (diff) {
|
||||||
|
out += "\n" + diff
|
||||||
|
}
|
||||||
|
new File(path).write(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
gradle.projectsEvaluated {
|
gradle.projectsEvaluated {
|
||||||
|
|
|
@ -34,13 +34,7 @@
|
||||||
/>
|
/>
|
||||||
<uses-feature android:name="android.hardware.nfc" android:required="false" />
|
<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.GET_ACCOUNTS" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
|
||||||
<application android:icon="@drawable/icon48x48"
|
<application android:icon="@drawable/icon48x48"
|
||||||
|
@ -208,16 +202,5 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</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>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -13,10 +13,9 @@
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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
|
<p>This release makes a couple of small UI tweaks.</p>
|
||||||
with title bars on some Samsung devices.</p>
|
|
||||||
|
|
||||||
<div id="survey">
|
<div id="survey">
|
||||||
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
|
<p>Please <a href="https://www.surveymonkey.com/s/GX3XLHR">take
|
||||||
|
@ -26,10 +25,12 @@
|
||||||
|
|
||||||
<h3>New with this release</h3>
|
<h3>New with this release</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Fix delays bringing up the Invite dialog for new games</li>
|
<li>Offer to "Archive" finished games</li>
|
||||||
<li>Explicitly specify application "theme" to fix a Samsung
|
<li>Make tap on thumbnail toggle whether game's selected rather
|
||||||
"upgrade" turning the titlebar white and so making menu
|
than open it. (Tap to the right still opens)</li>
|
||||||
icons disappear</li>
|
<li>Bug fix: don't allow duplicate group names</li>
|
||||||
|
<li>Fix battery-hogging behavior on non-Google-play
|
||||||
|
installs</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>(The full changelog
|
<p>(The full changelog
|
||||||
|
|
|
@ -550,7 +550,7 @@ public class BTService extends XWService {
|
||||||
} else {
|
} else {
|
||||||
short len = is.readShort();
|
short len = is.readShort();
|
||||||
byte[] nliData = new byte[len];
|
byte[] nliData = new byte[len];
|
||||||
is.read( nliData );
|
is.readFully( nliData );
|
||||||
nli = XwJNI.nliFromStream( nliData );
|
nli = XwJNI.nliFromStream( nliData );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,10 +573,8 @@ public class BTService extends XWService {
|
||||||
int gameID = dis.readInt();
|
int gameID = dis.readInt();
|
||||||
switch ( cmd ) {
|
switch ( cmd ) {
|
||||||
case MESG_SEND:
|
case MESG_SEND:
|
||||||
short len = dis.readShort();
|
byte[] buffer = new byte[dis.readShort()];
|
||||||
byte[] buffer = new byte[len];
|
dis.readFully( buffer );
|
||||||
int nRead = dis.read( buffer, 0, len );
|
|
||||||
if ( nRead == len ) {
|
|
||||||
BluetoothDevice host = socket.getRemoteDevice();
|
BluetoothDevice host = socket.getRemoteDevice();
|
||||||
addAddr( host );
|
addAddr( host );
|
||||||
|
|
||||||
|
@ -589,10 +587,6 @@ public class BTService extends XWService {
|
||||||
|
|
||||||
result = rslt == ReceiveResult.GAME_GONE ?
|
result = rslt == ReceiveResult.GAME_GONE ?
|
||||||
BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
|
BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
|
||||||
} else {
|
|
||||||
Log.e( TAG, "receiveMessage(): read only %d of %d bytes",
|
|
||||||
nRead, len );
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case MESG_GAMEGONE:
|
case MESG_GAMEGONE:
|
||||||
postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
|
postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
|
||||||
|
|
|
@ -188,10 +188,8 @@ public class BiDiSockWrap {
|
||||||
DataInputStream inStream
|
DataInputStream inStream
|
||||||
= new DataInputStream( mSocket.getInputStream() );
|
= new DataInputStream( mSocket.getInputStream() );
|
||||||
while ( mRunThreads ) {
|
while ( mRunThreads ) {
|
||||||
short len = inStream.readShort();
|
byte[] packet = new byte[inStream.readShort()];
|
||||||
Log.d( TAG, "got len: %d", len );
|
inStream.readFully( packet );
|
||||||
byte[] packet = new byte[len];
|
|
||||||
inStream.read( packet );
|
|
||||||
mIface.gotPacket( BiDiSockWrap.this, packet );
|
mIface.gotPacket( BiDiSockWrap.this, packet );
|
||||||
}
|
}
|
||||||
} catch( IOException ioe ) {
|
} catch( IOException ioe ) {
|
||||||
|
|
|
@ -322,28 +322,37 @@ 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 &&
|
if ( m_lastSecsLeft != secondsLeft || m_lastTimerPlayer != player ) {
|
||||||
(m_lastSecsLeft != secondsLeft || m_lastTimerPlayer != player) ) {
|
final Rect rectCopy = new Rect(rect);
|
||||||
m_lastSecsLeft = secondsLeft;
|
final int secondsLeftCopy = secondsLeft;
|
||||||
|
m_activity.runOnUiThread( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if ( null != m_jniThread ) {
|
||||||
|
m_lastSecsLeft = secondsLeftCopy;
|
||||||
m_lastTimerPlayer = player;
|
m_lastTimerPlayer = player;
|
||||||
|
|
||||||
String negSign = secondsLeft < 0? "-":"";
|
String negSign = secondsLeftCopy < 0? "-":"";
|
||||||
secondsLeft = Math.abs( secondsLeft );
|
int secondsLeft = Math.abs( secondsLeftCopy );
|
||||||
String time = String.format( "%s%d:%02d", negSign, secondsLeft/60,
|
String time =
|
||||||
secondsLeft%60 );
|
String.format( "%s%d:%02d", negSign,
|
||||||
|
secondsLeft/60, secondsLeft%60 );
|
||||||
|
|
||||||
fillRectOther( rect, CommonPrefs.COLOR_BACKGRND );
|
fillRectOther( rectCopy, CommonPrefs.COLOR_BACKGRND );
|
||||||
m_fillPaint.setColor( m_playerColors[player] );
|
m_fillPaint.setColor( m_playerColors[player] );
|
||||||
|
|
||||||
Rect shorter = new Rect( rect );
|
rectCopy.inset( 0, rectCopy.height() / 5 );
|
||||||
shorter.inset( 0, shorter.height() / 5 );
|
drawCentered( time, rectCopy, null );
|
||||||
drawCentered( time, shorter, null );
|
|
||||||
|
|
||||||
m_jniThread.handle( JNIThread.JNICmd.CMD_DRAW );
|
m_jniThread.handle( JNIThread.JNICmd.CMD_DRAW );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean drawCell( Rect rect, String text, int tile, int value,
|
public boolean drawCell( Rect rect, String text, int tile, int value,
|
||||||
int owner, int bonus, int hintAtts, int flags )
|
int owner, int bonus, int hintAtts, int flags )
|
||||||
|
|
|
@ -104,7 +104,6 @@ public class BoardDelegate extends DelegateBase
|
||||||
private Button m_exchCancelButton;
|
private Button m_exchCancelButton;
|
||||||
private SentInvitesInfo m_sentInfo;
|
private SentInvitesInfo m_sentInfo;
|
||||||
private Perms23.PermCbck m_permCbck;
|
private Perms23.PermCbck m_permCbck;
|
||||||
private ArrayList<String> m_pendingChats;
|
|
||||||
|
|
||||||
private CommsConnTypeSet m_connTypes = null;
|
private CommsConnTypeSet m_connTypes = null;
|
||||||
private String[] m_missingDevs;
|
private String[] m_missingDevs;
|
||||||
|
@ -205,6 +204,25 @@ public class BoardDelegate extends DelegateBase
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ab.setNegativeButton( R.string.button_rematch, lstnr );
|
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
|
} else if ( DlgID.DLG_CONNSTAT == dlgID
|
||||||
&& BuildConfig.DEBUG && null != m_connTypes
|
&& BuildConfig.DEBUG && null != m_connTypes
|
||||||
&& (m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY )
|
&& (m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY )
|
||||||
|
@ -553,8 +571,6 @@ public class BoardDelegate extends DelegateBase
|
||||||
m_isFirstLaunch = null == savedInstanceState;
|
m_isFirstLaunch = null == savedInstanceState;
|
||||||
getBundledData( savedInstanceState );
|
getBundledData( savedInstanceState );
|
||||||
|
|
||||||
m_pendingChats = new ArrayList<String>();
|
|
||||||
|
|
||||||
m_utils = new BoardUtilCtxt();
|
m_utils = new BoardUtilCtxt();
|
||||||
m_timers = new TimerRunnable[4]; // needs to be in sync with
|
m_timers = new TimerRunnable[4]; // needs to be in sync with
|
||||||
// XWTimerReason
|
// XWTimerReason
|
||||||
|
@ -843,7 +859,7 @@ public class BoardDelegate extends DelegateBase
|
||||||
Utils.setItemVisible( menu, R.id.board_menu_game_invites, enable );
|
Utils.setItemVisible( menu, R.id.board_menu_game_invites, enable );
|
||||||
|
|
||||||
enable = XWPrefs.getStudyEnabled( m_activity );
|
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;
|
return true;
|
||||||
} // onPrepareOptionsMenu
|
} // onPrepareOptionsMenu
|
||||||
|
@ -913,7 +929,7 @@ public class BoardDelegate extends DelegateBase
|
||||||
case R.id.board_menu_tray:
|
case R.id.board_menu_tray:
|
||||||
cmd = JNICmd.CMD_TOGGLE_TRAY;
|
cmd = JNICmd.CMD_TOGGLE_TRAY;
|
||||||
break;
|
break;
|
||||||
case R.id.games_menu_study:
|
case R.id.board_menu_study:
|
||||||
StudyListDelegate.launchOrAlert( getDelegator(), m_gi.dictLang, this );
|
StudyListDelegate.launchOrAlert( getDelegator(), m_gi.dictLang, this );
|
||||||
break;
|
break;
|
||||||
case R.id.board_menu_game_netstats:
|
case R.id.board_menu_game_netstats:
|
||||||
|
@ -1095,6 +1111,12 @@ public class BoardDelegate extends DelegateBase
|
||||||
makeOkOnlyBuilder( R.string.after_restart ).show();
|
makeOkOnlyBuilder( R.string.after_restart ).show();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARCHIVE_ACTION:
|
||||||
|
String archiveName = (String)params[0];
|
||||||
|
long archiveGroup = (Long)params[1];
|
||||||
|
archiveAndClose( archiveName, archiveGroup );
|
||||||
|
break;
|
||||||
|
|
||||||
case ENABLE_SMS_DO:
|
case ENABLE_SMS_DO:
|
||||||
post( new Runnable() {
|
post( new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
|
@ -2144,7 +2166,6 @@ public class BoardDelegate extends DelegateBase
|
||||||
|
|
||||||
if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) {
|
if ( m_gi.serverRole != DeviceRole.SERVER_STANDALONE ) {
|
||||||
warnIfNoTransport();
|
warnIfNoTransport();
|
||||||
trySendChats();
|
|
||||||
tickle( isStart );
|
tickle( isStart );
|
||||||
tryInvites();
|
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()
|
private void tryInvites()
|
||||||
{
|
{
|
||||||
if ( 0 < m_mySIS.nMissing && m_summary.hasRematchInfo() ) {
|
if ( 0 < m_mySIS.nMissing && m_summary.hasRematchInfo() ) {
|
||||||
|
@ -2588,6 +2600,16 @@ public class BoardDelegate extends DelegateBase
|
||||||
return wordsArray;
|
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
|
// For now, supported if standalone or either BT or SMS used for transport
|
||||||
private boolean rematchSupported( boolean showMulti )
|
private boolean rematchSupported( boolean showMulti )
|
||||||
{
|
{
|
||||||
|
|
|
@ -158,8 +158,7 @@ public class BoardView extends View implements BoardHandler, SyncedDraw {
|
||||||
|
|
||||||
if ( null != m_dims ) {
|
if ( null != m_dims ) {
|
||||||
if ( BoardContainer.getIsPortrait() != (m_dims.height > m_dims.width) ) {
|
if ( BoardContainer.getIsPortrait() != (m_dims.height > m_dims.width) ) {
|
||||||
// square possible; will break above!
|
// square possible; will break above! No. tested by forceing square
|
||||||
Assert.assertTrue( m_dims.height != m_dims.width );
|
|
||||||
Log.d( TAG, "onMeasure: discarding m_dims" );
|
Log.d( TAG, "onMeasure: discarding m_dims" );
|
||||||
if ( ++m_dimsTossCount < 4 ) {
|
if ( ++m_dimsTossCount < 4 ) {
|
||||||
m_dims = null;
|
m_dims = null;
|
||||||
|
|
|
@ -52,9 +52,6 @@ public class ConnStatusHandler {
|
||||||
public Handler getHandler();
|
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_IN = 0;
|
||||||
private static final int SUCCESS_OUT = 1;
|
private static final int SUCCESS_OUT = 1;
|
||||||
private static final int SHOW_SUCCESS_INTERVAL = 1000;
|
private static final int SHOW_SUCCESS_INTERVAL = 1000;
|
||||||
|
@ -340,7 +337,7 @@ public class ConnStatusHandler {
|
||||||
boolean isIn )
|
boolean isIn )
|
||||||
{
|
{
|
||||||
enabled = enabled && null != newestSuccess( connTypes, 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 );
|
canvas.drawRect( rect, s_fillPaint );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,9 @@ public class DBUtils {
|
||||||
private static long s_cachedRowID = ROWID_NOTFOUND;
|
private static long s_cachedRowID = ROWID_NOTFOUND;
|
||||||
private static byte[] s_cachedBytes = null;
|
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 static interface DBChangeListener {
|
||||||
public void gameSaved( long rowid, GameChangeType change );
|
public void gameSaved( long rowid, GameChangeType change );
|
||||||
|
@ -1616,21 +1618,34 @@ public class DBUtils {
|
||||||
return result;
|
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 )
|
public static long[] getGroupGames( Context context, long groupID )
|
||||||
{
|
{
|
||||||
long[] result = null;
|
long[] result = null;
|
||||||
initDB( context );
|
initDB( context );
|
||||||
String[] columns = { ROW_ID };
|
String[] columns = { ROW_ID, DBHelper.HASMSGS };
|
||||||
String selection = String.format( "%s=%d", DBHelper.GROUPID, groupID );
|
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 ) {
|
synchronized( s_dbHelper ) {
|
||||||
Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
|
Cursor cursor = s_db.query( DBHelper.TABLE_NAME_SUM, columns,
|
||||||
selection, // selection
|
selection, // selection
|
||||||
null, // args
|
null, // args
|
||||||
null, // groupBy
|
null, // groupBy
|
||||||
null, // having
|
null, // having
|
||||||
orderBy
|
s_getGroupGamesOrderBy
|
||||||
);
|
);
|
||||||
int index = cursor.getColumnIndex( ROW_ID );
|
int index = cursor.getColumnIndex( ROW_ID );
|
||||||
result = new long[ cursor.getCount() ];
|
result = new long[ cursor.getCount() ];
|
||||||
|
@ -1688,6 +1703,29 @@ public class DBUtils {
|
||||||
return result;
|
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 )
|
public static long addGroup( Context context, String name )
|
||||||
{
|
{
|
||||||
long rowid = GROUPID_UNSPEC;
|
long rowid = GROUPID_UNSPEC;
|
||||||
|
@ -1746,13 +1784,14 @@ public class DBUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change group id of a game
|
// 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 );
|
Assert.assertTrue( GROUPID_UNSPEC != groupID );
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put( DBHelper.GROUPID, groupID );
|
values.put( DBHelper.GROUPID, groupID );
|
||||||
updateRow( context, DBHelper.TABLE_NAME_SUM, gameid, values );
|
updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
|
||||||
invalGroupsCache();
|
invalGroupsCache();
|
||||||
|
notifyListeners( rowid, GameChangeType.GAME_MOVED );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getChatHistoryStr( Context context, long rowid )
|
private static String getChatHistoryStr( Context context, long rowid )
|
||||||
|
|
|
@ -163,6 +163,7 @@ public class DelegateBase implements DlgClickNotify,
|
||||||
}
|
}
|
||||||
if ( this != result ) {
|
if ( this != result ) {
|
||||||
Log.d( TAG, "%s.curThis() => " + result, this.toString() );
|
Log.d( TAG, "%s.curThis() => " + result, this.toString() );
|
||||||
|
Assert.fail();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,7 @@ public class DlgDelegate {
|
||||||
TRAY_PICKED,
|
TRAY_PICKED,
|
||||||
INVITE_INFO,
|
INVITE_INFO,
|
||||||
DISABLE_DUALPANE,
|
DISABLE_DUALPANE,
|
||||||
|
ARCHIVE_ACTION,
|
||||||
|
|
||||||
// Dict Browser
|
// Dict Browser
|
||||||
FINISH_ACTION,
|
FINISH_ACTION,
|
||||||
|
|
|
@ -22,7 +22,6 @@ package org.eehouse.android.xw4;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
@ -214,13 +213,13 @@ public class ExpiringDelegate {
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
int count = s_points.length;
|
int count = s_points.length;
|
||||||
if ( 0 < redWidth ) {
|
if ( 0 < redWidth ) {
|
||||||
s_paint.setColor( Color.RED );
|
s_paint.setColor( XWApp.RED );
|
||||||
canvas.drawLines( s_points, offset, count / 2, s_paint );
|
canvas.drawLines( s_points, offset, count / 2, s_paint );
|
||||||
count /= 2;
|
count /= 2;
|
||||||
offset += count;
|
offset += count;
|
||||||
}
|
}
|
||||||
if ( redWidth < width ) {
|
if ( redWidth < width ) {
|
||||||
s_paint.setColor( Color.GREEN );
|
s_paint.setColor( XWApp.GREEN );
|
||||||
}
|
}
|
||||||
canvas.drawLines( s_points, offset, count, s_paint );
|
canvas.drawLines( s_points, offset, count, s_paint );
|
||||||
}
|
}
|
||||||
|
@ -256,7 +255,7 @@ public class ExpiringDelegate {
|
||||||
|
|
||||||
Paint paint = new Paint();
|
Paint paint = new Paint();
|
||||||
paint.setStyle(Paint.Style.FILL);
|
paint.setStyle(Paint.Style.FILL);
|
||||||
paint.setColor( Color.RED );
|
paint.setColor( XWApp.RED );
|
||||||
canvas.drawRect( 0, 0, pct, 1, paint );
|
canvas.drawRect( 0, 0, pct, 1, paint );
|
||||||
paint.setColor( Utils.TURN_COLOR );
|
paint.setColor( Utils.TURN_COLOR );
|
||||||
canvas.drawRect( pct, 0, 100, 1, paint );
|
canvas.drawRect( pct, 0, 100, 1, paint );
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class GameListItem extends LinearLayout
|
||||||
private LinearLayout m_list;
|
private LinearLayout m_list;
|
||||||
private TextView m_state;
|
private TextView m_state;
|
||||||
private TextView m_modTime;
|
private TextView m_modTime;
|
||||||
private ImageView m_marker;
|
private ImageView m_gameTypeImage;
|
||||||
private TextView m_role;
|
private TextView m_role;
|
||||||
|
|
||||||
private boolean m_expanded, m_haveTurn, m_haveTurnLocal;
|
private boolean m_expanded, m_haveTurn, m_haveTurnLocal;
|
||||||
|
@ -90,16 +90,6 @@ public class GameListItem extends LinearLayout
|
||||||
m_lastMoveTime = 0;
|
m_lastMoveTime = 0;
|
||||||
m_loadingCount = 0;
|
m_loadingCount = 0;
|
||||||
m_dsdel = new DrawSelDelegate( this );
|
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()
|
public GameSummary getSummary()
|
||||||
|
@ -174,13 +164,32 @@ public class GameListItem extends LinearLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
// View.OnClickListener interface
|
// View.OnClickListener interface
|
||||||
public void onClick( View view ) {
|
public void onClick( View view )
|
||||||
|
{
|
||||||
|
int id = view.getId();
|
||||||
|
switch ( id ) {
|
||||||
|
case R.id.expander:
|
||||||
m_expanded = !m_expanded;
|
m_expanded = !m_expanded;
|
||||||
DBUtils.setExpanded( m_rowid, 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()
|
private void findViews()
|
||||||
|
@ -191,12 +200,15 @@ public class GameListItem extends LinearLayout
|
||||||
m_expandButton.setOnClickListener( this );
|
m_expandButton.setOnClickListener( this );
|
||||||
m_viewUnloaded = (TextView)findViewById( R.id.view_unloaded );
|
m_viewUnloaded = (TextView)findViewById( R.id.view_unloaded );
|
||||||
m_viewLoaded = findViewById( R.id.view_loaded );
|
m_viewLoaded = findViewById( R.id.view_loaded );
|
||||||
|
m_viewLoaded.setOnClickListener( this );
|
||||||
m_list = (LinearLayout)findViewById( R.id.player_list );
|
m_list = (LinearLayout)findViewById( R.id.player_list );
|
||||||
m_state = (TextView)findViewById( R.id.state );
|
m_state = (TextView)findViewById( R.id.state );
|
||||||
m_modTime = (TextView)findViewById( R.id.modtime );
|
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_thumb = (ImageView)findViewById( R.id.thumbnail );
|
||||||
m_role = (TextView)findViewById( R.id.role );
|
m_role = (TextView)findViewById( R.id.role );
|
||||||
|
|
||||||
|
findViewById( R.id.right_side ).setOnClickListener( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLoaded( boolean loaded )
|
private void setLoaded( boolean loaded )
|
||||||
|
@ -316,19 +328,20 @@ public class GameListItem extends LinearLayout
|
||||||
|
|
||||||
int iconID = summary.isMultiGame() ?
|
int iconID = summary.isMultiGame() ?
|
||||||
R.drawable.multigame__gen : R.drawable.sologame__gen;
|
R.drawable.multigame__gen : R.drawable.sologame__gen;
|
||||||
m_marker.setImageResource( iconID );
|
m_gameTypeImage.setImageResource( iconID );
|
||||||
m_marker.setOnClickListener( new View.OnClickListener() {
|
|
||||||
@Override
|
boolean hasChat = summary.isMultiGame();
|
||||||
public void onClick( View view ) {
|
if ( hasChat ) {
|
||||||
toggleSelected();
|
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 );
|
String roleSummary = summary.summarizeRole( m_context, m_rowid );
|
||||||
|
m_role.setVisibility( null == roleSummary ? View.GONE : View.VISIBLE );
|
||||||
if ( null != roleSummary ) {
|
if ( null != roleSummary ) {
|
||||||
m_role.setText( roleSummary );
|
m_role.setText( roleSummary );
|
||||||
} else {
|
|
||||||
m_role.setVisibility( View.GONE );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update( expanded, summary.lastMoveTime, haveATurn,
|
update( expanded, summary.lastMoveTime, haveATurn,
|
||||||
|
@ -420,6 +433,7 @@ public class GameListItem extends LinearLayout
|
||||||
// }
|
// }
|
||||||
// GameListAdapter.ClickHandler interface
|
// GameListAdapter.ClickHandler interface
|
||||||
|
|
||||||
|
@Override
|
||||||
public void longClicked()
|
public void longClicked()
|
||||||
{
|
{
|
||||||
toggleSelected();
|
toggleSelected();
|
||||||
|
|
|
@ -1196,7 +1196,7 @@ public class GameUtils {
|
||||||
for ( CommsConnType typ : conTypes ) {
|
for ( CommsConnType typ : conTypes ) {
|
||||||
switch ( typ ) {
|
switch ( typ ) {
|
||||||
case COMMS_CONN_RELAY:
|
case COMMS_CONN_RELAY:
|
||||||
tellRelayDied( context, summary, informNow );
|
// see below
|
||||||
break;
|
break;
|
||||||
case COMMS_CONN_BT:
|
case COMMS_CONN_BT:
|
||||||
BTService.gameDied( context, addr.bt_btAddr, gameID );
|
BTService.gameDied( context, addr.bt_btAddr, gameID );
|
||||||
|
@ -1211,6 +1211,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();
|
gamePtr.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -564,6 +564,7 @@ public class GamesListDelegate extends ListDelegateBase
|
||||||
private static final int[] DEBUG_ITEMS = {
|
private static final int[] DEBUG_ITEMS = {
|
||||||
// R.id.games_menu_loaddb,
|
// R.id.games_menu_loaddb,
|
||||||
R.id.games_menu_storedb,
|
R.id.games_menu_storedb,
|
||||||
|
R.id.games_menu_writegit,
|
||||||
};
|
};
|
||||||
private static final int[] NOSEL_ITEMS = {
|
private static final int[] NOSEL_ITEMS = {
|
||||||
R.id.games_menu_newgroup,
|
R.id.games_menu_newgroup,
|
||||||
|
@ -754,9 +755,18 @@ public class GamesListDelegate extends ListDelegateBase
|
||||||
lstnr = new OnClickListener() {
|
lstnr = new OnClickListener() {
|
||||||
public void onClick( DialogInterface dlg, int item ) {
|
public void onClick( DialogInterface dlg, int item ) {
|
||||||
String name = namer.getName();
|
String name = namer.getName();
|
||||||
|
long hasName = DBUtils.getGroup( m_activity, name );
|
||||||
|
if ( DBUtils.GROUPID_UNSPEC == hasName ) {
|
||||||
DBUtils.addGroup( m_activity, name );
|
DBUtils.addGroup( m_activity, name );
|
||||||
mkListAdapter();
|
mkListAdapter();
|
||||||
showNewGroupIf();
|
showNewGroupIf();
|
||||||
|
} else {
|
||||||
|
String msg = LocUtils
|
||||||
|
.getString( m_activity,
|
||||||
|
R.string.duplicate_group_name_fmt,
|
||||||
|
name );
|
||||||
|
makeOkOnlyBuilder( msg ).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
lstnr2 = new OnClickListener() {
|
lstnr2 = new OnClickListener() {
|
||||||
|
@ -1059,8 +1069,6 @@ public class GamesListDelegate extends ListDelegateBase
|
||||||
invalidateOptionsMenuIf();
|
invalidateOptionsMenuIf();
|
||||||
setTitle();
|
setTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
mkListAdapter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invalidateOptionsMenuIf()
|
public void invalidateOptionsMenuIf()
|
||||||
|
@ -1132,6 +1140,9 @@ public class GamesListDelegate extends ListDelegateBase
|
||||||
mkListAdapter();
|
mkListAdapter();
|
||||||
setSelGame( rowid );
|
setSelGame( rowid );
|
||||||
break;
|
break;
|
||||||
|
case GAME_MOVED:
|
||||||
|
mkListAdapter();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Assert.fail();
|
Assert.fail();
|
||||||
break;
|
break;
|
||||||
|
@ -1539,10 +1550,10 @@ public class GamesListDelegate extends ListDelegateBase
|
||||||
GameUtils.resendAllIf( m_activity, null, true, true );
|
GameUtils.resendAllIf( m_activity, null, true, true );
|
||||||
break;
|
break;
|
||||||
case R.id.games_menu_newgame_solo:
|
case R.id.games_menu_newgame_solo:
|
||||||
handleNewGame( true );
|
handleNewGameButton( true );
|
||||||
break;
|
break;
|
||||||
case R.id.games_menu_newgame_net:
|
case R.id.games_menu_newgame_net:
|
||||||
handleNewGame( false );
|
handleNewGameButton( false );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case R.id.games_menu_newgroup:
|
case R.id.games_menu_newgroup:
|
||||||
|
@ -1597,6 +1608,10 @@ public class GamesListDelegate extends ListDelegateBase
|
||||||
Action.STORAGE_CONFIRMED, itemID );
|
Action.STORAGE_CONFIRMED, itemID );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case R.id.games_menu_writegit:
|
||||||
|
Utils.gitInfoToClip( m_activity );
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
handled = handleSelGamesItem( itemID, selRowIDs )
|
handled = handleSelGamesItem( itemID, selRowIDs )
|
||||||
|| handleSelGroupsItem( itemID, getSelGroupIDs() );
|
|| handleSelGroupsItem( itemID, getSelGroupIDs() );
|
||||||
|
|
|
@ -184,7 +184,7 @@ public class NetUtils {
|
||||||
short len = dis.readShort();
|
short len = dis.readShort();
|
||||||
if ( len > 0 ) {
|
if ( len > 0 ) {
|
||||||
byte[] packet = new byte[len];
|
byte[] packet = new byte[len];
|
||||||
dis.read( packet );
|
dis.readFully( packet );
|
||||||
msgs[ii][jj] = 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
|
// Can't figure out how to read a null-terminated string
|
||||||
// from DataInputStream so parse it myself.
|
// from DataInputStream so parse it myself.
|
||||||
byte[] bytes = new byte[len];
|
byte[] bytes = new byte[len];
|
||||||
dis.read( bytes );
|
dis.readFully( bytes );
|
||||||
|
|
||||||
int index = -1;
|
int index = -1;
|
||||||
for ( int ii = 0; ii < nRooms; ++ii ) {
|
for ( int ii = 0; ii < nRooms; ++ii ) {
|
||||||
|
|
|
@ -736,7 +736,7 @@ public class RelayService extends XWService
|
||||||
case XWPDEV_MSG:
|
case XWPDEV_MSG:
|
||||||
int token = dis.readInt();
|
int token = dis.readInt();
|
||||||
byte[] msg = new byte[dis.available()];
|
byte[] msg = new byte[dis.available()];
|
||||||
dis.read( msg );
|
dis.readFully( msg );
|
||||||
postData( this, token, msg );
|
postData( this, token, msg );
|
||||||
|
|
||||||
// game-related packets only count
|
// game-related packets only count
|
||||||
|
@ -756,9 +756,8 @@ public class RelayService extends XWService
|
||||||
resetBackoff = true;
|
resetBackoff = true;
|
||||||
intent = getIntentTo( this, MsgCmds.GOT_INVITE );
|
intent = getIntentTo( this, MsgCmds.GOT_INVITE );
|
||||||
int srcDevID = dis.readInt();
|
int srcDevID = dis.readInt();
|
||||||
short len = dis.readShort();
|
byte[] nliData = new byte[dis.readShort()];
|
||||||
byte[] nliData = new byte[len];
|
dis.readFully( nliData );
|
||||||
dis.read( nliData );
|
|
||||||
NetLaunchInfo nli = XwJNI.nliFromStream( nliData );
|
NetLaunchInfo nli = XwJNI.nliFromStream( nliData );
|
||||||
intent.putExtra( INVITE_FROM, srcDevID );
|
intent.putExtra( INVITE_FROM, srcDevID );
|
||||||
String asStr = nli.toString();
|
String asStr = nli.toString();
|
||||||
|
@ -995,9 +994,8 @@ public class RelayService extends XWService
|
||||||
private String getVLIString( DataInputStream dis )
|
private String getVLIString( DataInputStream dis )
|
||||||
throws java.io.IOException
|
throws java.io.IOException
|
||||||
{
|
{
|
||||||
int len = vli2un( dis );
|
byte[] tmp = new byte[vli2un( dis )];
|
||||||
byte[] tmp = new byte[len];
|
dis.readFully( tmp );
|
||||||
dis.read( tmp );
|
|
||||||
String result = new String( tmp );
|
String result = new String( tmp );
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -522,7 +522,7 @@ public class SMSService extends XWService {
|
||||||
case DATA:
|
case DATA:
|
||||||
int gameID = dis.readInt();
|
int gameID = dis.readInt();
|
||||||
byte[] rest = new byte[dis.available()];
|
byte[] rest = new byte[dis.available()];
|
||||||
dis.read( rest );
|
dis.readFully( rest );
|
||||||
if ( feedMessage( gameID, rest, new CommsAddrRec( phone ) ) ) {
|
if ( feedMessage( gameID, rest, new CommsAddrRec( phone ) ) ) {
|
||||||
SMSResendReceiver.resetTimer( this );
|
SMSResendReceiver.resetTimer( this );
|
||||||
}
|
}
|
||||||
|
@ -618,7 +618,7 @@ public class SMSService extends XWService {
|
||||||
} else {
|
} else {
|
||||||
SMS_CMD cmd = SMS_CMD.values()[dis.readByte()];
|
SMS_CMD cmd = SMS_CMD.values()[dis.readByte()];
|
||||||
byte[] rest = new byte[dis.available()];
|
byte[] rest = new byte[dis.available()];
|
||||||
dis.read( rest );
|
dis.readFully( rest );
|
||||||
receive( cmd, rest, senderPhone );
|
receive( cmd, rest, senderPhone );
|
||||||
success = true;
|
success = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ public class StudyListDelegate extends ListDelegateBase
|
||||||
implements OnItemSelectedListener, SelectableItem,
|
implements OnItemSelectedListener, SelectableItem,
|
||||||
View.OnLongClickListener, View.OnClickListener,
|
View.OnLongClickListener, View.OnClickListener,
|
||||||
DBUtils.StudyListListener {
|
DBUtils.StudyListListener {
|
||||||
|
private static final String TAG = StudyListDelegate.class.getSimpleName();
|
||||||
|
|
||||||
protected static final int NO_LANG = -1;
|
protected static final int NO_LANG = -1;
|
||||||
|
|
||||||
|
@ -220,7 +221,8 @@ public class StudyListDelegate extends ListDelegateBase
|
||||||
showToast( msg );
|
showToast( msg );
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Assert.assertFalse( BuildConfig.DEBUG );
|
Log.d( TAG, "not handling: %s", action );
|
||||||
|
handled = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return handled;
|
return handled;
|
||||||
|
|
|
@ -32,7 +32,9 @@ import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.text.ClipboardManager;
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.media.Ringtone;
|
import android.media.Ringtone;
|
||||||
|
@ -55,9 +57,12 @@ import android.widget.Toast;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
@ -187,6 +192,33 @@ public class Utils {
|
||||||
context.startActivity( Intent.createChooser( intent, chooserMsg ) );
|
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,
|
public static void postNotification( Context context, Intent intent,
|
||||||
int titleID, int bodyID, int id )
|
int titleID, int bodyID, int id )
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,7 +36,6 @@ public class XWApp extends Application {
|
||||||
private static final String TAG = XWApp.class.getSimpleName();
|
private static final String TAG = XWApp.class.getSimpleName();
|
||||||
|
|
||||||
public static final boolean BTSUPPORTED = true;
|
public static final boolean BTSUPPORTED = true;
|
||||||
public static final boolean GCMSUPPORTED = true;
|
|
||||||
public static final boolean ATTACH_SUPPORTED = false;
|
public static final boolean ATTACH_SUPPORTED = false;
|
||||||
public static final boolean LOG_LIFECYLE = false;
|
public static final boolean LOG_LIFECYLE = false;
|
||||||
public static final boolean DEBUG_EXP_TIMERS = 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 MAX_TRAY_TILES = 7; // comtypes.h
|
||||||
public static final int SEL_COLOR = Color.argb( 0xFF, 0x09, 0x70, 0x93 );
|
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 UUID s_UUID = null;
|
||||||
private static Boolean s_onEmulator = null;
|
private static Boolean s_onEmulator = null;
|
||||||
private static Context s_context = 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
|
values-??/strings.xml
|
||||||
|
**/*__gen.png
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
<EditText android:id="@+id/chat_edit"
|
<EditText android:id="@+id/chat_edit"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="false"
|
|
||||||
android:inputType="textCapSentences|textMultiLine"
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:scrollHorizontally="false"
|
android:scrollHorizontally="false"
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_marginLeft="30dip"
|
android:layout_marginLeft="30dip"
|
||||||
android:layout_marginRight="30dip"
|
android:layout_marginRight="30dip"
|
||||||
android:autoText="false"
|
android:maxLines="1"
|
||||||
android:capitalize="words"
|
android:maxLength="32"
|
||||||
android:singleLine="true"
|
android:inputType="textCapWords"
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
<EditText android:id="@+id/word_edit"
|
<EditText android:id="@+id/word_edit"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
|
android:inputType="text"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:hint="@string/word_search_hint"
|
android:hint="@string/word_search_hint"
|
||||||
android:capitalize="characters"
|
android:capitalize="characters"
|
||||||
|
|
|
@ -138,9 +138,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:scrollHorizontally="false"
|
android:scrollHorizontally="false"
|
||||||
android:autoText="false"
|
android:maxLines="1"
|
||||||
android:capitalize="none"
|
android:inputType="text"
|
||||||
android:singleLine="true"
|
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
android:maxLength="31"
|
android:maxLength="31"
|
||||||
|
|
|
@ -31,13 +31,25 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
>
|
>
|
||||||
|
|
||||||
<ImageView android:id="@+id/msg_marker"
|
<RelativeLayout android:id="@+id/game_view_container"
|
||||||
android:layout_width="42dp"
|
android:layout_width="42dp"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:layout_gravity="center_vertical|center_horizontal"
|
android:layout_gravity="center_vertical|center_horizontal"
|
||||||
android:paddingLeft="8dip"
|
android:paddingLeft="8dip"
|
||||||
android:paddingRight="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"
|
<ImageView android:id="@+id/thumbnail"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -49,9 +61,12 @@
|
||||||
|
|
||||||
<!-- this layout is vertical, holds everything but the status
|
<!-- this layout is vertical, holds everything but the status
|
||||||
icon[s] (plural later) -->
|
icon[s] (plural later) -->
|
||||||
<LinearLayout android:orientation="vertical"
|
<LinearLayout android:id="@+id/right_side"
|
||||||
|
android:orientation="vertical"
|
||||||
android:layout_width="fill_parent"
|
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 -->
|
<!-- This is the game name and expander -->
|
||||||
|
@ -66,7 +81,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
android:layout_marginLeft="30dip"
|
android:layout_marginLeft="30dip"
|
||||||
android:layout_marginRight="30dip"
|
android:layout_marginRight="30dip"
|
||||||
android:autoText="false"
|
android:autoText="false"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
|
android:inputType="text"
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
/>
|
/>
|
||||||
|
@ -37,7 +38,8 @@
|
||||||
android:layout_marginLeft="30dip"
|
android:layout_marginLeft="30dip"
|
||||||
android:layout_marginRight="30dip"
|
android:layout_marginRight="30dip"
|
||||||
android:autoText="false"
|
android:autoText="false"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
|
android:inputType="text"
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,7 +19,8 @@
|
||||||
android:layout_marginLeft="30dip"
|
android:layout_marginLeft="30dip"
|
||||||
android:layout_marginRight="30dip"
|
android:layout_marginRight="30dip"
|
||||||
android:autoText="false"
|
android:autoText="false"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
|
android:inputType="text"
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
/>
|
/>
|
||||||
|
@ -37,7 +38,8 @@
|
||||||
android:layout_marginLeft="30dip"
|
android:layout_marginLeft="30dip"
|
||||||
android:layout_marginRight="30dip"
|
android:layout_marginRight="30dip"
|
||||||
android:autoText="false"
|
android:autoText="false"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
|
android:inputType="text"
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextView android:id="@+id/text_item2"
|
<TextView android:id="@+id/text_item2"
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:gravity="right"
|
android:gravity="right"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,11 @@
|
||||||
|
|
||||||
<TextView android:id="@+id/english_view"
|
<TextView android:id="@+id/english_view"
|
||||||
style="@style/evenly_spaced_horizontal"
|
style="@style/evenly_spaced_horizontal"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
/>
|
/>
|
||||||
<TextView android:id="@+id/xlated_view"
|
<TextView android:id="@+id/xlated_view"
|
||||||
style="@style/evenly_spaced_horizontal"
|
style="@style/evenly_spaced_horizontal"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</org.eehouse.android.xw4.loc.LocListItem>
|
</org.eehouse.android.xw4.loc.LocListItem>
|
||||||
|
|
|
@ -37,7 +37,8 @@
|
||||||
<EditText android:id="@+id/loc_search_field"
|
<EditText android:id="@+id/loc_search_field"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
|
android:inputType="text"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,9 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:scrollHorizontally="true"
|
android:scrollHorizontally="true"
|
||||||
android:autoText="false"
|
android:maxLines="1"
|
||||||
android:capitalize="none"
|
android:inputType="textCapWords"
|
||||||
|
android:maxLength="32"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -51,11 +51,11 @@
|
||||||
android:layout_marginLeft="20dip"
|
android:layout_marginLeft="20dip"
|
||||||
android:layout_marginRight="20dip"
|
android:layout_marginRight="20dip"
|
||||||
android:scrollHorizontally="true"
|
android:scrollHorizontally="true"
|
||||||
android:autoText="false"
|
|
||||||
android:capitalize="words"
|
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
android:gravity="fill_horizontal"
|
android:gravity="fill_horizontal"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
|
android:maxLength="32"
|
||||||
|
android:inputType="textCapWords"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -101,11 +101,9 @@
|
||||||
android:layout_marginLeft="20dip"
|
android:layout_marginLeft="20dip"
|
||||||
android:layout_marginRight="20dip"
|
android:layout_marginRight="20dip"
|
||||||
android:scrollHorizontally="true"
|
android:scrollHorizontally="true"
|
||||||
android:autoText="false"
|
|
||||||
android:capitalize="none"
|
|
||||||
android:gravity="fill_horizontal"
|
android:gravity="fill_horizontal"
|
||||||
android:password="true"
|
android:maxLines="1"
|
||||||
android:singleLine="true"
|
android:inputType="textPassword"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
/>
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -9,13 +9,13 @@
|
||||||
<TextView android:id="@+id/item_name"
|
<TextView android:id="@+id/item_name"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
android:gravity="left"
|
android:gravity="left"
|
||||||
/>
|
/>
|
||||||
<TextView android:id="@+id/item_score"
|
<TextView android:id="@+id/item_score"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
android:gravity="right"
|
android:gravity="right"
|
||||||
/>
|
/>
|
||||||
</org.eehouse.android.xw4.ExpiringLinearLayout>
|
</org.eehouse.android.xw4.ExpiringLinearLayout>
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
android:layout_marginLeft="30dip"
|
android:layout_marginLeft="30dip"
|
||||||
android:layout_marginRight="30dip"
|
android:layout_marginRight="30dip"
|
||||||
android:autoText="false"
|
android:autoText="false"
|
||||||
android:singleLine="true"
|
android:maxLines="1"
|
||||||
|
android:inputType="text"
|
||||||
android:selectAllOnFocus="true"
|
android:selectAllOnFocus="true"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -57,7 +57,7 @@
|
||||||
</menu>
|
</menu>
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item android:id="@+id/games_menu_study"
|
<item android:id="@+id/board_menu_study"
|
||||||
android:title="@string/gamel_menu_study"
|
android:title="@string/gamel_menu_study"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -120,5 +120,8 @@
|
||||||
<item android:id="@+id/games_menu_loaddb"
|
<item android:id="@+id/games_menu_loaddb"
|
||||||
android:title="@string/gamel_menu_loaddb"
|
android:title="@string/gamel_menu_loaddb"
|
||||||
/>
|
/>
|
||||||
|
<item android:id="@+id/games_menu_writegit"
|
||||||
|
android:title="@string/gamel_menu_writegit"
|
||||||
|
/>
|
||||||
|
|
||||||
</menu>
|
</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_trading">key_notagain_trading</string>
|
||||||
<string name="key_notagain_hidenewgamebuttons">key_notagain_hidenewgamebuttons</string>
|
<string name="key_notagain_hidenewgamebuttons">key_notagain_hidenewgamebuttons</string>
|
||||||
<string name="key_na_lookup">key_na_lookup</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_browse">key_na_browse</string>
|
||||||
<string name="key_na_browseall">key_na_browseall</string>
|
<string name="key_na_browseall">key_na_browseall</string>
|
||||||
<string name="key_na_values">key_na_values</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,
|
<string name="not_again_lookup">This button lets you look up,
|
||||||
online, the words just played.</string>
|
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_move">Move</string>
|
||||||
<string name="button_newgroup">New group</string>
|
<string name="button_newgroup">New group</string>
|
||||||
|
@ -2123,7 +2131,7 @@
|
||||||
<string name="newgroup_label">Name your new group:</string>
|
<string name="newgroup_label">Name your new group:</string>
|
||||||
|
|
||||||
<string name="list_group_delete">Delete 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_default">Put new games here</string>
|
||||||
<string name="list_group_moveup">Move up</string>
|
<string name="list_group_moveup">Move up</string>
|
||||||
<string name="list_group_movedown">Move down</string>
|
<string name="list_group_movedown">Move down</string>
|
||||||
|
@ -2158,6 +2166,10 @@
|
||||||
game with the same players and parameters as the one that
|
game with the same players and parameters as the one that
|
||||||
just ended. -->
|
just ended. -->
|
||||||
<string name="button_rematch">Rematch</string>
|
<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>
|
<string name="button_reconnect">Reconnect</string>
|
||||||
|
|
||||||
|
@ -2419,11 +2431,11 @@
|
||||||
|
|
||||||
<string name="set_pref">Hide buttons</string>
|
<string name="set_pref">Hide buttons</string>
|
||||||
|
|
||||||
<string name="not_again_hidenewgamebuttons">These two buttons do
|
<string name="not_again_hidenewgamebuttons">The two buttons at the
|
||||||
the same thing as the first two items in this window\'s Action Bar
|
bottom of this screen and the first two items in its Action Bar
|
||||||
(or menu). If you like you can hide the buttons to make more games
|
(or menu) do the same thing. If you like you can hide the buttons
|
||||||
visible.\n\n(If you later want to unhide them go to the Appearance
|
to make more games visible.\n\n(If you later want to unhide the
|
||||||
section of App settings).
|
buttons go to the Appearance section of App settings).
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
<string name="waiting_title">Waiting for players</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="name_dict_fmt">%1$s/%2$s</string>
|
||||||
<string name="gamel_menu_storedb">Write games to SD card</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_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="enable_dupes_title">Accept duplicate invites</string>
|
||||||
<string name="xlations_locale">Fake locale for translation</string>
|
<string name="xlations_locale">Fake locale for translation</string>
|
||||||
<string name="enable_dupes_summary">Accept invitations more than once</string>
|
<string name="enable_dupes_summary">Accept invitations more than once</string>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="AppTheme" parent="android:Theme.Material"/>
|
|
||||||
|
|
||||||
<style name="config_separator">
|
<style name="config_separator">
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
<item name="android:layout_width">fill_parent</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:title="@string/pref_human_name"
|
||||||
android:capitalize="words"
|
android:capitalize="words"
|
||||||
android:defaultValue=""
|
android:defaultValue=""
|
||||||
|
android:maxLines="1"
|
||||||
|
android:maxLength="32"
|
||||||
|
android:inputType="text"
|
||||||
/>
|
/>
|
||||||
<org.eehouse.android.xw4.XWEditTextPreference
|
<org.eehouse.android.xw4.XWEditTextPreference
|
||||||
android:key="@string/key_robot_name"
|
android:key="@string/key_robot_name"
|
||||||
android:title="@string/robot_label"
|
android:title="@string/robot_label"
|
||||||
android:capitalize="words"
|
android:capitalize="words"
|
||||||
android:defaultValue="@string/button_default_robot"
|
android:defaultValue="@string/button_default_robot"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:maxLength="32"
|
||||||
|
android:inputType="text"
|
||||||
/>
|
/>
|
||||||
</PreferenceScreen>
|
</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 org.json.JSONArray;
|
||||||
|
|
||||||
|
import junit.framework.Assert;
|
||||||
|
|
||||||
public class GCMIntentService extends GCMBaseIntentService {
|
public class GCMIntentService extends GCMBaseIntentService {
|
||||||
private static final String TAG = GCMIntentService.class.getSimpleName();
|
private static final String TAG = GCMIntentService.class.getSimpleName();
|
||||||
|
|
||||||
public GCMIntentService()
|
public GCMIntentService()
|
||||||
{
|
{
|
||||||
super( BuildConfig.GCM_SENDER_ID );
|
super( BuildConfig.GCM_SENDER_ID );
|
||||||
|
Assert.assertTrue( BuildConfig.GCM_SENDER_ID.length() > 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -120,8 +123,9 @@ public class GCMIntentService extends GCMBaseIntentService {
|
||||||
|
|
||||||
public static void init( Application app )
|
public static void init( Application app )
|
||||||
{
|
{
|
||||||
|
if ( 0 < BuildConfig.GCM_SENDER_ID.length() ) {
|
||||||
int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK );
|
int sdkVersion = Integer.valueOf( android.os.Build.VERSION.SDK );
|
||||||
if ( 8 <= sdkVersion && 0 < BuildConfig.GCM_SENDER_ID.length() ) {
|
if ( 8 <= sdkVersion ) {
|
||||||
try {
|
try {
|
||||||
GCMRegistrar.checkDevice( app );
|
GCMRegistrar.checkDevice( app );
|
||||||
// GCMRegistrar.checkManifest( app );
|
// GCMRegistrar.checkManifest( app );
|
||||||
|
@ -137,6 +141,7 @@ public class GCMIntentService extends GCMBaseIntentService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void notifyRelayService( Context context, boolean working )
|
private void notifyRelayService( Context context, boolean working )
|
||||||
{
|
{
|
|
@ -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.
|
@ -58,9 +58,9 @@
|
||||||
<plurals name="confirm_delete_dict_fmt">
|
<plurals name="confirm_delete_dict_fmt">
|
||||||
<item quantity="one">Er du sikker på at du vil slette ordlisten %1$s?</item>
|
<item quantity="one">Er du sikker på at du vil slette ordlisten %1$s?</item>
|
||||||
<item quantity="other">Er du sikker på at du vil slette ordlistene %1$s?</item>
|
<item quantity="other">Er du sikker på at du vil slette ordlistene %1$s?</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
<string name="confirm_deleteonly_dicts_fmt">Sletting av %1$s vil bety at du står uten noen %1$s-ordlister. Ett eller flere spill vil ikke kunne åpnes (til du laster ned en erstatningsliste).</string>
|
<string name="confirm_deleteonly_dicts_fmt">Sletting av %1$s vil bety at du står uten noen %2$s-ordlister. Ett eller flere spill vil ikke kunne åpnes (til du laster ned en erstatningsliste).</string>
|
||||||
|
|
||||||
<string name="button_default_human">Menneske</string>
|
<string name="button_default_human">Menneske</string>
|
||||||
<string name="button_default_robot">Maskin</string>
|
<string name="button_default_robot">Maskin</string>
|
||||||
|
@ -141,7 +141,7 @@
|
||||||
|
|
||||||
<string name="no_moves_made">(Ingen trekk enda)</string>
|
<string name="no_moves_made">(Ingen trekk enda)</string>
|
||||||
|
|
||||||
<string name="invit_expl_bt_fmt">Invitasjon sendt via Blåtann til tilknyttet enhet \"%1½s\" på %2$s</string>
|
<string name="invit_expl_bt_fmt">Invitasjon sendt via Blåtann til tilknyttet enhet \"%1$s\" på %2$s</string>
|
||||||
<string name="invit_expl_notarget_fmt">Invitasjon sendt via %1$s på %2$s. Ukjent mottaker.</string>
|
<string name="invit_expl_notarget_fmt">Invitasjon sendt via %1$s på %2$s. Ukjent mottaker.</string>
|
||||||
|
|
||||||
<string name="relay_alert">Tilkoblingsproblem</string>
|
<string name="relay_alert">Tilkoblingsproblem</string>
|
||||||
|
|
|
@ -27,12 +27,15 @@ getPackage() {
|
||||||
echo $PACK
|
echo $PACK
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# FIXME: not all options require a working directory, e.g. --apk
|
||||||
WD=$(pwd)
|
WD=$(pwd)
|
||||||
while :; do
|
while :; do
|
||||||
if [ -e ${WD}/AndroidManifest.xml -a -e ${WD}/build.xml ]; then
|
if [ -e ${WD}/AndroidManifest.xml -a -e ${WD}/build.xml ]; then
|
||||||
break
|
break
|
||||||
|
elif [ -e ${WD}/app/build.gradle ]; then
|
||||||
|
break
|
||||||
elif [ ${WD} = '/' ]; then
|
elif [ ${WD} = '/' ]; then
|
||||||
usage "reached / without finding AndroidManifest.xml"
|
usage "reached / without finding AndroidManifest.xml or build.gradle"
|
||||||
else
|
else
|
||||||
WD=$(cd $WD/.. && pwd)
|
WD=$(cd $WD/.. && pwd)
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -10,7 +10,7 @@ function printHead() {
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="/xw4mobile.css" />
|
<link rel="stylesheet" type="text/css" href="/xw4mobile.css" />
|
||||||
<title>Crosswords Invite redirect</title>
|
<title>CrossWords Invite redirect</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
|
@ -51,32 +51,31 @@ function printAndroid() {
|
||||||
print <<<EOF
|
print <<<EOF
|
||||||
<div>
|
<div>
|
||||||
<p>You'll have come here after clicking a link in an email or
|
<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>
|
this page.</p>
|
||||||
|
|
||||||
<p>If you got this page on your device, it means either
|
<p>If you got this page on your device, it means either
|
||||||
<ul>
|
<ul>
|
||||||
<li>The copy of Crosswords you have is NOT beta 56 or newer (dating from about Dec. 1, 2012).</li>
|
<li>You don't have CrossWords installed</li>
|
||||||
<li> OR </li>
|
<li>OR</li>
|
||||||
<li> that your copy of Crosswords is new enough <em>BUT</em> that
|
<li>that when you clicked on the link and were asked to choose between a
|
||||||
when you clicked on the link and were asked to choose between a
|
browser and CrossWords you chose the browser.</li>
|
||||||
browser and Crosswords you chose the browser.</li>
|
|
||||||
</ul></p>
|
</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
|
either <a href="market://search?q=pname:org.eehouse.android.xw4">via
|
||||||
the Google Play store</a> or
|
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
|
Sourceforge.net</a>. After the install is finished go back to the
|
||||||
invite email (or text) and tap the link again.</p>
|
invite email (or text) and tap the link again.</p>
|
||||||
|
|
||||||
<p>In the second case, hit your browser's back button, click the
|
<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
|
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
|
<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
|
will allow you to make CrossWords the default. If you do that
|
||||||
Crosswords will be given control of all URLs that start with
|
CrossWords will be given control of all URLs that start with
|
||||||
"http://eehouse.org/and/" -- not all URLs of any type.)</p>
|
"http://eehouse.org/and/" -- not all URLs of any type.)</p>
|
||||||
|
|
||||||
<p>Have fun. And as always, <a href="mailto:xwords@eehouse.org">let
|
<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
|
$(IMG_DEST)/drawable-hdpi/%__gen.png: $(IMG_SRC)/%.svg
|
||||||
convert $(PARAMS) -scale 48x48 $< $@
|
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_apkDir = "xw4/android/"
|
||||||
k_shelfFile = k_filebase + 'xw4/info_shelf_2'
|
k_shelfFile = k_filebase + 'xw4/info_shelf_2'
|
||||||
k_urlbase = "http://eehouse.org"
|
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
|
s_shelf = None
|
||||||
|
|
||||||
g_langs = {'English' : 'en',
|
g_langs = {'English' : 'en',
|
||||||
|
@ -127,7 +114,7 @@ def md5Checksums( sums, filePath ):
|
||||||
if filePath in sums:
|
if filePath in sums:
|
||||||
result = sums[filePath]
|
result = sums[filePath]
|
||||||
else:
|
else:
|
||||||
logging.debug( "opening %s" % (k_filebase + "and_wordlists/" + filePath))
|
# logging.debug( "opening %s" % (k_filebase + "and_wordlists/" + filePath))
|
||||||
try:
|
try:
|
||||||
file = open( k_filebase + "and_wordlists/" + filePath, 'rb' )
|
file = open( k_filebase + "and_wordlists/" + filePath, 'rb' )
|
||||||
md5 = hashlib.md5()
|
md5 = hashlib.md5()
|
||||||
|
@ -158,7 +145,7 @@ def openShelf():
|
||||||
if not k_SUMS in s_shelf: s_shelf[k_SUMS] = {}
|
if not k_SUMS in s_shelf: s_shelf[k_SUMS] = {}
|
||||||
if not k_COUNT in s_shelf: s_shelf[k_COUNT] = 0
|
if not k_COUNT in s_shelf: s_shelf[k_COUNT] = 0
|
||||||
s_shelf[k_COUNT] += 1
|
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():
|
def closeShelf():
|
||||||
global s_shelf
|
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]))
|
result = sorted(apkToCode.keys(), reverse=True, key=lambda file: (apkToCode[file], apkToMtime[file]))
|
||||||
return result
|
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 ):
|
def getVariantDir( name ):
|
||||||
result = ''
|
result = ''
|
||||||
splits = string.split( name, '.' )
|
splits = string.split( name, '.' )
|
||||||
|
@ -271,10 +286,10 @@ def dictVersion( req, name, lang, md5sum ):
|
||||||
closeShelf()
|
closeShelf()
|
||||||
return json.dumps( result )
|
return json.dumps( result )
|
||||||
|
|
||||||
def getApp( params, name ):
|
def getApp( params, name = None, debug = False):
|
||||||
result = None
|
result = None
|
||||||
if k_NAME in params:
|
if k_DEBUG in params: debug = params[k_DEBUG]
|
||||||
name = params[k_NAME]
|
if k_NAME in params: name = params[k_NAME]
|
||||||
if name:
|
if name:
|
||||||
variantDir = getVariantDir( name )
|
variantDir = getVariantDir( name )
|
||||||
# If we're a dev device, always push the latest
|
# If we're a dev device, always push the latest
|
||||||
|
@ -303,16 +318,19 @@ def getApp( params, name ):
|
||||||
result = {k_URL: url}
|
result = {k_URL: url}
|
||||||
logging.debug( result )
|
logging.debug( result )
|
||||||
|
|
||||||
elif k_GVERS in params:
|
elif k_AVERS in params:
|
||||||
gvers = params[k_GVERS]
|
vers = params[k_AVERS]
|
||||||
if k_INSTALLER in params: installer = params[k_INSTALLER]
|
if k_INSTALLER in params: installer = params[k_INSTALLER]
|
||||||
else: installer = ''
|
else: installer = ''
|
||||||
|
|
||||||
logging.debug( "name: %s; installer: %s; gvers: %s"
|
logging.debug( "name: %s; installer: %s; gvers: %s"
|
||||||
% (name, installer, gvers) )
|
% (name, installer, vers) )
|
||||||
if name in k_versions:
|
print "name: %s; installer: %s; vers: %s" % (name, installer, vers)
|
||||||
if k_GVERS in versForName and not gvers == versForName[k_GVERS]:
|
dir = k_filebase + k_apkDir + 'rel/'
|
||||||
result = {k_URL: k_urlbase + '/' + versForName[k_URL]}
|
apk = getNextAfter( dir, name, vers, debug )
|
||||||
|
if apk:
|
||||||
|
apk = apk[len(k_filebase):] # strip fs path
|
||||||
|
result = {k_URL: k_urlbase + '/' + apk}
|
||||||
else:
|
else:
|
||||||
logging.debug(name + " is up-to-date")
|
logging.debug(name + " is up-to-date")
|
||||||
else:
|
else:
|
||||||
|
@ -542,15 +560,13 @@ def getUpdates( req, params ):
|
||||||
result[k_DICTS] = dictsResult
|
result[k_DICTS] = dictsResult
|
||||||
|
|
||||||
# Let's not upgrade strings at the same time as we're upgrading the app
|
# Let's not upgrade strings at the same time as we're upgrading the app
|
||||||
if appResult:
|
# if appResult:
|
||||||
logging.debug( 'skipping xlation upgrade because app being updated' )
|
# logging.debug( 'skipping xlation upgrade because app being updated' )
|
||||||
elif k_XLATEINFO in asJson and k_NAME in asJson and k_STRINGSHASH in asJson:
|
# 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] )
|
# xlateResult = getXlate( asJson[k_XLATEINFO], asJson[k_NAME], asJson[k_STRINGSHASH] )
|
||||||
if xlateResult:
|
# if xlateResult:
|
||||||
logging.debug( xlateResult )
|
# logging.debug( xlateResult )
|
||||||
result[k_XLATEINFO] = xlateResult;
|
# result[k_XLATEINFO] = xlateResult;
|
||||||
else:
|
|
||||||
logging.debug( "NOT FOUND xlate info" )
|
|
||||||
|
|
||||||
result = json.dumps( result )
|
result = json.dumps( result )
|
||||||
# logging.debug( result )
|
# logging.debug( result )
|
||||||
|
@ -564,7 +580,7 @@ def clearShelf():
|
||||||
def usage(msg=None):
|
def usage(msg=None):
|
||||||
if msg: print "ERROR:", msg
|
if msg: print "ERROR:", msg
|
||||||
print "usage:", sys.argv[0], '--get-sums [lang/dict]*'
|
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 ' | --test-get-dicts name lang curSum'
|
||||||
print ' | --list-apks [--path <path/to/apks>] [--debug] --appID org.something'
|
print ' | --list-apks [--path <path/to/apks>] [--debug] --appID org.something'
|
||||||
print ' | --list-dicts'
|
print ' | --list-dicts'
|
||||||
|
@ -574,8 +590,9 @@ def usage(msg=None):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argc = len(sys.argv)
|
argc = len(sys.argv)
|
||||||
if 1 >= argc: usage();
|
if 1 >= argc: usage('too few args')
|
||||||
arg = sys.argv[1]
|
arg = sys.argv[1]
|
||||||
|
args = sys.argv[2:]
|
||||||
if arg == '--clear-shelf':
|
if arg == '--clear-shelf':
|
||||||
clearShelf()
|
clearShelf()
|
||||||
elif arg == '--list-dicts':
|
elif arg == '--list-dicts':
|
||||||
|
@ -589,12 +606,24 @@ def main():
|
||||||
print arg, md5Checksums(dictSums, arg)
|
print arg, md5Checksums(dictSums, arg)
|
||||||
s_shelf[k_SUMS] = dictSums
|
s_shelf[k_SUMS] = dictSums
|
||||||
closeShelf()
|
closeShelf()
|
||||||
elif arg == '--test-get-app':
|
elif arg == '--get-app':
|
||||||
if not 4 == argc: usage()
|
appID = None
|
||||||
params = { k_NAME: sys.argv[2],
|
vers = 0
|
||||||
k_GVERS: sys.argv[3],
|
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':
|
elif arg == '--test-get-dicts':
|
||||||
if not 5 == argc: usage()
|
if not 5 == argc: usage()
|
||||||
params = { k_NAME: sys.argv[2],
|
params = { k_NAME: sys.argv[2],
|
||||||
|
@ -607,7 +636,6 @@ def main():
|
||||||
path = ""
|
path = ""
|
||||||
debug = False
|
debug = False
|
||||||
appID = ''
|
appID = ''
|
||||||
args = sys.argv[2:]
|
|
||||||
while len(args):
|
while len(args):
|
||||||
arg = args.pop(0)
|
arg = args.pop(0)
|
||||||
if arg == '--appID': appID = args.pop(0)
|
if arg == '--appID': appID = args.pop(0)
|
||||||
|
|
|
@ -43,3 +43,8 @@ for SVG in img_src/*.svg; do
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
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 )
|
# define printDims( ldims )
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
|
/* For debugging the special case of square board */
|
||||||
|
// #define FORCE_SQUARE
|
||||||
|
|
||||||
void
|
void
|
||||||
board_figureLayout( BoardCtxt* board, const CurGameInfo* gi,
|
board_figureLayout( BoardCtxt* board, const CurGameInfo* gi,
|
||||||
XP_U16 bLeft, XP_U16 bTop,
|
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 colPctMax, XP_U16 scorePct, XP_U16 trayPct,
|
||||||
XP_U16 scoreWidth, XP_U16 fontWidth, XP_U16 fontHt,
|
XP_U16 scoreWidth, XP_U16 fontWidth, XP_U16 fontHt,
|
||||||
XP_Bool squareTiles, BoardDims* dimsp )
|
XP_Bool squareTiles, BoardDims* dimsp )
|
||||||
|
@ -465,6 +468,14 @@ board_figureLayout( BoardCtxt* board, const CurGameInfo* gi,
|
||||||
XP_U16 wantHt;
|
XP_U16 wantHt;
|
||||||
XP_U16 nToScroll;
|
XP_U16 nToScroll;
|
||||||
|
|
||||||
|
#ifdef FORCE_SQUARE
|
||||||
|
if ( bWidth > bHeight ) {
|
||||||
|
bWidth = bHeight;
|
||||||
|
} else {
|
||||||
|
bHeight = bWidth;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
ldims.left = bLeft;
|
ldims.left = bLeft;
|
||||||
ldims.top = bTop;
|
ldims.top = bTop;
|
||||||
ldims.width = bWidth;
|
ldims.width = bWidth;
|
||||||
|
@ -552,7 +563,13 @@ board_figureLayout( BoardCtxt* board, const CurGameInfo* gi,
|
||||||
|
|
||||||
ldims.boardHt = cellSize * nCells;
|
ldims.boardHt = cellSize * nCells;
|
||||||
ldims.trayTop = ldims.top + scoreHt + (cellSize * (nCells-nToScroll));
|
ldims.trayTop = ldims.top + scoreHt + (cellSize * (nCells-nToScroll));
|
||||||
ldims.height = heightUsed;
|
ldims.height =
|
||||||
|
#ifdef FORCE_SQUARE
|
||||||
|
ldims.width
|
||||||
|
#else
|
||||||
|
heightUsed
|
||||||
|
#endif
|
||||||
|
;
|
||||||
ldims.cellSize = cellSize;
|
ldims.cellSize = cellSize;
|
||||||
|
|
||||||
if ( gi->timerEnabled ) {
|
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. */
|
/* If we already have one, make sure it's the same! Else store. */
|
||||||
gchar buf[64];
|
gchar buf[64];
|
||||||
XP_Bool have = db_fetch( pDb, KEY_RDEVID, buf, sizeof(buf) );
|
XP_Bool have = db_fetch( pDb, KEY_RDEVID, buf, sizeof(buf) )
|
||||||
|
&& 0 == strcmp( buf, devID );
|
||||||
if ( !have ) {
|
if ( !have ) {
|
||||||
db_store( pDb, KEY_RDEVID, devID );
|
db_store( pDb, KEY_RDEVID, devID );
|
||||||
} else {
|
|
||||||
XP_ASSERT( 0 == strcmp( buf, devID ) );
|
|
||||||
}
|
}
|
||||||
(void)g_timeout_add_seconds( maxInterval, keepalive_timer, globals );
|
(void)g_timeout_add_seconds( maxInterval, keepalive_timer, globals );
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -112,6 +112,12 @@ destroyCairo( GtkDrawCtx* dctx )
|
||||||
dctx->_cairo = NULL;
|
dctx->_cairo = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static XP_Bool
|
||||||
|
haveCairo( const GtkDrawCtx* dctx )
|
||||||
|
{
|
||||||
|
return !!dctx->_cairo;
|
||||||
|
}
|
||||||
|
|
||||||
static cairo_t*
|
static cairo_t*
|
||||||
getCairo( const GtkDrawCtx* dctx )
|
getCairo( const GtkDrawCtx* dctx )
|
||||||
{
|
{
|
||||||
|
@ -1231,15 +1237,20 @@ gtk_draw_drawTimer( DrawCtx* p_dctx, const XP_Rect* rInner,
|
||||||
XP_U16 playerNum, XP_S16 secondsLeft )
|
XP_U16 playerNum, XP_S16 secondsLeft )
|
||||||
{
|
{
|
||||||
GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
|
GtkDrawCtx* dctx = (GtkDrawCtx*)p_dctx;
|
||||||
|
XP_Bool hadCairo = haveCairo( dctx );
|
||||||
|
if ( hadCairo || initCairo( dctx ) ) {
|
||||||
XP_UCHAR buf[10];
|
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 );
|
gtkEraseRect( dctx, rInner );
|
||||||
draw_string_at( dctx, NULL, buf, rInner->height-1,
|
draw_string_at( dctx, NULL, buf, rInner->height-1,
|
||||||
rInner, XP_GTK_JUST_CENTER,
|
rInner, XP_GTK_JUST_CENTER,
|
||||||
&dctx->playerColors[playerNum], NULL );
|
&dctx->playerColors[playerNum], NULL );
|
||||||
|
if ( !hadCairo ) {
|
||||||
|
destroyCairo( dctx );
|
||||||
|
}
|
||||||
|
}
|
||||||
} /* gtk_draw_drawTimer */
|
} /* gtk_draw_drawTimer */
|
||||||
|
|
||||||
#ifdef XWFEATURE_MINIWIN
|
#ifdef XWFEATURE_MINIWIN
|
||||||
|
|
|
@ -1577,7 +1577,7 @@ parsePair( const char* optarg, XP_U16* min, XP_U16* max )
|
||||||
} else {
|
} else {
|
||||||
int intmin, intmax;
|
int intmin, intmax;
|
||||||
if ( 2 == sscanf( optarg, "%d:%d", &intmin, &intmax ) ) {
|
if ( 2 == sscanf( optarg, "%d:%d", &intmin, &intmax ) ) {
|
||||||
if ( intmin <= intmin ) {
|
if ( intmin <= intmax ) {
|
||||||
*min = intmin;
|
*min = intmin;
|
||||||
*max = intmax;
|
*max = intmax;
|
||||||
success = true;
|
success = true;
|
||||||
|
@ -2284,6 +2284,7 @@ main( int argc, char** argv )
|
||||||
break;
|
break;
|
||||||
case CMD_PLAYERNAME:
|
case CMD_PLAYERNAME:
|
||||||
index = mainParams.pgi.nPlayers++;
|
index = mainParams.pgi.nPlayers++;
|
||||||
|
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||||
++mainParams.nLocalPlayers;
|
++mainParams.nLocalPlayers;
|
||||||
mainParams.pgi.players[index].robotIQ = 0; /* means human */
|
mainParams.pgi.players[index].robotIQ = 0; /* means human */
|
||||||
mainParams.pgi.players[index].isLocal = XP_TRUE;
|
mainParams.pgi.players[index].isLocal = XP_TRUE;
|
||||||
|
@ -2292,6 +2293,7 @@ main( int argc, char** argv )
|
||||||
break;
|
break;
|
||||||
case CMD_REMOTEPLAYER:
|
case CMD_REMOTEPLAYER:
|
||||||
index = mainParams.pgi.nPlayers++;
|
index = mainParams.pgi.nPlayers++;
|
||||||
|
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||||
mainParams.pgi.players[index].isLocal = XP_FALSE;
|
mainParams.pgi.players[index].isLocal = XP_FALSE;
|
||||||
++mainParams.info.serverInfo.nRemotePlayers;
|
++mainParams.info.serverInfo.nRemotePlayers;
|
||||||
break;
|
break;
|
||||||
|
@ -2302,6 +2304,7 @@ main( int argc, char** argv )
|
||||||
case CMD_ROBOTNAME:
|
case CMD_ROBOTNAME:
|
||||||
++robotCount;
|
++robotCount;
|
||||||
index = mainParams.pgi.nPlayers++;
|
index = mainParams.pgi.nPlayers++;
|
||||||
|
XP_ASSERT( index < MAX_NUM_PLAYERS );
|
||||||
++mainParams.nLocalPlayers;
|
++mainParams.nLocalPlayers;
|
||||||
mainParams.pgi.players[index].robotIQ = 1; /* real smart by default */
|
mainParams.pgi.players[index].robotIQ = 1; /* real smart by default */
|
||||||
mainParams.pgi.players[index].isLocal = XP_TRUE;
|
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,
|
gchar* b64 = g_base64_encode( (const guchar*)buf,
|
||||||
((0 <= nRead)? nRead : 0) );
|
((0 <= nRead)? nRead : 0) );
|
||||||
XP_LOGF( "%s: read %zd bytes ('%s')", __func__, nRead, b64 );
|
XP_LOGF( "%s: read %zd bytes ('%s')", __func__, nRead, b64 );
|
||||||
g_free( b64 );
|
|
||||||
#ifdef COMMS_CHECKSUM
|
#ifdef COMMS_CHECKSUM
|
||||||
gchar* sum = g_compute_checksum_for_data( G_CHECKSUM_MD5, buf, nRead );
|
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 );
|
XP_LOGF( "%s: read %zd bytes ('%s')(sum=%s)", __func__, nRead, b64, sum );
|
||||||
g_free( sum );
|
g_free( sum );
|
||||||
#endif
|
#endif
|
||||||
|
g_free( b64 );
|
||||||
|
|
||||||
if ( 0 <= nRead ) {
|
if ( 0 <= nRead ) {
|
||||||
const XP_U8* ptr = buf;
|
const XP_U8* ptr = buf;
|
||||||
const XP_U8* end = buf + nRead;
|
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
|
# STATIC ?= -static
|
||||||
GITINFO = gitversion.txt
|
GITINFO = gitversion.txt
|
||||||
HASH=$(shell git describe)
|
HASH=$(shell git rev-parse --verify HEAD)
|
||||||
|
|
||||||
OBJ = $(patsubst %.cpp,obj/%.o,$(SRC))
|
OBJ = $(patsubst %.cpp,obj/%.o,$(SRC))
|
||||||
#LDFLAGS += -pthread -g -lmcheck $(STATIC)
|
#LDFLAGS += -pthread -g -lmcheck $(STATIC)
|
||||||
|
@ -70,6 +70,24 @@ endif
|
||||||
|
|
||||||
memdebug all: xwrelay rq
|
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
|
# Manual config in order to place -lpq after the .obj files as
|
||||||
# required by something Ubuntu did upgrading natty to oneiric
|
# required by something Ubuntu did upgrading natty to oneiric
|
||||||
xwrelay: $(OBJ)
|
xwrelay: $(OBJ)
|
||||||
|
|
|
@ -875,13 +875,13 @@ putNetShort( uint8_t** bufpp, unsigned short s )
|
||||||
*bufpp += sizeof(s);
|
*bufpp += sizeof(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
int
|
||||||
CookieRef::store_message( HostID dest, const uint8_t* buf,
|
CookieRef::store_message( HostID dest, const uint8_t* buf,
|
||||||
unsigned int len )
|
unsigned int len )
|
||||||
{
|
{
|
||||||
logf( XW_LOGVERBOSE0, "%s: storing msg size %d for dest %d", __func__,
|
logf( XW_LOGVERBOSE0, "%s: storing msg size %d for dest %d", __func__,
|
||||||
len, dest );
|
len, dest );
|
||||||
DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len );
|
return DBMgr::Get()->StoreMessage( ConnName(), dest, buf, len );
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1044,6 +1044,7 @@ CookieRef::postCheckAllHere()
|
||||||
void
|
void
|
||||||
CookieRef::postDropDevice( HostID hostID )
|
CookieRef::postDropDevice( HostID hostID )
|
||||||
{
|
{
|
||||||
|
logf( XW_LOGINFO, "%s(hostID=%d)", __func__, hostID );
|
||||||
CRefEvent evt( XWE_ACKTIMEOUT );
|
CRefEvent evt( XWE_ACKTIMEOUT );
|
||||||
evt.u.ack.srcID = hostID;
|
evt.u.ack.srcID = hostID;
|
||||||
m_eventQueue.push_back( evt );
|
m_eventQueue.push_back( evt );
|
||||||
|
@ -1192,21 +1193,16 @@ CookieRef::sendAnyStored( const CRefEvent* evt )
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct _StoreData {
|
typedef struct _StoreData {
|
||||||
string connName;
|
int msgID;
|
||||||
HostID dest;
|
|
||||||
uint8_t* buf;
|
|
||||||
int buflen;
|
|
||||||
} StoreData;
|
} StoreData;
|
||||||
|
|
||||||
void
|
void
|
||||||
CookieRef::storeNoAck( bool acked, uint32_t packetID, void* data )
|
CookieRef::storeNoAck( bool acked, uint32_t packetID, void* data )
|
||||||
{
|
{
|
||||||
StoreData* sdata = (StoreData*)data;
|
StoreData* sdata = (StoreData*)data;
|
||||||
if ( !acked ) {
|
if ( acked ) {
|
||||||
DBMgr::Get()->StoreMessage( sdata->connName.c_str(), sdata->dest,
|
DBMgr::Get()->RemoveStoredMessages( &sdata->msgID, 1 );
|
||||||
sdata->buf, sdata->buflen );
|
|
||||||
}
|
}
|
||||||
free( sdata->buf );
|
|
||||||
delete sdata;
|
delete sdata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1237,17 +1233,13 @@ CookieRef::forward_or_store( const CRefEvent* evt )
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t packetID = 0;
|
uint32_t packetID = 0;
|
||||||
|
int msgID = store_message( dest, buf, buflen );
|
||||||
if ( (NULL == destAddr)
|
if ( (NULL == destAddr)
|
||||||
|| !send_with_length( destAddr, dest, buf, buflen, true,
|
|| !send_with_length( destAddr, dest, buf, buflen, true,
|
||||||
&packetID ) ) {
|
&packetID ) ) {
|
||||||
store_message( dest, buf, buflen );
|
} else if ( 0 != msgID && 0 != packetID ) { // sent via UDP
|
||||||
} else if ( 0 != packetID ) { // sent via UDP
|
|
||||||
StoreData* data = new StoreData;
|
StoreData* data = new StoreData;
|
||||||
data->connName = m_connName;
|
data->msgID = msgID;
|
||||||
data->dest = dest;
|
|
||||||
data->buf = (uint8_t*)malloc( buflen );
|
|
||||||
memcpy( data->buf, buf, buflen );
|
|
||||||
data->buflen = buflen;
|
|
||||||
UDPAckTrack::setOnAck( storeNoAck, packetID, data );
|
UDPAckTrack::setOnAck( storeNoAck, packetID, data );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1376,20 +1368,16 @@ CookieRef::sendAllHere( bool initial )
|
||||||
through the vector each time. */
|
through the vector each time. */
|
||||||
HostID dest;
|
HostID dest;
|
||||||
for ( dest = 1; dest <= m_nPlayersSought; ++dest ) {
|
for ( dest = 1; dest <= m_nPlayersSought; ++dest ) {
|
||||||
bool sent = false;
|
|
||||||
*idLoc = dest; /* write in this target's hostId */
|
*idLoc = dest; /* write in this target's hostId */
|
||||||
|
|
||||||
{
|
{
|
||||||
RWReadLock rrl( &m_socketsRWLock );
|
RWReadLock rrl( &m_socketsRWLock );
|
||||||
HostRec* hr = m_sockets[dest-1];
|
HostRec* hr = m_sockets[dest-1];
|
||||||
if ( !!hr ) {
|
if ( !!hr ) {
|
||||||
sent = send_with_length( &hr->m_addr, dest, buf,
|
(void)send_with_length( &hr->m_addr, dest, buf, bufp-buf, true );
|
||||||
bufp-buf, true );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( !sent ) {
|
(void)store_message( dest, buf, bufp-buf );
|
||||||
store_message( dest, buf, bufp-buf );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} /* sendAllHere */
|
} /* sendAllHere */
|
||||||
|
|
||||||
|
|
|
@ -275,8 +275,7 @@ class CookieRef {
|
||||||
|
|
||||||
bool notInUse(void) { return m_cid == 0; }
|
bool notInUse(void) { return m_cid == 0; }
|
||||||
|
|
||||||
void store_message( HostID dest, const uint8_t* buf,
|
int store_message( HostID dest, const uint8_t* buf, unsigned int len );
|
||||||
unsigned int len );
|
|
||||||
void send_stored_messages( HostID dest, const AddrInfo* addr );
|
void send_stored_messages( HostID dest, const AddrInfo* addr );
|
||||||
|
|
||||||
void printSeeds( const char* caller );
|
void printSeeds( const char* caller );
|
||||||
|
|
|
@ -337,7 +337,7 @@ CRefMgr::getMakeCookieRef( const char* connName, const char* cookie,
|
||||||
} /* getMakeCookieRef */
|
} /* getMakeCookieRef */
|
||||||
|
|
||||||
CidInfo*
|
CidInfo*
|
||||||
CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead )
|
CRefMgr::getMakeCookieRef( const char* const connName, HostID hid, bool* isDead )
|
||||||
{
|
{
|
||||||
CookieRef* cref = NULL;
|
CookieRef* cref = NULL;
|
||||||
CidInfo* cinfo = NULL;
|
CidInfo* cinfo = NULL;
|
||||||
|
@ -347,7 +347,7 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead )
|
||||||
int nAlreadyHere = 0;
|
int nAlreadyHere = 0;
|
||||||
|
|
||||||
for ( ; ; ) { /* for: see comment above */
|
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,
|
&curLangCode, &nPlayersT, &nAlreadyHere,
|
||||||
isDead );
|
isDead );
|
||||||
if ( 0 != cid ) { /* already open */
|
if ( 0 != cid ) { /* already open */
|
||||||
|
@ -375,6 +375,48 @@ CRefMgr::getMakeCookieRef( const char* const connName, bool* isDead )
|
||||||
return cinfo;
|
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
|
void
|
||||||
CRefMgr::RemoveSocketRefs( const AddrInfo* addr )
|
CRefMgr::RemoveSocketRefs( const AddrInfo* addr )
|
||||||
{
|
{
|
||||||
|
@ -649,10 +691,14 @@ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid,
|
||||||
nPlayersS, gameSeed, langCode,
|
nPlayersS, gameSeed, langCode,
|
||||||
wantsPublic || makePublic, &isDead );
|
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 ) {
|
if ( NULL == cinfo ) {
|
||||||
logf( XW_LOGINFO, "%s: taking a second crack", __func__ );
|
logf( XW_LOGINFO, "%s: taking a second crack; (cur hid: %d)",
|
||||||
m_hid = HOST_ID_NONE;
|
__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,
|
cinfo = m_mgr->getMakeCookieRef( cookie, nPlayersH, nPlayersS,
|
||||||
langCode, gameSeed, clientIndx,
|
langCode, gameSeed, clientIndx,
|
||||||
wantsPublic, makePublic, &m_seenSeed );
|
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 */
|
/* 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_cinfo( NULL )
|
||||||
, m_mgr( CRefMgr::Get() )
|
, m_mgr( CRefMgr::Get() )
|
||||||
, m_isValid( false )
|
, m_isValid( false )
|
||||||
{
|
{
|
||||||
bool isDead = 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() ) {
|
if ( NULL != cinfo && NULL != cinfo->GetRef() ) {
|
||||||
assert( cinfo->GetCid() == cinfo->GetRef()->GetCid() );
|
assert( cinfo->GetCid() == cinfo->GetRef()->GetCid() );
|
||||||
m_locked = cinfo->GetRef()->Lock();
|
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()
|
SafeCref::~SafeCref()
|
||||||
{
|
{
|
||||||
if ( m_cinfo != NULL ) {
|
if ( m_cinfo != NULL ) {
|
||||||
|
|
|
@ -128,7 +128,8 @@ class CRefMgr {
|
||||||
int nPlayersS, int seed, int langCode,
|
int nPlayersS, int seed, int langCode,
|
||||||
bool isPublic, bool* isDead );
|
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( CookieID cid, bool failOk = false );
|
||||||
CidInfo* getCookieRef( const AddrInfo* addr );
|
CidInfo* getCookieRef( const AddrInfo* addr );
|
||||||
|
@ -179,9 +180,10 @@ class SafeCref {
|
||||||
const AddrInfo* addr, int clientVersion, DevID* devID,
|
const AddrInfo* addr, int clientVersion, DevID* devID,
|
||||||
int nPlayersH, int nPlayersS, unsigned short gameSeed,
|
int nPlayersH, int nPlayersS, unsigned short gameSeed,
|
||||||
int clientIndx, int langCode, bool wantsPublic, bool makePublic );
|
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( CookieID cid, bool failOk = false );
|
||||||
SafeCref( const AddrInfo* addr );
|
SafeCref( const AddrInfo* addr );
|
||||||
|
SafeCref( const AddrInfo::ClientToken clientToken, HostID srcID );
|
||||||
/* SafeCref( CookieRef* cref ); */
|
/* SafeCref( CookieRef* cref ); */
|
||||||
~SafeCref();
|
~SafeCref();
|
||||||
|
|
||||||
|
|
|
@ -70,20 +70,6 @@ DBMgr::DBMgr()
|
||||||
|
|
||||||
pthread_mutex_init( &m_haveNoMessagesMutex, NULL );
|
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 ) );
|
srand( time( NULL ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +122,7 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
||||||
{
|
{
|
||||||
bool found = false;
|
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 "
|
GAMES_TABLE " WHERE connName = '%s' AND nTotal = %d "
|
||||||
"AND %d = seeds[%d] AND 'A' = ack[%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 ) );
|
assert( 1 >= PQntuples( result ) );
|
||||||
found = 1 == PQntuples( result );
|
found = 1 == PQntuples( result );
|
||||||
if ( found ) {
|
if ( found ) {
|
||||||
*cidp = atoi( PQgetvalue( result, 0, 0 ) );
|
int col = 0;
|
||||||
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
*cidp = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
*langP = atoi( PQgetvalue( result, 0, 2 ) );
|
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||||
*isDead = 't' == PQgetvalue( result, 0, 4 )[0];
|
*langP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
|
*isDead = 't' == PQgetvalue( result, 0, col++ )[0];
|
||||||
}
|
}
|
||||||
PQclear( result );
|
PQclear( result );
|
||||||
|
|
||||||
|
@ -160,28 +147,29 @@ DBMgr::FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
||||||
} /* FindGameFor */
|
} /* FindGameFor */
|
||||||
|
|
||||||
CookieID
|
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 )
|
int* langP, int* nPlayersTP, int* nPlayersHP, bool* isDead )
|
||||||
{
|
{
|
||||||
CookieID cid = 0;
|
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'"
|
GAMES_TABLE " WHERE connName = '%s'"
|
||||||
// " LIMIT 1"
|
// " LIMIT 1"
|
||||||
;
|
;
|
||||||
StrWPF query;
|
StrWPF query;
|
||||||
query.catf( fmt, connName );
|
query.catf( fmt, hid, connName );
|
||||||
logf( XW_LOGINFO, "query: %s", query.c_str() );
|
logf( XW_LOGINFO, "query: %s", query.c_str() );
|
||||||
|
|
||||||
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||||
assert( 1 >= PQntuples( result ) );
|
assert( 1 >= PQntuples( result ) );
|
||||||
if ( 1 == PQntuples( result ) ) {
|
if ( 1 == PQntuples( result ) ) {
|
||||||
cid = atoi( PQgetvalue( result, 0, 0 ) );
|
int col = 0;
|
||||||
snprintf( cookieBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
*langP = atoi( PQgetvalue( result, 0, 2 ) );
|
snprintf( roomBuf, roomBufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||||
*nPlayersTP = atoi( PQgetvalue( result, 0, 3 ) );
|
*langP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
*nPlayersHP = atoi( PQgetvalue( result, 0, 4 ) );
|
*nPlayersTP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
*isDead = 't' == PQgetvalue( result, 0, 5 )[0];
|
*nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
|
*isDead = 't' == PQgetvalue( result, 0, col++ )[0];
|
||||||
}
|
}
|
||||||
PQclear( result );
|
PQclear( result );
|
||||||
|
|
||||||
|
@ -189,6 +177,40 @@ DBMgr::FindGame( const char* connName, char* cookieBuf, int bufLen,
|
||||||
return cid;
|
return cid;
|
||||||
} /* FindGame */
|
} /* 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
|
bool
|
||||||
DBMgr::FindPlayer( DevIDRelay relayID, AddrInfo::ClientToken token,
|
DBMgr::FindPlayer( DevIDRelay relayID, AddrInfo::ClientToken token,
|
||||||
string& connName, HostID* hidp, unsigned short* seed )
|
string& connName, HostID* hidp, unsigned short* seed )
|
||||||
|
@ -294,11 +316,13 @@ DBMgr::SeenSeed( const char* cookie, unsigned short seed,
|
||||||
NULL, NULL, 0 );
|
NULL, NULL, 0 );
|
||||||
bool found = 1 == PQntuples( result );
|
bool found = 1 == PQntuples( result );
|
||||||
if ( found ) {
|
if ( found ) {
|
||||||
*cid = atoi( PQgetvalue( result, 0, 0 ) );
|
int col = 0;
|
||||||
*nPlayersHP = here_less_seed( PQgetvalue( result, 0, 2 ),
|
*cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
atoi( PQgetvalue( result, 0, 3 ) ),
|
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||||
seed );
|
|
||||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
const char* seeds = PQgetvalue( result, 0, col++ );
|
||||||
|
int perDeviceSum = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
|
*nPlayersHP = here_less_seed( seeds, perDeviceSum, seed );
|
||||||
}
|
}
|
||||||
PQclear( result );
|
PQclear( result );
|
||||||
logf( XW_LOGINFO, "%s(%4X)=>%s", __func__, seed, found?"true":"false" );
|
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 );
|
NULL, NULL, 0 );
|
||||||
CookieID cid = 0;
|
CookieID cid = 0;
|
||||||
if ( 1 == PQntuples( result ) ) {
|
if ( 1 == PQntuples( result ) ) {
|
||||||
cid = atoi( PQgetvalue( result, 0, 0 ) );
|
int col = 0;
|
||||||
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, 1 ) );
|
cid = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
*nPlayersHP = atoi( PQgetvalue( result, 0, 2 ) );
|
snprintf( connNameBuf, bufLen, "%s", PQgetvalue( result, 0, col++ ) );
|
||||||
|
*nPlayersHP = atoi( PQgetvalue( result, 0, col++ ) );
|
||||||
/* cid may be 0, but should use game anyway */
|
/* cid may be 0, but should use game anyway */
|
||||||
}
|
}
|
||||||
PQclear( result );
|
PQclear( result );
|
||||||
|
@ -699,9 +724,11 @@ DBMgr::RecordSent( const int* msgIDs, int nMsgIDs )
|
||||||
if ( PGRES_TUPLES_OK == PQresultStatus( result ) ) {
|
if ( PGRES_TUPLES_OK == PQresultStatus( result ) ) {
|
||||||
int ntuples = PQntuples( result );
|
int ntuples = PQntuples( result );
|
||||||
for ( int ii = 0; ii < ntuples; ++ii ) {
|
for ( int ii = 0; ii < ntuples; ++ii ) {
|
||||||
RecordSent( PQgetvalue( result, ii, 0 ),
|
int col = 0;
|
||||||
atoi( PQgetvalue( result, ii, 1 ) ),
|
const char* const connName = PQgetvalue( result, ii, col++ );
|
||||||
atoi( PQgetvalue( result, ii, 2 ) ) );
|
HostID hid = atoi( PQgetvalue( result, ii, col++ ) );
|
||||||
|
int nBytes = atoi( PQgetvalue( result, ii, col++ ) );
|
||||||
|
RecordSent( connName, hid, nBytes );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PQclear( result );
|
PQclear( result );
|
||||||
|
@ -1014,43 +1041,51 @@ DBMgr::CountStoredMessages( DevIDRelay relayID )
|
||||||
return getCountWhere( MSGS_TABLE, test );
|
return getCountWhere( MSGS_TABLE, test );
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
int
|
||||||
DBMgr::StoreMessage( DevIDRelay devID, const uint8_t* const buf,
|
DBMgr::StoreMessage( DevIDRelay destDevID, const uint8_t* const buf,
|
||||||
int len )
|
int len )
|
||||||
{
|
{
|
||||||
clearHasNoMessages( devID );
|
int msgID = 0;
|
||||||
|
clearHasNoMessages( destDevID );
|
||||||
|
|
||||||
size_t newLen;
|
size_t newLen;
|
||||||
const char* fmt = "INSERT INTO " MSGS_TABLE " "
|
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;
|
StrWPF query;
|
||||||
if ( m_useB64 ) {
|
if ( m_useB64 ) {
|
||||||
gchar* b64 = g_base64_encode( buf, len );
|
gchar* b64 = g_base64_encode( buf, len );
|
||||||
query.catf( fmt, "msg64", devID, "", b64, len );
|
query.catf( fmt, "msg64", destDevID, "", b64, len );
|
||||||
g_free( b64 );
|
g_free( b64 );
|
||||||
} else {
|
} else {
|
||||||
uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf,
|
uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf,
|
||||||
len, &newLen );
|
len, &newLen );
|
||||||
assert( NULL != bytes );
|
assert( NULL != bytes );
|
||||||
query.catf( fmt, "msg", devID, "E", bytes, len );
|
query.catf( fmt, "msg", destDevID, "E", bytes, len );
|
||||||
PQfreemem( bytes );
|
PQfreemem( bytes );
|
||||||
}
|
}
|
||||||
|
|
||||||
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
|
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
|
||||||
execSql( query );
|
|
||||||
|
PGresult* result = PQexec( getThreadConn(), query.c_str() );
|
||||||
|
if ( 1 == PQntuples( result ) ) {
|
||||||
|
msgID = atoi( PQgetvalue( result, 0, 0 ) );
|
||||||
|
}
|
||||||
|
PQclear( result );
|
||||||
|
return msgID;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
int
|
||||||
DBMgr::StoreMessage( const char* const connName, int hid,
|
DBMgr::StoreMessage( const char* const connName, int destHid,
|
||||||
const uint8_t* buf, int len )
|
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 ) {
|
if ( DEVID_NONE == devID ) {
|
||||||
logf( XW_LOGERROR, "%s: warning: devid not found for connName=%s, "
|
logf( XW_LOGERROR, "%s: warning: devid not found for connName=%s, "
|
||||||
"hid=%d", __func__, connName, hid );
|
"hid=%d", __func__, connName, destHid );
|
||||||
} else {
|
} else {
|
||||||
clearHasNoMessages( devID );
|
clearHasNoMessages( devID );
|
||||||
}
|
}
|
||||||
|
@ -1066,7 +1101,7 @@ DBMgr::StoreMessage( const char* const connName, int hid,
|
||||||
StrWPF query;
|
StrWPF query;
|
||||||
if ( m_useB64 ) {
|
if ( m_useB64 ) {
|
||||||
gchar* b64 = g_base64_encode( buf, len );
|
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 );
|
"", b64, len );
|
||||||
|
|
||||||
query.catf( " WHERE NOT EXISTS (SELECT 1 FROM " MSGS_TABLE
|
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
|
#ifdef HAVE_STIME
|
||||||
" AND stime='epoch'"
|
" AND stime='epoch'"
|
||||||
#endif
|
#endif
|
||||||
" );", connName, hid, b64 );
|
" )", connName, destHid, b64 );
|
||||||
g_free( b64 );
|
g_free( b64 );
|
||||||
} else {
|
} else {
|
||||||
uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf,
|
uint8_t* bytes = PQescapeByteaConn( getThreadConn(), buf,
|
||||||
len, &newLen );
|
len, &newLen );
|
||||||
assert( NULL != bytes );
|
assert( NULL != bytes );
|
||||||
|
|
||||||
query.catf( fmt, "msg", connName, hid, devID, hid, connName,
|
query.catf( fmt, "msg", connName, destHid, devID, destHid, connName,
|
||||||
"E", bytes, len );
|
"E", bytes, len );
|
||||||
PQfreemem( bytes );
|
PQfreemem( bytes );
|
||||||
}
|
}
|
||||||
|
query.catf(" RETURNING id;");
|
||||||
|
|
||||||
logf( XW_LOGINFO, "%s: query: %s", __func__, query.c_str() );
|
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
|
void
|
||||||
|
|
|
@ -75,9 +75,13 @@ class DBMgr {
|
||||||
bool FindRelayIDFor( const char* connName, HostID hid, unsigned short seed,
|
bool FindRelayIDFor( const char* connName, HostID hid, unsigned short seed,
|
||||||
const DevID* host, DevIDRelay* devID );
|
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,
|
int* langP, int* nPlayersTP, int* nPlayersHP,
|
||||||
bool* isDead );
|
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,
|
bool FindGameFor( const char* connName, char* cookieBuf, int bufLen,
|
||||||
unsigned short seed, HostID hid,
|
unsigned short seed, HostID hid,
|
||||||
|
@ -137,8 +141,9 @@ class DBMgr {
|
||||||
/* message storage -- different DB */
|
/* message storage -- different DB */
|
||||||
int CountStoredMessages( const char* const connName );
|
int CountStoredMessages( const char* const connName );
|
||||||
int CountStoredMessages( DevIDRelay relayID );
|
int CountStoredMessages( DevIDRelay relayID );
|
||||||
void StoreMessage( DevIDRelay relayID, const uint8_t* const buf, int len );
|
int StoreMessage( DevIDRelay destRelayID, const uint8_t* const buf,
|
||||||
void StoreMessage( const char* const connName, int hid,
|
int len );
|
||||||
|
int StoreMessage( const char* const connName, int destHid,
|
||||||
const uint8_t* const buf, int len );
|
const uint8_t* const buf, int len );
|
||||||
void GetStoredMessages( DevIDRelay relayID, vector<MsgInfo>& msgs );
|
void GetStoredMessages( DevIDRelay relayID, vector<MsgInfo>& msgs );
|
||||||
void GetStoredMessages( const char* const connName, HostID hid,
|
void GetStoredMessages( const char* const connName, HostID hid,
|
||||||
|
@ -170,6 +175,7 @@ class DBMgr {
|
||||||
int clientVersion, const char* const model,
|
int clientVersion, const char* const model,
|
||||||
const char* const osVers, DevIDRelay relayID );
|
const char* const osVers, DevIDRelay relayID );
|
||||||
|
|
||||||
|
|
||||||
PGconn* getThreadConn( void );
|
PGconn* getThreadConn( void );
|
||||||
void clearThreadConn();
|
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;")
|
echo "Row count:" $(psql -t xwgames -c "select count(*) FROM games $QUERY;")
|
||||||
|
|
||||||
# Games
|
# 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;" \
|
"FROM games $QUERY ORDER BY NOT dead, ctime DESC LIMIT $LIMIT;" \
|
||||||
| psql xwgames
|
| psql xwgames
|
||||||
|
|
||||||
# Messages
|
# Messages
|
||||||
echo "SELECT * "\
|
echo "Unack'd msgs count:" $(psql -t xwgames -c "select count(*) FROM msgs where stime = 'epoch' AND connname IN (SELECT connname from games $QUERY);")
|
||||||
"FROM msgs WHERE 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;" \
|
"ORDER BY ctime DESC, connname LIMIT $LIMIT;" \
|
||||||
| psql xwgames
|
| psql xwgames
|
||||||
|
|
||||||
|
|
|
@ -550,18 +550,18 @@ assemble_packet( vector<uint8_t>& packet, uint32_t* packetIDP, XWRelayReg cmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef LOG_UDP_PACKETS
|
#ifdef LOG_UDP_PACKETS
|
||||||
gsize size = 0;
|
// gsize size = 0;
|
||||||
gint state = 0;
|
// gint state = 0;
|
||||||
gint save = 0;
|
// gint save = 0;
|
||||||
gchar out[1024];
|
// gchar out[1024];
|
||||||
for ( unsigned int ii = 0; ii < iocount; ++ii ) {
|
// for ( unsigned int ii = 0; ii < iocount; ++ii ) {
|
||||||
size += g_base64_encode_step( (const guchar*)vec[ii].iov_base,
|
// size += g_base64_encode_step( (const guchar*)vec[ii].iov_base,
|
||||||
vec[ii].iov_len,
|
// vec[ii].iov_len,
|
||||||
FALSE, &out[size], &state, &save );
|
// FALSE, &out[size], &state, &save );
|
||||||
}
|
// }
|
||||||
size += g_base64_encode_close( FALSE, &out[size], &state, &save );
|
// size += g_base64_encode_close( FALSE, &out[size], &state, &save );
|
||||||
assert( size < sizeof(out) );
|
// assert( size < sizeof(out) );
|
||||||
out[size] = '\0';
|
// out[size] = '\0';
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,8 +640,10 @@ send_via_udp_impl( int sock, const struct sockaddr* dest_addr,
|
||||||
#ifdef LOG_UDP_PACKETS
|
#ifdef LOG_UDP_PACKETS
|
||||||
gchar* b64 = g_base64_encode( (uint8_t*)dest_addr,
|
gchar* b64 = g_base64_encode( (uint8_t*)dest_addr,
|
||||||
sizeof(*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,
|
logf( XW_LOGINFO, "%s()=>%d; addr='%s'; msg='%s'", __func__, nSent,
|
||||||
b64, out );
|
b64, out );
|
||||||
|
g_free( out );
|
||||||
g_free( b64 );
|
g_free( b64 );
|
||||||
#else
|
#else
|
||||||
logf( XW_LOGINFO, "%s()=>%d", __func__, nSent );
|
logf( XW_LOGINFO, "%s()=>%d", __func__, nSent );
|
||||||
|
@ -760,15 +762,19 @@ send_havemsgs( const AddrInfo* addr )
|
||||||
|
|
||||||
class MsgClosure {
|
class MsgClosure {
|
||||||
public:
|
public:
|
||||||
MsgClosure( DevIDRelay devid, const vector<uint8_t>* packet,
|
MsgClosure( DevIDRelay dest, const vector<uint8_t>* packet,
|
||||||
OnMsgAckProc proc, void* procClosure )
|
int msgID, OnMsgAckProc proc, void* procClosure )
|
||||||
{
|
{
|
||||||
m_devid = devid;
|
assert(m_msgID != 0);
|
||||||
|
m_destDevID = dest;
|
||||||
m_packet = *packet;
|
m_packet = *packet;
|
||||||
m_proc = proc;
|
m_proc = proc;
|
||||||
m_procClosure = procClosure;
|
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;
|
vector<uint8_t> m_packet;
|
||||||
OnMsgAckProc m_proc;
|
OnMsgAckProc m_proc;
|
||||||
void* m_procClosure;
|
void* m_procClosure;
|
||||||
|
@ -778,22 +784,29 @@ static void
|
||||||
onPostedMsgAcked( bool acked, uint32_t packetID, void* data )
|
onPostedMsgAcked( bool acked, uint32_t packetID, void* data )
|
||||||
{
|
{
|
||||||
MsgClosure* mc = (MsgClosure*)data;
|
MsgClosure* mc = (MsgClosure*)data;
|
||||||
if ( !acked ) {
|
int msgID = mc->getMsgID();
|
||||||
DBMgr::Get()->StoreMessage( mc->m_devid, mc->m_packet.data(),
|
if ( acked ) {
|
||||||
mc->m_packet.size() );
|
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 ) {
|
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;
|
delete mc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
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 )
|
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 canSendNow = !!addru;
|
||||||
|
|
||||||
bool sent = false;
|
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 ) ) {
|
if ( get_addr_info_if( &addr, &sock, &dest_addr ) ) {
|
||||||
sent = 0 < send_packet_via_udp_impl( packet, sock, dest_addr );
|
sent = 0 < send_packet_via_udp_impl( packet, sock, dest_addr );
|
||||||
|
|
||||||
if ( sent ) {
|
if ( sent && msgID != 0 ) {
|
||||||
MsgClosure* mc = new MsgClosure( devid, &packet,
|
MsgClosure* mc = new MsgClosure( destDevID, &packet, msgID,
|
||||||
proc, procClosure );
|
proc, procClosure );
|
||||||
UDPAckTrack::setOnAck( onPostedMsgAcked, packetID, (void*)mc );
|
UDPAckTrack::setOnAck( onPostedMsgAcked, packetID, (void*)mc );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( !sent ) {
|
|
||||||
DBMgr::Get()->StoreMessage( devid, packet.data(), packet.size() );
|
|
||||||
}
|
|
||||||
return sent;
|
return sent;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
post_message( DevIDRelay devid, const char* message, OnMsgAckProc proc,
|
post_message( DevIDRelay destDevID, const char* message, OnMsgAckProc proc,
|
||||||
void* procClosure )
|
void* procClosure )
|
||||||
{
|
{
|
||||||
vector<uint8_t> packet;
|
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,
|
assemble_packet( packet, &packetID, XWPDEV_ALERT, lenbuf, lenlen,
|
||||||
message, len, NULL );
|
message, len, NULL );
|
||||||
|
|
||||||
return post_or_store( devid, packet, packetID, proc, procClosure );
|
return post_or_store( destDevID, packet, packetID, proc, procClosure );
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -988,13 +998,13 @@ processReconnect( const uint8_t* bufp, int bufLen, const AddrInfo* addr )
|
||||||
} /* processReconnect */
|
} /* processReconnect */
|
||||||
|
|
||||||
static bool
|
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;
|
bool success = false;
|
||||||
const uint8_t* end = bufp + bufLen;
|
const uint8_t* end = bufp + bufLen;
|
||||||
HostID srcID;
|
HostID srcID;
|
||||||
if ( getNetByte( &bufp, end, &srcID ) ) {
|
if ( getNetByte( &bufp, end, &srcID ) ) {
|
||||||
SafeCref scr( addr );
|
SafeCref scr( clientToken, srcID );
|
||||||
success = scr.HandleAck( srcID );
|
success = scr.HandleAck( srcID );
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
|
@ -1084,7 +1094,8 @@ forwardMessage( const uint8_t* buf, int buflen, const AddrInfo* addr )
|
||||||
} /* forwardMessage */
|
} /* forwardMessage */
|
||||||
|
|
||||||
static bool
|
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 */
|
bool success = false; /* default is failure */
|
||||||
XWRELAY_Cmd cmd = *buf;
|
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 );
|
success = processReconnect( buf+1, bufLen-1, addr );
|
||||||
break;
|
break;
|
||||||
case XWRELAY_ACK:
|
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;
|
break;
|
||||||
case XWRELAY_GAME_DISCONNECT:
|
case XWRELAY_GAME_DISCONNECT:
|
||||||
success = processDisconnect( buf+1, bufLen-1, addr );
|
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 );
|
logf( XW_LOGVERBOSE0, "%s: wrote %d bytes", __func__, nwritten );
|
||||||
if ( sendFull && nwritten >= 0 && (size_t)nwritten == out.size() ) {
|
if ( sendFull && nwritten >= 0 && (size_t)nwritten == out.size() ) {
|
||||||
dbmgr->RecordSent( &msgIDs[0], msgIDs.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 );
|
dbmgr->RemoveStoredMessages( msgIDs );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1438,7 +1456,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const uint8_t* bufp,
|
||||||
}
|
}
|
||||||
unsigned short nMsgs;
|
unsigned short nMsgs;
|
||||||
if ( getNetShort( &bufp, end, &nMsgs ) ) {
|
if ( getNetShort( &bufp, end, &nMsgs ) ) {
|
||||||
SafeCref scr( connName );
|
SafeCref scr( connName, hid );
|
||||||
while ( scr.IsValid() && nMsgs-- > 0 ) {
|
while ( scr.IsValid() && nMsgs-- > 0 ) {
|
||||||
unsigned short len;
|
unsigned short len;
|
||||||
if ( getNetShort( &bufp, end, &len ) ) {
|
if ( getNetShort( &bufp, end, &len ) ) {
|
||||||
|
@ -1460,7 +1478,7 @@ handleProxyMsgs( int sock, const AddrInfo* addr, const uint8_t* bufp,
|
||||||
static void
|
static void
|
||||||
game_thread_proc( UdpThreadClosure* utc )
|
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() );
|
XWThreadPool::GetTPool()->CloseSocket( utc->addr() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1528,7 +1546,7 @@ proxy_thread_proc( UdpThreadClosure* utc )
|
||||||
sizeof( connName ), &hid ) ) {
|
sizeof( connName ), &hid ) ) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
SafeCref scr( connName );
|
SafeCref scr( connName, hid );
|
||||||
scr.DeviceGone( hid, seed );
|
scr.DeviceGone( hid, seed );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1748,7 +1766,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
||||||
clientToken = ntohl( clientToken );
|
clientToken = ntohl( clientToken );
|
||||||
if ( AddrInfo::NULL_TOKEN != clientToken ) {
|
if ( AddrInfo::NULL_TOKEN != clientToken ) {
|
||||||
AddrInfo addr( g_udpsock, clientToken, utc->saddr() );
|
AddrInfo addr( g_udpsock, clientToken, utc->saddr() );
|
||||||
(void)processMessage( ptr, end - ptr, &addr );
|
(void)processMessage( ptr, end - ptr, &addr, clientToken );
|
||||||
} else {
|
} else {
|
||||||
logf( XW_LOGERROR, "%s: dropping packet with token of 0",
|
logf( XW_LOGERROR, "%s: dropping packet with token of 0",
|
||||||
__func__ );
|
__func__ );
|
||||||
|
@ -1766,7 +1784,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
||||||
logf( XW_LOGERROR, "parse failed!!!" );
|
logf( XW_LOGERROR, "parse failed!!!" );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
SafeCref scr( connName );
|
SafeCref scr( connName, hid );
|
||||||
if ( scr.IsValid() ) {
|
if ( scr.IsValid() ) {
|
||||||
AddrInfo addr( g_udpsock, clientToken, utc->saddr() );
|
AddrInfo addr( g_udpsock, clientToken, utc->saddr() );
|
||||||
handlePutMessage( scr, hid, &addr, end - ptr, &ptr, end );
|
handlePutMessage( scr, hid, &addr, end - ptr, &ptr, end );
|
||||||
|
@ -1833,7 +1851,7 @@ handle_udp_packet( UdpThreadClosure* utc )
|
||||||
string connName;
|
string connName;
|
||||||
if ( DBMgr::Get()->FindPlayer( devID.asRelayID(), clientToken,
|
if ( DBMgr::Get()->FindPlayer( devID.asRelayID(), clientToken,
|
||||||
connName, &hid, &seed ) ) {
|
connName, &hid, &seed ) ) {
|
||||||
SafeCref scr( connName.c_str() );
|
SafeCref scr( connName.c_str(), hid );
|
||||||
scr.DeviceGone( hid, seed );
|
scr.DeviceGone( hid, seed );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1980,7 +1998,7 @@ maint_str_loop( int udpsock, const char* str )
|
||||||
} // maint_str_loop
|
} // maint_str_loop
|
||||||
|
|
||||||
static uint32_t
|
static uint32_t
|
||||||
getIPAddr( void )
|
getUDPIPAddr( void )
|
||||||
{
|
{
|
||||||
uint32_t result = INADDR_ANY;
|
uint32_t result = INADDR_ANY;
|
||||||
char iface[16] = {0};
|
char iface[16] = {0};
|
||||||
|
@ -2215,7 +2233,7 @@ main( int argc, char** argv )
|
||||||
struct sockaddr_in saddr;
|
struct sockaddr_in saddr;
|
||||||
g_udpsock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
|
g_udpsock = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
|
||||||
saddr.sin_family = PF_INET;
|
saddr.sin_family = PF_INET;
|
||||||
saddr.sin_addr.s_addr = getIPAddr();
|
saddr.sin_addr.s_addr = getUDPIPAddr();
|
||||||
saddr.sin_port = htons(udpport);
|
saddr.sin_port = htons(udpport);
|
||||||
int err = bind( g_udpsock, (struct sockaddr*)&saddr, sizeof(saddr) );
|
int err = bind( g_udpsock, (struct sockaddr*)&saddr, sizeof(saddr) );
|
||||||
if ( 0 == err ) {
|
if ( 0 == err ) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ LOGFILE=/tmp/xwrelay_log_$$.txt
|
||||||
date > $LOGFILE
|
date > $LOGFILE
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "usage: $0 start | stop | restart | mkdb"
|
echo "usage: $0 start | stop | restart | mkdb | debs_install"
|
||||||
}
|
}
|
||||||
|
|
||||||
make_db() {
|
make_db() {
|
||||||
|
@ -28,7 +28,7 @@ make_db() {
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
createdb $DBNAME
|
createdb $DBNAME
|
||||||
cat | psql $DBNAME --file - <<EOF
|
cat <<-EOF | psql $DBNAME --file -
|
||||||
create or replace function sum_array( DECIMAL [] )
|
create or replace function sum_array( DECIMAL [] )
|
||||||
returns decimal
|
returns decimal
|
||||||
as \$\$
|
as \$\$
|
||||||
|
@ -40,7 +40,7 @@ from generate_series(
|
||||||
\$\$ language sql immutable;
|
\$\$ language sql immutable;
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat | psql $DBNAME --file - <<EOF
|
cat <<-EOF | psql $DBNAME --file -
|
||||||
CREATE TABLE games (
|
CREATE TABLE games (
|
||||||
cid integer
|
cid integer
|
||||||
,room VARCHAR(32)
|
,room VARCHAR(32)
|
||||||
|
@ -62,7 +62,7 @@ cid integer
|
||||||
);
|
);
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat | psql $DBNAME --file - <<EOF
|
cat <<-EOF | psql $DBNAME --file -
|
||||||
CREATE TABLE msgs (
|
CREATE TABLE msgs (
|
||||||
id SERIAL
|
id SERIAL
|
||||||
,connName VARCHAR(64)
|
,connName VARCHAR(64)
|
||||||
|
@ -78,7 +78,7 @@ id SERIAL
|
||||||
);
|
);
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat | psql $DBNAME --file - <<EOF
|
cat <<-EOF | psql $DBNAME --file -
|
||||||
CREATE TABLE devices (
|
CREATE TABLE devices (
|
||||||
id INTEGER UNIQUE PRIMARY KEY
|
id INTEGER UNIQUE PRIMARY KEY
|
||||||
,devTypes INTEGER[]
|
,devTypes INTEGER[]
|
||||||
|
@ -114,6 +114,10 @@ do_start() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_debs() {
|
||||||
|
sudo apt-get install postgresql-client postgresql
|
||||||
|
}
|
||||||
|
|
||||||
case $1 in
|
case $1 in
|
||||||
|
|
||||||
stop)
|
stop)
|
||||||
|
@ -149,6 +153,9 @@ case $1 in
|
||||||
make_db
|
make_db
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
debs_install)
|
||||||
|
install_debs
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
@ -52,8 +52,8 @@ void send_havemsgs( const AddrInfo* addr );
|
||||||
|
|
||||||
typedef void (*OnMsgAckProc)( bool acked, DevIDRelay devid, uint32_t packetID,
|
typedef void (*OnMsgAckProc)( bool acked, DevIDRelay devid, uint32_t packetID,
|
||||||
void* data );
|
void* data );
|
||||||
bool post_message( DevIDRelay devid, const char* message, OnMsgAckProc proc,
|
bool post_message( DevIDRelay destDevID, const char* message,
|
||||||
void* data );
|
OnMsgAckProc proc, void* data );
|
||||||
void post_upgrade( DevIDRelay devid );
|
void post_upgrade( DevIDRelay devid );
|
||||||
|
|
||||||
time_t uptime(void);
|
time_t uptime(void);
|
||||||
|
|
Loading…
Add table
Reference in a new issue