mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-08 05:24:39 +01:00
f2c4c82129
Ideally the comms module wouldn't go through its connecting routine in order to join a game. To that end I added a join() method to relay.py and code to call it. Joins happen (pairing games, starting new ones, etc.), but after that communication doesn't. First part of fixing that would be to make cookieID persistent and transmit it back with the rest of what join sends (since it's used by all the messages currently sent in a connected state), but I suspect there's more to be done, and even that requires a fair number of changes on the relay side. So all that's wrapped in #ifdef RELAY_VIA_HTTP (and turned off.)
245 lines
9 KiB
Python
Executable file
245 lines
9 KiB
Python
Executable file
#!/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, timeoutSecs = 1.0):
|
|
err = 'none'
|
|
jobj = json.loads(params)
|
|
data = base64.b64decode(jobj['data'])
|
|
|
|
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
udpSock.settimeout(float(timeoutSecs)) # seconds
|
|
addr = ("127.0.0.1", 10997)
|
|
udpSock.sendto(data, 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
|
|
|
|
jobj = {'err' : err, 'data' : responses}
|
|
return json.dumps(jobj)
|
|
|
|
def query(req, ids, timeoutSecs = 5.0):
|
|
print('ids', ids)
|
|
ids = json.loads(ids)
|
|
|
|
idsLen = 0
|
|
for id in ids: idsLen += len(id)
|
|
|
|
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
tcpSock.settimeout(float(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':
|
|
result = query(None, json.dumps(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()
|