diff --git a/run_sync_server.py b/run_sync_server.py index c46ee1d..ea00ce1 100755 --- a/run_sync_server.py +++ b/run_sync_server.py @@ -2,7 +2,10 @@ from twisted.internet import reactor -from syncplay.server import SyncFactory - -reactor.listenTCP(8999, SyncFactory()) +from syncplay.server import SyncFactory +from syncplay import utils + +argsGetter = utils.ServerConfigurationGetter() +args = argsGetter.getConfiguration() +reactor.listenTCP(8999, SyncFactory(args.password, args.banlist, args.isolate_rooms)) reactor.run() diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 02e7060..1e1ebe9 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,3 +1,3 @@ -version = '0.2.0' +version = '0.3.0' milestone = 'Clara' -projectURL = '' \ No newline at end of file +projectURL = 'http://droptable.co.cc' \ No newline at end of file diff --git a/syncplay/client.py b/syncplay/client.py index dbfdf9e..81ebdf8 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -7,6 +7,7 @@ from twisted.internet.protocol import ClientFactory import time import itertools import syncplay +import hashlib class SyncClientProtocol(CommandProtocol): def __init__(self, syncplayClient): @@ -15,7 +16,7 @@ class SyncClientProtocol(CommandProtocol): self.sender = self._MessagesSender(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) def connectionLost(self, reason): @@ -98,9 +99,10 @@ class SyncClientProtocol(CommandProtocol): @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") + if(syncplay.version <> args[0]): + 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) def playing(self, args): @@ -205,10 +207,15 @@ class SyncClientFactory(ClientFactory): self.retry = False 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.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.protocol_factory = None self.protocol = None diff --git a/syncplay/server.py b/syncplay/server.py index 87a5f8a..52705e7 100644 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -4,7 +4,7 @@ import re import time import random from functools import wraps - +import hashlib from twisted.internet import reactor from twisted.internet.protocol import Factory @@ -36,6 +36,10 @@ class SyncServerProtocol(CommandProtocol): self.factory = factory self.state = 'init' + def dropWithError(self, error): + CommandProtocol.dropWithError(self, error) + print "Client drop: %s -- %s" % (self.transport.getPeer().host, error) + def __hash__(self): return hash('|'.join(( self.transport.getPeer().host, @@ -63,13 +67,25 @@ class SyncServerProtocol(CommandProtocol): return True @state('init') - @argumentCount(1) + @argumentCount(2, 3) def iam(self, args): - name = re.sub('[^\w]','',args[0]) + name = re.sub('[^\w]','',args.pop(0)) if not name: self.dropWithError('Invalid nickname') 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') @@ -203,7 +219,7 @@ class SyncServerProtocol(CommandProtocol): class WatcherInfo(object): - def __init__(self, watcher_proto, name): + def __init__(self, watcher_proto, name, room): self.watcher_proto = watcher_proto self.name = name self.active = True @@ -215,7 +231,7 @@ class WatcherInfo(object): self.last_update = None self.last_update_sent = None - self.room = 'default' + self.room = room self.ping = None self.time_offset = 0 self.time_offset_data = [] @@ -225,26 +241,32 @@ class WatcherInfo(object): self.counter = 0 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.paused = {} - self.paused['default'] = True self.pause_change_time = 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.update_time_limit = update_time_limit def buildProtocol(self, addr): return SyncServerProtocol(self) - def add_watcher(self, watcher_proto, name): + def add_watcher(self, watcher_proto, name, room): allnames = [] for watcher in self.watchers.itervalues(): allnames.append(watcher.name.lower()) while name.lower() in allnames: - name += '_' - watcher = WatcherInfo(watcher_proto, name) + name += '_' + if(not self.paused.has_key(room)): + self.paused[room] = True + watcher = WatcherInfo(watcher_proto, name, room) if self.watchers: watcher.max_position = min(w.max_position for w in self.watchers.itervalues()) self.watchers[watcher_proto] = watcher @@ -267,10 +289,7 @@ class SyncFactory(Factory): def remove_room_if_empty(self, room): room_user_count = sum(1 if watcher.room == room else 0 for watcher in self.watchers.itervalues()) if not room_user_count: - if room == 'default': - self.paused['default'] = True - else: - self.paused.pop(room) + self.paused.pop(room) def update_state(self, watcher_proto, counter, ctime, paused, position): @@ -424,6 +443,8 @@ class SyncFactory(Factory): what(receiver) def broadcast(self, sender, what): + if(self.isolate_rooms): + self.broadcast_room(sender, what) for receiver in self.watchers.itervalues(): #if receiver != sender: what(receiver) diff --git a/syncplay/utils.py b/syncplay/utils.py index f7b4887..fb316ac 100644 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -5,6 +5,7 @@ import argparse import os import re import sys +import itertools class ArgumentParser(): RE_ARG = re.compile(r"('(?:[^\\']+|\\\\|\\')*'|[^\s']+)(?:\s+|\Z)") @@ -51,6 +52,7 @@ class ArgumentParser(): @staticmethod def joinArguments(args): + args = list(itertools.ifilterfalse(lambda x: None == x, args)) return ' '.join(ArgumentParser.quoteArgument(arg) for arg in args) 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('-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('--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('_args', metavar='options', type=str, nargs='*', help='player options, if you need to pass options starting with - prepend them with single \'--\' argument') @@ -129,7 +133,15 @@ class ConfigurationGetter(object): self._splitPortAndHost() self._config.set(section_name, 'host', self._args.host) 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): self._openConfigFile() section_name = self._getSectionName() @@ -137,15 +149,20 @@ class ConfigurationGetter(object): self._valuesToReadFromConfig(section_name) except ConfigParser.NoSectionError: pass - except ConfigParser.NoOptionError: - pass + def _valuesToReadFromConfig(self, section_name): 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): - 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): if(self._args.host): if ':' in self._args.host: @@ -154,16 +171,15 @@ class ConfigurationGetter(object): else: self._args.port = 8999 - def prepareClientConfiguration(self): + def getConfiguration(self): self._findWorkingDirectory() self._prepareArgParser() self._args = self._parser.parse_args() self._readMissingValuesFromConfigFile() self.saveValuesIntoConfigFile() self._splitPortAndHost() - - def getClientConfiguration(self): return self._args + class MPCConfigurationGetter(ConfigurationGetter): def _prepareArgParser(self): @@ -177,14 +193,28 @@ class MPCConfigurationGetter(ConfigurationGetter): def _valuesToReadFromConfig(self, section_name): ConfigurationGetter._valuesToReadFromConfig(self, section_name) 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): - return self._args._args.extend(['/open', '/new']) + self._args._args.extend(['/open', '/new']) - def prepareClientConfiguration(self): - ConfigurationGetter.prepareClientConfiguration(self) + def getConfiguration(self): + ConfigurationGetter.getConfiguration(self) 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?') + + \ No newline at end of file diff --git a/syncplay_mplayer.py b/syncplay_mplayer.py index 496f107..89277f8 100644 --- a/syncplay_mplayer.py +++ b/syncplay_mplayer.py @@ -12,19 +12,18 @@ class SyncplayMplayer: self._promptForMissingArguments() self.args._args.extend(('-slave', '-msglevel', 'all=1:global=4')) 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) syncplayClient.start(self.args.host, self.args.port) def _prepareArguments(self): self.argsGetter = utils.ConfigurationGetter() - self.argsGetter.prepareClientConfiguration() - self.args = self.argsGetter.getClientConfiguration() + self.args = self.argsGetter.getConfiguration() def _promptForMissingArguments(self): 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): - 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() if __name__ == '__main__':