Merge branch 'android_branch' into android_gcm

Conflicts:
	xwords4/android/XWords4/res/values/common_rsrc.xml
This commit is contained in:
Eric House 2012-10-30 07:10:42 -07:00
commit c93dfd6605
33 changed files with 416 additions and 135 deletions

View file

@ -22,7 +22,7 @@
to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.eehouse.android.xw4"
android:versionCode="44"
android:versionCode="45"
android:versionName="@string/app_version"
>

View file

@ -64,6 +64,49 @@
/>
</LinearLayout>
<!-- networked game -->
<TextView style="@style/config_separator"
android:layout_marginTop="10dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/newgame_networked_header"
/>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:layout_marginBottom="10dip"
>
<ImageView android:src="@drawable/relaygame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_gravity="center_vertical|center_horizontal"
/>
<TextView android:text="@string/newgame_networked_desc"
style="@style/relay_explain"
/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<Button android:id="@+id/newgame_invite"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_invite"
/>
<Button android:id="@+id/newgame_net_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_net_config"
/>
</LinearLayout>
<!-- SMS game -->
<TextView android:id="@+id/sms_separator"
style="@style/config_separator"
@ -132,49 +175,6 @@
</LinearLayout>
</LinearLayout>
<!-- networked game -->
<TextView style="@style/config_separator"
android:layout_marginTop="10dip"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/newgame_networked_header"
/>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:layout_marginBottom="10dip"
>
<ImageView android:src="@drawable/relaygame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_gravity="center_vertical|center_horizontal"
/>
<TextView android:text="@string/newgame_networked_desc"
style="@style/relay_explain"
/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<Button android:id="@+id/newgame_invite"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_invite"
/>
<Button android:id="@+id/newgame_net_config"
android:layout_width="fill_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="@string/newgame_net_config"
/>
</LinearLayout>
<!-- Bluetooth -->
<TextView android:id="@+id/bt_separator"
style="@style/config_separator"

View file

@ -5,16 +5,34 @@
</style>
</head>
<body>
<b>Crosswords 4.4 beta 52 release</b>
<b>Crosswords 4.4 beta 53 release</b>
<ul>
<li>Remember wordlist browser position, word size, etc.</li>
<li>Fix wordlist browser bugs for languages with more than one
letter on a tile</li>
<li>Add a second way devices can play: via SMS. If you and your
friends have unlimited texting plans (ONLY IF!!) this is the way to
go: connecting is easier, moves get transmitted more quickly, and
less battery is required. <a
href="http://xwords.sf.net/and_faq.php">See the FAQ</a></li>
<li>New word lookup URLs for Catalan language lists</li>
<li>In the main games-list screen, replace green highlighting of the
player whose turn it is with green transitioning to red over three
days. That way you can see how long it's been that person's turn.
Nothing happens -- yet -- when the three days is up. Maybe later
that'll force a resignation.</li>
<li>Display wordlist comment if present</li>
<li>Download wordlists without using the browser when possible</li>
<li>Warn when you join a network game with a different wordlist from
the host's, and give a chance to download and switch.</li>
<li>Don't allow networked games to have different wordlist for each
player. That feature is for local games only now.</li>
<li>There's now a preference for where downloaded wordlists are
stored instead of a question asked each time.</li>
<li>Recast game-over feature as "resigning".</li>
</ul>
<p>Please remember that this is beta software. Please let me know (at

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_version">4.4 beta 52</string>
<string name="app_version">4.4 beta 53</string>
</resources>

View file

@ -69,6 +69,7 @@
<string name="key_connstat_data">key_connstat_data</string>
<string name="key_dev_id">key_dev_id</string>
<string name="key_gcm_regid">key_gcm_regid</string>
<string name="key_checked_sms">key_checked_sms</string>
<string name="key_notagain_sync">key_notagain_sync</string>
<string name="key_notagain_chat">key_notagain_chat</string>

View file

@ -1786,8 +1786,9 @@
<!-- Another paragraph in the about dialog -->
<string name="about_web">For a manual or sourcecode see:
http://xwords.sf.net. To report bugs, suggest features, offer to
help, etc., please email: xwords@eehouse.org.</string>
http://xwords.sf.net/android.php. To report bugs, suggest
features, offer to help, etc., please email:
xwords@eehouse.org.</string>
<!-- Empty in English, this should contain the name of the
translator/creator of the strings.xml file for this
@ -2074,7 +2075,7 @@
phones with unlimited texting plans. Once you enable it dozens of
SMS (text) messages will be sent (invisibly) for each game
played. If you don\'t have an unlimited plan your carrier may
charge you for each and every one!\n\nShould play via SMS be
charge you for each and every message!\n\nShould play via SMS be
enabled?</string>
<!-- -->
<string name="confirm_sms_prompt">Enable play via SMS?</string>
@ -2140,4 +2141,5 @@
<string name="default_loc">Store wordlists internally</string>
<string name="default_loc_summary">(Not in external/sdcard memory)</string>
<string name="sms_searching_toast">Searching SMS messages...</string>
</resources>

View file

@ -101,6 +101,7 @@ public class BoardActivity extends XWActivity
private static final int VALUES_ACTION = 16;
private static final int BT_PICK_ACTION = 17;
private static final int SMS_PICK_ACTION = 18;
private static final int SMS_CONFIG_ACTION = 19;
private static final String DLG_TITLE = "DLG_TITLE";
private static final String DLG_TITLESTR = "DLG_TITLESTR";
@ -787,7 +788,7 @@ public class BoardActivity extends XWActivity
case R.id.board_menu_file_prefs:
m_firingPrefs = true;
startActivity( new Intent( this, PrefsActivity.class ) );
Utils.launchSettings( this );
break;
case R.id.board_menu_file_about:
@ -836,6 +837,10 @@ public class BoardActivity extends XWActivity
GameUtils.launchSMSInviter( this, m_nMissingPlayers,
SMS_INVITE_RESULT );
break;
case SMS_CONFIG_ACTION:
Utils.launchSettings( this );
break;
case COMMIT_ACTION:
cmd = JNICmd.CMD_COMMIT;
break;
@ -1904,7 +1909,9 @@ public class BoardActivity extends XWActivity
switch( m_connType ) {
case COMMS_CONN_SMS:
if ( XWApp.SMSSUPPORTED && !XWPrefs.getSMSEnabled( this ) ) {
showOKOnlyDialog( R.string.warn_sms_disabled );
showConfirmThen( R.string.warn_sms_disabled,
R.string.newgame_enable_sms,
SMS_CONFIG_ACTION );
}
break;
}

View file

@ -404,7 +404,7 @@ public class GameUtils {
public static long saveNew( Context context, CurGameInfo gi )
{
long rowid = -1;
long rowid = DBUtils.ROWID_NOTFOUND;
byte[] bytes = XwJNI.gi_to_stream( gi );
if ( null != bytes ) {
GameLock lock = DBUtils.saveNewGame( context, bytes );
@ -438,9 +438,11 @@ public class GameUtils {
Assert.assertTrue( gi.nPlayers == nPlayersT );
rowid = saveNew( context, gi );
GameLock lock = new GameLock( rowid, true ).lock();
applyChanges( context, gi, addr, inviteID, lock, false );
lock.unlock();
if ( DBUtils.ROWID_NOTFOUND != rowid ) {
GameLock lock = new GameLock( rowid, true ).lock();
applyChanges( context, gi, addr, inviteID, lock, false );
lock.unlock();
}
return rowid;
}

View file

@ -587,8 +587,7 @@ public class GamesList extends XWListActivity
break;
case R.id.gamel_menu_prefs:
intent = new Intent( this, PrefsActivity.class );
startActivity( intent );
Utils.launchSettings( this );
break;
case R.id.gamel_menu_about:

View file

@ -39,6 +39,7 @@ public class PrefsActivity extends PreferenceActivity
private String m_keyLogging;
private String m_smsToasting;
private String m_smsEnable;
@Override
protected Dialog onCreateDialog( int id )
@ -118,6 +119,7 @@ public class PrefsActivity extends PreferenceActivity
m_keyLogging = getString( R.string.key_logging_on );
m_smsToasting = getString( R.string.key_show_sms );
m_smsEnable = getString( R.string.key_enable_sms );
Button button = (Button)findViewById( R.id.revert_colors );
button.setOnClickListener( new View.OnClickListener() {
@ -156,6 +158,12 @@ public class PrefsActivity extends PreferenceActivity
DbgUtils.logEnable( sp.getBoolean( key, false ) );
} else if ( key.equals( m_smsToasting ) ) {
SMSService.smsToastEnable( sp.getBoolean( key, false ) );
} else if ( key.equals( m_smsEnable ) ) {
if ( sp.getBoolean( key, true ) ) {
SMSService.checkForInvites( this );
} else {
XWPrefs.setHaveCheckedSMS( this, false );
}
}
}
@ -166,7 +174,7 @@ public class PrefsActivity extends PreferenceActivity
// Now replace this activity with a new copy
// so the new values get loaded.
startActivity( new Intent( this, PrefsActivity.class ) );
Utils.launchSettings( this );
finish();
}

View file

@ -24,6 +24,7 @@ import android.app.Activity;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@ -76,7 +77,6 @@ public class SMSService extends Service {
private static Boolean s_showToasts = null;
private static MultiService s_srcMgr = null;
private static boolean s_dbCheckPending = true;
// All messages are base64-encoded byte arrays. The first byte is
// always one of these. What follows depends.
@ -101,7 +101,8 @@ public class SMSService extends Service {
}
}
public static void handleFrom( Context context, String buffer, String phone )
public static void handleFrom( Context context, String buffer,
String phone )
{
Intent intent = getIntentTo( context, HANDLE );
intent.putExtra( BUFFER, buffer );
@ -230,15 +231,14 @@ public class SMSService extends Service {
int cmd = intent.getIntExtra( CMD_STR, -1 );
switch( cmd ) {
case CHECK_MSGDB:
if ( s_dbCheckPending ) {
if ( Utils.firstBootEver( this ) ) {
new Thread( new Runnable() {
if ( ! XWPrefs.getHaveCheckedSMS( this ) ) {
Utils.showToast( this, R.string.sms_searching_toast );
XWPrefs.setHaveCheckedSMS( this, true );
new Thread( new Runnable() {
public void run() {
checkMsgDB();
}
} ).start();
}
s_dbCheckPending = false;
}
break;
case HANDLE:
@ -288,7 +288,7 @@ public class SMSService extends Service {
result = Service.START_STICKY_COMPATIBILITY;
}
return result;
}
} // onStartCommand
@Override
public IBinder onBind( Intent intent )
@ -632,25 +632,27 @@ public class SMSService extends Service {
private void checkMsgDB()
{
Uri uri = Uri.parse( "content://sms/inbox" );
ContentResolver resolver = getContentResolver();
String[] columns = new String[] { "body","address" };
Cursor cursor = getContentResolver().query( uri, columns,
null, null, null );
try {
int bodyIndex = cursor.getColumnIndexOrThrow( "body" );
int phoneIndex = cursor.getColumnIndexOrThrow( "address" );
while ( cursor.moveToNext() ) {
String msg = fromPublicFmt( cursor.getString( bodyIndex ) );
if ( null != msg ) {
String number = cursor.getString( phoneIndex );
if ( null != number ) {
handleFrom( this, msg, number );
Cursor cursor = resolver.query( uri, columns, null, null, null );
if ( 0 < cursor.getCount() ) {
try {
int bodyIndex = cursor.getColumnIndexOrThrow( "body" );
int phoneIndex = cursor.getColumnIndexOrThrow( "address" );
while ( cursor.moveToNext() ) {
String msg = fromPublicFmt( cursor.getString( bodyIndex ) );
if ( null != msg ) {
String number = cursor.getString( phoneIndex );
if ( null != number ) {
handleFrom( this, msg, number );
}
}
}
} catch ( Exception ee ) {
DbgUtils.loge( ee );
}
cursor.close();
} catch ( Exception ee ) {
DbgUtils.loge( ee );
}
cursor.close();
}
private void sendResult( MultiEvent event, Object ... args )

View file

@ -153,6 +153,11 @@ public class XWActivity extends Activity
m_delegate.showConfirmThen( msg, action );
}
protected void showConfirmThen( int msg, int posButton, int action )
{
m_delegate.showConfirmThen( getString(msg), posButton, action );
}
public void showEmailOrSMSThen( int action )
{
m_delegate.showEmailOrSMSThen( action );

View file

@ -204,6 +204,16 @@ public class XWPrefs {
return getPrefsBoolean( context, R.string.key_default_loc, true );
}
public static boolean getHaveCheckedSMS( Context context )
{
return getPrefsBoolean( context, R.string.key_checked_sms, false );
}
public static void setHaveCheckedSMS( Context context, boolean newValue )
{
setPrefsBoolean( context, R.string.key_checked_sms, newValue );
}
public static DictUtils.DictLoc getDefaultLoc( Context context )
{
boolean internal = getDefaultLocInternal( context );

58
xwords4/android/scripts/a.py Executable file
View file

@ -0,0 +1,58 @@
#!/usr/bin/python
from mod_python import apache
def a(req):
return """
<html>
<head>
<link rel="stylesheet" type="text/css" href="/xw4mobile.css" />
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-15">
<title>What gibberish is my friend sending me?</title>
</head>
<body>
<h3>Gibberish Text explained</h3>
<p>You probably reached this page because a friend sent you a text
message with this URL and a bunch of gibberish.</p>
<p>Well, that gibberish was meant for an Android app called
<a href="http://xwords.sf.net/android.php">Crosswords</a>, and your
friend hoped you had it installed and would want to play. If you
like,
<span style="color:red">
AND YOU HAVE AN UNLIMITED TEXT MESSAGING PLAN,
</span>
you can download it via the button below. It's free (costs you neither
money nor time with annoying advertising) and open-source -- and
besides, your friend is recommending it. :-)</p>
<div>
<form method="get" action="http://eehouse.org/market_redir.php">
<input type="submit" value="Install Crosswords via Google Play"/>
</form>
</div>
<p>(If you don't have access to the Google Play store from your
device you can
instead <a href="http://eehouse.org/sms_latest.apk">download
Crosswords directly</a>.)
</p>
<p style="color:red"> Do not play Crosswords via SMS unless you
have an unlimited Text Messaging plan with your cellular carrier.
When communicating via SMS Crosswords can send and receive 50-100
messages in the course of a single game. If each one costs you 25 or
50 cents -- well, do the math.</p>
<p>If you don&apos;t have an unlimited texting plan, you can
still <a href="http://eehouse.org/market_redir.php">install
Crosswords</a> and play with your friend over the internet. Just
don't enable the SMS feature. (And don't worry: that's impossible
to do accidentally.)</p>
</body> </html>
"""

View file

@ -185,6 +185,11 @@ static XP_Bool sendNoConn( CommsCtxt* comms,
const MsgQueueElem* elem, XWHostID destID );
static XWHostID getDestID( CommsCtxt* comms, XP_PlayerAddr channelNo );
static void set_reset_timer( CommsCtxt* comms );
# ifdef XWFEATURE_DEVID
static void putDevID( const CommsCtxt* comms, XWStreamCtxt* stream );
# else
# define putDevID( comms, stream )
# endif
# ifdef DEBUG
static const char* relayCmdToStr( XWRELAY_Cmd cmd );
# endif
@ -2120,6 +2125,7 @@ msg_to_stream( CommsCtxt* comms, XWRELAY_Cmd cmd, XWHostID destID,
stream_putU8( stream, comms->r.nPlayersTotal );
stream_putU16( stream, comms_getChannelSeed(comms) );
stream_putU8( stream, comms->util->gameInfo->dictLang );
putDevID( comms, stream );
set_relay_state( comms, COMMS_RELAYSTATE_CONNECT_PENDING );
break;
@ -2292,6 +2298,27 @@ relayDisconnect( CommsCtxt* comms )
set_relay_state( comms, COMMS_RELAYSTATE_UNCONNECTED );
}
} /* relayDisconnect */
#ifdef XWFEATURE_DEVID
static void
putDevID( const CommsCtxt* comms, XWStreamCtxt* stream )
{
# if XWRELAY_PROTO_VERSION >= XWRELAY_PROTO_VERSION_CLIENTID
DevIDType typ;
const XP_UCHAR* devID = util_getDevID( comms->util, &typ );
stream_putU8( stream, typ );
if ( ID_TYPE_NONE != typ ) {
stream_catString( stream, devID );
stream_putU8( stream, '\0' );
}
# else
XP_ASSERT(0);
XP_USE(comms);
XP_USE(stream);
# endif
}
#endif
#endif
EXTERN_C_END

View file

@ -55,6 +55,12 @@ typedef enum {
, COMMS_RELAYSTATE_ALLCONNECTED
} CommsRelayState;
typedef enum {
ID_TYPE_NONE
,ID_TYPE_LINUX
,ID_TYPE_ANDROID_GCM
} DevIDType;
#ifdef XWFEATURE_BLUETOOTH
# define XW_BT_UUID "7be0d084-ff89-4d6d-9c78-594773a6f963"

View file

@ -1384,6 +1384,9 @@ server_sendInitialMessage( ServerCtxt* server )
XP_U16 nPlayers = server->vol.gi->nPlayers;
CurGameInfo localGI;
XP_U32 gameID = server->vol.gi->gameID;
#ifdef STREAM_VERS_BIGBOARD
XP_U8 streamVersion = server->nv.streamVersion;
#endif
XP_ASSERT( server->nv.nDevices > 1 );
for ( deviceIndex = 1; deviceIndex < server->nv.nDevices;
@ -1397,8 +1400,8 @@ server_sendInitialMessage( ServerCtxt* server )
writeProto( server, stream, XWPROTO_CLIENT_SETUP );
#ifdef STREAM_VERS_BIGBOARD
XP_ASSERT( 0 < server->nv.streamVersion );
stream_putU8( stream, server->nv.streamVersion );
XP_ASSERT( 0 < streamVersion );
stream_putU8( stream, streamVersion );
#else
stream_putU8( stream, CUR_STREAM_VERS );
#endif
@ -1411,7 +1414,7 @@ server_sendInitialMessage( ServerCtxt* server )
dict_writeToStream( dict, stream );
#ifdef STREAM_VERS_BIGBOARD
if ( STREAM_VERS_DICTNAME <= addr->streamVersion ) {
if ( STREAM_VERS_DICTNAME <= streamVersion ) {
stringToStream( stream, dict_getShortName(dict) );
stringToStream( stream, dict_getMd5Sum(dict) );
}
@ -2429,6 +2432,7 @@ server_getLastMoveTime( const ServerCtxt* server )
static void
doEndGame( ServerCtxt* server, XP_S16 quitter )
{
XP_ASSERT( quitter < server->vol.gi->nPlayers );
SETSTATE( server, XWSTATE_GAMEOVER );
setTurn( server, -1 );
server->nv.quitter = quitter;
@ -2445,7 +2449,7 @@ putQuitter( const ServerCtxt* server, XWStreamCtxt* stream, XP_S16 quitter )
}
static void
getQuitter( const ServerCtxt* server, XWStreamCtxt* stream, XP_S16* quitter )
getQuitter( const ServerCtxt* server, XWStreamCtxt* stream, XP_S8* quitter )
{
*quitter = STREAM_VERS_DICTNAME <= server->nv.streamVersion
? stream_getU8( stream ) : -1;
@ -2462,6 +2466,7 @@ static void
endGameInternal( ServerCtxt* server, GameEndReason XP_UNUSED(why), XP_S16 quitter )
{
XP_ASSERT( server->nv.gameState != XWSTATE_GAMEOVER );
XP_ASSERT( quitter < server->vol.gi->nPlayers );
if ( server->vol.gi->serverRole != SERVER_ISCLIENT ) {
@ -2756,7 +2761,7 @@ server_receiveMessage( ServerCtxt* server, XWStreamCtxt* incoming )
XP_FREE( server->mpool, msg );
#endif
} else if ( readStreamHeader( server, incoming ) ) {
XP_S16 quitter;
XP_S8 quitter;
switch( code ) {
/* case XWPROTO_MOVEMADE_INFO: */
/* accepted = client_reflectMoveMade( server, incoming ); */

View file

@ -152,7 +152,9 @@ typedef struct UtilVtable {
XP_Bool (*m_util_altKeyDown)( XW_UtilCtxt* uc );
XP_U32 (*m_util_getCurSeconds)( XW_UtilCtxt* uc );
#ifdef XWFEATURE_DEVID
const XP_UCHAR* (*m_util_getDevID)( XW_UtilCtxt* uc, DevIDType* typ );
#endif
DictionaryCtxt* (*m_util_makeEmptyDict)( XW_UtilCtxt* uc );
const XP_UCHAR* (*m_util_getUserString)( XW_UtilCtxt* uc,
@ -280,6 +282,11 @@ struct XW_UtilCtxt {
#define util_getCurSeconds(uc) \
(uc)->vtable->m_util_getCurSeconds((uc))
#ifdef XWFEATURE_DEVID
# define util_getDevID( uc, t ) \
(uc)->vtable->m_util_getDevID((uc),(t))
#endif
#define util_makeEmptyDict( uc ) \
(uc)->vtable->m_util_makeEmptyDict((uc))

View file

@ -110,6 +110,7 @@ DEFINES += -DXWFEATURE_BONUSALL
DEFINES += -DXWFEATURE_HILITECELL
# allow change dict inside running game
DEFINES += -DXWFEATURE_CHANGEDICT
# DEFINES += -DXWFEATURE_DEVID
# MAX_ROWS controls STREAM_VERS_BIGBOARD and with it move hashing
DEFINES += -DMAX_ROWS=32

View file

@ -216,6 +216,7 @@ void
catFinalScores( const CommonGlobals* cGlobals, XP_S16 quitter )
{
XWStreamCtxt* stream;
XP_ASSERT( quitter < cGlobals->params->gi.nPlayers );
stream = mem_stream_make( MPPARM(cGlobals->params->util->mpool)
cGlobals->params->vtMgr,
@ -502,6 +503,9 @@ typedef enum {
,CMD_DICTDIR
,CMD_PLAYERDICT
,CMD_SEED
#ifdef XWFEATURE_DEVID
,CMD_DEVID
#endif
,CMD_GAMESEED
,CMD_GAMEFILE
,CMD_SAVEFAIL_PCT
@ -595,6 +599,9 @@ static CmdInfoRec CmdInfoRecs[] = {
,{ CMD_DICTDIR, true, "dict-dir", "path to dir in which dicts will be sought" }
,{ CMD_PLAYERDICT, true, "player-dict", "dictionary name for player (in sequence)" }
,{ CMD_SEED, true, "seed", "random seed" }
#ifdef XWFEATURE_DEVID
,{ CMD_DEVID, true, "devid", "device ID (for testing GCM stuff)" }
#endif
,{ CMD_GAMESEED, true, "game-seed", "game seed (for relay play)" }
,{ CMD_GAMEFILE, true, "file", "file to save to/read from" }
,{ CMD_SAVEFAIL_PCT, true, "savefail-pct", "How often, at random, does save fail?" }
@ -1584,6 +1591,9 @@ main( int argc, char** argv )
mainParams.allowPeek = XP_TRUE;
mainParams.showRobotScores = XP_FALSE;
mainParams.useMmap = XP_TRUE;
#ifdef XWFEATURE_DEVID
mainParams.devID = "";
#endif
char* envDictPath = getenv( "XW_DICTSPATH" );
if ( !!envDictPath ) {
@ -1669,6 +1679,11 @@ main( int argc, char** argv )
case CMD_SEED:
seed = atoi(optarg);
break;
#ifdef XWFEATURE_DEVID
case CMD_DEVID:
mainParams.devID = optarg;
break;
#endif
case CMD_GAMESEED:
mainParams.gameSeed = atoi(optarg);
break;

View file

@ -344,6 +344,16 @@ linux_util_getUserString( XW_UtilCtxt* XP_UNUSED(uc), XP_U16 code )
}
} /* linux_util_getUserString */
#ifdef XWFEATURE_DEVID
static const XP_UCHAR*
linux_util_getDevID( XW_UtilCtxt* uc, DevIDType* typ )
{
CommonGlobals* cGlobals = (CommonGlobals*)uc->closure;
*typ = ID_TYPE_LINUX;
return cGlobals->params->devID;
}
#endif
void
linux_util_vt_init( MPFORMAL XW_UtilCtxt* util )
{
@ -353,6 +363,9 @@ linux_util_vt_init( MPFORMAL XW_UtilCtxt* util )
util->vtable->m_util_getSquareBonus = linux_util_getSquareBonus;
util->vtable->m_util_getCurSeconds = linux_util_getCurSeconds;
util->vtable->m_util_getUserString = linux_util_getUserString;
#ifdef XWFEATURE_DEVID
util->vtable->m_util_getDevID = linux_util_getDevID;
#endif
}
void

View file

@ -59,6 +59,9 @@ typedef struct LaunchParams {
char* pipe;
char* nbs;
char* bonusFile;
#ifdef XWFEATURE_DEVID
char* devID;
#endif
VTableMgr* vtMgr;
XP_U16 nLocalPlayers;
XP_U16 nHidden;

View file

@ -79,6 +79,9 @@ function pick_ndevs() {
elif [ $RNUM -gt 75 -a $MAXDEVS -ge 3 ]; then
NDEVS=3
fi
if [ -n "$MINDEVS" -a "$NDEVS" -lt "$MINDEVS" ]; then
NDEVS=$MINDEVS
fi
echo $NDEVS
}

View file

@ -179,7 +179,8 @@ CookieRef::Unlock() {
}
bool
CookieRef::_Connect( int socket, int clientVersion, int nPlayersH, int nPlayersS, int seed,
CookieRef::_Connect( int socket, int clientVersion, DevID* devID,
int nPlayersH, int nPlayersS, int seed,
bool seenSeed, in_addr& addr )
{
bool connected = false;
@ -199,7 +200,8 @@ CookieRef::_Connect( int socket, int clientVersion, int nPlayersH, int nPlayersS
if ( !connected ) {
set<int> sockets = GetSockets();
if ( sockets.end() == sockets.find( socket ) ) {
pushConnectEvent( socket, clientVersion, nPlayersH, nPlayersS, seed, addr );
pushConnectEvent( socket, clientVersion, devID, nPlayersH,
nPlayersS, seed, addr );
handleEvents();
connected = HasSocket_locked( socket );
} else {
@ -210,8 +212,8 @@ CookieRef::_Connect( int socket, int clientVersion, int nPlayersH, int nPlayersS
}
bool
CookieRef::_Reconnect( int socket, int clientVersion, HostID hid,
int nPlayersH, int nPlayersS,
CookieRef::_Reconnect( int socket, int clientVersion, DevID* devID,
HostID hid, int nPlayersH, int nPlayersS,
int seed, in_addr& addr, bool gameDead )
{
bool spotTaken = false;
@ -223,7 +225,8 @@ CookieRef::_Reconnect( int socket, int clientVersion, HostID hid,
logf( XW_LOGINFO, "%s: dropping because already here",
__func__ );
} else {
pushReconnectEvent( socket, clientVersion, hid, nPlayersH, nPlayersS, seed, addr );
pushReconnectEvent( socket, clientVersion, devID, hid, nPlayersH,
nPlayersS, seed, addr );
}
if ( gameDead ) {
pushGameDead( socket );
@ -243,7 +246,8 @@ CookieRef::_HandleAck( HostID hostID )
}
void
CookieRef::_PutMsg( HostID srcID, in_addr& addr, HostID destID, unsigned char* buf, int buflen )
CookieRef::_PutMsg( HostID srcID, in_addr& addr, HostID destID,
unsigned char* buf, int buflen )
{
CRefEvent evt( XWE_PROXYMSG );
evt.u.fwd.src = srcID;
@ -513,13 +517,15 @@ CookieRef::_Remove( int socket )
} /* Forward */
void
CookieRef::pushConnectEvent( int socket, int clientVersion, int nPlayersH, int nPlayersS,
CookieRef::pushConnectEvent( int socket, int clientVersion, DevID* devID,
int nPlayersH, int nPlayersS,
int seed, in_addr& addr )
{
CRefEvent evt;
evt.type = XWE_DEVCONNECT;
evt.u.con.socket = socket;
evt.u.con.clientVersion = clientVersion;
evt.u.con.devID = devID;
evt.u.con.srcID = HOST_ID_NONE;
evt.u.con.nPlayersH = nPlayersH;
evt.u.con.nPlayersS = nPlayersS;
@ -529,13 +535,14 @@ CookieRef::pushConnectEvent( int socket, int clientVersion, int nPlayersH, int n
} /* pushConnectEvent */
void
CookieRef::pushReconnectEvent( int socket, int clientVersion, HostID srcID,
int nPlayersH, int nPlayersS, int seed,
in_addr& addr )
CookieRef::pushReconnectEvent( int socket, int clientVersion, DevID* devID,
HostID srcID, int nPlayersH, int nPlayersS,
int seed, in_addr& addr )
{
CRefEvent evt( XWE_RECONNECT );
evt.u.con.socket = socket;
evt.u.con.clientVersion = clientVersion;
evt.u.con.devID = devID;
evt.u.con.srcID = srcID;
evt.u.con.nPlayersH = nPlayersH;
evt.u.con.nPlayersS = nPlayersS;
@ -879,7 +886,7 @@ CookieRef::increasePlayerCounts( CRefEvent* evt, bool reconn, HostID* hidp )
evt->u.con.srcID =
DBMgr::Get()->AddDevice( ConnName(), evt->u.con.srcID,
evt->u.con.clientVersion, nPlayersH, seed,
evt->u.con.addr, reconn );
evt->u.con.addr, evt->u.con.devID, reconn );
HostID hostid = evt->u.con.srcID;
if ( NULL != hidp ) {

View file

@ -63,6 +63,13 @@ public:
class CookieRef* m_this;
};
class DevID {
public:
DevID() { m_devIDType = 0; }
string m_devIDString;
unsigned char m_devIDType;
};
class CookieRef {
public:
set<int> GetSockets();
@ -119,10 +126,11 @@ class CookieRef {
static void Delete( CookieID cid );
static void Delete( const char* name );
bool _Connect( int socket, int clientVersion, int nPlayersH, int nPlayersS,
int seed, bool seenSeed, in_addr& addr );
bool _Reconnect( int socket, int clientVersion, HostID srcID,
int nPlayersH, int nPlayersS,
bool _Connect( int socket, int clientVersion, DevID* devID,
int nPlayersH, int nPlayersS, int seed, bool seenSeed,
in_addr& addr );
bool _Reconnect( int socket, int clientVersion, DevID* devID,
HostID srcID, int nPlayersH, int nPlayersS,
int seed, in_addr& addr, bool gameDead );
void _HandleAck( HostID hostID );
void _PutMsg( HostID srcID, in_addr& addr, HostID destID, unsigned char* buf, int buflen );
@ -157,6 +165,7 @@ class CookieRef {
struct {
int socket;
int clientVersion;
DevID* devID;
int nPlayersH;
int nPlayersS;
int seed;
@ -195,10 +204,11 @@ class CookieRef {
bool cascade );
void send_msg( int socket, HostID id, XWRelayMsg msg, XWREASON why,
bool cascade );
void pushConnectEvent( int socket, int clientVersion, int nPlayersH, int nPlayersS,
void pushConnectEvent( int socket, int clientVersion, DevID* devID,
int nPlayersH, int nPlayersS,
int seed, in_addr& addr );
void pushReconnectEvent( int socket, int clientVersion, HostID srcID,
int nPlayersH, int nPlayersS,
void pushReconnectEvent( int socket, int clientVersion, DevID* devID,
HostID srcID, int nPlayersH, int nPlayersS,
int seed, in_addr& addr );
void pushHeartbeatEvent( HostID id, int socket );
void pushHeartFailedEvent( int socket );

View file

@ -590,12 +590,13 @@ CookieMapIterator::Next()
/* connect case */
SafeCref::SafeCref( const char* cookie, int socket, int clientVersion,
int nPlayersH, int nPlayersS,
DevID* devID, int nPlayersH, int nPlayersS,
unsigned short gameSeed, int langCode, bool wantsPublic,
bool makePublic )
: m_cinfo( NULL )
, m_mgr( CRefMgr::Get() )
, m_clientVersion( clientVersion )
, m_devID( devID )
, m_isValid( false )
, m_seenSeed( false )
{
@ -621,6 +622,7 @@ SafeCref::SafeCref( const char* connName, const char* cookie, HostID hid,
: m_cinfo( NULL )
, m_mgr( CRefMgr::Get() )
, m_clientVersion( clientVersion )
, m_devID( NULL )
, m_isValid( false )
{
CidInfo* cinfo;

View file

@ -172,7 +172,7 @@ class SafeCref {
public:
/* for connect */
SafeCref( const char* cookie, int socket, int clientVersion,
int nPlayersH, int nPlayersS,
DevID* devID, int nPlayersH, int nPlayersS,
unsigned short gameSeed, int langCode, bool wantsPublic,
bool makePublic );
/* for reconnect */
@ -212,8 +212,8 @@ class SafeCref {
if ( IsValid() ) {
CookieRef* cref = m_cinfo->GetRef();
assert( 0 != cref->GetCid() );
return cref->_Connect( socket, m_clientVersion, nPlayersH,
nPlayersS, seed,
return cref->_Connect( socket, m_clientVersion, m_devID,
nPlayersH, nPlayersS, seed,
m_seenSeed, addr );
} else {
return false;
@ -229,9 +229,9 @@ class SafeCref {
if ( m_dead ) {
*errp = XWRELAY_ERROR_DEADGAME;
} else {
success = cref->_Reconnect( socket, m_clientVersion, srcID,
nPlayersH, nPlayersS, seed, addr,
m_dead );
success = cref->_Reconnect( socket, m_clientVersion, m_devID,
srcID, nPlayersH, nPlayersS, seed,
addr, m_dead );
}
}
return success;
@ -391,6 +391,7 @@ class SafeCref {
CidInfo* m_cinfo;
CRefMgr* m_mgr;
int m_clientVersion;
DevID* m_devID;
bool m_isValid;
bool m_locked;
bool m_dead;

View file

@ -244,8 +244,9 @@ DBMgr::AllDevsAckd( const char* const connName )
}
HostID
DBMgr::AddDevice( const char* connName, HostID curID, int clientVersion, int nToAdd,
unsigned short seed, const in_addr& addr, bool ackd )
DBMgr::AddDevice( const char* connName, HostID curID, int clientVersion,
int nToAdd, unsigned short seed, const in_addr& addr,
const DevID* devID, bool ackd )
{
HostID newID = curID;
@ -260,14 +261,23 @@ DBMgr::AddDevice( const char* connName, HostID curID, int clientVersion, int nTo
}
assert( newID <= 4 );
char devIDBuf[512] = {0};
if ( !!devID ) {
snprintf( devIDBuf, sizeof(devIDBuf),
"devids[%d] = \'%s\', devTypes[%d] = %d,",
newID, devID->m_devIDString.c_str(),
newID, devID->m_devIDType );
}
const char* fmt = "UPDATE " GAMES_TABLE " SET nPerDevice[%d] = %d,"
" clntVers[%d] = %d,"
" seeds[%d] = %d, addrs[%d] = \'%s\', mtimes[%d]='now', ack[%d]=\'%c\'"
" seeds[%d] = %d, addrs[%d] = \'%s\', %s"
" mtimes[%d]='now', ack[%d]=\'%c\'"
" WHERE connName = '%s'";
char query[256];
char query[1024];
snprintf( query, sizeof(query), fmt, newID, nToAdd, newID, clientVersion,
newID, seed, newID, inet_ntoa(addr), newID,
newID, ackd?'A':'a', connName );
newID, seed, newID, inet_ntoa(addr), devIDBuf,
newID, newID, ackd?'A':'a', connName );
logf( XW_LOGINFO, "%s: query: %s", __func__, query );
execSql( query );
@ -437,7 +447,8 @@ DBMgr::RecordSent( const int* msgIDs, int nMsgIDs )
}
void
DBMgr::RecordAddress( const char* const connName, HostID hid, const in_addr& addr )
DBMgr::RecordAddress( const char* const connName, HostID hid,
const in_addr& addr )
{
assert( hid >= 0 && hid <= 4 );
const char* fmt = "UPDATE " GAMES_TABLE " SET addrs[%d] = \'%s\'"
@ -558,6 +569,22 @@ DBMgr::readArray( const char* const connName, int arr[] ) /* len 4 */
PQclear( result );
}
void
DBMgr::getDevID( const char* connName, int hid, DevID& devID )
{
const char* fmt = "SELECT devids[%d], devTypes[%d] FROM " GAMES_TABLE " WHERE connName='%s'";
char query[256];
snprintf( query, sizeof(query), fmt, hid, hid, connName );
logf( XW_LOGINFO, "%s: query: %s", __func__, query );
PGresult* result = PQexec( getThreadConn(), query );
assert( 1 == PQntuples( result ) );
devID.m_devIDString = PQgetvalue( result, 0, 0 );
devID.m_devIDType = (unsigned char)atoi( PQgetvalue( result, 0, 1 ) );
assert( devID.m_devIDType <= 2 ); // for now!!!
PQclear( result );
}
/*
id | connname | hid | msg
----+-----------+-----+---------
@ -598,16 +625,20 @@ void
DBMgr::StoreMessage( const char* const connName, int hid,
const unsigned char* buf, int len )
{
DevID devID;
getDevID( connName, hid, devID );
size_t newLen;
const char* fmt = "INSERT INTO " MSGS_TABLE " (connname, hid, msg, msglen)"
" VALUES( '%s', %d, E'%s', %d)";
const char* fmt = "INSERT INTO " MSGS_TABLE " (connname, hid, devid, devType, msg, msglen)"
" VALUES( '%s', %d, '%s', %d, E'%s', %d)";
unsigned char* bytes = PQescapeByteaConn( getThreadConn(), buf, len, &newLen );
assert( NULL != bytes );
char query[newLen+128];
unsigned int siz = snprintf( query, sizeof(query), fmt, connName, hid,
bytes, len );
devID.m_devIDString.c_str(),
devID.m_devIDType, bytes, len );
logf( XW_LOGINFO, "%s: query: %s", __func__, query );
PQfreemem( bytes );
assert( siz < sizeof(query) );

View file

@ -26,6 +26,7 @@
#include "xwrelay.h"
#include "xwrelay_priv.h"
#include "cref.h"
#include <libpq-fe.h>
using namespace std;
@ -56,7 +57,8 @@ class DBMgr {
bool AllDevsAckd( const char* const connName );
HostID AddDevice( const char* const connName, HostID curID, int clientVersion,
int nToAdd, unsigned short seed, const in_addr& addr, bool unAckd );
int nToAdd, unsigned short seed, const in_addr& addr,
const DevID* devID, bool unAckd );
void NoteAckd( const char* const connName, HostID id );
HostID HIDForSeed( const char* const connName, unsigned short seed );
bool RmDeviceByHid( const char* const connName, HostID id );
@ -97,6 +99,7 @@ class DBMgr {
DBMgr();
bool execSql( const char* const query ); /* no-results query */
void readArray( const char* const connName, int arr[] );
void getDevID( const char* connName, int hid, DevID& devID );
PGconn* getThreadConn( void );

View file

@ -26,12 +26,12 @@ QUERY="WHERE NOT -NTOTAL = sum_array(nperdevice)"
echo "Device (pid) count: $(pidof xwords | wc | awk '{print $2}')"
echo "Row count:" $(psql -t xwgames -c "select count(*) FROM games $QUERY;")
echo "SELECT dead,connname,cid,room,lang,clntVers,ntotal,nperdevice,seeds,ack,nsent "\
echo "SELECT dead,connname,cid,room,lang,clntVers,ntotal,nperdevice,seeds,devTypes,devids,ack,nsent "\
"FROM games $QUERY ORDER BY NOT dead, connname LIMIT $LIMIT;" \
| psql xwgames
echo "SELECT connname, hid, count(*), sum(msglen) "\
echo "SELECT connname, hid, devType, devid, count(*), sum(msglen) "\
"FROM msgs where connname in (SELECT connname from games where not games.dead group by connname)" \
"GROUP BY connname, hid ORDER BY connname;" \
"GROUP BY connname, hid, devType, devid ORDER BY connname;" \
| psql xwgames

View file

@ -228,6 +228,20 @@ getNetByte( unsigned char** bufpp, const unsigned char* end,
return ok;
} /* getNetByte */
static bool
getNetString( unsigned char** bufpp, const unsigned char* end, string& out )
{
char* str = (char*)*bufpp;
size_t len = 1 + strlen( str );
bool success = str + len <= (char*)end;
if ( success ) {
out = str;
*bufpp += len;
}
// logf( XW_LOGERROR, "%s => %d", __func__, out.c_str() );
return success;
}
#ifdef RELAY_HEARTBEAT
static bool
processHeartbeat( unsigned char* buf, int bufLen, int socket )
@ -268,12 +282,14 @@ readStr( unsigned char** bufp, const unsigned char* end,
static XWREASON
flagsOK( unsigned char** bufp, unsigned char const* end,
unsigned short* clientVersion )
unsigned short* clientVersion, unsigned short* flagsp )
{
XWREASON err = XWRELAY_ERROR_OLDFLAGS;
unsigned char flags;
if ( getNetByte( bufp, end, &flags ) ) {
*flagsp = flags;
switch ( flags ) {
case XWRELAY_PROTO_VERSION_CLIENTID:
case XWRELAY_PROTO_VERSION_CLIENTVERS:
if ( getNetShort( bufp, end, clientVersion ) ) {
err = XWRELAY_ERROR_NONE;
@ -346,7 +362,8 @@ processConnect( unsigned char* bufp, int bufLen, int socket, in_addr& addr )
cookie[0] = '\0';
unsigned short clientVersion;
XWREASON err = flagsOK( &bufp, end, &clientVersion );
unsigned short flags;
XWREASON err = flagsOK( &bufp, end, &clientVersion, &flags );
if ( err == XWRELAY_ERROR_NONE ) {
/* HostID srcID; */
unsigned char nPlayersH;
@ -362,6 +379,17 @@ processConnect( unsigned char* bufp, int bufLen, int socket, in_addr& addr )
&& getNetByte( &bufp, end, &nPlayersT )
&& getNetShort( &bufp, end, &seed )
&& getNetByte( &bufp, end, &langCode ) ) {
DevID devID;
if ( XWRELAY_PROTO_VERSION_CLIENTID <= flags ) {
unsigned char devIDType = 0;
if ( getNetByte( &bufp, end, &devIDType )
&& 0 != devIDType ) {
getNetString( &bufp, end, devID.m_devIDString );
devID.m_devIDType = devIDType;
}
}
logf( XW_LOGINFO, "%s(): langCode=%d; nPlayersT=%d; "
"wantsPublic=%d; seed=%.4X",
__func__, langCode, nPlayersT, wantsPublic, seed );
@ -371,8 +399,9 @@ processConnect( unsigned char* bufp, int bufLen, int socket, in_addr& addr )
static pthread_mutex_t s_newCookieLock = PTHREAD_MUTEX_INITIALIZER;
MutexLock ml( &s_newCookieLock );
SafeCref scr( cookie, socket, clientVersion, nPlayersH, nPlayersT,
seed, langCode, wantsPublic, makePublic );
SafeCref scr( cookie, socket, clientVersion, &devID,
nPlayersH, nPlayersT, seed, langCode,
wantsPublic, makePublic );
/* nPlayersT etc could be slots in SafeCref to avoid passing
here */
success = scr.Connect( socket, nPlayersH, nPlayersT, seed, addr );
@ -396,7 +425,8 @@ processReconnect( unsigned char* bufp, int bufLen, int socket, in_addr& addr )
logf( XW_LOGINFO, "%s()", __func__ );
unsigned short clientVersion;
XWREASON err = flagsOK( &bufp, end, &clientVersion );
unsigned short flags;
XWREASON err = flagsOK( &bufp, end, &clientVersion, &flags );
if ( err == XWRELAY_ERROR_NONE ) {
char cookie[MAX_INVITE_LEN+1];
char connName[MAX_CONNNAME_LEN+1] = {0};

View file

@ -121,6 +121,7 @@ typedef unsigned char XWRELAY_Cmd;
#define XWRELAY_PROTO_VERSION_LATE_COOKIEID 0x03
#define XWRELAY_PROTO_VERSION_NOCLIENT 0x04
#define XWRELAY_PROTO_VERSION_CLIENTVERS 0x05
#define XWRELAY_PROTO_VERSION_CLIENTID 0x06
#define XWRELAY_PROTO_VERSION XWRELAY_PROTO_VERSION_CLIENTVERS
/* Errors passed with denied */

View file

@ -58,6 +58,8 @@ cid integer
,ctime TIMESTAMP (0) DEFAULT CURRENT_TIMESTAMP
,mtimes TIMESTAMP(0)[]
,addrs INET[]
,devTypes INTEGER[]
,devids TEXT[]
);
EOF
@ -67,6 +69,8 @@ id SERIAL
,connName VARCHAR(64)
,hid INTEGER
,ctime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
,devType INTEGER
,devid TEXT
,msg BYTEA
,msglen INTEGER
,UNIQUE ( connName, hid, msg )