Upgraded server file to new protocol idea

This commit is contained in:
Uriziel 2012-09-17 21:19:58 +02:00
parent 4a5462dcae
commit 09fc738cc3
2 changed files with 182 additions and 149 deletions

View File

@ -6,6 +6,7 @@ from twisted.internet import reactor
from twisted.internet.protocol import ClientFactory from twisted.internet.protocol import ClientFactory
import time import time
import itertools import itertools
import syncplay
class SyncClientProtocol(CommandProtocol): class SyncClientProtocol(CommandProtocol):
def __init__(self, syncplayClient): def __init__(self, syncplayClient):
@ -28,6 +29,7 @@ class SyncClientProtocol(CommandProtocol):
def dropWithError(self, error): def dropWithError(self, error):
self.syncplayClient.ui.showErrorMessage(error) self.syncplayClient.ui.showErrorMessage(error)
self.syncplayClient.protocol_factory.retry = False
CommandProtocol.dropWithError(self, error) CommandProtocol.dropWithError(self, error)
def lineReceived(self, line): def lineReceived(self, line):
@ -40,6 +42,9 @@ class SyncClientProtocol(CommandProtocol):
self.__syncplayClient = syncplayClient self.__syncplayClient = syncplayClient
self._lastServerTimestamp = 0 self._lastServerTimestamp = 0
def dropWithError(self, error):
self.__protocol.dropWithError(error)
@argumentCount(1) @argumentCount(1)
def hello(self, args): def hello(self, args):
message ='Connected as ' + args[0] message ='Connected as ' + args[0]
@ -90,6 +95,13 @@ class SyncClientProtocol(CommandProtocol):
def ping(self, args): def ping(self, args):
self.__syncplayClient.protocol.sendMessage('pong', args[0], int(time.time()*100000)) self.__syncplayClient.protocol.sendMessage('pong', args[0], int(time.time()*100000))
@argumentCount(2)
def error(self, args):
self.__protocol.dropWithError(args[1])
self.__syncplayClient.ui.showMessage("Mismatch between client and server versions detected")
self.__syncplayClient.ui.showMessage("Your version is %s against server's %s" % (syncplay.version, args[0]))
self.__syncplayClient.ui.showMessage("Please use latest version of client and server")
@argumentCount(3) @argumentCount(3)
def playing(self, args): def playing(self, args):
who, where, what = args who, where, what = args

View File

@ -3,7 +3,7 @@
import re import re
import time import time
import random import random
from functools import wraps
from twisted.internet import reactor from twisted.internet import reactor
from twisted.internet.protocol import Factory from twisted.internet.protocol import Factory
@ -17,84 +17,142 @@ CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
def random_chars(): def random_chars():
return ''.join(random.choice(CHARS) for _ in xrange(10)) return ''.join(random.choice(CHARS) for _ in xrange(10))
def state(state):
def decorator(f):
@wraps(f)
def wrapper(self, args):
if (not self.compareState(state)):
self.dropWithError('Invalid watcher state')
return
return f(self, args)
return wrapper
return decorator
class SyncServerProtocol(CommandProtocol): class SyncServerProtocol(CommandProtocol):
def __init__(self, factory): def __init__(self, factory):
CommandProtocol.__init__(self) CommandProtocol.__init__(self)
self.handler = self._MessagesHandler(self, factory)
self.sender = self._MessagesSender(self)
self.factory = factory self.factory = factory
self.state = 'init'
def connectionLost(self, reason): def connectionLost(self, reason):
self.factory.remove_watcher(self) self.factory.remove_watcher(self)
def handle_init_iam(self, args): def change_state(self, state):
if not len(args) == 1: self.state = state
self.dropWithError('Invalid arguments')
return
name = re.sub('[^\w]','',args[0])
if not name:
self.dropWithError('Invalid nickname')
return
self.factory.add_watcher(self, name)
self.change_state('connected')
@argumentCount(1) class _MessagesHandler(object):
def handle_connected_room(self, args): def __init__(self, protocol, factory):
watcher = self.factory.watchers.get(self) self.__protocol = protocol
old_room = watcher.room self.factory = factory
watcher.room = str(re.sub('[^\w]','',args[0]))
self.factory.broadcast(watcher, lambda receiver: receiver.watcher_proto.send_room(watcher.name,watcher.room))
if not watcher.room in self.factory.paused:
self.factory.paused[watcher.room] = True
self.factory.remove_room_if_empty(old_room)
watcher = self.factory.watchers.get(self)
@argumentCount(4) def dropWithError(self, error):
def handle_connected_state(self, args): self.__protocol.dropWithError(error)
args = self.__parse_state(args)
if not args:
self.dropWithError('Malformed state attributes')
return
counter, ctime, paused, position, _ = args
self.factory.update_state(self, counter, ctime, paused, position)
@argumentCount(0) def compareState(self, state):
def handle_connected_list(self, args): if state != self.__protocol.state:
watcher = self.factory.watchers.get(self) return False
for w in self.factory.watchers.itervalues(): else:
if w == watcher: return True
continue
self.send_present(w.name, w.room, w.filename)
@argumentCount(3) @state('init')
def handle_connected_seek(self, args): @argumentCount(1)
counter, ctime, position = args def iam(self, args):
try: name = re.sub('[^\w]','',args[0])
counter = int(counter) if not name:
ctime = int(ctime) self.dropWithError('Invalid nickname')
position = int(position) return
except ValueError: self.factory.add_watcher(self.__protocol, name)
self.dropWithError('Invalid arguments') self.__protocol.change_state('connected')
ctime /= 1000.0
position /= 1000.0
self.factory.seek(self, counter, ctime, position) @state('connected')
@argumentCount(3)
def seek(self, args):
counter, ctime, position = args
try:
counter = int(counter)
ctime = int(ctime)
position = int(position)
except ValueError:
self.dropWithError('Invalid arguments')
@argumentCount(2) ctime /= 1000.0
def handle_connected_pong(self, args): position /= 1000.0
value, ctime = args
try:
ctime = int(ctime)
except ValueError:
self.dropWithError('Invalid arguments')
ctime /= 100000.0 self.factory.seek(self, counter, ctime, position)
self.factory.pong_received(self, value, ctime) @state('connected')
@argumentCount(2)
def pong(self, args):
value, ctime = args
try:
ctime = int(ctime)
except ValueError:
self.dropWithError('Invalid arguments')
@argumentCount(1) ctime /= 100000.0
def handle_connected_playing(self, args):
self.factory.playing_received(self, args[0]) self.factory.pong_received(self, value, ctime)
@state('connected')
@argumentCount(1)
def playing(self, args):
self.factory.playing_received(self, args[0])
@state('connected')
@argumentCount(1)
def room(self, args):
watcher = self.factory.watchers.get(self)
old_room = watcher.room
watcher.room = str(re.sub('[^\w]','',args[0]))
self.factory.broadcast(watcher, lambda receiver: receiver.watcher_proto.send_room(watcher.name,watcher.room))
if not watcher.room in self.factory.paused:
self.factory.paused[watcher.room] = True
self.factory.remove_room_if_empty(old_room)
watcher = self.factory.watchers.get(self)
@state('connected')
@argumentCount(0)
def list(self, args):
watcher = self.factory.watchers.get(self)
for w in self.factory.watchers.itervalues():
if w == watcher:
continue
self.__protocol.sender.send_present(w.name, w.room, w.filename)
@state('connected')
@argumentCount(4)
def state(self, args):
args = self.__parseState(args)
if not args:
self.dropWithError('Malformed state attributes')
return
counter, ctime, paused, position, _ = args
self.factory.update_state(self, counter, ctime, paused, position)
def __parseState(self, args):
if len(args) == 4:
counter, ctime, state, position = args
who_changed_state = None
elif len(args) == 5:
counter, ctime, state, position, who_changed_state = args
else:
return
if not state in ('paused', 'playing'):
return
paused = state == 'paused'
try:
counter = int(counter)
ctime = int(ctime)
position = int(position)
except ValueError:
return
ctime /= 1000.0
position /= 1000.0
return counter, ctime, paused, position, who_changed_state
def __hash__(self): def __hash__(self):
return hash('|'.join(( return hash('|'.join((
@ -102,84 +160,47 @@ class SyncServerProtocol(CommandProtocol):
str(id(self)), str(id(self)),
))) )))
class _MessagesSender(object):
def __init__(self, protocol):
self.__protocol = protocol
def send_state(self, counter, ctime, paused, position, who_last_changed): def send_state(self, counter, ctime, paused, position, who_last_changed):
ctime = int(ctime*1000) ctime = int(ctime*1000)
paused = 'paused' if paused else 'playing' paused = 'paused' if paused else 'playing'
position = int(position*1000) position = int(position*1000)
if who_last_changed is None: if who_last_changed is None:
self.send_message('state', counter, ctime, paused, position) self.__protocol.sendMessage('state', counter, ctime, paused, position)
else: else:
self.send_message('state', counter, ctime, paused, position, who_last_changed) self.__protocol.sendMessage('state', counter, ctime, paused, position, who_last_changed)
def send_seek(self, ctime, position, who_seeked): def send_seek(self, ctime, position, who_seeked):
self.send_message('seek', int(ctime*1000), int(position*1000), who_seeked) self.__protocol.sendMessage('seek', int(ctime*1000), int(position*1000), who_seeked)
def send_ping(self, value): def send_ping(self, value):
self.send_message('ping', value) self.__protocol.sendMessage('ping', value)
def send_playing(self, who, where, what): def send_playing(self, who, where, what):
self.send_message('playing', who, where, what) self.__protocol.sendMessage('playing', who, where, what)
def send_room(self, who, where): def send_room(self, who, where):
self.send_message('room', who, where) self.__protocol.sendMessage('room', who, where)
def send_present(self, who, where, what): def send_present(self, who, where, what):
if what: if what:
self.send_message('present', who, where, what) self.__protocol.sendMessage('present', who, where, what)
else: else:
self.send_message('present', who, where) self.__protocol.sendMessage('present', who, where)
def send_joined(self, who): def send_joined(self, who):
self.send_message('joined', who) self.__protocol.sendMessage('joined', who)
def send_left(self, who): def send_left(self, who):
self.send_message('left', who) self.__protocol.sendMessage('left', who)
def send_hello(self, name): def send_hello(self, name):
self.send_message('hello', name) self.__protocol.sendMessage('hello', name)
states = dict(
init = dict(
iam = 'handle_init_iam',
),
connected = dict(
room = 'handle_connected_room',
list = 'handle_connected_list',
state = 'handle_connected_state',
seek = 'handle_connected_seek',
pong = 'handle_connected_pong',
playing = 'handle_connected_playing',
),
)
initial_state = 'init'
def __parseState(self, args):
if len(args) == 4:
counter, ctime, state, position = args
who_changed_state = None
elif len(args) == 5:
counter, ctime, state, position, who_changed_state = args
else:
return
if not state in ('paused', 'playing'):
return
paused = state == 'paused'
try:
counter = int(counter)
ctime = int(ctime)
position = int(position)
except ValueError:
return
ctime /= 1000.0
position /= 1000.0
return counter, ctime, paused, position, who_changed_state
class WatcherInfo(object): class WatcherInfo(object):
def __init__(self, watcher_proto, name): def __init__(self, watcher_proto, name):
@ -204,7 +225,7 @@ class WatcherInfo(object):
self.counter = 0 self.counter = 0
class SyncFactory(Factory): class SyncFactory(Factory):
def __init__(self, min_pause_lock = 2, update_time_limit = 1): def __init__(self, min_pause_lock = 2 , update_time_limit = 1):
self.watchers = dict() self.watchers = dict()
self.paused = {} self.paused = {}
self.paused['default'] = True self.paused['default'] = True
@ -228,7 +249,7 @@ class SyncFactory(Factory):
if self.watchers: if self.watchers:
watcher.max_position = min(w.max_position for w in self.watchers.itervalues()) watcher.max_position = min(w.max_position for w in self.watchers.itervalues())
self.watchers[watcher_proto] = watcher self.watchers[watcher_proto] = watcher
watcher_proto.send_hello(name) watcher_proto.sender.send_hello(name)
self.send_state_to(watcher) self.send_state_to(watcher)
self.send_ping_to(watcher) self.send_ping_to(watcher)
@ -238,7 +259,7 @@ class SyncFactory(Factory):
return return
self.remove_room_if_empty(watcher.room) self.remove_room_if_empty(watcher.room)
watcher.active = False watcher.active = False
self.broadcast(watcher, lambda receiver: receiver.watcher_proto.send_left(watcher.name)) self.broadcast(watcher, lambda receiver: receiver.watcher_proto.sender.send_left(watcher.name))
if self.pause_change_by == watcher: if self.pause_change_by == watcher:
self.pause_change_time = None self.pause_change_time = None
@ -305,7 +326,7 @@ class SyncFactory(Factory):
def __do_seek(self, receiver, position, watcher, curtime): def __do_seek(self, receiver, position, watcher, curtime):
receiver.max_position = position receiver.max_position = position
receiver.watcher_proto.send_seek(curtime-receiver.time_offset, position, watcher.name) receiver.watcher_proto.sender.send_seek(curtime-receiver.time_offset, position, watcher.name)
def send_state_to(self, watcher, position=None, curtime=None): def send_state_to(self, watcher, position=None, curtime=None):
if position is None: if position is None:
@ -316,9 +337,9 @@ class SyncFactory(Factory):
ctime = curtime - watcher.time_offset ctime = curtime - watcher.time_offset
if self.pause_change_by: if self.pause_change_by:
watcher.watcher_proto.send_state(watcher.counter, ctime, self.paused[watcher.room], position, self.pause_change_by.name) watcher.watcher_proto.sender.send_state(watcher.counter, ctime, self.paused[watcher.room], position, self.pause_change_by.name)
else: else:
watcher.watcher_proto.send_state(watcher.counter, ctime, self.paused[watcher.room], position, None) watcher.watcher_proto.sender.send_state(watcher.counter, ctime, self.paused[watcher.room], position, None)
watcher.last_update_sent = curtime watcher.last_update_sent = curtime
@ -382,7 +403,7 @@ class SyncFactory(Factory):
watcher.watcher_proto.drop() watcher.watcher_proto.drop()
return return
watcher.watcher_proto.send_ping(chars) watcher.watcher_proto.sender.send_ping(chars)
watcher.pings_sent[chars] = time.time() watcher.pings_sent[chars] = time.time()
if len(watcher.pings_sent) > 30: if len(watcher.pings_sent) > 30:
@ -397,7 +418,7 @@ class SyncFactory(Factory):
if not watcher: if not watcher:
return return
watcher.filename = filename watcher.filename = filename
self.broadcast(watcher, lambda receiver: receiver.watcher_proto.send_playing(watcher.name, watcher.room, filename)) self.broadcast(watcher, lambda receiver: receiver.watcher_proto.sender.send_playing(watcher.name, watcher.room, filename))
def broadcast_room(self, sender, what): def broadcast_room(self, sender, what):
for receiver in self.watchers.itervalues(): for receiver in self.watchers.itervalues():