Added server password

Added server argument parser
Added isolated rooms
Added more verbose server settings
Added configurable default room for client and server
This commit is contained in:
Uriziel 2012-09-18 13:08:48 +02:00
parent a636fd4176
commit 698fba9054
6 changed files with 106 additions and 46 deletions

View File

@ -3,6 +3,9 @@
from twisted.internet import reactor from twisted.internet import reactor
from syncplay.server import SyncFactory from syncplay.server import SyncFactory
from syncplay import utils
reactor.listenTCP(8999, SyncFactory()) argsGetter = utils.ServerConfigurationGetter()
args = argsGetter.getConfiguration()
reactor.listenTCP(8999, SyncFactory(args.password, args.banlist, args.isolate_rooms))
reactor.run() reactor.run()

View File

@ -1,3 +1,3 @@
version = '0.2.0' version = '0.3.0'
milestone = 'Clara' milestone = 'Clara'
projectURL = '' projectURL = 'http://droptable.co.cc'

View File

@ -7,6 +7,7 @@ from twisted.internet.protocol import ClientFactory
import time import time
import itertools import itertools
import syncplay import syncplay
import hashlib
class SyncClientProtocol(CommandProtocol): class SyncClientProtocol(CommandProtocol):
def __init__(self, syncplayClient): def __init__(self, syncplayClient):
@ -15,7 +16,7 @@ class SyncClientProtocol(CommandProtocol):
self.sender = self._MessagesSender(self) self.sender = self._MessagesSender(self)
def connectionMade(self): def connectionMade(self):
self.sendMessage('iam', self.syncplayClient.users.currentUser.name) self.sendMessage('iam', self.syncplayClient.users.currentUser.name, self.syncplayClient.users.currentUser.room, self.syncplayClient.serverPassword)
self.syncplayClient.initProtocol(self) self.syncplayClient.initProtocol(self)
def connectionLost(self, reason): def connectionLost(self, reason):
@ -98,9 +99,10 @@ class SyncClientProtocol(CommandProtocol):
@argumentCount(2) @argumentCount(2)
def error(self, args): def error(self, args):
self.__protocol.dropWithError(args[1]) self.__protocol.dropWithError(args[1])
self.__syncplayClient.ui.showMessage("Mismatch between client and server versions detected") if(syncplay.version <> args[0]):
self.__syncplayClient.ui.showMessage("Your version is %s against server's %s" % (syncplay.version, args[0])) self.__syncplayClient.ui.showMessage("Mismatch between client and server versions detected")
self.__syncplayClient.ui.showMessage("Please use latest version of client and server") 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):
@ -205,10 +207,15 @@ class SyncClientFactory(ClientFactory):
self.retry = False self.retry = False
class SyncplayClient(object): class SyncplayClient(object):
def __init__(self, name, make_player, ui, debug): def __init__(self, name, make_player, ui, debug, room, password = None):
self.users = self.UserList() self.users = self.UserList()
self.users.currentUser.name = name self.users.currentUser.name = name
self.users.currentUser.room = 'default' if(room == None):
room = 'default'
self.users.currentUser.room = room
if(password):
password = hashlib.md5(password).hexdigest()
self.serverPassword = password
self.ui = self.UiManager(self, ui, debug) self.ui = self.UiManager(self, ui, debug)
self.protocol_factory = None self.protocol_factory = None
self.protocol = None self.protocol = None

View File

@ -4,7 +4,7 @@ import re
import time import time
import random import random
from functools import wraps from functools import wraps
import hashlib
from twisted.internet import reactor from twisted.internet import reactor
from twisted.internet.protocol import Factory from twisted.internet.protocol import Factory
@ -36,6 +36,10 @@ class SyncServerProtocol(CommandProtocol):
self.factory = factory self.factory = factory
self.state = 'init' self.state = 'init'
def dropWithError(self, error):
CommandProtocol.dropWithError(self, error)
print "Client drop: %s -- %s" % (self.transport.getPeer().host, error)
def __hash__(self): def __hash__(self):
return hash('|'.join(( return hash('|'.join((
self.transport.getPeer().host, self.transport.getPeer().host,
@ -63,13 +67,25 @@ class SyncServerProtocol(CommandProtocol):
return True return True
@state('init') @state('init')
@argumentCount(1) @argumentCount(2, 3)
def iam(self, args): def iam(self, args):
name = re.sub('[^\w]','',args[0]) name = re.sub('[^\w]','',args.pop(0))
if not name: if not name:
self.dropWithError('Invalid nickname') self.dropWithError('Invalid nickname')
return return
self.factory.add_watcher(self.__protocol, name) room = re.sub('[^\w]','',args.pop(0))
if not room:
self.dropWithError('Invalid room')
return
if(self.factory.password):
if(len(args)):
password = args.pop(0)
if(self.factory.password <> password):
self.dropWithError('Wrong server password specified')
else:
self.dropWithError('No password specified')
return
self.factory.add_watcher(self.__protocol, name, room)
self.__protocol.change_state('connected') self.__protocol.change_state('connected')
@ -203,7 +219,7 @@ class SyncServerProtocol(CommandProtocol):
class WatcherInfo(object): class WatcherInfo(object):
def __init__(self, watcher_proto, name): def __init__(self, watcher_proto, name, room):
self.watcher_proto = watcher_proto self.watcher_proto = watcher_proto
self.name = name self.name = name
self.active = True self.active = True
@ -215,7 +231,7 @@ class WatcherInfo(object):
self.last_update = None self.last_update = None
self.last_update_sent = None self.last_update_sent = None
self.room = 'default' self.room = room
self.ping = None self.ping = None
self.time_offset = 0 self.time_offset = 0
self.time_offset_data = [] self.time_offset_data = []
@ -225,12 +241,16 @@ 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, password = '', banlist = None , isolate_rooms = False, min_pause_lock = 2 , update_time_limit = 1):
self.watchers = dict() self.watchers = dict()
self.paused = {} self.paused = {}
self.paused['default'] = True
self.pause_change_time = None self.pause_change_time = None
self.pause_change_by = None self.pause_change_by = None
if(password):
password = hashlib.md5(password).hexdigest()
self.password = password
self.banlsit = banlist
self.isolate_rooms = isolate_rooms
self.min_pause_lock = min_pause_lock self.min_pause_lock = min_pause_lock
self.update_time_limit = update_time_limit self.update_time_limit = update_time_limit
@ -238,13 +258,15 @@ class SyncFactory(Factory):
def buildProtocol(self, addr): def buildProtocol(self, addr):
return SyncServerProtocol(self) return SyncServerProtocol(self)
def add_watcher(self, watcher_proto, name): def add_watcher(self, watcher_proto, name, room):
allnames = [] allnames = []
for watcher in self.watchers.itervalues(): for watcher in self.watchers.itervalues():
allnames.append(watcher.name.lower()) allnames.append(watcher.name.lower())
while name.lower() in allnames: while name.lower() in allnames:
name += '_' name += '_'
watcher = WatcherInfo(watcher_proto, name) if(not self.paused.has_key(room)):
self.paused[room] = True
watcher = WatcherInfo(watcher_proto, name, room)
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
@ -267,10 +289,7 @@ class SyncFactory(Factory):
def remove_room_if_empty(self, room): def remove_room_if_empty(self, room):
room_user_count = sum(1 if watcher.room == room else 0 for watcher in self.watchers.itervalues()) room_user_count = sum(1 if watcher.room == room else 0 for watcher in self.watchers.itervalues())
if not room_user_count: if not room_user_count:
if room == 'default': self.paused.pop(room)
self.paused['default'] = True
else:
self.paused.pop(room)
def update_state(self, watcher_proto, counter, ctime, paused, position): def update_state(self, watcher_proto, counter, ctime, paused, position):
@ -424,6 +443,8 @@ class SyncFactory(Factory):
what(receiver) what(receiver)
def broadcast(self, sender, what): def broadcast(self, sender, what):
if(self.isolate_rooms):
self.broadcast_room(sender, what)
for receiver in self.watchers.itervalues(): for receiver in self.watchers.itervalues():
#if receiver != sender: #if receiver != sender:
what(receiver) what(receiver)

View File

@ -5,6 +5,7 @@ import argparse
import os import os
import re import re
import sys import sys
import itertools
class ArgumentParser(): class ArgumentParser():
RE_ARG = re.compile(r"('(?:[^\\']+|\\\\|\\')*'|[^\s']+)(?:\s+|\Z)") RE_ARG = re.compile(r"('(?:[^\\']+|\\\\|\\')*'|[^\s']+)(?:\s+|\Z)")
@ -51,6 +52,7 @@ class ArgumentParser():
@staticmethod @staticmethod
def joinArguments(args): def joinArguments(args):
args = list(itertools.ifilterfalse(lambda x: None == x, args))
return ' '.join(ArgumentParser.quoteArgument(arg) for arg in args) return ' '.join(ArgumentParser.quoteArgument(arg) for arg in args)
def find_exec_path(name): def find_exec_path(name):
@ -104,6 +106,8 @@ class ConfigurationGetter(object):
self._parser.add_argument('--name', metavar='username', type=str, help='desired username') self._parser.add_argument('--name', metavar='username', type=str, help='desired username')
self._parser.add_argument('-d','--debug', action='store_true', help='debug mode') self._parser.add_argument('-d','--debug', action='store_true', help='debug mode')
self._parser.add_argument('--no-store', action='store_true', help='don\'t store values in syncplay.ini') self._parser.add_argument('--no-store', action='store_true', help='don\'t store values in syncplay.ini')
self._parser.add_argument('--room', metavar='room', type=str, nargs='?', help='default room')
self._parser.add_argument('--password', metavar='password', type=str, nargs='?', help='server password')
self._parser.add_argument('file', metavar='file', type=str, nargs='?', help='file to play') self._parser.add_argument('file', metavar='file', type=str, nargs='?', help='file to play')
self._parser.add_argument('_args', metavar='options', type=str, nargs='*', help='player options, if you need to pass options starting with - prepend them with single \'--\' argument') self._parser.add_argument('_args', metavar='options', type=str, nargs='*', help='player options, if you need to pass options starting with - prepend them with single \'--\' argument')
@ -129,6 +133,14 @@ class ConfigurationGetter(object):
self._splitPortAndHost() self._splitPortAndHost()
self._config.set(section_name, 'host', self._args.host) self._config.set(section_name, 'host', self._args.host)
self._config.set(section_name, 'name', self._args.name) self._config.set(section_name, 'name', self._args.name)
self._config.set(section_name, 'room', self._args.room)
self._config.set(section_name, 'password', self._args.password)
def _readConfigValue(self, section_name, name):
try:
return self._config.get(section_name, name)
except ConfigParser.NoOptionError:
return None
def _readMissingValuesFromConfigFile(self): def _readMissingValuesFromConfigFile(self):
self._openConfigFile() self._openConfigFile()
@ -137,14 +149,19 @@ class ConfigurationGetter(object):
self._valuesToReadFromConfig(section_name) self._valuesToReadFromConfig(section_name)
except ConfigParser.NoSectionError: except ConfigParser.NoSectionError:
pass pass
except ConfigParser.NoOptionError:
pass
def _valuesToReadFromConfig(self, section_name): def _valuesToReadFromConfig(self, section_name):
if (self._args.host == None): if (self._args.host == None):
self._args.host = self._config.get(section_name, 'host') self._args.host = self._readConfigValue(section_name, 'host')
if (self._args.name == None): if (self._args.name == None):
self._args.name = self._config.get(section_name, 'name') self._args.name = self._readConfigValue(section_name, 'name')
if (self._args.room == None):
self._args.room = self._readConfigValue(section_name, 'room')
if (self._args.password == None):
self._args.password = self._readConfigValue(section_name, 'password')
def _splitPortAndHost(self): def _splitPortAndHost(self):
if(self._args.host): if(self._args.host):
@ -154,17 +171,16 @@ class ConfigurationGetter(object):
else: else:
self._args.port = 8999 self._args.port = 8999
def prepareClientConfiguration(self): def getConfiguration(self):
self._findWorkingDirectory() self._findWorkingDirectory()
self._prepareArgParser() self._prepareArgParser()
self._args = self._parser.parse_args() self._args = self._parser.parse_args()
self._readMissingValuesFromConfigFile() self._readMissingValuesFromConfigFile()
self.saveValuesIntoConfigFile() self.saveValuesIntoConfigFile()
self._splitPortAndHost() self._splitPortAndHost()
def getClientConfiguration(self):
return self._args return self._args
class MPCConfigurationGetter(ConfigurationGetter): class MPCConfigurationGetter(ConfigurationGetter):
def _prepareArgParser(self): def _prepareArgParser(self):
ConfigurationGetter._prepareArgParser(self) ConfigurationGetter._prepareArgParser(self)
@ -177,14 +193,28 @@ class MPCConfigurationGetter(ConfigurationGetter):
def _valuesToReadFromConfig(self, section_name): def _valuesToReadFromConfig(self, section_name):
ConfigurationGetter._valuesToReadFromConfig(self, section_name) ConfigurationGetter._valuesToReadFromConfig(self, section_name)
if (self._args.mpc_path == None): if (self._args.mpc_path == None):
self._args.mpc_path = self._config.get(section_name, 'mpc_path') self._args.mpc_path = self._readConfigValue(section_name, 'mpc_path')
def __addSpecialMPCFlags(self): def __addSpecialMPCFlags(self):
return self._args._args.extend(['/open', '/new']) self._args._args.extend(['/open', '/new'])
def prepareClientConfiguration(self): def getConfiguration(self):
ConfigurationGetter.prepareClientConfiguration(self) ConfigurationGetter.getConfiguration(self)
self.__addSpecialMPCFlags() self.__addSpecialMPCFlags()
return self._args
class ServerConfigurationGetter(ConfigurationGetter):
def getConfiguration(self):
self._prepareArgParser()
self._args = self._parser.parse_args()
return self._args
def _prepareArgParser(self):
self._parser = argparse.ArgumentParser(description='Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network. Server instance',
epilog='If no options supplied _config values will be used')
self._parser.add_argument('--password', metavar='password', type=str, nargs='?', help='server password')
self._parser.add_argument('--banlist', metavar='banlist', type=str, nargs='?', help='server banlist file')
self._parser.add_argument('--isolate-rooms', action='store_true', help='should rooms be isolated?')

View File

@ -12,19 +12,18 @@ class SyncplayMplayer:
self._promptForMissingArguments() self._promptForMissingArguments()
self.args._args.extend(('-slave', '-msglevel', 'all=1:global=4')) self.args._args.extend(('-slave', '-msglevel', 'all=1:global=4'))
if(self.args.file): self.args._args.extend((self.args.file,)) if(self.args.file): self.args._args.extend((self.args.file,))
syncplayClient = client.SyncplayClient(self.args.name, lambda m: mplayer.run_mplayer(m, 'mplayer', self.args._args), self.interface, self.args.debug) syncplayClient = client.SyncplayClient(self.args.name, lambda m: mplayer.run_mplayer(m, 'mplayer', self.args._args), self.interface, self.args.debug, self.args.room, self.args.password)
self.interface.addClient(syncplayClient) self.interface.addClient(syncplayClient)
syncplayClient.start(self.args.host, self.args.port) syncplayClient.start(self.args.host, self.args.port)
def _prepareArguments(self): def _prepareArguments(self):
self.argsGetter = utils.ConfigurationGetter() self.argsGetter = utils.ConfigurationGetter()
self.argsGetter.prepareClientConfiguration() self.args = self.argsGetter.getConfiguration()
self.args = self.argsGetter.getClientConfiguration()
def _promptForMissingArguments(self): def _promptForMissingArguments(self):
if (self.args.host == None): if (self.args.host == None):
self.args.host = self.interface.promptFor(promptName = "Hostname", message = "You must supply hostname on the first run, it's easier trough command line arguments.") self.args.host = self.interface.promptFor(promptName = "Hostname", message = "You must supply hostname on the first run, it's easier through command line arguments.")
if (self.args.name == None): if (self.args.name == None):
self.args.name = self.interface.promptFor(promptName = "Username", message = "You must supply username on the first run, it's easier trough command line arguments.") self.args.name = self.interface.promptFor(promptName = "Username", message = "You must supply username on the first run, it's easier through command line arguments.")
self.argsGetter.saveValuesIntoConfigFile() self.argsGetter.saveValuesIntoConfigFile()
if __name__ == '__main__': if __name__ == '__main__':