Changes in protocol

* Escape characters and allow more than one string with spaces
* Time is now in miliseconds
* More proper error handling (though argument count checking need
  improvement)
This commit is contained in:
Tomasz Fluxid Kowalczyk 2012-02-04 16:26:38 +01:00
parent 2b472fa802
commit 671eb79c08
4 changed files with 126 additions and 39 deletions

View File

@ -22,21 +22,31 @@ class SyncClientProtocol(CommandProtocol):
def connectionLost(self, reason):
self.manager.protocol = None
def handle_connected_state(self, arg):
arg = parse_state(arg)
if not arg:
def handle_error(self, args):
self.manager.stop()
CommandProtocol.handle_error(self, args)
def handle_connected_state(self, args):
args = parse_state(args)
if not args:
self.drop_with_error('Malformed state attributes')
return
counter, paused, position, name = arg
counter, paused, position, name = args
self.manager.update_global_state(counter, paused, position, name)
def handle_connected_ping(self, arg):
self.send_message('pong', arg)
def handle_connected_ping(self, args):
if not len(args) == 1:
self.drop_with_error('Invalid arguments')
return
self.send_message('pong', args[0])
def send_state(self, counter, paused, position):
self.send_message('state', counter, ('paused' if paused else 'playing'), int(position*100))
self.send_message('state', counter, ('paused' if paused else 'playing'), int(position*1000))
def send_playing(self, filename):
self.send_message('playing', filename)
states = dict(
@ -44,6 +54,7 @@ class SyncClientProtocol(CommandProtocol):
state = 'handle_connected_state',
seek = 'handle_connected_seek',
ping = 'handle_connected_ping',
playing = 'handle_connected_playing',
),
)
initial_state = 'connected'
@ -63,7 +74,7 @@ class SyncClientFactory(ClientFactory):
def clientConnectionLost(self, connector, reason):
if self.retry:
print 'Connection lost, reconnecting'
connector.connect()
reactor.callLater(0.1, connector.connect)
else:
print 'Disconnected'
@ -150,7 +161,9 @@ class Manager(object):
def init_protocol(self, protocol):
self.protocol = protocol
self.schedule_send_status()
self.make_player(self)
self.send_filename()
if self.player is None:
self.make_player(self)
def schedule_ask_player(self, when=0.2):
@ -181,6 +194,10 @@ class Manager(object):
self.protocol.send_state(self.counter, self.player_paused, self.player_position)
self.schedule_send_status()
def send_filename(self):
if self.protocol and self.player_filename:
self.protocol.send_playing(self.player_filename)
def update_player_status(self, paused, position):
old_paused = self.player_paused
@ -215,6 +232,7 @@ class Manager(object):
def update_filename(self, filename):
self.player_filename = filename
self.send_filename()
def update_global_state(self, counter, paused, position, name):
curtime = time.time()

View File

@ -15,6 +15,11 @@ from twisted.web.iweb import IBodyProducer
from zope.interface import implements
from .utils import (
join_args,
split_args,
)
class CommandProtocol(LineReceiver):
states = None
@ -25,11 +30,16 @@ class CommandProtocol(LineReceiver):
line = line.strip()
if not line:
return
line = line.split(None, 1)
if len(line) != 2:
#print '>>>', line
args = split_args(line)
if not args:
self.drop_with_error('Malformed line')
return
command, arg = line
command = args.pop(0)
if command == 'error':
self.handle_error(args)
return
available_commands = self.states.get(self._state)
handler = available_commands.get(command)
@ -39,7 +49,10 @@ class CommandProtocol(LineReceiver):
self.drop_with_error('Unknown command: `%s`' % command)
return # TODO log it too
handler(arg)
handler(args)
def handle_error(self, args):
print 'Error received from other side:', args
def change_state(self, new_state):
if new_state not in self.states:
@ -47,11 +60,9 @@ class CommandProtocol(LineReceiver):
self._state = new_state
def send_message(self, *args):
self.sendLine(' '.join(
(arg if isinstance(arg, basestring) else str(arg))
for arg in args
if arg is not None
))
line = join_args(args)
#print '<<<', line
self.sendLine(line)
def drop(self):
self.transport.loseConnection()

View File

@ -25,32 +25,48 @@ class SyncServerProtocol(CommandProtocol):
def connectionLost(self, reason):
self.factory.remove_watcher(self)
def handle_init_iam(self, arg):
self.factory.add_watcher(self, arg.strip())
def handle_init_iam(self, args):
if not len(args) == 1:
self.drop_with_error('Invalid arguments')
return
self.factory.add_watcher(self, args[0])
self.change_state('connected')
def handle_connected_state(self, arg):
arg = parse_state(arg)
if not arg:
def handle_connected_state(self, args):
args = parse_state(args)
if not args:
self.drop_with_error('Malformed state attributes')
return
counter, paused, position, _ = arg
counter, paused, position, _ = args
self.factory.update_state(self, counter, paused, position)
def handle_connected_seek(self, arg):
def handle_connected_seek(self, args):
if not len(args) == 1:
self.drop_with_error('Invalid arguments')
return
try:
position = int(arg)
position = int(args[0])
except ValueError:
self.drop_with_error('Invalid position numeral')
position /= 100.0
position /= 1000.0
self.factory.seek(self, position)
def handle_connected_pong(self, arg):
self.factory.pong_received(self, arg)
def handle_connected_pong(self, args):
if not len(args) == 1:
self.drop_with_error('Invalid arguments')
return
self.factory.pong_received(self, args[0])
def handle_connected_playing(self, args):
if not len(args) == 1:
self.drop_with_error('Invalid arguments')
return
#self.factory.pong_received(self, args[0])
def __hash__(self):
return hash('|'.join((
@ -60,10 +76,15 @@ class SyncServerProtocol(CommandProtocol):
def send_state(self, counter, paused, position, who_last_changed):
self.send_message('state', counter, ('paused' if paused else 'playing'), int(position*100), who_last_changed)
paused = 'paused' if paused else 'playing'
position = int(position*1000)
if who_last_changed is None:
self.send_message('state', counter, paused, position)
else:
self.send_message('state', counter, paused, position, who_last_changed)
def send_seek(self, position, who_seeked):
self.send_message('seek', int(position*100), who_seeked)
self.send_message('seek', int(position*1000), who_seeked)
def send_ping(self, value):
self.send_message('ping', value)
@ -77,6 +98,7 @@ class SyncServerProtocol(CommandProtocol):
state = 'handle_connected_state',
seek = 'handle_connected_seek',
pong = 'handle_connected_pong',
playing = 'handle_connected_playing',
),
)
initial_state = 'init'

View File

@ -1,19 +1,55 @@
#coding:utf8
import os
import re
RE_ARG = re.compile(r"('(?:[^\\']+|\\\\|\\')*'|[^\s']+)(?:\s+|\Z)")
RE_NEED_QUOTING = re.compile(r"[\s'\\]")
RE_QUOTABLE = re.compile(r"['\\]")
RE_UNQUOTABLE = re.compile(r"\\(['\\])")
class InvalidArgumentException(Exception):
pass
def quote_arg(arg):
if isinstance(arg, unicode):
arg = arg.encode('utf8')
elif not isinstance(arg, str):
arg = str(arg)
if not arg or RE_NEED_QUOTING.search(arg):
return "'%s'" % RE_QUOTABLE.sub(r'\\\g<0>', arg)
return arg
def unqote_arg(arg):
if arg.startswith("'") and len(arg) > 1:
arg = RE_UNQUOTABLE.sub(r'\1', arg[1:-1])
return arg.decode('utf8', 'replace')
def _split_args(args):
pos = 0
while pos < len(args):
match = RE_ARG.match(args, pos)
if not match:
raise InvalidArgumentException()
pos = match.end()
yield unqote_arg(match.group(1))
def split_args(args):
try:
return list(_split_args(args))
except InvalidArgumentException:
return None
def join_args(args):
return ' '.join(quote_arg(arg) for arg in args)
def split_args(args, number):
# FIXME Make argument format smarter
return args.split(None, number-1)
def parse_state(args):
args = split_args(args, 4)
l = len(args)
if l == 3:
if len(args) == 3:
counter, state, position = args
who_changed_state = None
elif l == 4:
elif len(args) == 4:
counter, state, position, who_changed_state = args
else:
return
@ -33,7 +69,7 @@ def parse_state(args):
except ValueError:
return
position /= 100.0
position /= 1000.0
return counter, paused, position, who_changed_state