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,56 +17,59 @@ 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 class _MessagesHandler(object):
def __init__(self, protocol, factory):
self.__protocol = protocol
self.factory = factory
def dropWithError(self, error):
self.__protocol.dropWithError(error)
def compareState(self, state):
if state != self.__protocol.state:
return False
else:
return True
@state('init')
@argumentCount(1)
def iam(self, args):
name = re.sub('[^\w]','',args[0]) name = re.sub('[^\w]','',args[0])
if not name: if not name:
self.dropWithError('Invalid nickname') self.dropWithError('Invalid nickname')
return return
self.factory.add_watcher(self, name) self.factory.add_watcher(self.__protocol, name)
self.change_state('connected') self.__protocol.change_state('connected')
@argumentCount(1)
def handle_connected_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)
@argumentCount(4)
def handle_connected_state(self, args):
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 handle_connected_list(self, args):
watcher = self.factory.watchers.get(self)
for w in self.factory.watchers.itervalues():
if w == watcher:
continue
self.send_present(w.name, w.room, w.filename)
@state('connected')
@argumentCount(3) @argumentCount(3)
def handle_connected_seek(self, args): def seek(self, args):
counter, ctime, position = args counter, ctime, position = args
try: try:
counter = int(counter) counter = int(counter)
@ -80,8 +83,9 @@ class SyncServerProtocol(CommandProtocol):
self.factory.seek(self, counter, ctime, position) self.factory.seek(self, counter, ctime, position)
@state('connected')
@argumentCount(2) @argumentCount(2)
def handle_connected_pong(self, args): def pong(self, args):
value, ctime = args value, ctime = args
try: try:
ctime = int(ctime) ctime = int(ctime)
@ -92,68 +96,42 @@ class SyncServerProtocol(CommandProtocol):
self.factory.pong_received(self, value, ctime) self.factory.pong_received(self, value, ctime)
@state('connected')
@argumentCount(1) @argumentCount(1)
def handle_connected_playing(self, args): def playing(self, args):
self.factory.playing_received(self, args[0]) self.factory.playing_received(self, args[0])
def __hash__(self):
return hash('|'.join((
self.transport.getPeer().host,
str(id(self)),
)))
@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)
def send_state(self, counter, ctime, paused, position, who_last_changed): @state('connected')
ctime = int(ctime*1000) @argumentCount(0)
paused = 'paused' if paused else 'playing' def list(self, args):
position = int(position*1000) watcher = self.factory.watchers.get(self)
if who_last_changed is None: for w in self.factory.watchers.itervalues():
self.send_message('state', counter, ctime, paused, position) if w == watcher:
else: continue
self.send_message('state', counter, ctime, paused, position, who_last_changed) self.__protocol.sender.send_present(w.name, w.room, w.filename)
def send_seek(self, ctime, position, who_seeked): @state('connected')
self.send_message('seek', int(ctime*1000), int(position*1000), who_seeked) @argumentCount(4)
def state(self, args):
def send_ping(self, value): args = self.__parseState(args)
self.send_message('ping', value) if not args:
self.dropWithError('Malformed state attributes')
def send_playing(self, who, where, what): return
self.send_message('playing', who, where, what) counter, ctime, paused, position, _ = args
self.factory.update_state(self, counter, ctime, paused, position)
def send_room(self, who, where):
self.send_message('room', who, where)
def send_present(self, who, where, what):
if what:
self.send_message('present', who, where, what)
else:
self.send_message('present', who, where)
def send_joined(self, who):
self.send_message('joined', who)
def send_left(self, who):
self.send_message('left', who)
def send_hello(self, name):
self.send_message('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): def __parseState(self, args):
if len(args) == 4: if len(args) == 4:
@ -163,24 +141,67 @@ class SyncServerProtocol(CommandProtocol):
counter, ctime, state, position, who_changed_state = args counter, ctime, state, position, who_changed_state = args
else: else:
return return
if not state in ('paused', 'playing'): if not state in ('paused', 'playing'):
return return
paused = state == 'paused' paused = state == 'paused'
try: try:
counter = int(counter) counter = int(counter)
ctime = int(ctime) ctime = int(ctime)
position = int(position) position = int(position)
except ValueError: except ValueError:
return return
ctime /= 1000.0 ctime /= 1000.0
position /= 1000.0 position /= 1000.0
return counter, ctime, paused, position, who_changed_state return counter, ctime, paused, position, who_changed_state
def __hash__(self):
return hash('|'.join((
self.transport.getPeer().host,
str(id(self)),
)))
class _MessagesSender(object):
def __init__(self, protocol):
self.__protocol = protocol
def send_state(self, counter, ctime, paused, position, who_last_changed):
ctime = int(ctime*1000)
paused = 'paused' if paused else 'playing'
position = int(position*1000)
if who_last_changed is None:
self.__protocol.sendMessage('state', counter, ctime, paused, position)
else:
self.__protocol.sendMessage('state', counter, ctime, paused, position, who_last_changed)
def send_seek(self, ctime, position, who_seeked):
self.__protocol.sendMessage('seek', int(ctime*1000), int(position*1000), who_seeked)
def send_ping(self, value):
self.__protocol.sendMessage('ping', value)
def send_playing(self, who, where, what):
self.__protocol.sendMessage('playing', who, where, what)
def send_room(self, who, where):
self.__protocol.sendMessage('room', who, where)
def send_present(self, who, where, what):
if what:
self.__protocol.sendMessage('present', who, where, what)
else:
self.__protocol.sendMessage('present', who, where)
def send_joined(self, who):
self.__protocol.sendMessage('joined', who)
def send_left(self, who):
self.__protocol.sendMessage('left', who)
def send_hello(self, name):
self.__protocol.sendMessage('hello', name)
class WatcherInfo(object): class WatcherInfo(object):
def __init__(self, watcher_proto, name): def __init__(self, watcher_proto, name):
self.watcher_proto = watcher_proto self.watcher_proto = watcher_proto
@ -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():