mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-07 05:24:46 +01:00
Merge branch 'android_branch' into relay_via_http
This commit is contained in:
commit
5166933be6
11 changed files with 347 additions and 42 deletions
|
@ -550,7 +550,7 @@ public class BTService extends XWService {
|
|||
} else {
|
||||
short len = is.readShort();
|
||||
byte[] nliData = new byte[len];
|
||||
is.read( nliData );
|
||||
is.readFully( nliData );
|
||||
nli = XwJNI.nliFromStream( nliData );
|
||||
}
|
||||
|
||||
|
@ -573,26 +573,20 @@ public class BTService extends XWService {
|
|||
int gameID = dis.readInt();
|
||||
switch ( cmd ) {
|
||||
case MESG_SEND:
|
||||
short len = dis.readShort();
|
||||
byte[] buffer = new byte[len];
|
||||
int nRead = dis.read( buffer, 0, len );
|
||||
if ( nRead == len ) {
|
||||
BluetoothDevice host = socket.getRemoteDevice();
|
||||
addAddr( host );
|
||||
byte[] buffer = new byte[dis.readShort()];
|
||||
dis.readFully( buffer );
|
||||
BluetoothDevice host = socket.getRemoteDevice();
|
||||
addAddr( host );
|
||||
|
||||
CommsAddrRec addr = new CommsAddrRec( host.getName(),
|
||||
host.getAddress() );
|
||||
ReceiveResult rslt
|
||||
= BTService.this.receiveMessage( BTService.this,
|
||||
gameID, m_btMsgSink,
|
||||
buffer, addr );
|
||||
CommsAddrRec addr = new CommsAddrRec( host.getName(),
|
||||
host.getAddress() );
|
||||
ReceiveResult rslt
|
||||
= BTService.this.receiveMessage( BTService.this,
|
||||
gameID, m_btMsgSink,
|
||||
buffer, addr );
|
||||
|
||||
result = rslt == ReceiveResult.GAME_GONE ?
|
||||
BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
|
||||
} else {
|
||||
Log.e( TAG, "receiveMessage(): read only %d of %d bytes",
|
||||
nRead, len );
|
||||
}
|
||||
result = rslt == ReceiveResult.GAME_GONE ?
|
||||
BTCmd.MESG_GAMEGONE : BTCmd.MESG_ACCPT;
|
||||
break;
|
||||
case MESG_GAMEGONE:
|
||||
postEvent( MultiEvent.MESSAGE_NOGAME, gameID );
|
||||
|
|
|
@ -188,10 +188,8 @@ public class BiDiSockWrap {
|
|||
DataInputStream inStream
|
||||
= new DataInputStream( mSocket.getInputStream() );
|
||||
while ( mRunThreads ) {
|
||||
short len = inStream.readShort();
|
||||
Log.d( TAG, "got len: %d", len );
|
||||
byte[] packet = new byte[len];
|
||||
inStream.read( packet );
|
||||
byte[] packet = new byte[inStream.readShort()];
|
||||
inStream.readFully( packet );
|
||||
mIface.gotPacket( BiDiSockWrap.this, packet );
|
||||
}
|
||||
} catch( IOException ioe ) {
|
||||
|
|
|
@ -204,6 +204,21 @@ public class BoardDelegate extends DelegateBase
|
|||
}
|
||||
};
|
||||
ab.setNegativeButton( R.string.button_rematch, lstnr );
|
||||
|
||||
// If we're not already in the "archive" group, offer to move
|
||||
final String archiveName = LocUtils
|
||||
.getString( m_activity, R.string.group_name_archive );
|
||||
final long archiveGroup = DBUtils.getGroup( m_activity, archiveName );
|
||||
long curGroup = DBUtils.getGroupForGame( m_activity, m_rowid );
|
||||
if ( curGroup != archiveGroup ) {
|
||||
lstnr = new OnClickListener() {
|
||||
public void onClick( DialogInterface dlg,
|
||||
int whichButton ) {
|
||||
archiveAndClose( archiveName, archiveGroup );
|
||||
}
|
||||
};
|
||||
ab.setNeutralButton( R.string.button_archive, lstnr );
|
||||
}
|
||||
} else if ( DlgID.DLG_CONNSTAT == dlgID
|
||||
&& BuildConfig.DEBUG && null != m_connTypes
|
||||
&& (m_connTypes.contains( CommsConnType.COMMS_CONN_RELAY )
|
||||
|
@ -2575,6 +2590,16 @@ public class BoardDelegate extends DelegateBase
|
|||
return wordsArray;
|
||||
}
|
||||
|
||||
private void archiveAndClose( String archiveName, long groupID )
|
||||
{
|
||||
if ( DBUtils.GROUPID_UNSPEC == groupID ) {
|
||||
groupID = DBUtils.addGroup( m_activity, archiveName );
|
||||
}
|
||||
DBUtils.moveGame( m_activity, m_rowid, groupID );
|
||||
waitCloseGame( false );
|
||||
finish();
|
||||
}
|
||||
|
||||
// For now, supported if standalone or either BT or SMS used for transport
|
||||
private boolean rematchSupported( boolean showMulti )
|
||||
{
|
||||
|
|
|
@ -81,7 +81,9 @@ public class DBUtils {
|
|||
private static long s_cachedRowID = ROWID_NOTFOUND;
|
||||
private static byte[] s_cachedBytes = null;
|
||||
|
||||
public static enum GameChangeType { GAME_CHANGED, GAME_CREATED, GAME_DELETED };
|
||||
public static enum GameChangeType { GAME_CHANGED, GAME_CREATED,
|
||||
GAME_DELETED, GAME_MOVED,
|
||||
};
|
||||
|
||||
public static interface DBChangeListener {
|
||||
public void gameSaved( long rowid, GameChangeType change );
|
||||
|
@ -1701,6 +1703,29 @@ public class DBUtils {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static long getGroup( Context context, String name )
|
||||
{
|
||||
long result = GROUPID_UNSPEC;
|
||||
String[] columns = { ROW_ID };
|
||||
String selection = DBHelper.GROUPNAME + " = ?";
|
||||
String[] selArgs = { name };
|
||||
|
||||
initDB( context );
|
||||
synchronized( s_dbHelper ) {
|
||||
Cursor cursor = s_db.query( DBHelper.TABLE_NAME_GROUPS, columns,
|
||||
selection, selArgs,
|
||||
null, // groupBy
|
||||
null, // having
|
||||
null // orderby
|
||||
);
|
||||
if ( cursor.moveToNext() ) {
|
||||
result = cursor.getLong( cursor.getColumnIndex( ROW_ID ) );
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static long addGroup( Context context, String name )
|
||||
{
|
||||
long rowid = GROUPID_UNSPEC;
|
||||
|
@ -1759,13 +1784,14 @@ public class DBUtils {
|
|||
}
|
||||
|
||||
// Change group id of a game
|
||||
public static void moveGame( Context context, long gameid, long groupID )
|
||||
public static void moveGame( Context context, long rowid, long groupID )
|
||||
{
|
||||
Assert.assertTrue( GROUPID_UNSPEC != groupID );
|
||||
ContentValues values = new ContentValues();
|
||||
values.put( DBHelper.GROUPID, groupID );
|
||||
updateRow( context, DBHelper.TABLE_NAME_SUM, gameid, values );
|
||||
updateRow( context, DBHelper.TABLE_NAME_SUM, rowid, values );
|
||||
invalGroupsCache();
|
||||
notifyListeners( rowid, GameChangeType.GAME_MOVED );
|
||||
}
|
||||
|
||||
private static String getChatHistoryStr( Context context, long rowid )
|
||||
|
|
|
@ -755,9 +755,18 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
lstnr = new OnClickListener() {
|
||||
public void onClick( DialogInterface dlg, int item ) {
|
||||
String name = namer.getName();
|
||||
DBUtils.addGroup( m_activity, name );
|
||||
mkListAdapter();
|
||||
showNewGroupIf();
|
||||
long hasName = DBUtils.getGroup( m_activity, name );
|
||||
if ( DBUtils.GROUPID_UNSPEC == hasName ) {
|
||||
DBUtils.addGroup( m_activity, name );
|
||||
mkListAdapter();
|
||||
showNewGroupIf();
|
||||
} else {
|
||||
String msg = LocUtils
|
||||
.getString( m_activity,
|
||||
R.string.duplicate_group_name_fmt,
|
||||
name );
|
||||
makeOkOnlyBuilder( msg ).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
lstnr2 = new OnClickListener() {
|
||||
|
@ -1060,8 +1069,6 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
invalidateOptionsMenuIf();
|
||||
setTitle();
|
||||
}
|
||||
|
||||
mkListAdapter();
|
||||
}
|
||||
|
||||
public void invalidateOptionsMenuIf()
|
||||
|
@ -1133,6 +1140,9 @@ public class GamesListDelegate extends ListDelegateBase
|
|||
mkListAdapter();
|
||||
setSelGame( rowid );
|
||||
break;
|
||||
case GAME_MOVED:
|
||||
mkListAdapter();
|
||||
break;
|
||||
default:
|
||||
Assert.fail();
|
||||
break;
|
||||
|
|
|
@ -223,7 +223,7 @@ public class NetUtils {
|
|||
short len = dis.readShort();
|
||||
if ( len > 0 ) {
|
||||
byte[] packet = new byte[len];
|
||||
dis.read( packet );
|
||||
dis.readFully( packet );
|
||||
msgs[ii][jj] = packet;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ public class RefreshNamesTask extends AsyncTask<Void, Void, String[]> {
|
|||
// Can't figure out how to read a null-terminated string
|
||||
// from DataInputStream so parse it myself.
|
||||
byte[] bytes = new byte[len];
|
||||
dis.read( bytes );
|
||||
dis.readFully( bytes );
|
||||
|
||||
int index = -1;
|
||||
for ( int ii = 0; ii < nRooms; ++ii ) {
|
||||
|
|
|
@ -829,7 +829,7 @@ public class RelayService extends XWService
|
|||
case XWPDEV_MSG:
|
||||
int token = dis.readInt();
|
||||
byte[] msg = new byte[dis.available()];
|
||||
dis.read( msg );
|
||||
dis.readFully( msg );
|
||||
postData( this, token, msg );
|
||||
|
||||
// game-related packets only count
|
||||
|
@ -849,9 +849,8 @@ public class RelayService extends XWService
|
|||
resetBackoff = true;
|
||||
intent = getIntentTo( this, MsgCmds.GOT_INVITE );
|
||||
int srcDevID = dis.readInt();
|
||||
short len = dis.readShort();
|
||||
byte[] nliData = new byte[len];
|
||||
dis.read( nliData );
|
||||
byte[] nliData = new byte[dis.readShort()];
|
||||
dis.readFully( nliData );
|
||||
NetLaunchInfo nli = XwJNI.nliFromStream( nliData );
|
||||
intent.putExtra( INVITE_FROM, srcDevID );
|
||||
String asStr = nli.toString();
|
||||
|
@ -1092,9 +1091,8 @@ public class RelayService extends XWService
|
|||
private String getVLIString( DataInputStream dis )
|
||||
throws java.io.IOException
|
||||
{
|
||||
int len = vli2un( dis );
|
||||
byte[] tmp = new byte[len];
|
||||
dis.read( tmp );
|
||||
byte[] tmp = new byte[vli2un( dis )];
|
||||
dis.readFully( tmp );
|
||||
String result = new String( tmp );
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -522,7 +522,7 @@ public class SMSService extends XWService {
|
|||
case DATA:
|
||||
int gameID = dis.readInt();
|
||||
byte[] rest = new byte[dis.available()];
|
||||
dis.read( rest );
|
||||
dis.readFully( rest );
|
||||
if ( feedMessage( gameID, rest, new CommsAddrRec( phone ) ) ) {
|
||||
SMSResendReceiver.resetTimer( this );
|
||||
}
|
||||
|
@ -618,7 +618,7 @@ public class SMSService extends XWService {
|
|||
} else {
|
||||
SMS_CMD cmd = SMS_CMD.values()[dis.readByte()];
|
||||
byte[] rest = new byte[dis.available()];
|
||||
dis.read( rest );
|
||||
dis.readFully( rest );
|
||||
receive( cmd, rest, senderPhone );
|
||||
success = true;
|
||||
}
|
||||
|
|
|
@ -2158,6 +2158,10 @@
|
|||
game with the same players and parameters as the one that
|
||||
just ended. -->
|
||||
<string name="button_rematch">Rematch</string>
|
||||
<string name="button_archive">Archive\u200C</string>
|
||||
<string name="group_name_archive">Archive</string>
|
||||
|
||||
<string name="duplicate_group_name_fmt">The group \"%1$s\" already exists.</string>
|
||||
|
||||
<string name="button_reconnect">Reconnect</string>
|
||||
|
||||
|
|
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()
|
Loading…
Reference in a new issue