mirror of
git://xwords.git.sourceforge.net/gitroot/xwords/xwords
synced 2025-01-04 23:02:02 +01:00
243eb213bb
Makes it easier to debug by adding stuff on the relay end
255 lines
9.4 KiB
Python
Executable file
255 lines
9.4 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):
|
|
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')
|
|
|
|
result = {'ids':ids}
|
|
try:
|
|
msgsLists = {}
|
|
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
|
|
|
|
result['msgs'] = msgsLists
|
|
except:
|
|
# Anything but a timeout should mean we abort/send nothing
|
|
result['err'] = 'hit exception'
|
|
None
|
|
|
|
return json.dumps(result)
|
|
|
|
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()
|