Use getMessage/EN where possible
This commit is contained in:
parent
10c65aea7f
commit
35f3c3e6b3
@ -1,26 +1,27 @@
|
||||
from syncplay.client import SyncplayClient
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
|
||||
from syncplay import ui
|
||||
try:
|
||||
from syncplay.players.mpc import MPCHCAPIPlayer
|
||||
except ImportError:
|
||||
MPCHCAPIPlayer = None
|
||||
from syncplay.players.mplayer import MplayerPlayer
|
||||
|
||||
class SyncplayClientManager(object):
|
||||
def run(self):
|
||||
config = ConfigurationGetter().getConfiguration()
|
||||
interface = ui.getUi(graphical=not config["noGui"])
|
||||
syncplayClient = None
|
||||
if(config['playerType'] == "mpc"):
|
||||
syncplayClient = SyncplayClient(MPCHCAPIPlayer, interface, config)
|
||||
elif(config['playerType'] == "mplayer"):
|
||||
syncplayClient = SyncplayClient(MplayerPlayer, interface, config)
|
||||
if(syncplayClient):
|
||||
interface.addClient(syncplayClient)
|
||||
syncplayClient.start(config['host'], config['port'])
|
||||
else:
|
||||
interface.showErrorMessage("Unable to start client")
|
||||
|
||||
|
||||
|
||||
from syncplay.client import SyncplayClient
|
||||
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
|
||||
from syncplay import ui
|
||||
from syncplay.messages import getMessage
|
||||
try:
|
||||
from syncplay.players.mpc import MPCHCAPIPlayer
|
||||
except ImportError:
|
||||
MPCHCAPIPlayer = None
|
||||
from syncplay.players.mplayer import MplayerPlayer
|
||||
|
||||
class SyncplayClientManager(object):
|
||||
def run(self):
|
||||
config = ConfigurationGetter().getConfiguration()
|
||||
interface = ui.getUi(graphical=not config["noGui"])
|
||||
syncplayClient = None
|
||||
if(config['playerType'] == "mpc"):
|
||||
syncplayClient = SyncplayClient(MPCHCAPIPlayer, interface, config)
|
||||
elif(config['playerType'] == "mplayer"):
|
||||
syncplayClient = SyncplayClient(MplayerPlayer, interface, config)
|
||||
if(syncplayClient):
|
||||
interface.addClient(syncplayClient)
|
||||
syncplayClient.start(config['host'], config['port'])
|
||||
else:
|
||||
interface.showErrorMessage(getMessage("en", "unable-to-start-client-error"))
|
||||
|
||||
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ en = {
|
||||
"commandlist-notification/seek" : "\t[s][+-]time - seek to the given value of time, if + or - is not specified it's absolute time in seconds or min:sec",
|
||||
"commandlist-notification/help" : "\th - this help",
|
||||
"syncplay-version-notification" : "Syncplay version: {}", #syncplay.version
|
||||
"more-info-notification" : "More info available at: {}", #projectURL)
|
||||
"more-info-notification" : "More info available at: {}", #projectURL
|
||||
|
||||
# Client prompts
|
||||
"enter-to-exit-prompt" : "Press enter to exit\n",
|
||||
@ -90,7 +90,7 @@ en = {
|
||||
"path-label" : 'Path to player executable: ',
|
||||
|
||||
# Server notifications
|
||||
"-welcome-server-notification" : "Welcome to Syncplay server, ver. {0}", #version
|
||||
"welcome-server-notification" : "Welcome to Syncplay server, ver. {0}", #version
|
||||
"client-connected-room-server-notification" : "{0}({2}) connected to room '{1}'", #username, host, room
|
||||
"client-left-server-notification" : "{0} left server", #name
|
||||
|
||||
|
||||
@ -1,454 +1,455 @@
|
||||
#coding:utf8
|
||||
import time
|
||||
import threading
|
||||
import thread
|
||||
import win32con, win32api, win32gui, ctypes, ctypes.wintypes #@UnresolvedImport @UnusedImport
|
||||
from functools import wraps
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
import re
|
||||
from syncplay.utils import retry
|
||||
from syncplay import constants
|
||||
|
||||
class MpcHcApi:
|
||||
def __init__(self):
|
||||
self.callbacks = self.__Callbacks()
|
||||
self.loadState = None
|
||||
self.playState = None
|
||||
self.filePlaying = None
|
||||
self.fileDuration = None
|
||||
self.filePath = None
|
||||
self.lastFilePosition = None
|
||||
self.version = None
|
||||
self.__playpause_warden = False
|
||||
self.__locks = self.__Locks()
|
||||
self.__mpcExistenceChecking = threading.Thread(target=self.__mpcReadyInSlaveMode, name="Check MPC window")
|
||||
self.__mpcExistenceChecking.setDaemon(True)
|
||||
self.__listener = self.__Listener(self, self.__locks)
|
||||
self.__listener.setDaemon(True)
|
||||
self.__listener.start()
|
||||
self.__locks.listenerStart.wait()
|
||||
|
||||
def waitForFileStateReady(f): #@NoSelf
|
||||
@wraps(f)
|
||||
def wrapper(self, *args, **kwds):
|
||||
if(not self.__locks.fileReady.wait(constants.MPC_LOCK_WAIT_TIME)):
|
||||
raise self.PlayerNotReadyException()
|
||||
return f(self, *args, **kwds)
|
||||
return wrapper
|
||||
|
||||
def startMpc(self, path, args=()):
|
||||
args = "%s /slave %s" % (" ".join(args), str(self.__listener.hwnd))
|
||||
win32api.ShellExecute(0, "open", path, args, None, 1)
|
||||
if(not self.__locks.mpcStart.wait(constants.MPC_OPEN_MAX_WAIT_TIME)):
|
||||
raise self.NoSlaveDetectedException("Unable to start MPC in slave mode!")
|
||||
self.__mpcExistenceChecking.start()
|
||||
|
||||
def openFile(self, filePath):
|
||||
self.__listener.SendCommand(self.CMD_OPENFILE, filePath)
|
||||
|
||||
def isPaused(self):
|
||||
return (self.playState <> self.__MPC_PLAYSTATE.PS_PLAY and self.playState <> None)
|
||||
|
||||
def askForVersion(self):
|
||||
self.__listener.SendCommand(self.CMD_GETVERSION)
|
||||
|
||||
@waitForFileStateReady
|
||||
def pause(self):
|
||||
self.__listener.SendCommand(self.CMD_PAUSE)
|
||||
|
||||
@waitForFileStateReady
|
||||
def playPause(self):
|
||||
self.__listener.SendCommand(self.CMD_PLAYPAUSE)
|
||||
|
||||
@waitForFileStateReady
|
||||
def unpause(self):
|
||||
self.__listener.SendCommand(self.CMD_PLAY)
|
||||
|
||||
@waitForFileStateReady
|
||||
def askForCurrentPosition(self):
|
||||
self.__listener.SendCommand(self.CMD_GETCURRENTPOSITION)
|
||||
|
||||
@waitForFileStateReady
|
||||
def seek(self, position):
|
||||
self.__listener.SendCommand(self.CMD_SETPOSITION, unicode(position))
|
||||
|
||||
@waitForFileStateReady
|
||||
def setSpeed(self, rate):
|
||||
self.__listener.SendCommand(self.CMD_SETSPEED, unicode(rate))
|
||||
|
||||
def sendOsd(self, message, MsgPos=constants.MPC_OSD_POSITION, DurationMs=constants.OSD_DURATION):
|
||||
class __OSDDATASTRUCT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('nMsgPos', ctypes.c_int32),
|
||||
('nDurationMS', ctypes.c_int32),
|
||||
('strMsg', ctypes.c_wchar * (len(message) + 1))
|
||||
]
|
||||
cmessage = __OSDDATASTRUCT()
|
||||
cmessage.nMsgPos = MsgPos
|
||||
cmessage.nDurationMS = DurationMs
|
||||
cmessage.strMsg = message
|
||||
self.__listener.SendCommand(self.CMD_OSDSHOWMESSAGE, cmessage)
|
||||
|
||||
def sendRawCommand(self, cmd, value):
|
||||
self.__listener.SendCommand(cmd, value)
|
||||
|
||||
def handleCommand(self, cmd, value):
|
||||
if (cmd == self.CMD_CONNECT):
|
||||
self.__listener.mpcHandle = int(value)
|
||||
self.__locks.mpcStart.set()
|
||||
if(self.callbacks.onConnected):
|
||||
thread.start_new_thread(self.callbacks.onConnected, ())
|
||||
|
||||
elif(cmd == self.CMD_STATE):
|
||||
self.loadState = int(value)
|
||||
fileNotReady = self.loadState == self.__MPC_LOADSTATE.MLS_CLOSING or self.loadState == self.__MPC_LOADSTATE.MLS_LOADING
|
||||
if(fileNotReady):
|
||||
self.playState = None
|
||||
self.__locks.fileReady.clear()
|
||||
else:
|
||||
self.__locks.fileReady.set()
|
||||
if(self.callbacks.onFileStateChange):
|
||||
thread.start_new_thread(self.callbacks.onFileStateChange, (self.loadState,))
|
||||
|
||||
elif(cmd == self.CMD_PLAYMODE):
|
||||
self.playState = int(value)
|
||||
if(self.callbacks.onUpdatePlaystate):
|
||||
thread.start_new_thread(self.callbacks.onUpdatePlaystate, (self.playState,))
|
||||
|
||||
elif(cmd == self.CMD_NOWPLAYING):
|
||||
value = re.split(r'(?<!\\)\|', value)
|
||||
self.filePath = value[3]
|
||||
self.filePlaying = value[3].split('\\').pop()
|
||||
self.fileDuration = float(value[4])
|
||||
if(self.callbacks.onUpdatePath):
|
||||
thread.start_new_thread(self.callbacks.onUpdatePath, (self.onUpdatePath,))
|
||||
if(self.callbacks.onUpdateFilename):
|
||||
thread.start_new_thread(self.callbacks.onUpdateFilename, (self.filePlaying,))
|
||||
if(self.callbacks.onUpdateFileDuration):
|
||||
thread.start_new_thread(self.callbacks.onUpdateFileDuration, (self.fileDuration,))
|
||||
|
||||
elif(cmd == self.CMD_CURRENTPOSITION):
|
||||
self.lastFilePosition = float(value)
|
||||
if(self.callbacks.onGetCurrentPosition):
|
||||
thread.start_new_thread(self.callbacks.onGetCurrentPosition, (self.lastFilePosition,))
|
||||
|
||||
elif(cmd == self.CMD_NOTIFYSEEK):
|
||||
if(self.lastFilePosition <> float(value)): #Notify seek is sometimes sent twice
|
||||
self.lastFilePosition = float(value)
|
||||
if(self.callbacks.onSeek):
|
||||
thread.start_new_thread(self.callbacks.onSeek, (self.lastFilePosition,))
|
||||
|
||||
elif(cmd == self.CMD_DISCONNECT):
|
||||
if(self.callbacks.onMpcClosed):
|
||||
thread.start_new_thread(self.callbacks.onMpcClosed, (None,))
|
||||
|
||||
elif(cmd == self.CMD_VERSION):
|
||||
if(self.callbacks.onVersion):
|
||||
self.version = value
|
||||
thread.start_new_thread(self.callbacks.onVersion, (value,))
|
||||
|
||||
class PlayerNotReadyException(Exception):
|
||||
pass
|
||||
|
||||
class __Callbacks:
|
||||
def __init__(self):
|
||||
self.onConnected = None
|
||||
self.onSeek = None
|
||||
self.onUpdatePath = None
|
||||
self.onUpdateFilename = None
|
||||
self.onUpdateFileDuration = None
|
||||
self.onGetCurrentPosition = None
|
||||
self.onUpdatePlaystate = None
|
||||
self.onFileStateChange = None
|
||||
self.onMpcClosed = None
|
||||
self.onVersion = None
|
||||
|
||||
class __Locks:
|
||||
def __init__(self):
|
||||
self.listenerStart = threading.Event()
|
||||
self.mpcStart = threading.Event()
|
||||
self.fileReady = threading.Event()
|
||||
|
||||
def __mpcReadyInSlaveMode(self):
|
||||
while(True):
|
||||
time.sleep(10)
|
||||
if not win32gui.IsWindow(self.__listener.mpcHandle):
|
||||
if(self.callbacks.onMpcClosed):
|
||||
self.callbacks.onMpcClosed(None)
|
||||
break
|
||||
|
||||
CMD_CONNECT = 0x50000000
|
||||
CMD_STATE = 0x50000001
|
||||
CMD_PLAYMODE = 0x50000002
|
||||
CMD_NOWPLAYING = 0x50000003
|
||||
CMD_LISTSUBTITLETRACKS = 0x50000004
|
||||
CMD_LISTAUDIOTRACKS = 0x50000005
|
||||
CMD_CURRENTPOSITION = 0x50000007
|
||||
CMD_NOTIFYSEEK = 0x50000008
|
||||
CMD_NOTIFYENDOFSTREAM = 0x50000009
|
||||
CMD_PLAYLIST = 0x50000006
|
||||
CMD_OPENFILE = 0xA0000000
|
||||
CMD_STOP = 0xA0000001
|
||||
CMD_CLOSEFILE = 0xA0000002
|
||||
CMD_PLAYPAUSE = 0xA0000003
|
||||
CMD_ADDTOPLAYLIST = 0xA0001000
|
||||
CMD_CLEARPLAYLIST = 0xA0001001
|
||||
CMD_STARTPLAYLIST = 0xA0001002
|
||||
CMD_REMOVEFROMPLAYLIST = 0xA0001003 # TODO
|
||||
CMD_SETPOSITION = 0xA0002000
|
||||
CMD_SETAUDIODELAY = 0xA0002001
|
||||
CMD_SETSUBTITLEDELAY = 0xA0002002
|
||||
CMD_SETINDEXPLAYLIST = 0xA0002003 # DOESNT WORK
|
||||
CMD_SETAUDIOTRACK = 0xA0002004
|
||||
CMD_SETSUBTITLETRACK = 0xA0002005
|
||||
CMD_GETSUBTITLETRACKS = 0xA0003000
|
||||
CMD_GETCURRENTPOSITION = 0xA0003004
|
||||
CMD_JUMPOFNSECONDS = 0xA0003005
|
||||
CMD_GETAUDIOTRACKS = 0xA0003001
|
||||
CMD_GETNOWPLAYING = 0xA0003002
|
||||
CMD_GETPLAYLIST = 0xA0003003
|
||||
CMD_TOGGLEFULLSCREEN = 0xA0004000
|
||||
CMD_JUMPFORWARDMED = 0xA0004001
|
||||
CMD_JUMPBACKWARDMED = 0xA0004002
|
||||
CMD_INCREASEVOLUME = 0xA0004003
|
||||
CMD_DECREASEVOLUME = 0xA0004004
|
||||
CMD_SHADER_TOGGLE = 0xA0004005
|
||||
CMD_CLOSEAPP = 0xA0004006
|
||||
CMD_OSDSHOWMESSAGE = 0xA0005000
|
||||
CMD_VERSION = 0x5000000A
|
||||
CMD_DISCONNECT = 0x5000000B
|
||||
CMD_PLAY = 0xA0000004
|
||||
CMD_PAUSE = 0xA0000005
|
||||
CMD_GETVERSION = 0xA0003006
|
||||
CMD_SETSPEED = 0xA0004008
|
||||
|
||||
class __MPC_LOADSTATE:
|
||||
MLS_CLOSED = 0
|
||||
MLS_LOADING = 1
|
||||
MLS_LOADED = 2
|
||||
MLS_CLOSING = 3
|
||||
|
||||
class __MPC_PLAYSTATE:
|
||||
PS_PLAY = 0
|
||||
PS_PAUSE = 1
|
||||
PS_STOP = 2
|
||||
PS_UNUSED = 3
|
||||
|
||||
class __Listener(threading.Thread):
|
||||
def __init__(self, mpcApi, locks):
|
||||
self.__mpcApi = mpcApi
|
||||
self.locks = locks
|
||||
self.mpcHandle = None
|
||||
self.hwnd = None
|
||||
self.__PCOPYDATASTRUCT = ctypes.POINTER(self.__COPYDATASTRUCT)
|
||||
threading.Thread.__init__(self, name="MPC Listener")
|
||||
|
||||
def run(self):
|
||||
message_map = {
|
||||
win32con.WM_COPYDATA: self.OnCopyData
|
||||
}
|
||||
wc = win32gui.WNDCLASS()
|
||||
wc.lpfnWndProc = message_map
|
||||
wc.lpszClassName = 'MPCApiListener'
|
||||
hinst = wc.hInstance = win32api.GetModuleHandle(None)
|
||||
classAtom = win32gui.RegisterClass(wc)
|
||||
self.hwnd = win32gui.CreateWindow (
|
||||
classAtom,
|
||||
"ListenerGUI",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
win32con.CW_USEDEFAULT,
|
||||
win32con.CW_USEDEFAULT,
|
||||
0,
|
||||
0,
|
||||
hinst,
|
||||
None
|
||||
)
|
||||
self.locks.listenerStart.set()
|
||||
win32gui.PumpMessages()
|
||||
|
||||
|
||||
def OnCopyData(self, hwnd, msg, wparam, lparam):
|
||||
pCDS = ctypes.cast(lparam, self.__PCOPYDATASTRUCT)
|
||||
#print "API:\tin>\t 0x%X\t" % int(pCDS.contents.dwData), ctypes.wstring_at(pCDS.contents.lpData)
|
||||
self.__mpcApi.handleCommand(pCDS.contents.dwData, ctypes.wstring_at(pCDS.contents.lpData))
|
||||
|
||||
def SendCommand(self, cmd, message=u''):
|
||||
#print "API:\t<out\t 0x%X\t" % int(cmd), message
|
||||
if not win32gui.IsWindow(self.mpcHandle):
|
||||
if(self.__mpcApi.callbacks.onMpcClosed):
|
||||
self.__mpcApi.callbacks.onMpcClosed(None)
|
||||
cs = self.__COPYDATASTRUCT()
|
||||
cs.dwData = cmd;
|
||||
|
||||
if(isinstance(message, (unicode, str))):
|
||||
message = ctypes.create_unicode_buffer(message, len(message) + 1)
|
||||
elif(isinstance(message, ctypes.Structure)):
|
||||
pass
|
||||
else:
|
||||
raise TypeError
|
||||
cs.lpData = ctypes.addressof(message)
|
||||
cs.cbData = ctypes.sizeof(message)
|
||||
ptr = ctypes.addressof(cs)
|
||||
win32api.SendMessage(self.mpcHandle, win32con.WM_COPYDATA, self.hwnd, ptr)
|
||||
|
||||
class __COPYDATASTRUCT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('dwData', ctypes.wintypes.LPARAM),
|
||||
('cbData', ctypes.wintypes.DWORD),
|
||||
('lpData', ctypes.c_void_p)
|
||||
]
|
||||
|
||||
class MPCHCAPIPlayer(BasePlayer):
|
||||
speedSupported = False
|
||||
|
||||
def __init__(self, client):
|
||||
self.__client = client
|
||||
self._mpcApi = MpcHcApi()
|
||||
self._mpcApi.callbacks.onUpdateFilename = lambda _: self.__makePing()
|
||||
self._mpcApi.callbacks.onMpcClosed = lambda _: self.__client.stop(False)
|
||||
self._mpcApi.callbacks.onFileStateChange = lambda _: self.__lockAsking()
|
||||
self._mpcApi.callbacks.onUpdatePlaystate = lambda _: self.__unlockAsking()
|
||||
self._mpcApi.callbacks.onGetCurrentPosition = lambda _: self.__onGetPosition()
|
||||
self._mpcApi.callbacks.onVersion = lambda _: self.__versionUpdate.set()
|
||||
self.__switchPauseCalls = False
|
||||
self.__preventAsking = threading.Event()
|
||||
self.__positionUpdate = threading.Event()
|
||||
self.__versionUpdate = threading.Event()
|
||||
self.__fileUpdate = threading.RLock()
|
||||
self.__versionUpdate.clear()
|
||||
|
||||
def drop(self):
|
||||
self.__preventAsking.set()
|
||||
self.__positionUpdate.set()
|
||||
self.__versionUpdate.set()
|
||||
self._mpcApi.sendRawCommand(MpcHcApi.CMD_CLOSEAPP, "")
|
||||
|
||||
@staticmethod
|
||||
def run(client, playerPath, filePath, args):
|
||||
mpc = MPCHCAPIPlayer(client)
|
||||
mpc._mpcApi.callbacks.onConnected = lambda: mpc.initPlayer(filePath if(filePath) else None)
|
||||
mpc._mpcApi.startMpc(playerPath, args)
|
||||
return mpc
|
||||
|
||||
def __lockAsking(self):
|
||||
self.__preventAsking.clear()
|
||||
|
||||
def __unlockAsking(self):
|
||||
self.__preventAsking.set()
|
||||
|
||||
def __onGetPosition(self):
|
||||
self.__positionUpdate.set()
|
||||
|
||||
def setSpeed(self, value):
|
||||
try:
|
||||
self._mpcApi.setSpeed(value)
|
||||
except MpcHcApi.PlayerNotReadyException:
|
||||
self.setSpeed(value)
|
||||
|
||||
def __dropIfNotSufficientVersion(self):
|
||||
self._mpcApi.askForVersion()
|
||||
if(not self.__versionUpdate.wait(0.1) or not self._mpcApi.version):
|
||||
self.__mpcError("MPC version not sufficient, please use `mpc-hc` >= `1.6.4`")
|
||||
self.__client.stop(True)
|
||||
|
||||
def __testMpcReady(self):
|
||||
if(not self.__preventAsking.wait(10)):
|
||||
raise Exception("Player failed opening file")
|
||||
|
||||
def __makePing(self):
|
||||
try:
|
||||
self.__testMpcReady()
|
||||
self._mpcApi.callbacks.onUpdateFilename = lambda _: self.__handleUpdatedFilename()
|
||||
self.__client.initPlayer(self)
|
||||
self.__handleUpdatedFilename()
|
||||
self.askForStatus()
|
||||
except Exception, err:
|
||||
self.__client.ui.showErrorMessage(err.message)
|
||||
self.__client.stop()
|
||||
|
||||
def initPlayer(self, filePath):
|
||||
self.__dropIfNotSufficientVersion()
|
||||
if(not self._mpcApi.version):
|
||||
return
|
||||
self.__mpcVersion = self._mpcApi.version.split('.')
|
||||
if(self.__mpcVersion[0:3] == ['1', '6', '4']):
|
||||
self.__switchPauseCalls = True
|
||||
if(self.__mpcVersion[0:3] >= ['1', '6', '5']):
|
||||
self.speedSupported = True
|
||||
if(filePath):
|
||||
self._mpcApi.openFile(filePath)
|
||||
|
||||
def displayMessage(self, message):
|
||||
self._mpcApi.sendOsd(message)
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def setPaused(self, value):
|
||||
if self.__switchPauseCalls:
|
||||
value = not value
|
||||
if value:
|
||||
self._mpcApi.pause()
|
||||
else:
|
||||
self._mpcApi.unpause()
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def setPosition(self, value):
|
||||
self._mpcApi.seek(value)
|
||||
|
||||
def __getPosition(self):
|
||||
self.__positionUpdate.clear()
|
||||
self._mpcApi.askForCurrentPosition()
|
||||
self.__positionUpdate.wait(constants.MPC_LOCK_WAIT_TIME)
|
||||
return self._mpcApi.lastFilePosition
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def askForStatus(self):
|
||||
if(self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0)):
|
||||
self.__fileUpdate.release()
|
||||
position = self.__getPosition()
|
||||
paused = self._mpcApi.isPaused()
|
||||
position = float(position)
|
||||
if(self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0)):
|
||||
self.__client.updatePlayerStatus(paused, position)
|
||||
self.__fileUpdate.release()
|
||||
return
|
||||
self.__echoGlobalStatus()
|
||||
|
||||
def __echoGlobalStatus(self):
|
||||
self.__client.updatePlayerStatus(self.__client.getGlobalPaused(), self.__client.getGlobalPosition())
|
||||
|
||||
def __forcePause(self):
|
||||
for _ in xrange(constants.MPC_MAX_RETRIES):
|
||||
self.setPaused(True)
|
||||
time.sleep(constants.MPC_RETRY_WAIT_TIME)
|
||||
|
||||
def __refreshMpcPlayState(self):
|
||||
for _ in xrange(2):
|
||||
self._mpcApi.playPause()
|
||||
time.sleep(constants.MPC_PAUSE_TOGGLE_DELAY)
|
||||
|
||||
def _setPausedAccordinglyToServer(self):
|
||||
self.__forcePause()
|
||||
self.setPaused(self.__client.getGlobalPaused())
|
||||
if(self._mpcApi.isPaused() <> self.__client.getGlobalPaused()):
|
||||
self.__refreshMpcPlayState()
|
||||
if(self._mpcApi.isPaused() <> self.__client.getGlobalPaused()):
|
||||
self.__setUpStateForNewlyOpenedFile()
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def __setUpStateForNewlyOpenedFile(self):
|
||||
self._setPausedAccordinglyToServer()
|
||||
self._mpcApi.seek(self.__client.getGlobalPosition())
|
||||
|
||||
def __handleUpdatedFilename(self):
|
||||
with self.__fileUpdate:
|
||||
self.__setUpStateForNewlyOpenedFile()
|
||||
self.__client.updateFile(self._mpcApi.filePlaying, self._mpcApi.fileDuration, self._mpcApi.filePath)
|
||||
|
||||
def __mpcError(self, err=""):
|
||||
self.__client.ui.showErrorMessage(err)
|
||||
self.__client.stop()
|
||||
|
||||
def sendCustomCommand(self, cmd, val):
|
||||
self._mpcApi.sendRawCommand(cmd, val)
|
||||
#coding:utf8
|
||||
import time
|
||||
import threading
|
||||
import thread
|
||||
import win32con, win32api, win32gui, ctypes, ctypes.wintypes #@UnresolvedImport @UnusedImport
|
||||
from functools import wraps
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
import re
|
||||
from syncplay.utils import retry
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
|
||||
class MpcHcApi:
|
||||
def __init__(self):
|
||||
self.callbacks = self.__Callbacks()
|
||||
self.loadState = None
|
||||
self.playState = None
|
||||
self.filePlaying = None
|
||||
self.fileDuration = None
|
||||
self.filePath = None
|
||||
self.lastFilePosition = None
|
||||
self.version = None
|
||||
self.__playpause_warden = False
|
||||
self.__locks = self.__Locks()
|
||||
self.__mpcExistenceChecking = threading.Thread(target=self.__mpcReadyInSlaveMode, name="Check MPC window")
|
||||
self.__mpcExistenceChecking.setDaemon(True)
|
||||
self.__listener = self.__Listener(self, self.__locks)
|
||||
self.__listener.setDaemon(True)
|
||||
self.__listener.start()
|
||||
self.__locks.listenerStart.wait()
|
||||
|
||||
def waitForFileStateReady(f): #@NoSelf
|
||||
@wraps(f)
|
||||
def wrapper(self, *args, **kwds):
|
||||
if(not self.__locks.fileReady.wait(constants.MPC_LOCK_WAIT_TIME)):
|
||||
raise self.PlayerNotReadyException()
|
||||
return f(self, *args, **kwds)
|
||||
return wrapper
|
||||
|
||||
def startMpc(self, path, args=()):
|
||||
args = "%s /slave %s" % (" ".join(args), str(self.__listener.hwnd))
|
||||
win32api.ShellExecute(0, "open", path, args, None, 1)
|
||||
if(not self.__locks.mpcStart.wait(constants.MPC_OPEN_MAX_WAIT_TIME)):
|
||||
raise self.NoSlaveDetectedException(getMessage("en", "mpc-slave-error"))
|
||||
self.__mpcExistenceChecking.start()
|
||||
|
||||
def openFile(self, filePath):
|
||||
self.__listener.SendCommand(self.CMD_OPENFILE, filePath)
|
||||
|
||||
def isPaused(self):
|
||||
return (self.playState <> self.__MPC_PLAYSTATE.PS_PLAY and self.playState <> None)
|
||||
|
||||
def askForVersion(self):
|
||||
self.__listener.SendCommand(self.CMD_GETVERSION)
|
||||
|
||||
@waitForFileStateReady
|
||||
def pause(self):
|
||||
self.__listener.SendCommand(self.CMD_PAUSE)
|
||||
|
||||
@waitForFileStateReady
|
||||
def playPause(self):
|
||||
self.__listener.SendCommand(self.CMD_PLAYPAUSE)
|
||||
|
||||
@waitForFileStateReady
|
||||
def unpause(self):
|
||||
self.__listener.SendCommand(self.CMD_PLAY)
|
||||
|
||||
@waitForFileStateReady
|
||||
def askForCurrentPosition(self):
|
||||
self.__listener.SendCommand(self.CMD_GETCURRENTPOSITION)
|
||||
|
||||
@waitForFileStateReady
|
||||
def seek(self, position):
|
||||
self.__listener.SendCommand(self.CMD_SETPOSITION, unicode(position))
|
||||
|
||||
@waitForFileStateReady
|
||||
def setSpeed(self, rate):
|
||||
self.__listener.SendCommand(self.CMD_SETSPEED, unicode(rate))
|
||||
|
||||
def sendOsd(self, message, MsgPos=constants.MPC_OSD_POSITION, DurationMs=constants.OSD_DURATION):
|
||||
class __OSDDATASTRUCT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('nMsgPos', ctypes.c_int32),
|
||||
('nDurationMS', ctypes.c_int32),
|
||||
('strMsg', ctypes.c_wchar * (len(message) + 1))
|
||||
]
|
||||
cmessage = __OSDDATASTRUCT()
|
||||
cmessage.nMsgPos = MsgPos
|
||||
cmessage.nDurationMS = DurationMs
|
||||
cmessage.strMsg = message
|
||||
self.__listener.SendCommand(self.CMD_OSDSHOWMESSAGE, cmessage)
|
||||
|
||||
def sendRawCommand(self, cmd, value):
|
||||
self.__listener.SendCommand(cmd, value)
|
||||
|
||||
def handleCommand(self, cmd, value):
|
||||
if (cmd == self.CMD_CONNECT):
|
||||
self.__listener.mpcHandle = int(value)
|
||||
self.__locks.mpcStart.set()
|
||||
if(self.callbacks.onConnected):
|
||||
thread.start_new_thread(self.callbacks.onConnected, ())
|
||||
|
||||
elif(cmd == self.CMD_STATE):
|
||||
self.loadState = int(value)
|
||||
fileNotReady = self.loadState == self.__MPC_LOADSTATE.MLS_CLOSING or self.loadState == self.__MPC_LOADSTATE.MLS_LOADING
|
||||
if(fileNotReady):
|
||||
self.playState = None
|
||||
self.__locks.fileReady.clear()
|
||||
else:
|
||||
self.__locks.fileReady.set()
|
||||
if(self.callbacks.onFileStateChange):
|
||||
thread.start_new_thread(self.callbacks.onFileStateChange, (self.loadState,))
|
||||
|
||||
elif(cmd == self.CMD_PLAYMODE):
|
||||
self.playState = int(value)
|
||||
if(self.callbacks.onUpdatePlaystate):
|
||||
thread.start_new_thread(self.callbacks.onUpdatePlaystate, (self.playState,))
|
||||
|
||||
elif(cmd == self.CMD_NOWPLAYING):
|
||||
value = re.split(r'(?<!\\)\|', value)
|
||||
self.filePath = value[3]
|
||||
self.filePlaying = value[3].split('\\').pop()
|
||||
self.fileDuration = float(value[4])
|
||||
if(self.callbacks.onUpdatePath):
|
||||
thread.start_new_thread(self.callbacks.onUpdatePath, (self.onUpdatePath,))
|
||||
if(self.callbacks.onUpdateFilename):
|
||||
thread.start_new_thread(self.callbacks.onUpdateFilename, (self.filePlaying,))
|
||||
if(self.callbacks.onUpdateFileDuration):
|
||||
thread.start_new_thread(self.callbacks.onUpdateFileDuration, (self.fileDuration,))
|
||||
|
||||
elif(cmd == self.CMD_CURRENTPOSITION):
|
||||
self.lastFilePosition = float(value)
|
||||
if(self.callbacks.onGetCurrentPosition):
|
||||
thread.start_new_thread(self.callbacks.onGetCurrentPosition, (self.lastFilePosition,))
|
||||
|
||||
elif(cmd == self.CMD_NOTIFYSEEK):
|
||||
if(self.lastFilePosition <> float(value)): #Notify seek is sometimes sent twice
|
||||
self.lastFilePosition = float(value)
|
||||
if(self.callbacks.onSeek):
|
||||
thread.start_new_thread(self.callbacks.onSeek, (self.lastFilePosition,))
|
||||
|
||||
elif(cmd == self.CMD_DISCONNECT):
|
||||
if(self.callbacks.onMpcClosed):
|
||||
thread.start_new_thread(self.callbacks.onMpcClosed, (None,))
|
||||
|
||||
elif(cmd == self.CMD_VERSION):
|
||||
if(self.callbacks.onVersion):
|
||||
self.version = value
|
||||
thread.start_new_thread(self.callbacks.onVersion, (value,))
|
||||
|
||||
class PlayerNotReadyException(Exception):
|
||||
pass
|
||||
|
||||
class __Callbacks:
|
||||
def __init__(self):
|
||||
self.onConnected = None
|
||||
self.onSeek = None
|
||||
self.onUpdatePath = None
|
||||
self.onUpdateFilename = None
|
||||
self.onUpdateFileDuration = None
|
||||
self.onGetCurrentPosition = None
|
||||
self.onUpdatePlaystate = None
|
||||
self.onFileStateChange = None
|
||||
self.onMpcClosed = None
|
||||
self.onVersion = None
|
||||
|
||||
class __Locks:
|
||||
def __init__(self):
|
||||
self.listenerStart = threading.Event()
|
||||
self.mpcStart = threading.Event()
|
||||
self.fileReady = threading.Event()
|
||||
|
||||
def __mpcReadyInSlaveMode(self):
|
||||
while(True):
|
||||
time.sleep(10)
|
||||
if not win32gui.IsWindow(self.__listener.mpcHandle):
|
||||
if(self.callbacks.onMpcClosed):
|
||||
self.callbacks.onMpcClosed(None)
|
||||
break
|
||||
|
||||
CMD_CONNECT = 0x50000000
|
||||
CMD_STATE = 0x50000001
|
||||
CMD_PLAYMODE = 0x50000002
|
||||
CMD_NOWPLAYING = 0x50000003
|
||||
CMD_LISTSUBTITLETRACKS = 0x50000004
|
||||
CMD_LISTAUDIOTRACKS = 0x50000005
|
||||
CMD_CURRENTPOSITION = 0x50000007
|
||||
CMD_NOTIFYSEEK = 0x50000008
|
||||
CMD_NOTIFYENDOFSTREAM = 0x50000009
|
||||
CMD_PLAYLIST = 0x50000006
|
||||
CMD_OPENFILE = 0xA0000000
|
||||
CMD_STOP = 0xA0000001
|
||||
CMD_CLOSEFILE = 0xA0000002
|
||||
CMD_PLAYPAUSE = 0xA0000003
|
||||
CMD_ADDTOPLAYLIST = 0xA0001000
|
||||
CMD_CLEARPLAYLIST = 0xA0001001
|
||||
CMD_STARTPLAYLIST = 0xA0001002
|
||||
CMD_REMOVEFROMPLAYLIST = 0xA0001003 # TODO
|
||||
CMD_SETPOSITION = 0xA0002000
|
||||
CMD_SETAUDIODELAY = 0xA0002001
|
||||
CMD_SETSUBTITLEDELAY = 0xA0002002
|
||||
CMD_SETINDEXPLAYLIST = 0xA0002003 # DOESNT WORK
|
||||
CMD_SETAUDIOTRACK = 0xA0002004
|
||||
CMD_SETSUBTITLETRACK = 0xA0002005
|
||||
CMD_GETSUBTITLETRACKS = 0xA0003000
|
||||
CMD_GETCURRENTPOSITION = 0xA0003004
|
||||
CMD_JUMPOFNSECONDS = 0xA0003005
|
||||
CMD_GETAUDIOTRACKS = 0xA0003001
|
||||
CMD_GETNOWPLAYING = 0xA0003002
|
||||
CMD_GETPLAYLIST = 0xA0003003
|
||||
CMD_TOGGLEFULLSCREEN = 0xA0004000
|
||||
CMD_JUMPFORWARDMED = 0xA0004001
|
||||
CMD_JUMPBACKWARDMED = 0xA0004002
|
||||
CMD_INCREASEVOLUME = 0xA0004003
|
||||
CMD_DECREASEVOLUME = 0xA0004004
|
||||
CMD_SHADER_TOGGLE = 0xA0004005
|
||||
CMD_CLOSEAPP = 0xA0004006
|
||||
CMD_OSDSHOWMESSAGE = 0xA0005000
|
||||
CMD_VERSION = 0x5000000A
|
||||
CMD_DISCONNECT = 0x5000000B
|
||||
CMD_PLAY = 0xA0000004
|
||||
CMD_PAUSE = 0xA0000005
|
||||
CMD_GETVERSION = 0xA0003006
|
||||
CMD_SETSPEED = 0xA0004008
|
||||
|
||||
class __MPC_LOADSTATE:
|
||||
MLS_CLOSED = 0
|
||||
MLS_LOADING = 1
|
||||
MLS_LOADED = 2
|
||||
MLS_CLOSING = 3
|
||||
|
||||
class __MPC_PLAYSTATE:
|
||||
PS_PLAY = 0
|
||||
PS_PAUSE = 1
|
||||
PS_STOP = 2
|
||||
PS_UNUSED = 3
|
||||
|
||||
class __Listener(threading.Thread):
|
||||
def __init__(self, mpcApi, locks):
|
||||
self.__mpcApi = mpcApi
|
||||
self.locks = locks
|
||||
self.mpcHandle = None
|
||||
self.hwnd = None
|
||||
self.__PCOPYDATASTRUCT = ctypes.POINTER(self.__COPYDATASTRUCT)
|
||||
threading.Thread.__init__(self, name="MPC Listener")
|
||||
|
||||
def run(self):
|
||||
message_map = {
|
||||
win32con.WM_COPYDATA: self.OnCopyData
|
||||
}
|
||||
wc = win32gui.WNDCLASS()
|
||||
wc.lpfnWndProc = message_map
|
||||
wc.lpszClassName = 'MPCApiListener'
|
||||
hinst = wc.hInstance = win32api.GetModuleHandle(None)
|
||||
classAtom = win32gui.RegisterClass(wc)
|
||||
self.hwnd = win32gui.CreateWindow (
|
||||
classAtom,
|
||||
"ListenerGUI",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
win32con.CW_USEDEFAULT,
|
||||
win32con.CW_USEDEFAULT,
|
||||
0,
|
||||
0,
|
||||
hinst,
|
||||
None
|
||||
)
|
||||
self.locks.listenerStart.set()
|
||||
win32gui.PumpMessages()
|
||||
|
||||
|
||||
def OnCopyData(self, hwnd, msg, wparam, lparam):
|
||||
pCDS = ctypes.cast(lparam, self.__PCOPYDATASTRUCT)
|
||||
#print "API:\tin>\t 0x%X\t" % int(pCDS.contents.dwData), ctypes.wstring_at(pCDS.contents.lpData)
|
||||
self.__mpcApi.handleCommand(pCDS.contents.dwData, ctypes.wstring_at(pCDS.contents.lpData))
|
||||
|
||||
def SendCommand(self, cmd, message=u''):
|
||||
#print "API:\t<out\t 0x%X\t" % int(cmd), message
|
||||
if not win32gui.IsWindow(self.mpcHandle):
|
||||
if(self.__mpcApi.callbacks.onMpcClosed):
|
||||
self.__mpcApi.callbacks.onMpcClosed(None)
|
||||
cs = self.__COPYDATASTRUCT()
|
||||
cs.dwData = cmd;
|
||||
|
||||
if(isinstance(message, (unicode, str))):
|
||||
message = ctypes.create_unicode_buffer(message, len(message) + 1)
|
||||
elif(isinstance(message, ctypes.Structure)):
|
||||
pass
|
||||
else:
|
||||
raise TypeError
|
||||
cs.lpData = ctypes.addressof(message)
|
||||
cs.cbData = ctypes.sizeof(message)
|
||||
ptr = ctypes.addressof(cs)
|
||||
win32api.SendMessage(self.mpcHandle, win32con.WM_COPYDATA, self.hwnd, ptr)
|
||||
|
||||
class __COPYDATASTRUCT(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('dwData', ctypes.wintypes.LPARAM),
|
||||
('cbData', ctypes.wintypes.DWORD),
|
||||
('lpData', ctypes.c_void_p)
|
||||
]
|
||||
|
||||
class MPCHCAPIPlayer(BasePlayer):
|
||||
speedSupported = False
|
||||
|
||||
def __init__(self, client):
|
||||
self.__client = client
|
||||
self._mpcApi = MpcHcApi()
|
||||
self._mpcApi.callbacks.onUpdateFilename = lambda _: self.__makePing()
|
||||
self._mpcApi.callbacks.onMpcClosed = lambda _: self.__client.stop(False)
|
||||
self._mpcApi.callbacks.onFileStateChange = lambda _: self.__lockAsking()
|
||||
self._mpcApi.callbacks.onUpdatePlaystate = lambda _: self.__unlockAsking()
|
||||
self._mpcApi.callbacks.onGetCurrentPosition = lambda _: self.__onGetPosition()
|
||||
self._mpcApi.callbacks.onVersion = lambda _: self.__versionUpdate.set()
|
||||
self.__switchPauseCalls = False
|
||||
self.__preventAsking = threading.Event()
|
||||
self.__positionUpdate = threading.Event()
|
||||
self.__versionUpdate = threading.Event()
|
||||
self.__fileUpdate = threading.RLock()
|
||||
self.__versionUpdate.clear()
|
||||
|
||||
def drop(self):
|
||||
self.__preventAsking.set()
|
||||
self.__positionUpdate.set()
|
||||
self.__versionUpdate.set()
|
||||
self._mpcApi.sendRawCommand(MpcHcApi.CMD_CLOSEAPP, "")
|
||||
|
||||
@staticmethod
|
||||
def run(client, playerPath, filePath, args):
|
||||
mpc = MPCHCAPIPlayer(client)
|
||||
mpc._mpcApi.callbacks.onConnected = lambda: mpc.initPlayer(filePath if(filePath) else None)
|
||||
mpc._mpcApi.startMpc(playerPath, args)
|
||||
return mpc
|
||||
|
||||
def __lockAsking(self):
|
||||
self.__preventAsking.clear()
|
||||
|
||||
def __unlockAsking(self):
|
||||
self.__preventAsking.set()
|
||||
|
||||
def __onGetPosition(self):
|
||||
self.__positionUpdate.set()
|
||||
|
||||
def setSpeed(self, value):
|
||||
try:
|
||||
self._mpcApi.setSpeed(value)
|
||||
except MpcHcApi.PlayerNotReadyException:
|
||||
self.setSpeed(value)
|
||||
|
||||
def __dropIfNotSufficientVersion(self):
|
||||
self._mpcApi.askForVersion()
|
||||
if(not self.__versionUpdate.wait(0.1) or not self._mpcApi.version):
|
||||
self.__mpcError(getMessage("en", "mpc-version-insufficient-error"))
|
||||
self.__client.stop(True)
|
||||
|
||||
def __testMpcReady(self):
|
||||
if(not self.__preventAsking.wait(10)):
|
||||
raise Exception(getMessage("en", "player-file-open-error"))
|
||||
|
||||
def __makePing(self):
|
||||
try:
|
||||
self.__testMpcReady()
|
||||
self._mpcApi.callbacks.onUpdateFilename = lambda _: self.__handleUpdatedFilename()
|
||||
self.__client.initPlayer(self)
|
||||
self.__handleUpdatedFilename()
|
||||
self.askForStatus()
|
||||
except Exception, err:
|
||||
self.__client.ui.showErrorMessage(err.message)
|
||||
self.__client.stop()
|
||||
|
||||
def initPlayer(self, filePath):
|
||||
self.__dropIfNotSufficientVersion()
|
||||
if(not self._mpcApi.version):
|
||||
return
|
||||
self.__mpcVersion = self._mpcApi.version.split('.')
|
||||
if(self.__mpcVersion[0:3] == ['1', '6', '4']):
|
||||
self.__switchPauseCalls = True
|
||||
if(self.__mpcVersion[0:3] >= ['1', '6', '5']):
|
||||
self.speedSupported = True
|
||||
if(filePath):
|
||||
self._mpcApi.openFile(filePath)
|
||||
|
||||
def displayMessage(self, message):
|
||||
self._mpcApi.sendOsd(message)
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def setPaused(self, value):
|
||||
if self.__switchPauseCalls:
|
||||
value = not value
|
||||
if value:
|
||||
self._mpcApi.pause()
|
||||
else:
|
||||
self._mpcApi.unpause()
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def setPosition(self, value):
|
||||
self._mpcApi.seek(value)
|
||||
|
||||
def __getPosition(self):
|
||||
self.__positionUpdate.clear()
|
||||
self._mpcApi.askForCurrentPosition()
|
||||
self.__positionUpdate.wait(constants.MPC_LOCK_WAIT_TIME)
|
||||
return self._mpcApi.lastFilePosition
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def askForStatus(self):
|
||||
if(self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0)):
|
||||
self.__fileUpdate.release()
|
||||
position = self.__getPosition()
|
||||
paused = self._mpcApi.isPaused()
|
||||
position = float(position)
|
||||
if(self.__preventAsking.wait(0) and self.__fileUpdate.acquire(0)):
|
||||
self.__client.updatePlayerStatus(paused, position)
|
||||
self.__fileUpdate.release()
|
||||
return
|
||||
self.__echoGlobalStatus()
|
||||
|
||||
def __echoGlobalStatus(self):
|
||||
self.__client.updatePlayerStatus(self.__client.getGlobalPaused(), self.__client.getGlobalPosition())
|
||||
|
||||
def __forcePause(self):
|
||||
for _ in xrange(constants.MPC_MAX_RETRIES):
|
||||
self.setPaused(True)
|
||||
time.sleep(constants.MPC_RETRY_WAIT_TIME)
|
||||
|
||||
def __refreshMpcPlayState(self):
|
||||
for _ in xrange(2):
|
||||
self._mpcApi.playPause()
|
||||
time.sleep(constants.MPC_PAUSE_TOGGLE_DELAY)
|
||||
|
||||
def _setPausedAccordinglyToServer(self):
|
||||
self.__forcePause()
|
||||
self.setPaused(self.__client.getGlobalPaused())
|
||||
if(self._mpcApi.isPaused() <> self.__client.getGlobalPaused()):
|
||||
self.__refreshMpcPlayState()
|
||||
if(self._mpcApi.isPaused() <> self.__client.getGlobalPaused()):
|
||||
self.__setUpStateForNewlyOpenedFile()
|
||||
|
||||
@retry(MpcHcApi.PlayerNotReadyException, constants.MPC_MAX_RETRIES, constants.MPC_RETRY_WAIT_TIME, 1)
|
||||
def __setUpStateForNewlyOpenedFile(self):
|
||||
self._setPausedAccordinglyToServer()
|
||||
self._mpcApi.seek(self.__client.getGlobalPosition())
|
||||
|
||||
def __handleUpdatedFilename(self):
|
||||
with self.__fileUpdate:
|
||||
self.__setUpStateForNewlyOpenedFile()
|
||||
self.__client.updateFile(self._mpcApi.filePlaying, self._mpcApi.fileDuration, self._mpcApi.filePath)
|
||||
|
||||
def __mpcError(self, err=""):
|
||||
self.__client.ui.showErrorMessage(err)
|
||||
self.__client.stop()
|
||||
|
||||
def sendCustomCommand(self, cmd, val):
|
||||
self._mpcApi.sendRawCommand(cmd, val)
|
||||
|
||||
@ -1,170 +1,171 @@
|
||||
import subprocess
|
||||
import re
|
||||
import threading
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
from syncplay import constants
|
||||
|
||||
class MplayerPlayer(BasePlayer):
|
||||
speedSupported = True
|
||||
RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX)
|
||||
def __init__(self, client, playerPath, filePath, args):
|
||||
self._client = client
|
||||
self._paused = None
|
||||
self._duration = None
|
||||
self._filename = None
|
||||
self._filepath = None
|
||||
try:
|
||||
self._listener = self.__Listener(self, playerPath, filePath, args)
|
||||
except ValueError:
|
||||
self._client.ui.showMessage("Syncplay using mplayer requires you to provide file when starting")
|
||||
self._client.ui.showMessage("Usage example: syncplay [options] [url|path/]filename")
|
||||
self._client.stop(True)
|
||||
return
|
||||
self._listener.setDaemon(True)
|
||||
self._listener.start()
|
||||
|
||||
self._durationAsk = threading.Event()
|
||||
self._filenameAsk = threading.Event()
|
||||
self._pathAsk = threading.Event()
|
||||
|
||||
self._positionAsk = threading.Event()
|
||||
self._pausedAsk = threading.Event()
|
||||
|
||||
self._preparePlayer()
|
||||
|
||||
def _fileUpdateClearEvents(self):
|
||||
self._durationAsk.clear()
|
||||
self._filenameAsk.clear()
|
||||
self._pathAsk.clear()
|
||||
|
||||
def _fileUpdateWaitEvents(self):
|
||||
self._durationAsk.wait()
|
||||
self._filenameAsk.wait()
|
||||
self._pathAsk.wait()
|
||||
|
||||
def _onFileUpdate(self):
|
||||
self._fileUpdateClearEvents()
|
||||
self._getFilename()
|
||||
self._getLength()
|
||||
self._getFilepath()
|
||||
self._fileUpdateWaitEvents()
|
||||
self._client.updateFile(self._filename, self._duration, self._filepath)
|
||||
|
||||
def _preparePlayer(self):
|
||||
self.setPaused(self._client.getGlobalPaused())
|
||||
self.setPosition(self._client.getGlobalPosition())
|
||||
self._client.initPlayer(self)
|
||||
self._onFileUpdate()
|
||||
|
||||
def askForStatus(self):
|
||||
self._positionAsk.clear()
|
||||
self._pausedAsk.clear()
|
||||
self._getPaused()
|
||||
self._getPosition()
|
||||
self._positionAsk.wait()
|
||||
self._pausedAsk.wait()
|
||||
self._client.updatePlayerStatus(self._paused, self._position)
|
||||
|
||||
def _setProperty(self, property_, value):
|
||||
self._listener.sendLine("set_property {} {}".format(property_, value))
|
||||
|
||||
def _getProperty(self, property_):
|
||||
self._listener.sendLine("get_property {}".format(property_))
|
||||
|
||||
def displayMessage(self, message):
|
||||
self._listener.sendLine('osd_show_text "{!s}" {} {}'.format(message, constants.OSD_DURATION, constants.MPLAYER_OSD_LEVEL))
|
||||
|
||||
def setSpeed(self, value):
|
||||
self._setProperty('speed', "{:.2f}".format(value))
|
||||
|
||||
def setPosition(self, value):
|
||||
self._position = value
|
||||
self._setProperty('time_pos', "{}".format(value))
|
||||
|
||||
def setPaused(self, value):
|
||||
self._paused = value
|
||||
self._setProperty('pause', 'yes' if value else 'no')
|
||||
|
||||
def _getFilename(self):
|
||||
self._getProperty('filename')
|
||||
|
||||
def _getLength(self):
|
||||
self._getProperty('length')
|
||||
|
||||
def _getFilepath(self):
|
||||
self._getProperty('path')
|
||||
|
||||
def _getPaused(self):
|
||||
self._getProperty('pause')
|
||||
|
||||
def _getPosition(self):
|
||||
self._getProperty('time_pos')
|
||||
|
||||
def lineReceived(self, line):
|
||||
match = self.RE_ANSWER.match(line)
|
||||
if not match:
|
||||
return
|
||||
name, value = match.group(1).lower(), match.group(2)
|
||||
|
||||
if(name == "time_pos"):
|
||||
self._position = float(value)
|
||||
self._positionAsk.set()
|
||||
elif(name == "pause"):
|
||||
self._paused = bool(value == 'yes')
|
||||
self._pausedAsk.set()
|
||||
elif(name == "length"):
|
||||
self._duration = float(value)
|
||||
self._durationAsk.set()
|
||||
elif(name == "path"):
|
||||
self._filepath = value
|
||||
self._pathAsk.set()
|
||||
elif(name == "filename"):
|
||||
self._filename = value
|
||||
self._filenameAsk.set()
|
||||
|
||||
@staticmethod
|
||||
def run(client, playerPath, filePath, args):
|
||||
mplayer = MplayerPlayer(client, playerPath, filePath, args)
|
||||
return mplayer
|
||||
|
||||
def drop(self):
|
||||
self._listener.sendLine('quit')
|
||||
self._durationAsk.set()
|
||||
self._filenameAsk.set()
|
||||
self._pathAsk.set()
|
||||
self._positionAsk.set()
|
||||
self._pausedAsk.set()
|
||||
self._client.stop(False)
|
||||
for line in self._listener.readStderrLine():
|
||||
self._client.ui.showMessage(line, True, True)
|
||||
|
||||
class __Listener(threading.Thread):
|
||||
def __init__(self, playerController, playerPath, filePath, args):
|
||||
self.__playerController = playerController
|
||||
if(not filePath):
|
||||
raise ValueError
|
||||
call = [playerPath, filePath]
|
||||
call.extend(constants.MPLAYER_SLAVE_ARGS)
|
||||
if(args):
|
||||
call.extend(args)
|
||||
self.__process = subprocess.Popen(call, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
threading.Thread.__init__(self, name="MPlayer Listener")
|
||||
|
||||
def run(self):
|
||||
while(self.__process.poll() is None):
|
||||
line = self.__process.stdout.readline()
|
||||
line = line.rstrip("\r\n")
|
||||
self.__playerController.lineReceived(line)
|
||||
self.__playerController.drop()
|
||||
|
||||
def sendLine(self, line):
|
||||
try:
|
||||
self.__process.stdin.write(line + "\n")
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
def readStderrLine(self):
|
||||
for line in self.__process.stderr.readlines():
|
||||
yield line
|
||||
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import threading
|
||||
from syncplay.players.basePlayer import BasePlayer
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
|
||||
class MplayerPlayer(BasePlayer):
|
||||
speedSupported = True
|
||||
RE_ANSWER = re.compile(constants.MPLAYER_ANSWER_REGEX)
|
||||
def __init__(self, client, playerPath, filePath, args):
|
||||
self._client = client
|
||||
self._paused = None
|
||||
self._duration = None
|
||||
self._filename = None
|
||||
self._filepath = None
|
||||
try:
|
||||
self._listener = self.__Listener(self, playerPath, filePath, args)
|
||||
except ValueError:
|
||||
self._client.ui.showMessage(getMessage("en", "mplayer-file-required-notification"))
|
||||
self._client.ui.showMessage(getMessage("en", "mplayer-file-required-notification/example"))
|
||||
self._client.stop(True)
|
||||
return
|
||||
self._listener.setDaemon(True)
|
||||
self._listener.start()
|
||||
|
||||
self._durationAsk = threading.Event()
|
||||
self._filenameAsk = threading.Event()
|
||||
self._pathAsk = threading.Event()
|
||||
|
||||
self._positionAsk = threading.Event()
|
||||
self._pausedAsk = threading.Event()
|
||||
|
||||
self._preparePlayer()
|
||||
|
||||
def _fileUpdateClearEvents(self):
|
||||
self._durationAsk.clear()
|
||||
self._filenameAsk.clear()
|
||||
self._pathAsk.clear()
|
||||
|
||||
def _fileUpdateWaitEvents(self):
|
||||
self._durationAsk.wait()
|
||||
self._filenameAsk.wait()
|
||||
self._pathAsk.wait()
|
||||
|
||||
def _onFileUpdate(self):
|
||||
self._fileUpdateClearEvents()
|
||||
self._getFilename()
|
||||
self._getLength()
|
||||
self._getFilepath()
|
||||
self._fileUpdateWaitEvents()
|
||||
self._client.updateFile(self._filename, self._duration, self._filepath)
|
||||
|
||||
def _preparePlayer(self):
|
||||
self.setPaused(self._client.getGlobalPaused())
|
||||
self.setPosition(self._client.getGlobalPosition())
|
||||
self._client.initPlayer(self)
|
||||
self._onFileUpdate()
|
||||
|
||||
def askForStatus(self):
|
||||
self._positionAsk.clear()
|
||||
self._pausedAsk.clear()
|
||||
self._getPaused()
|
||||
self._getPosition()
|
||||
self._positionAsk.wait()
|
||||
self._pausedAsk.wait()
|
||||
self._client.updatePlayerStatus(self._paused, self._position)
|
||||
|
||||
def _setProperty(self, property_, value):
|
||||
self._listener.sendLine("set_property {} {}".format(property_, value))
|
||||
|
||||
def _getProperty(self, property_):
|
||||
self._listener.sendLine("get_property {}".format(property_))
|
||||
|
||||
def displayMessage(self, message):
|
||||
self._listener.sendLine('osd_show_text "{!s}" {} {}'.format(message, constants.OSD_DURATION, constants.MPLAYER_OSD_LEVEL))
|
||||
|
||||
def setSpeed(self, value):
|
||||
self._setProperty('speed', "{:.2f}".format(value))
|
||||
|
||||
def setPosition(self, value):
|
||||
self._position = value
|
||||
self._setProperty('time_pos', "{}".format(value))
|
||||
|
||||
def setPaused(self, value):
|
||||
self._paused = value
|
||||
self._setProperty('pause', 'yes' if value else 'no')
|
||||
|
||||
def _getFilename(self):
|
||||
self._getProperty('filename')
|
||||
|
||||
def _getLength(self):
|
||||
self._getProperty('length')
|
||||
|
||||
def _getFilepath(self):
|
||||
self._getProperty('path')
|
||||
|
||||
def _getPaused(self):
|
||||
self._getProperty('pause')
|
||||
|
||||
def _getPosition(self):
|
||||
self._getProperty('time_pos')
|
||||
|
||||
def lineReceived(self, line):
|
||||
match = self.RE_ANSWER.match(line)
|
||||
if not match:
|
||||
return
|
||||
name, value = match.group(1).lower(), match.group(2)
|
||||
|
||||
if(name == "time_pos"):
|
||||
self._position = float(value)
|
||||
self._positionAsk.set()
|
||||
elif(name == "pause"):
|
||||
self._paused = bool(value == 'yes')
|
||||
self._pausedAsk.set()
|
||||
elif(name == "length"):
|
||||
self._duration = float(value)
|
||||
self._durationAsk.set()
|
||||
elif(name == "path"):
|
||||
self._filepath = value
|
||||
self._pathAsk.set()
|
||||
elif(name == "filename"):
|
||||
self._filename = value
|
||||
self._filenameAsk.set()
|
||||
|
||||
@staticmethod
|
||||
def run(client, playerPath, filePath, args):
|
||||
mplayer = MplayerPlayer(client, playerPath, filePath, args)
|
||||
return mplayer
|
||||
|
||||
def drop(self):
|
||||
self._listener.sendLine('quit')
|
||||
self._durationAsk.set()
|
||||
self._filenameAsk.set()
|
||||
self._pathAsk.set()
|
||||
self._positionAsk.set()
|
||||
self._pausedAsk.set()
|
||||
self._client.stop(False)
|
||||
for line in self._listener.readStderrLine():
|
||||
self._client.ui.showMessage(line, True, True)
|
||||
|
||||
class __Listener(threading.Thread):
|
||||
def __init__(self, playerController, playerPath, filePath, args):
|
||||
self.__playerController = playerController
|
||||
if(not filePath):
|
||||
raise ValueError
|
||||
call = [playerPath, filePath]
|
||||
call.extend(constants.MPLAYER_SLAVE_ARGS)
|
||||
if(args):
|
||||
call.extend(args)
|
||||
self.__process = subprocess.Popen(call, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
threading.Thread.__init__(self, name="MPlayer Listener")
|
||||
|
||||
def run(self):
|
||||
while(self.__process.poll() is None):
|
||||
line = self.__process.stdout.readline()
|
||||
line = line.rstrip("\r\n")
|
||||
self.__playerController.lineReceived(line)
|
||||
self.__playerController.drop()
|
||||
|
||||
def sendLine(self, line):
|
||||
try:
|
||||
self.__process.stdin.write(line + "\n")
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
def readStderrLine(self):
|
||||
for line in self.__process.stderr.readlines():
|
||||
yield line
|
||||
|
||||
|
||||
|
||||
@ -1,395 +1,396 @@
|
||||
#coding:utf8
|
||||
from twisted.protocols.basic import LineReceiver
|
||||
import json
|
||||
import syncplay
|
||||
from functools import wraps
|
||||
import time
|
||||
|
||||
class JSONCommandProtocol(LineReceiver):
|
||||
def handleMessages(self, messages):
|
||||
for message in messages.iteritems():
|
||||
command = message[0]
|
||||
if command == "Hello":
|
||||
self.handleHello(message[1])
|
||||
elif command == "Set":
|
||||
self.handleSet(message[1])
|
||||
elif command == "List":
|
||||
self.handleList(message[1])
|
||||
elif command == "State":
|
||||
self.handleState(message[1])
|
||||
elif command == "Error":
|
||||
self.handleError(message[1])
|
||||
else:
|
||||
self.dropWithError("Unknown Command\n" + message[1]) #TODO: log, not drop
|
||||
|
||||
def printReceived(self, line): #TODO: remove
|
||||
#print ">>i", line
|
||||
pass
|
||||
|
||||
def printSent(self, line):
|
||||
#print "o<<", line
|
||||
pass
|
||||
|
||||
def lineReceived(self, line):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
return
|
||||
self.printReceived(line)
|
||||
try:
|
||||
messages = json.loads(line)
|
||||
except:
|
||||
self.dropWithError("Not a json encoded string\n" + line)
|
||||
return
|
||||
self.handleMessages(messages)
|
||||
|
||||
def sendMessage(self, dict_):
|
||||
line = json.dumps(dict_)
|
||||
self.printSent(line)
|
||||
self.sendLine(line)
|
||||
|
||||
def drop(self):
|
||||
self.transport.loseConnection()
|
||||
|
||||
def dropWithError(self, error):
|
||||
raise NotImplementedError()
|
||||
|
||||
class SyncClientProtocol(JSONCommandProtocol):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
self.serverIgnoringOnTheFly = 0
|
||||
self.logged = False
|
||||
|
||||
def connectionMade(self):
|
||||
self._client.initProtocol(self)
|
||||
self.sendHello()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self._client.destroyProtocol()
|
||||
|
||||
def dropWithError(self, error):
|
||||
self._client.ui.showErrorMessage(error)
|
||||
self._client.protocolFactory.stopRetrying()
|
||||
self.drop()
|
||||
|
||||
def _extractHelloArguments(self, hello):
|
||||
username = hello["username"] if hello.has_key("username") else None
|
||||
roomName = hello["room"]["name"] if hello.has_key("room") else None
|
||||
version = hello["version"] if hello.has_key("version") else None
|
||||
return username, roomName, version
|
||||
|
||||
def handleHello(self, hello):
|
||||
username, roomName, version = self._extractHelloArguments(hello)
|
||||
if(not username or not roomName or not version):
|
||||
self.dropWithError("Not enough Hello arguments\n" + hello)
|
||||
elif(version.split(".")[0:2] != syncplay.version.split(".")[0:2]):
|
||||
self.dropWithError("Mismatch between versions of client and server\n" + hello)
|
||||
else:
|
||||
self._client.setUsername(username)
|
||||
self._client.setRoom(roomName)
|
||||
self.logged = True
|
||||
self._client.ui.showMessage("Successfully connected to server")
|
||||
self._client.sendFile()
|
||||
|
||||
def sendHello(self):
|
||||
hello = {}
|
||||
hello["username"] = self._client.getUsername()
|
||||
password = self._client.getPassword()
|
||||
if(password): hello["password"] = password
|
||||
room = self._client.getRoom()
|
||||
if(room): hello["room"] = {"name" :room}
|
||||
hello["version"] = syncplay.version
|
||||
self.sendMessage({"Hello": hello})
|
||||
|
||||
def _SetUser(self, users):
|
||||
for user in users.iteritems():
|
||||
username = user[0]
|
||||
settings = user[1]
|
||||
room = settings["room"]["name"] if settings.has_key("room") else None
|
||||
file_ = settings["file"] if settings.has_key("file") else None
|
||||
if(settings.has_key("event")):
|
||||
if(settings["event"].has_key("joined")):
|
||||
self._client.userlist.addUser(username, room, file_)
|
||||
elif(settings["event"].has_key("left")):
|
||||
self._client.userlist.removeUser(username)
|
||||
else:
|
||||
self._client.userlist.modUser(username, room, file_)
|
||||
|
||||
def handleSet(self, settings):
|
||||
for set_ in settings.iteritems():
|
||||
command = set_[0]
|
||||
if command == "room":
|
||||
roomName = set_[1]["name"] if set_[1].has_key("name") else None
|
||||
self._client.setRoom(roomName)
|
||||
elif command == "user":
|
||||
self._SetUser(set_[1])
|
||||
|
||||
def sendSet(self, setting):
|
||||
self.sendMessage({"Set": setting})
|
||||
|
||||
def sendRoomSetting(self, roomName, password=None):
|
||||
setting = {}
|
||||
setting["name"] = roomName
|
||||
if(password): setting["password"] = password
|
||||
self.sendSet({"room": setting})
|
||||
|
||||
def sendFileSetting(self, file_):
|
||||
self.sendSet({"file": file_})
|
||||
self.sendList()
|
||||
|
||||
def handleList(self, userList):
|
||||
self._client.userlist.clearList()
|
||||
for room in userList.iteritems():
|
||||
roomName = room[0]
|
||||
for user in room[1].iteritems():
|
||||
userName = user[0]
|
||||
file_ = user[1]['file'] if user[1]['file'] <> {} else None
|
||||
position = user[1]['position']
|
||||
self._client.userlist.addUser(userName, roomName, file_, position, noMessage=True)
|
||||
self._client.userlist.showUserList()
|
||||
|
||||
def sendList(self):
|
||||
self.sendMessage({"List": None})
|
||||
|
||||
def _extractStatePlaystateArguments(self, state):
|
||||
position = state["playstate"]["position"] if state["playstate"].has_key("position") else 0
|
||||
paused = state["playstate"]["paused"] if state["playstate"].has_key("paused") else None
|
||||
doSeek = state["playstate"]["doSeek"] if state["playstate"].has_key("doSeek") else None
|
||||
setBy = state["playstate"]["setBy"] if state["playstate"].has_key("setBy") else None
|
||||
return position, paused, doSeek, setBy
|
||||
|
||||
def _handleStatePing(self, state):
|
||||
yourLatency = state["ping"]["yourLatency"] if state["ping"].has_key("yourLatency") else 0
|
||||
senderLatency = state["ping"]["senderLatency"] if state["ping"].has_key("senderLatency") else 0
|
||||
if (state["ping"].has_key("latencyCalculation")):
|
||||
latencyCalculation = state["ping"]["latencyCalculation"]
|
||||
return yourLatency, senderLatency, latencyCalculation
|
||||
|
||||
def handleState(self, state):
|
||||
position, paused, doSeek, setBy = None, None, None, None
|
||||
yourLatency, senderLatency = 0, 0
|
||||
if(state.has_key("ignoringOnTheFly")):
|
||||
ignore = state["ignoringOnTheFly"]
|
||||
if(ignore.has_key("server")):
|
||||
self.serverIgnoringOnTheFly = ignore["server"]
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
elif(ignore.has_key("client")):
|
||||
if(ignore['client']) == self.clientIgnoringOnTheFly:
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
if(state.has_key("playstate")):
|
||||
position, paused, doSeek, setBy = self._extractStatePlaystateArguments(state)
|
||||
if(state.has_key("ping")):
|
||||
yourLatency, senderLatency, latencyCalculation = self._handleStatePing(state)
|
||||
if(position is not None and paused is not None and not self.clientIgnoringOnTheFly):
|
||||
latency = yourLatency + senderLatency
|
||||
self._client.updateGlobalState(position, paused, doSeek, setBy, latency)
|
||||
position, paused, doSeek, stateChange = self._client.getLocalState()
|
||||
self.sendState(position, paused, doSeek, latencyCalculation, stateChange)
|
||||
|
||||
def sendState(self, position, paused, doSeek, latencyCalculation, stateChange = False):
|
||||
state = {}
|
||||
positionAndPausedIsSet = position is not None and paused is not None
|
||||
clientIgnoreIsNotSet = self.clientIgnoringOnTheFly == 0 or self.serverIgnoringOnTheFly != 0
|
||||
if(clientIgnoreIsNotSet and positionAndPausedIsSet):
|
||||
state["playstate"] = {}
|
||||
state["playstate"]["position"] = position
|
||||
state["playstate"]["paused"] = paused
|
||||
if(doSeek): state["playstate"]["doSeek"] = doSeek
|
||||
if(latencyCalculation):
|
||||
state["ping"] = {"latencyCalculation": latencyCalculation}
|
||||
if(stateChange):
|
||||
self.clientIgnoringOnTheFly += 1
|
||||
if(self.serverIgnoringOnTheFly or self.clientIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"] = {}
|
||||
if(self.serverIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"]["server"] = self.serverIgnoringOnTheFly
|
||||
self.serverIgnoringOnTheFly = 0
|
||||
if(self.clientIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
|
||||
self.sendMessage({"State": state})
|
||||
|
||||
def handleError(self, error):
|
||||
self.dropWithError(error["message"]) #TODO: more processing and fallbacking
|
||||
|
||||
def sendError(self, message):
|
||||
self.sendMessage({"Error": {"message": message}})
|
||||
|
||||
|
||||
class SyncServerProtocol(JSONCommandProtocol):
|
||||
def __init__(self, factory):
|
||||
self._factory = factory
|
||||
self._logged = False
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
self.serverIgnoringOnTheFly = 0
|
||||
|
||||
def __hash__(self):
|
||||
return hash('|'.join((
|
||||
self.transport.getPeer().host,
|
||||
str(id(self)),
|
||||
)))
|
||||
|
||||
def requireLogged(f): #@NoSelf
|
||||
@wraps(f)
|
||||
def wrapper(self, *args, **kwds):
|
||||
if(not self._logged):
|
||||
self.dropWithError("You must be known to server before sending this command")
|
||||
return f(self, *args, **kwds)
|
||||
return wrapper
|
||||
|
||||
def dropWithError(self, error):
|
||||
print "Client drop: %s -- %s" % (self.transport.getPeer().host, error)
|
||||
self.sendError(error)
|
||||
self.drop()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self._factory.removeWatcher(self)
|
||||
|
||||
def _extractHelloArguments(self, hello):
|
||||
roomName, roomPassword = None, None
|
||||
username = hello["username"] if hello.has_key("username") else None
|
||||
username = username.strip()
|
||||
serverPassword = hello["password"] if hello.has_key("password") else None
|
||||
room = hello["room"] if hello.has_key("room") else None
|
||||
if(room):
|
||||
roomName = room["name"] if room.has_key("name") else None
|
||||
roomName = roomName.strip()
|
||||
roomPassword = room["password"] if room.has_key("password") else None
|
||||
version = hello["version"] if hello.has_key("version") else None
|
||||
return username, serverPassword, roomName, roomPassword, version
|
||||
|
||||
def _checkPassword(self, serverPassword):
|
||||
if(self._factory.password):
|
||||
if(not serverPassword):
|
||||
self.dropWithError("Password required")
|
||||
return False
|
||||
if(serverPassword != self._factory.password):
|
||||
self.dropWithError("Wrong password supplied")
|
||||
return False
|
||||
return True
|
||||
|
||||
def handleHello(self, hello):
|
||||
username, serverPassword, roomName, roomPassword, version = self._extractHelloArguments(hello)
|
||||
if(not username or not roomName or not version):
|
||||
self.dropWithError("Not enough Hello arguments")
|
||||
elif(version.split(".")[0:2] != syncplay.version.split(".")[0:2]):
|
||||
self.dropWithError("Mismatch between versions of client and server")
|
||||
else:
|
||||
if(not self._checkPassword(serverPassword)):
|
||||
return
|
||||
self._factory.addWatcher(self, username, roomName, roomPassword)
|
||||
self._logged = True
|
||||
self.sendHello()
|
||||
|
||||
def sendHello(self):
|
||||
hello = {}
|
||||
hello["username"] = self._factory.watcherGetUsername(self)
|
||||
room = self._factory.watcherGetRoom(self)
|
||||
if(room): hello["room"] = {"name": room}
|
||||
hello["version"] = syncplay.version
|
||||
self.sendMessage({"Hello": hello})
|
||||
|
||||
@requireLogged
|
||||
def handleSet(self, settings):
|
||||
for set_ in settings.iteritems():
|
||||
command = set_[0]
|
||||
if command == "room":
|
||||
roomName = set_[1]["name"] if set_[1].has_key("name") else None
|
||||
self._factory.watcherSetRoom(self, roomName)
|
||||
elif command == "file":
|
||||
self._factory.watcherSetFile(self, set_[1])
|
||||
|
||||
def sendSet(self, setting):
|
||||
self.sendMessage({"Set": setting})
|
||||
|
||||
def sendRoomSetting(self, roomName):
|
||||
self.sendSet({"room": {"name": roomName}})
|
||||
|
||||
def sendUserSetting(self, username, roomName, file_, event):
|
||||
room = {"name": roomName}
|
||||
user = {}
|
||||
user[username] = {}
|
||||
user[username]["room"] = room
|
||||
if(file_):
|
||||
user[username]["file"] = file_
|
||||
if(event):
|
||||
user[username]["event"] = event
|
||||
self.sendSet({"user": user})
|
||||
|
||||
def _addUserOnList(self, userlist, roomPositions, watcher):
|
||||
if (not userlist.has_key(watcher.room)):
|
||||
userlist[watcher.room] = {}
|
||||
roomPositions[watcher.room] = watcher.getRoomPosition()
|
||||
userlist[watcher.room][watcher.name] = {
|
||||
"file": watcher.file if watcher.file else {},
|
||||
"position": roomPositions[watcher.room] if roomPositions[watcher.room] else 0
|
||||
}
|
||||
def sendList(self):
|
||||
userlist = {}
|
||||
roomPositions = {}
|
||||
watchers = self._factory.getAllWatchers(self)
|
||||
for watcher in watchers.itervalues():
|
||||
self._addUserOnList(userlist, roomPositions, watcher)
|
||||
self.sendMessage({"List": userlist})
|
||||
|
||||
@requireLogged
|
||||
def handleList(self, _):
|
||||
self.sendList()
|
||||
|
||||
def sendState(self, position, paused, doSeek, setBy, senderLatency, watcherLatency, forced = False):
|
||||
playstate = {
|
||||
"position": position,
|
||||
"paused": paused,
|
||||
"doSeek": doSeek,
|
||||
"setBy": setBy
|
||||
}
|
||||
ping = {
|
||||
"yourLatency": watcherLatency,
|
||||
"senderLatency": senderLatency,
|
||||
"latencyCalculation": time.time()
|
||||
}
|
||||
state = {
|
||||
"ping": ping,
|
||||
"playstate": playstate,
|
||||
}
|
||||
if(forced):
|
||||
self.serverIgnoringOnTheFly += 1
|
||||
if(self.serverIgnoringOnTheFly or self.clientIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"] = {}
|
||||
if(self.serverIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"]["server"] = self.serverIgnoringOnTheFly
|
||||
if(self.clientIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
if(self.serverIgnoringOnTheFly == 0 or forced):
|
||||
self.sendMessage({"State": state})
|
||||
|
||||
|
||||
def _extractStatePlaystateArguments(self, state):
|
||||
position = state["playstate"]["position"] if state["playstate"].has_key("position") else 0
|
||||
paused = state["playstate"]["paused"] if state["playstate"].has_key("paused") else None
|
||||
doSeek = state["playstate"]["doSeek"] if state["playstate"].has_key("doSeek") else None
|
||||
return position, paused, doSeek
|
||||
|
||||
@requireLogged
|
||||
def handleState(self, state):
|
||||
position, paused, doSeek, latencyCalculation = None, None, None, None
|
||||
if(state.has_key("ignoringOnTheFly")):
|
||||
ignore = state["ignoringOnTheFly"]
|
||||
if(ignore.has_key("server")):
|
||||
if(self.serverIgnoringOnTheFly == ignore["server"]):
|
||||
self.serverIgnoringOnTheFly = 0
|
||||
if(ignore.has_key("client")):
|
||||
self.clientIgnoringOnTheFly = ignore["client"]
|
||||
if(state.has_key("playstate")):
|
||||
position, paused, doSeek = self._extractStatePlaystateArguments(state)
|
||||
if(state.has_key("ping")):
|
||||
latencyCalculation = state["ping"]["latencyCalculation"] if state["ping"].has_key("latencyCalculation") else None
|
||||
if(self.serverIgnoringOnTheFly == 0):
|
||||
self._factory.updateWatcherState(self, position, paused, doSeek, latencyCalculation)
|
||||
|
||||
def handleError(self, error):
|
||||
self.dropWithError(error["message"]) #TODO: more processing and fallbacking
|
||||
|
||||
def sendError(self, message):
|
||||
self.sendMessage({"Error": {"message": message}})
|
||||
|
||||
#coding:utf8
|
||||
from twisted.protocols.basic import LineReceiver
|
||||
import json
|
||||
import syncplay
|
||||
from functools import wraps
|
||||
import time
|
||||
from syncplay.messages import getMessage
|
||||
|
||||
class JSONCommandProtocol(LineReceiver):
|
||||
def handleMessages(self, messages):
|
||||
for message in messages.iteritems():
|
||||
command = message[0]
|
||||
if command == "Hello":
|
||||
self.handleHello(message[1])
|
||||
elif command == "Set":
|
||||
self.handleSet(message[1])
|
||||
elif command == "List":
|
||||
self.handleList(message[1])
|
||||
elif command == "State":
|
||||
self.handleState(message[1])
|
||||
elif command == "Error":
|
||||
self.handleError(message[1])
|
||||
else:
|
||||
self.dropWithError("Unknown Command\n" + message[1]) #TODO: log, not drop
|
||||
|
||||
def printReceived(self, line): #TODO: remove
|
||||
#print ">>i", line
|
||||
pass
|
||||
|
||||
def printSent(self, line):
|
||||
#print "o<<", line
|
||||
pass
|
||||
|
||||
def lineReceived(self, line):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
return
|
||||
self.printReceived(line)
|
||||
try:
|
||||
messages = json.loads(line)
|
||||
except:
|
||||
self.dropWithError(getMessage("en", "not-json-error") + line)
|
||||
return
|
||||
self.handleMessages(messages)
|
||||
|
||||
def sendMessage(self, dict_):
|
||||
line = json.dumps(dict_)
|
||||
self.printSent(line)
|
||||
self.sendLine(line)
|
||||
|
||||
def drop(self):
|
||||
self.transport.loseConnection()
|
||||
|
||||
def dropWithError(self, error):
|
||||
raise NotImplementedError()
|
||||
|
||||
class SyncClientProtocol(JSONCommandProtocol):
|
||||
def __init__(self, client):
|
||||
self._client = client
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
self.serverIgnoringOnTheFly = 0
|
||||
self.logged = False
|
||||
|
||||
def connectionMade(self):
|
||||
self._client.initProtocol(self)
|
||||
self.sendHello()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self._client.destroyProtocol()
|
||||
|
||||
def dropWithError(self, error):
|
||||
self._client.ui.showErrorMessage(error)
|
||||
self._client.protocolFactory.stopRetrying()
|
||||
self.drop()
|
||||
|
||||
def _extractHelloArguments(self, hello):
|
||||
username = hello["username"] if hello.has_key("username") else None
|
||||
roomName = hello["room"]["name"] if hello.has_key("room") else None
|
||||
version = hello["version"] if hello.has_key("version") else None
|
||||
return username, roomName, version
|
||||
|
||||
def handleHello(self, hello):
|
||||
username, roomName, version = self._extractHelloArguments(hello)
|
||||
if(not username or not roomName or not version):
|
||||
self.dropWithError(getMessage("en", "hello-arguments-error") + hello)
|
||||
elif(version.split(".")[0:2] != syncplay.version.split(".")[0:2]):
|
||||
self.dropWithError(getMessage("en", "version-mismatch-error") + hello)
|
||||
else:
|
||||
self._client.setUsername(username)
|
||||
self._client.setRoom(roomName)
|
||||
self.logged = True
|
||||
self._client.ui.showMessage(getMessage("en", "connected-successful-notification"))
|
||||
self._client.sendFile()
|
||||
|
||||
def sendHello(self):
|
||||
hello = {}
|
||||
hello["username"] = self._client.getUsername()
|
||||
password = self._client.getPassword()
|
||||
if(password): hello["password"] = password
|
||||
room = self._client.getRoom()
|
||||
if(room): hello["room"] = {"name" :room}
|
||||
hello["version"] = syncplay.version
|
||||
self.sendMessage({"Hello": hello})
|
||||
|
||||
def _SetUser(self, users):
|
||||
for user in users.iteritems():
|
||||
username = user[0]
|
||||
settings = user[1]
|
||||
room = settings["room"]["name"] if settings.has_key("room") else None
|
||||
file_ = settings["file"] if settings.has_key("file") else None
|
||||
if(settings.has_key("event")):
|
||||
if(settings["event"].has_key("joined")):
|
||||
self._client.userlist.addUser(username, room, file_)
|
||||
elif(settings["event"].has_key("left")):
|
||||
self._client.userlist.removeUser(username)
|
||||
else:
|
||||
self._client.userlist.modUser(username, room, file_)
|
||||
|
||||
def handleSet(self, settings):
|
||||
for set_ in settings.iteritems():
|
||||
command = set_[0]
|
||||
if command == "room":
|
||||
roomName = set_[1]["name"] if set_[1].has_key("name") else None
|
||||
self._client.setRoom(roomName)
|
||||
elif command == "user":
|
||||
self._SetUser(set_[1])
|
||||
|
||||
def sendSet(self, setting):
|
||||
self.sendMessage({"Set": setting})
|
||||
|
||||
def sendRoomSetting(self, roomName, password=None):
|
||||
setting = {}
|
||||
setting["name"] = roomName
|
||||
if(password): setting["password"] = password
|
||||
self.sendSet({"room": setting})
|
||||
|
||||
def sendFileSetting(self, file_):
|
||||
self.sendSet({"file": file_})
|
||||
self.sendList()
|
||||
|
||||
def handleList(self, userList):
|
||||
self._client.userlist.clearList()
|
||||
for room in userList.iteritems():
|
||||
roomName = room[0]
|
||||
for user in room[1].iteritems():
|
||||
userName = user[0]
|
||||
file_ = user[1]['file'] if user[1]['file'] <> {} else None
|
||||
position = user[1]['position']
|
||||
self._client.userlist.addUser(userName, roomName, file_, position, noMessage=True)
|
||||
self._client.userlist.showUserList()
|
||||
|
||||
def sendList(self):
|
||||
self.sendMessage({"List": None})
|
||||
|
||||
def _extractStatePlaystateArguments(self, state):
|
||||
position = state["playstate"]["position"] if state["playstate"].has_key("position") else 0
|
||||
paused = state["playstate"]["paused"] if state["playstate"].has_key("paused") else None
|
||||
doSeek = state["playstate"]["doSeek"] if state["playstate"].has_key("doSeek") else None
|
||||
setBy = state["playstate"]["setBy"] if state["playstate"].has_key("setBy") else None
|
||||
return position, paused, doSeek, setBy
|
||||
|
||||
def _handleStatePing(self, state):
|
||||
yourLatency = state["ping"]["yourLatency"] if state["ping"].has_key("yourLatency") else 0
|
||||
senderLatency = state["ping"]["senderLatency"] if state["ping"].has_key("senderLatency") else 0
|
||||
if (state["ping"].has_key("latencyCalculation")):
|
||||
latencyCalculation = state["ping"]["latencyCalculation"]
|
||||
return yourLatency, senderLatency, latencyCalculation
|
||||
|
||||
def handleState(self, state):
|
||||
position, paused, doSeek, setBy = None, None, None, None
|
||||
yourLatency, senderLatency = 0, 0
|
||||
if(state.has_key("ignoringOnTheFly")):
|
||||
ignore = state["ignoringOnTheFly"]
|
||||
if(ignore.has_key("server")):
|
||||
self.serverIgnoringOnTheFly = ignore["server"]
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
elif(ignore.has_key("client")):
|
||||
if(ignore['client']) == self.clientIgnoringOnTheFly:
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
if(state.has_key("playstate")):
|
||||
position, paused, doSeek, setBy = self._extractStatePlaystateArguments(state)
|
||||
if(state.has_key("ping")):
|
||||
yourLatency, senderLatency, latencyCalculation = self._handleStatePing(state)
|
||||
if(position is not None and paused is not None and not self.clientIgnoringOnTheFly):
|
||||
latency = yourLatency + senderLatency
|
||||
self._client.updateGlobalState(position, paused, doSeek, setBy, latency)
|
||||
position, paused, doSeek, stateChange = self._client.getLocalState()
|
||||
self.sendState(position, paused, doSeek, latencyCalculation, stateChange)
|
||||
|
||||
def sendState(self, position, paused, doSeek, latencyCalculation, stateChange = False):
|
||||
state = {}
|
||||
positionAndPausedIsSet = position is not None and paused is not None
|
||||
clientIgnoreIsNotSet = self.clientIgnoringOnTheFly == 0 or self.serverIgnoringOnTheFly != 0
|
||||
if(clientIgnoreIsNotSet and positionAndPausedIsSet):
|
||||
state["playstate"] = {}
|
||||
state["playstate"]["position"] = position
|
||||
state["playstate"]["paused"] = paused
|
||||
if(doSeek): state["playstate"]["doSeek"] = doSeek
|
||||
if(latencyCalculation):
|
||||
state["ping"] = {"latencyCalculation": latencyCalculation}
|
||||
if(stateChange):
|
||||
self.clientIgnoringOnTheFly += 1
|
||||
if(self.serverIgnoringOnTheFly or self.clientIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"] = {}
|
||||
if(self.serverIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"]["server"] = self.serverIgnoringOnTheFly
|
||||
self.serverIgnoringOnTheFly = 0
|
||||
if(self.clientIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
|
||||
self.sendMessage({"State": state})
|
||||
|
||||
def handleError(self, error):
|
||||
self.dropWithError(error["message"]) #TODO: more processing and fallbacking
|
||||
|
||||
def sendError(self, message):
|
||||
self.sendMessage({"Error": {"message": message}})
|
||||
|
||||
|
||||
class SyncServerProtocol(JSONCommandProtocol):
|
||||
def __init__(self, factory):
|
||||
self._factory = factory
|
||||
self._logged = False
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
self.serverIgnoringOnTheFly = 0
|
||||
|
||||
def __hash__(self):
|
||||
return hash('|'.join((
|
||||
self.transport.getPeer().host,
|
||||
str(id(self)),
|
||||
)))
|
||||
|
||||
def requireLogged(f): #@NoSelf
|
||||
@wraps(f)
|
||||
def wrapper(self, *args, **kwds):
|
||||
if(not self._logged):
|
||||
self.dropWithError(getMessage("en", "not-known-server-error"))
|
||||
return f(self, *args, **kwds)
|
||||
return wrapper
|
||||
|
||||
def dropWithError(self, error):
|
||||
print getMessage("en", "client-drop-server-error") % (self.transport.getPeer().host, error)
|
||||
self.sendError(error)
|
||||
self.drop()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
self._factory.removeWatcher(self)
|
||||
|
||||
def _extractHelloArguments(self, hello):
|
||||
roomName, roomPassword = None, None
|
||||
username = hello["username"] if hello.has_key("username") else None
|
||||
username = username.strip()
|
||||
serverPassword = hello["password"] if hello.has_key("password") else None
|
||||
room = hello["room"] if hello.has_key("room") else None
|
||||
if(room):
|
||||
roomName = room["name"] if room.has_key("name") else None
|
||||
roomName = roomName.strip()
|
||||
roomPassword = room["password"] if room.has_key("password") else None
|
||||
version = hello["version"] if hello.has_key("version") else None
|
||||
return username, serverPassword, roomName, roomPassword, version
|
||||
|
||||
def _checkPassword(self, serverPassword):
|
||||
if(self._factory.password):
|
||||
if(not serverPassword):
|
||||
self.dropWithError(getMessage("en", "password-required-server-error"))
|
||||
return False
|
||||
if(serverPassword != self._factory.password):
|
||||
self.dropWithError(getMessage("en", "wrong-password-server-error"))
|
||||
return False
|
||||
return True
|
||||
|
||||
def handleHello(self, hello):
|
||||
username, serverPassword, roomName, roomPassword, version = self._extractHelloArguments(hello)
|
||||
if(not username or not roomName or not version):
|
||||
self.dropWithError(getMessage("en", "hello-server-error"))
|
||||
elif(version.split(".")[0:2] != syncplay.version.split(".")[0:2]):
|
||||
self.dropWithError(getMessage("en", "version-mismatch-server-error"))
|
||||
else:
|
||||
if(not self._checkPassword(serverPassword)):
|
||||
return
|
||||
self._factory.addWatcher(self, username, roomName, roomPassword)
|
||||
self._logged = True
|
||||
self.sendHello()
|
||||
|
||||
def sendHello(self):
|
||||
hello = {}
|
||||
hello["username"] = self._factory.watcherGetUsername(self)
|
||||
room = self._factory.watcherGetRoom(self)
|
||||
if(room): hello["room"] = {"name": room}
|
||||
hello["version"] = syncplay.version
|
||||
self.sendMessage({"Hello": hello})
|
||||
|
||||
@requireLogged
|
||||
def handleSet(self, settings):
|
||||
for set_ in settings.iteritems():
|
||||
command = set_[0]
|
||||
if command == "room":
|
||||
roomName = set_[1]["name"] if set_[1].has_key("name") else None
|
||||
self._factory.watcherSetRoom(self, roomName)
|
||||
elif command == "file":
|
||||
self._factory.watcherSetFile(self, set_[1])
|
||||
|
||||
def sendSet(self, setting):
|
||||
self.sendMessage({"Set": setting})
|
||||
|
||||
def sendRoomSetting(self, roomName):
|
||||
self.sendSet({"room": {"name": roomName}})
|
||||
|
||||
def sendUserSetting(self, username, roomName, file_, event):
|
||||
room = {"name": roomName}
|
||||
user = {}
|
||||
user[username] = {}
|
||||
user[username]["room"] = room
|
||||
if(file_):
|
||||
user[username]["file"] = file_
|
||||
if(event):
|
||||
user[username]["event"] = event
|
||||
self.sendSet({"user": user})
|
||||
|
||||
def _addUserOnList(self, userlist, roomPositions, watcher):
|
||||
if (not userlist.has_key(watcher.room)):
|
||||
userlist[watcher.room] = {}
|
||||
roomPositions[watcher.room] = watcher.getRoomPosition()
|
||||
userlist[watcher.room][watcher.name] = {
|
||||
"file": watcher.file if watcher.file else {},
|
||||
"position": roomPositions[watcher.room] if roomPositions[watcher.room] else 0
|
||||
}
|
||||
def sendList(self):
|
||||
userlist = {}
|
||||
roomPositions = {}
|
||||
watchers = self._factory.getAllWatchers(self)
|
||||
for watcher in watchers.itervalues():
|
||||
self._addUserOnList(userlist, roomPositions, watcher)
|
||||
self.sendMessage({"List": userlist})
|
||||
|
||||
@requireLogged
|
||||
def handleList(self, _):
|
||||
self.sendList()
|
||||
|
||||
def sendState(self, position, paused, doSeek, setBy, senderLatency, watcherLatency, forced = False):
|
||||
playstate = {
|
||||
"position": position,
|
||||
"paused": paused,
|
||||
"doSeek": doSeek,
|
||||
"setBy": setBy
|
||||
}
|
||||
ping = {
|
||||
"yourLatency": watcherLatency,
|
||||
"senderLatency": senderLatency,
|
||||
"latencyCalculation": time.time()
|
||||
}
|
||||
state = {
|
||||
"ping": ping,
|
||||
"playstate": playstate,
|
||||
}
|
||||
if(forced):
|
||||
self.serverIgnoringOnTheFly += 1
|
||||
if(self.serverIgnoringOnTheFly or self.clientIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"] = {}
|
||||
if(self.serverIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"]["server"] = self.serverIgnoringOnTheFly
|
||||
if(self.clientIgnoringOnTheFly):
|
||||
state["ignoringOnTheFly"]["client"] = self.clientIgnoringOnTheFly
|
||||
self.clientIgnoringOnTheFly = 0
|
||||
if(self.serverIgnoringOnTheFly == 0 or forced):
|
||||
self.sendMessage({"State": state})
|
||||
|
||||
|
||||
def _extractStatePlaystateArguments(self, state):
|
||||
position = state["playstate"]["position"] if state["playstate"].has_key("position") else 0
|
||||
paused = state["playstate"]["paused"] if state["playstate"].has_key("paused") else None
|
||||
doSeek = state["playstate"]["doSeek"] if state["playstate"].has_key("doSeek") else None
|
||||
return position, paused, doSeek
|
||||
|
||||
@requireLogged
|
||||
def handleState(self, state):
|
||||
position, paused, doSeek, latencyCalculation = None, None, None, None
|
||||
if(state.has_key("ignoringOnTheFly")):
|
||||
ignore = state["ignoringOnTheFly"]
|
||||
if(ignore.has_key("server")):
|
||||
if(self.serverIgnoringOnTheFly == ignore["server"]):
|
||||
self.serverIgnoringOnTheFly = 0
|
||||
if(ignore.has_key("client")):
|
||||
self.clientIgnoringOnTheFly = ignore["client"]
|
||||
if(state.has_key("playstate")):
|
||||
position, paused, doSeek = self._extractStatePlaystateArguments(state)
|
||||
if(state.has_key("ping")):
|
||||
latencyCalculation = state["ping"]["latencyCalculation"] if state["ping"].has_key("latencyCalculation") else None
|
||||
if(self.serverIgnoringOnTheFly == 0):
|
||||
self._factory.updateWatcherState(self, position, paused, doSeek, latencyCalculation)
|
||||
|
||||
def handleError(self, error):
|
||||
self.dropWithError(error["message"]) #TODO: more processing and fallbacking
|
||||
|
||||
def sendError(self, message):
|
||||
self.sendMessage({"Error": {"message": message}})
|
||||
|
||||
|
||||
@ -1,256 +1,257 @@
|
||||
#coding:utf8
|
||||
#TODO: #12, #13, #8;
|
||||
import hashlib
|
||||
from twisted.internet import task, reactor
|
||||
from twisted.internet.protocol import Factory
|
||||
import syncplay
|
||||
from syncplay.protocols import SyncServerProtocol
|
||||
import time
|
||||
from syncplay import constants
|
||||
import threading
|
||||
|
||||
class SyncFactory(Factory):
|
||||
def __init__(self, password = ''):
|
||||
print "Welcome to Syncplay server, ver. {0}".format(syncplay.version)
|
||||
if(password):
|
||||
password = hashlib.md5(password).hexdigest()
|
||||
self.password = password
|
||||
self._rooms = {}
|
||||
self._roomStates = {}
|
||||
self._roomUpdate = threading.RLock()
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return SyncServerProtocol(self)
|
||||
|
||||
def _createRoomIfDoesntExist(self, roomName):
|
||||
if (not self._rooms.has_key(roomName)):
|
||||
with self._roomUpdate:
|
||||
self._rooms[roomName] = {}
|
||||
self._roomStates[roomName] = {
|
||||
"position": 0.0,
|
||||
"paused": True,
|
||||
"setBy": None,
|
||||
"lastUpdate": time.time()
|
||||
}
|
||||
|
||||
def addWatcher(self, watcherProtocol, username, roomName, roomPassword):
|
||||
allnames = []
|
||||
for room in self._rooms.itervalues():
|
||||
for watcher in room.itervalues():
|
||||
allnames.append(watcher.name.lower())
|
||||
while username.lower() in allnames:
|
||||
username += '_'
|
||||
self._createRoomIfDoesntExist(roomName)
|
||||
watcher = Watcher(self, watcherProtocol, username, roomName)
|
||||
with self._roomUpdate:
|
||||
self._rooms[roomName][watcherProtocol] = watcher
|
||||
print "{0}({2}) connected to room '{1}'".format(username, roomName, watcherProtocol.transport.getPeer().host)
|
||||
reactor.callLater(0.1, watcher.scheduleSendState)
|
||||
l = lambda w: w.sendUserSetting(username, roomName, None, {"joined": True})
|
||||
self.broadcast(watcherProtocol, l)
|
||||
|
||||
def getWatcher(self, watcherProtocol):
|
||||
for room in self._rooms.itervalues():
|
||||
if(room.has_key(watcherProtocol)):
|
||||
return room[watcherProtocol]
|
||||
|
||||
def getAllWatchers(self, watcherProtocol): #TODO: Optimize me
|
||||
watchers = {}
|
||||
for room in self._rooms.itervalues():
|
||||
for watcher in room.itervalues():
|
||||
watchers[watcher.watcherProtocol] = watcher
|
||||
return watchers
|
||||
|
||||
def _removeWatcherFromTheRoom(self, watcherProtocol):
|
||||
for room in self._rooms.itervalues():
|
||||
with self._roomUpdate:
|
||||
watcher = room.pop(watcherProtocol, None)
|
||||
if(watcher):
|
||||
return watcher
|
||||
|
||||
def _deleteRoomIfEmpty(self, room):
|
||||
if (self._rooms[room] == {}):
|
||||
with self._roomUpdate:
|
||||
self._rooms.pop(room)
|
||||
self._roomStates.pop(room)
|
||||
|
||||
def getRoomPausedAndPosition(self, room):
|
||||
position = self._roomStates[room]["position"]
|
||||
paused = self._roomStates[room]["paused"]
|
||||
if (not paused):
|
||||
timePassedSinceSet = time.time() - self._roomStates[room]["lastUpdate"]
|
||||
position += timePassedSinceSet
|
||||
return paused, position
|
||||
|
||||
def sendState(self, watcherProtocol, doSeek = False, senderLatency = 0, forcedUpdate = False):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
if(not watcher):
|
||||
return
|
||||
room = watcher.room
|
||||
paused, position = self.getRoomPausedAndPosition(room)
|
||||
setBy = self._roomStates[room]["setBy"]
|
||||
watcher.paused = paused
|
||||
watcher.position = position
|
||||
watcherProtocol.sendState(position, paused, doSeek, setBy, senderLatency, watcher.latency, forcedUpdate)
|
||||
if(time.time() - watcher.lastUpdate > constants.PROTOCOL_TIMEOUT):
|
||||
watcherProtocol.drop()
|
||||
self.removeWatcher(watcherProtocol)
|
||||
|
||||
def __updateWatcherPing(self, latencyCalculation, watcher):
|
||||
if (latencyCalculation):
|
||||
ping = (time.time() - latencyCalculation) / 2
|
||||
if (watcher.latency):
|
||||
watcher.latency = watcher.latency * (constants.PING_MOVING_AVERAGE_WEIGHT) + ping * (1-constants.PING_MOVING_AVERAGE_WEIGHT) #Exponential moving average
|
||||
else:
|
||||
watcher.latency = ping
|
||||
|
||||
def __shouldServerForceUpdateOnRoom(self, pauseChanged, doSeek):
|
||||
return doSeek or pauseChanged
|
||||
|
||||
def __updatePausedState(self, paused, watcher):
|
||||
watcher.paused = paused
|
||||
if(self._roomStates[watcher.room]["paused"] <> paused):
|
||||
self._roomStates[watcher.room]["setBy"] = watcher.name
|
||||
self._roomStates[watcher.room]["paused"] = paused
|
||||
self._roomStates[watcher.room]["lastUpdate"] = time.time()
|
||||
return True
|
||||
|
||||
def __updatePositionState(self, position, doSeek, watcher):
|
||||
watcher.position = position
|
||||
if (doSeek):
|
||||
self._roomStates[watcher.room]["position"] = position
|
||||
self._roomStates[watcher.room]["setBy"] = watcher.name
|
||||
self._roomStates[watcher.room]["lastUpdate"] = time.time()
|
||||
else:
|
||||
setter = min(self._rooms[watcher.room].values())
|
||||
self._roomStates[watcher.room]["position"] = setter.position
|
||||
self._roomStates[watcher.room]["setBy"] = setter.name
|
||||
self._roomStates[watcher.room]["lastUpdate"] = setter.lastUpdate
|
||||
|
||||
def updateWatcherState(self, watcherProtocol, position, paused, doSeek, latencyCalculation):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
self.__updateWatcherPing(latencyCalculation, watcher)
|
||||
watcher.lastUpdate = time.time()
|
||||
if(watcher.file):
|
||||
if(position is not None):
|
||||
self.__updatePositionState(position, doSeek, watcher)
|
||||
pauseChanged = False
|
||||
if(paused is not None):
|
||||
pauseChanged = self.__updatePausedState(paused, watcher)
|
||||
forceUpdate = self.__shouldServerForceUpdateOnRoom(pauseChanged, doSeek)
|
||||
if(forceUpdate):
|
||||
l = lambda w: self.sendState(w, doSeek, watcher.latency, forceUpdate)
|
||||
self.broadcastRoom(watcher.watcherProtocol, l)
|
||||
|
||||
def removeWatcher(self, watcherProtocol):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
if(not watcher):
|
||||
return
|
||||
l = lambda w: w.sendUserSetting(watcher.name, watcher.room, None, {"left": True})
|
||||
self.broadcast(watcherProtocol, l)
|
||||
self._removeWatcherFromTheRoom(watcherProtocol)
|
||||
watcher.deactivate()
|
||||
self._deleteRoomIfEmpty(watcher.room)
|
||||
print "{0} left server".format(watcher.name)
|
||||
|
||||
def watcherGetUsername(self, watcherProtocol):
|
||||
return self.getWatcher(watcherProtocol).name
|
||||
|
||||
def watcherGetRoom(self, watcherProtocol):
|
||||
return self.getWatcher(watcherProtocol).room
|
||||
|
||||
def watcherSetRoom(self, watcherProtocol, room):
|
||||
watcher = self._removeWatcherFromTheRoom(watcherProtocol)
|
||||
if(not watcher):
|
||||
return
|
||||
watcher.resetStateTimer()
|
||||
oldRoom = watcher.room
|
||||
self._createRoomIfDoesntExist(room)
|
||||
with self._roomUpdate:
|
||||
self._rooms[room][watcherProtocol] = watcher
|
||||
self._roomStates[room]["position"] = watcher.position
|
||||
self._roomStates[room]["setBy"] = watcher.name
|
||||
self._roomStates[room]["lastUpdate"] = time.time()
|
||||
self._deleteRoomIfEmpty(oldRoom)
|
||||
watcher.room = room
|
||||
l = lambda w: w.sendUserSetting(watcher.name, watcher.room, watcher.file, None)
|
||||
self.broadcast(watcherProtocol, l)
|
||||
|
||||
def watcherSetFile(self, watcherProtocol, file_):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
watcher.file = file_
|
||||
l = lambda w: w.sendUserSetting(watcher.name, watcher.room, watcher.file, None)
|
||||
self.broadcast(watcherProtocol, l)
|
||||
|
||||
def broadcastRoom(self, sender, what):
|
||||
room = self._rooms[self.watcherGetRoom(sender)]
|
||||
if(room):
|
||||
with self._roomUpdate:
|
||||
for receiver in room:
|
||||
what(receiver)
|
||||
|
||||
def broadcast(self, sender, what):
|
||||
with self._roomUpdate:
|
||||
for room in self._rooms.itervalues():
|
||||
for receiver in room:
|
||||
what(receiver)
|
||||
|
||||
class SyncIsolatedFactory(SyncFactory):
|
||||
def broadcast(self, sender, what):
|
||||
self.broadcastRoom(sender, what)
|
||||
|
||||
def getAllWatchers(self, watcherProtocol):
|
||||
room = self.getWatcher(watcherProtocol).room
|
||||
if(self._rooms.has_key(room)):
|
||||
return self._rooms[room]
|
||||
else:
|
||||
return {}
|
||||
|
||||
def watcherSetRoom(self, watcherProtocol, room):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
oldRoom = watcher.room
|
||||
l = lambda w: w.sendUserSetting(watcher.name, oldRoom, None, {"left": True})
|
||||
self.broadcast(watcherProtocol, l)
|
||||
SyncFactory.watcherSetRoom(self, watcherProtocol, room)
|
||||
|
||||
|
||||
class Watcher(object):
|
||||
def __init__(self, factory, watcherProtocol, name, room):
|
||||
self.factory = factory
|
||||
self.watcherProtocol = watcherProtocol
|
||||
self.name = name
|
||||
self.room = room
|
||||
self.file = None
|
||||
self._sendStateTimer = None
|
||||
self.position = None
|
||||
self.latency = 0
|
||||
self.lastUpdate = time.time()
|
||||
|
||||
def __lt__(self, b):
|
||||
if(self.position is None):
|
||||
return False
|
||||
elif(b.position is None):
|
||||
return True
|
||||
else:
|
||||
return self.position < b.position
|
||||
|
||||
def getRoomPosition(self):
|
||||
_, position = self.factory.getRoomPausedAndPosition(self.room)
|
||||
return position
|
||||
|
||||
def scheduleSendState(self):
|
||||
self._sendStateTimer = task.LoopingCall(self.sendState)
|
||||
self._sendStateTimer.start(constants.SERVER_STATE_INTERVAL, True)
|
||||
|
||||
def sendState(self):
|
||||
self.factory.sendState(self.watcherProtocol)
|
||||
|
||||
def resetStateTimer(self):
|
||||
if(self._sendStateTimer):
|
||||
self._sendStateTimer.stop()
|
||||
self._sendStateTimer.start(constants.SERVER_STATE_INTERVAL)
|
||||
|
||||
def deactivate(self):
|
||||
if(self._sendStateTimer):
|
||||
self._sendStateTimer.stop()
|
||||
|
||||
#coding:utf8
|
||||
#TODO: #12, #13, #8;
|
||||
import hashlib
|
||||
from twisted.internet import task, reactor
|
||||
from twisted.internet.protocol import Factory
|
||||
import syncplay
|
||||
from syncplay.protocols import SyncServerProtocol
|
||||
import time
|
||||
from syncplay import constants
|
||||
import threading
|
||||
from syncplay.messages import getMessage
|
||||
|
||||
class SyncFactory(Factory):
|
||||
def __init__(self, password = ''):
|
||||
print getMessage("en", "welcome-server-notification").format(syncplay.version)
|
||||
if(password):
|
||||
password = hashlib.md5(password).hexdigest()
|
||||
self.password = password
|
||||
self._rooms = {}
|
||||
self._roomStates = {}
|
||||
self._roomUpdate = threading.RLock()
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return SyncServerProtocol(self)
|
||||
|
||||
def _createRoomIfDoesntExist(self, roomName):
|
||||
if (not self._rooms.has_key(roomName)):
|
||||
with self._roomUpdate:
|
||||
self._rooms[roomName] = {}
|
||||
self._roomStates[roomName] = {
|
||||
"position": 0.0,
|
||||
"paused": True,
|
||||
"setBy": None,
|
||||
"lastUpdate": time.time()
|
||||
}
|
||||
|
||||
def addWatcher(self, watcherProtocol, username, roomName, roomPassword):
|
||||
allnames = []
|
||||
for room in self._rooms.itervalues():
|
||||
for watcher in room.itervalues():
|
||||
allnames.append(watcher.name.lower())
|
||||
while username.lower() in allnames:
|
||||
username += '_'
|
||||
self._createRoomIfDoesntExist(roomName)
|
||||
watcher = Watcher(self, watcherProtocol, username, roomName)
|
||||
with self._roomUpdate:
|
||||
self._rooms[roomName][watcherProtocol] = watcher
|
||||
print getMessage("en", "client-connected-room-server-notification").format(username, roomName, watcherProtocol.transport.getPeer().host)
|
||||
reactor.callLater(0.1, watcher.scheduleSendState)
|
||||
l = lambda w: w.sendUserSetting(username, roomName, None, {"joined": True})
|
||||
self.broadcast(watcherProtocol, l)
|
||||
|
||||
def getWatcher(self, watcherProtocol):
|
||||
for room in self._rooms.itervalues():
|
||||
if(room.has_key(watcherProtocol)):
|
||||
return room[watcherProtocol]
|
||||
|
||||
def getAllWatchers(self, watcherProtocol): #TODO: Optimize me
|
||||
watchers = {}
|
||||
for room in self._rooms.itervalues():
|
||||
for watcher in room.itervalues():
|
||||
watchers[watcher.watcherProtocol] = watcher
|
||||
return watchers
|
||||
|
||||
def _removeWatcherFromTheRoom(self, watcherProtocol):
|
||||
for room in self._rooms.itervalues():
|
||||
with self._roomUpdate:
|
||||
watcher = room.pop(watcherProtocol, None)
|
||||
if(watcher):
|
||||
return watcher
|
||||
|
||||
def _deleteRoomIfEmpty(self, room):
|
||||
if (self._rooms[room] == {}):
|
||||
with self._roomUpdate:
|
||||
self._rooms.pop(room)
|
||||
self._roomStates.pop(room)
|
||||
|
||||
def getRoomPausedAndPosition(self, room):
|
||||
position = self._roomStates[room]["position"]
|
||||
paused = self._roomStates[room]["paused"]
|
||||
if (not paused):
|
||||
timePassedSinceSet = time.time() - self._roomStates[room]["lastUpdate"]
|
||||
position += timePassedSinceSet
|
||||
return paused, position
|
||||
|
||||
def sendState(self, watcherProtocol, doSeek = False, senderLatency = 0, forcedUpdate = False):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
if(not watcher):
|
||||
return
|
||||
room = watcher.room
|
||||
paused, position = self.getRoomPausedAndPosition(room)
|
||||
setBy = self._roomStates[room]["setBy"]
|
||||
watcher.paused = paused
|
||||
watcher.position = position
|
||||
watcherProtocol.sendState(position, paused, doSeek, setBy, senderLatency, watcher.latency, forcedUpdate)
|
||||
if(time.time() - watcher.lastUpdate > constants.PROTOCOL_TIMEOUT):
|
||||
watcherProtocol.drop()
|
||||
self.removeWatcher(watcherProtocol)
|
||||
|
||||
def __updateWatcherPing(self, latencyCalculation, watcher):
|
||||
if (latencyCalculation):
|
||||
ping = (time.time() - latencyCalculation) / 2
|
||||
if (watcher.latency):
|
||||
watcher.latency = watcher.latency * (constants.PING_MOVING_AVERAGE_WEIGHT) + ping * (1-constants.PING_MOVING_AVERAGE_WEIGHT) #Exponential moving average
|
||||
else:
|
||||
watcher.latency = ping
|
||||
|
||||
def __shouldServerForceUpdateOnRoom(self, pauseChanged, doSeek):
|
||||
return doSeek or pauseChanged
|
||||
|
||||
def __updatePausedState(self, paused, watcher):
|
||||
watcher.paused = paused
|
||||
if(self._roomStates[watcher.room]["paused"] <> paused):
|
||||
self._roomStates[watcher.room]["setBy"] = watcher.name
|
||||
self._roomStates[watcher.room]["paused"] = paused
|
||||
self._roomStates[watcher.room]["lastUpdate"] = time.time()
|
||||
return True
|
||||
|
||||
def __updatePositionState(self, position, doSeek, watcher):
|
||||
watcher.position = position
|
||||
if (doSeek):
|
||||
self._roomStates[watcher.room]["position"] = position
|
||||
self._roomStates[watcher.room]["setBy"] = watcher.name
|
||||
self._roomStates[watcher.room]["lastUpdate"] = time.time()
|
||||
else:
|
||||
setter = min(self._rooms[watcher.room].values())
|
||||
self._roomStates[watcher.room]["position"] = setter.position
|
||||
self._roomStates[watcher.room]["setBy"] = setter.name
|
||||
self._roomStates[watcher.room]["lastUpdate"] = setter.lastUpdate
|
||||
|
||||
def updateWatcherState(self, watcherProtocol, position, paused, doSeek, latencyCalculation):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
self.__updateWatcherPing(latencyCalculation, watcher)
|
||||
watcher.lastUpdate = time.time()
|
||||
if(watcher.file):
|
||||
if(position is not None):
|
||||
self.__updatePositionState(position, doSeek, watcher)
|
||||
pauseChanged = False
|
||||
if(paused is not None):
|
||||
pauseChanged = self.__updatePausedState(paused, watcher)
|
||||
forceUpdate = self.__shouldServerForceUpdateOnRoom(pauseChanged, doSeek)
|
||||
if(forceUpdate):
|
||||
l = lambda w: self.sendState(w, doSeek, watcher.latency, forceUpdate)
|
||||
self.broadcastRoom(watcher.watcherProtocol, l)
|
||||
|
||||
def removeWatcher(self, watcherProtocol):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
if(not watcher):
|
||||
return
|
||||
l = lambda w: w.sendUserSetting(watcher.name, watcher.room, None, {"left": True})
|
||||
self.broadcast(watcherProtocol, l)
|
||||
self._removeWatcherFromTheRoom(watcherProtocol)
|
||||
watcher.deactivate()
|
||||
self._deleteRoomIfEmpty(watcher.room)
|
||||
print getMessage("en", "client-left-server-notification").format(watcher.name)
|
||||
|
||||
def watcherGetUsername(self, watcherProtocol):
|
||||
return self.getWatcher(watcherProtocol).name
|
||||
|
||||
def watcherGetRoom(self, watcherProtocol):
|
||||
return self.getWatcher(watcherProtocol).room
|
||||
|
||||
def watcherSetRoom(self, watcherProtocol, room):
|
||||
watcher = self._removeWatcherFromTheRoom(watcherProtocol)
|
||||
if(not watcher):
|
||||
return
|
||||
watcher.resetStateTimer()
|
||||
oldRoom = watcher.room
|
||||
self._createRoomIfDoesntExist(room)
|
||||
with self._roomUpdate:
|
||||
self._rooms[room][watcherProtocol] = watcher
|
||||
self._roomStates[room]["position"] = watcher.position
|
||||
self._roomStates[room]["setBy"] = watcher.name
|
||||
self._roomStates[room]["lastUpdate"] = time.time()
|
||||
self._deleteRoomIfEmpty(oldRoom)
|
||||
watcher.room = room
|
||||
l = lambda w: w.sendUserSetting(watcher.name, watcher.room, watcher.file, None)
|
||||
self.broadcast(watcherProtocol, l)
|
||||
|
||||
def watcherSetFile(self, watcherProtocol, file_):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
watcher.file = file_
|
||||
l = lambda w: w.sendUserSetting(watcher.name, watcher.room, watcher.file, None)
|
||||
self.broadcast(watcherProtocol, l)
|
||||
|
||||
def broadcastRoom(self, sender, what):
|
||||
room = self._rooms[self.watcherGetRoom(sender)]
|
||||
if(room):
|
||||
with self._roomUpdate:
|
||||
for receiver in room:
|
||||
what(receiver)
|
||||
|
||||
def broadcast(self, sender, what):
|
||||
with self._roomUpdate:
|
||||
for room in self._rooms.itervalues():
|
||||
for receiver in room:
|
||||
what(receiver)
|
||||
|
||||
class SyncIsolatedFactory(SyncFactory):
|
||||
def broadcast(self, sender, what):
|
||||
self.broadcastRoom(sender, what)
|
||||
|
||||
def getAllWatchers(self, watcherProtocol):
|
||||
room = self.getWatcher(watcherProtocol).room
|
||||
if(self._rooms.has_key(room)):
|
||||
return self._rooms[room]
|
||||
else:
|
||||
return {}
|
||||
|
||||
def watcherSetRoom(self, watcherProtocol, room):
|
||||
watcher = self.getWatcher(watcherProtocol)
|
||||
oldRoom = watcher.room
|
||||
l = lambda w: w.sendUserSetting(watcher.name, oldRoom, None, {"left": True})
|
||||
self.broadcast(watcherProtocol, l)
|
||||
SyncFactory.watcherSetRoom(self, watcherProtocol, room)
|
||||
|
||||
|
||||
class Watcher(object):
|
||||
def __init__(self, factory, watcherProtocol, name, room):
|
||||
self.factory = factory
|
||||
self.watcherProtocol = watcherProtocol
|
||||
self.name = name
|
||||
self.room = room
|
||||
self.file = None
|
||||
self._sendStateTimer = None
|
||||
self.position = None
|
||||
self.latency = 0
|
||||
self.lastUpdate = time.time()
|
||||
|
||||
def __lt__(self, b):
|
||||
if(self.position is None):
|
||||
return False
|
||||
elif(b.position is None):
|
||||
return True
|
||||
else:
|
||||
return self.position < b.position
|
||||
|
||||
def getRoomPosition(self):
|
||||
_, position = self.factory.getRoomPausedAndPosition(self.room)
|
||||
return position
|
||||
|
||||
def scheduleSendState(self):
|
||||
self._sendStateTimer = task.LoopingCall(self.sendState)
|
||||
self._sendStateTimer.start(constants.SERVER_STATE_INTERVAL, True)
|
||||
|
||||
def sendState(self):
|
||||
self.factory.sendState(self.watcherProtocol)
|
||||
|
||||
def resetStateTimer(self):
|
||||
if(self._sendStateTimer):
|
||||
self._sendStateTimer.stop()
|
||||
self._sendStateTimer.start(constants.SERVER_STATE_INTERVAL)
|
||||
|
||||
def deactivate(self):
|
||||
if(self._sendStateTimer):
|
||||
self._sendStateTimer.stop()
|
||||
|
||||
|
||||
@ -1,235 +1,236 @@
|
||||
from ConfigParser import SafeConfigParser
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from syncplay import constants
|
||||
try:
|
||||
from syncplay.ui.GuiConfiguration import GuiConfiguration
|
||||
except ImportError:
|
||||
GuiConfiguration = None
|
||||
|
||||
class InvalidConfigValue(Exception):
|
||||
def __init__(self, message):
|
||||
Exception.__init__(self, message)
|
||||
|
||||
class ConfigurationGetter(object):
|
||||
def __init__(self):
|
||||
self._config = {
|
||||
"host": None,
|
||||
"port": constants.DEFAULT_PORT,
|
||||
"name": None,
|
||||
"debug": False,
|
||||
"forceGuiPrompt": False,
|
||||
"noGui": False,
|
||||
"noStore": False,
|
||||
"room": "",
|
||||
"password": None,
|
||||
"playerPath": None,
|
||||
"file": None,
|
||||
"playerArgs": [],
|
||||
"playerType": None,
|
||||
}
|
||||
|
||||
#
|
||||
#Custom validation in self._validateArguments
|
||||
#
|
||||
self._required = [
|
||||
"host",
|
||||
"port",
|
||||
"name",
|
||||
"playerPath",
|
||||
"playerType",
|
||||
]
|
||||
|
||||
self._iniStructure = {
|
||||
"server_data": ["host", "port", "password"],
|
||||
"client_settings": ["name", "room", "playerPath"]
|
||||
}
|
||||
|
||||
#
|
||||
#Watch out for the method self._overrideConfigWithArgs when you're adding custom multi-word command line arguments
|
||||
#
|
||||
self._argparser = argparse.ArgumentParser(description='Solution to synchronize playback of multiple MPlayer and MPC-HC instances over the network.',
|
||||
epilog='If no options supplied values from configuration file will be used')
|
||||
self._argparser.add_argument('--no-gui', action='store_true', help='show no GUI')
|
||||
self._argparser.add_argument('-a', '--host', metavar='hostname', type=str, help='server\'s address')
|
||||
self._argparser.add_argument('-n', '--name', metavar='username', type=str, help='desired username')
|
||||
self._argparser.add_argument('-d', '--debug', action='store_true', help='debug mode')
|
||||
self._argparser.add_argument('-g', '--force-gui-prompt', action='store_true', help='make configuration prompt appear')
|
||||
self._argparser.add_argument('--no-store', action='store_true', help='don\'t store values in .syncplay')
|
||||
self._argparser.add_argument('-r', '--room', metavar='room', type=str, nargs='?', help='default room')
|
||||
self._argparser.add_argument('-p', '--password', metavar='password', type=str, nargs='?', help='server password')
|
||||
self._argparser.add_argument('--player-path', metavar='path', type=str, help='path to your player executable')
|
||||
self._argparser.add_argument('file', metavar='file', type=str, nargs='?', help='file to play')
|
||||
self._argparser.add_argument('_args', metavar='options', type=str, nargs='*', help='player options, if you need to pass options starting with - prepend them with single \'--\' argument')
|
||||
|
||||
def _validateArguments(self):
|
||||
for key in self._required:
|
||||
if(key == "playerPath"):
|
||||
if(self._isPlayerMPCAndValid(self._config["playerPath"])):
|
||||
self._config["playerType"] = "mpc"
|
||||
self.__addSpecialMPCFlags()
|
||||
elif(self._isMplayerPathAndValid(self._config["playerPath"])):
|
||||
self._config["playerType"] = "mplayer"
|
||||
else:
|
||||
raise InvalidConfigValue("Player path is not set properly")
|
||||
elif(key == "host"):
|
||||
self._config["host"], self._config["port"] = self._splitPortAndHost(self._config["host"])
|
||||
hostNotValid = (self._config["host"] == "" or self._config["host"] is None)
|
||||
portNotValid = (self._config["port"] == "" or self._config["port"] is None)
|
||||
if(hostNotValid or portNotValid):
|
||||
raise InvalidConfigValue("Hostname can't be empty")
|
||||
elif(self._config[key] == "" or self._config[key] is None):
|
||||
raise InvalidConfigValue("{} can't be empty".format(key))
|
||||
|
||||
def _overrideConfigWithArgs(self, args):
|
||||
for key, val in vars(args).items():
|
||||
if(val):
|
||||
if(key == "force_gui_prompt"):
|
||||
key = "forceGuiPrompt"
|
||||
if(key == "no_store"):
|
||||
key = "noStore"
|
||||
if(key == "player_path"):
|
||||
key = "playerPath"
|
||||
if(key == "_args"):
|
||||
key = "playerArgs"
|
||||
if(key == "no_gui"):
|
||||
key = "noGui"
|
||||
self._config[key] = val
|
||||
|
||||
def _isPlayerMPCAndValid(self, path):
|
||||
if(os.path.isfile(path)):
|
||||
if(path[-10:] == 'mpc-hc.exe' or path[-12:] == 'mpc-hc64.exe'):
|
||||
return True
|
||||
if(os.path.isfile(path + "\\mpc-hc.exe")):
|
||||
path += "\\mpc-hc.exe"
|
||||
return True
|
||||
if(os.path.isfile(path + "\\mpc-hc64.exe")):
|
||||
path += "\\mpc-hc64.exe"
|
||||
return True
|
||||
return False
|
||||
|
||||
def __addSpecialMPCFlags(self):
|
||||
self._config['playerArgs'].extend(['/open', '/new'])
|
||||
|
||||
def _isMplayerPathAndValid(self, playerPath):
|
||||
if("mplayer" in playerPath):
|
||||
if os.access(playerPath, os.X_OK):
|
||||
return True
|
||||
for path in os.environ['PATH'].split(':'):
|
||||
path = os.path.join(os.path.realpath(path), playerPath)
|
||||
if os.access(path, os.X_OK):
|
||||
self._config['playerPath'] = path
|
||||
return True
|
||||
return False
|
||||
|
||||
def _splitPortAndHost(self, host):
|
||||
port = constants.DEFAULT_PORT if not self._config["port"] else self._config["port"]
|
||||
if(host):
|
||||
if ':' in host:
|
||||
host, port = host.split(':', 1)
|
||||
return host, int(port)
|
||||
|
||||
def _findWorkingDir(self):
|
||||
frozen = getattr(sys, 'frozen', '')
|
||||
if not frozen:
|
||||
path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
elif frozen in ('dll', 'console_exe', 'windows_exe'):
|
||||
path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
|
||||
else:
|
||||
path = ""
|
||||
return path
|
||||
|
||||
def _checkForPortableFile(self):
|
||||
path = self._findWorkingDir()
|
||||
if(os.path.isfile(os.path.join(path, constants.DEFAULT_CONFIG_NAME))):
|
||||
return os.path.join(path, constants.DEFAULT_CONFIG_NAME)
|
||||
|
||||
def _getConfigurationFilePath(self):
|
||||
configFile = self._checkForPortableFile()
|
||||
if(not configFile):
|
||||
if(os.name <> 'nt'):
|
||||
configFile = os.path.join(os.getenv('HOME', '.'), constants.DEFAULT_CONFIG_NAME)
|
||||
else:
|
||||
configFile = os.path.join(os.getenv('APPDATA', '.'), constants.DEFAULT_CONFIG_NAME)
|
||||
return configFile
|
||||
|
||||
def _parseConfigFile(self, iniPath):
|
||||
parser = SafeConfigParser()
|
||||
parser.read(iniPath)
|
||||
for section, options in self._iniStructure.items():
|
||||
if(parser.has_section(section)):
|
||||
for option in options:
|
||||
if(parser.has_option(section, option)):
|
||||
self._config[option] = parser.get(section, option)
|
||||
|
||||
def _checkConfig(self):
|
||||
try:
|
||||
self._validateArguments()
|
||||
except InvalidConfigValue:
|
||||
try:
|
||||
for key, value in self._promptForMissingArguments().items():
|
||||
self._config[key] = value
|
||||
self._checkConfig()
|
||||
except:
|
||||
sys.exit()
|
||||
|
||||
def _promptForMissingArguments(self):
|
||||
if(self._config['noGui']):
|
||||
print "Some necessary arguments are missing, refer to --help"
|
||||
sys.exit()
|
||||
elif(GuiConfiguration):
|
||||
return GuiConfiguration(self._config).getProcessedConfiguration()
|
||||
|
||||
def __wasOptionChanged(self, parser, section, option):
|
||||
if (parser.has_option(section, option)):
|
||||
if (parser.get(section, option) != str(self._config[option])):
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
def _saveConfig(self, iniPath):
|
||||
changed = False
|
||||
if(self._config['noStore']):
|
||||
return
|
||||
parser = SafeConfigParser()
|
||||
parser.read(iniPath)
|
||||
for section, options in self._iniStructure.items():
|
||||
if(not parser.has_section(section)):
|
||||
parser.add_section(section)
|
||||
changed = True
|
||||
for option in options:
|
||||
if(self.__wasOptionChanged(parser, section, option)):
|
||||
changed = True
|
||||
parser.set(section, option, str(self._config[option]))
|
||||
if(changed):
|
||||
parser.write(file(iniPath, "w"))
|
||||
|
||||
def getConfiguration(self):
|
||||
iniPath = self._getConfigurationFilePath()
|
||||
self._parseConfigFile(iniPath)
|
||||
args = self._argparser.parse_args()
|
||||
self._overrideConfigWithArgs(args)
|
||||
if(self._config['forceGuiPrompt']):
|
||||
try:
|
||||
self._promptForMissingArguments()
|
||||
except:
|
||||
sys.exit()
|
||||
self._checkConfig()
|
||||
self._saveConfig(iniPath)
|
||||
return self._config
|
||||
|
||||
class ServerConfigurationGetter(object):
|
||||
def getConfiguration(self):
|
||||
self._prepareArgParser()
|
||||
self._args = self._argparser.parse_args()
|
||||
if(self._args.port == None):
|
||||
self._args.port = constants.DEFAULT_PORT
|
||||
return self._args
|
||||
|
||||
def _prepareArgParser(self):
|
||||
self._argparser = 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._argparser.add_argument('--port', metavar='port', type=str, nargs='?', help='server TCP port')
|
||||
self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help='server password')
|
||||
self._argparser.add_argument('--isolate-rooms', action='store_true', help='should rooms be isolated?')
|
||||
from ConfigParser import SafeConfigParser
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
try:
|
||||
from syncplay.ui.GuiConfiguration import GuiConfiguration
|
||||
except ImportError:
|
||||
GuiConfiguration = None
|
||||
|
||||
class InvalidConfigValue(Exception):
|
||||
def __init__(self, message):
|
||||
Exception.__init__(self, message)
|
||||
|
||||
class ConfigurationGetter(object):
|
||||
def __init__(self):
|
||||
self._config = {
|
||||
"host": None,
|
||||
"port": constants.DEFAULT_PORT,
|
||||
"name": None,
|
||||
"debug": False,
|
||||
"forceGuiPrompt": False,
|
||||
"noGui": False,
|
||||
"noStore": False,
|
||||
"room": "",
|
||||
"password": None,
|
||||
"playerPath": None,
|
||||
"file": None,
|
||||
"playerArgs": [],
|
||||
"playerType": None,
|
||||
}
|
||||
|
||||
#
|
||||
#Custom validation in self._validateArguments
|
||||
#
|
||||
self._required = [
|
||||
"host",
|
||||
"port",
|
||||
"name",
|
||||
"playerPath",
|
||||
"playerType",
|
||||
]
|
||||
|
||||
self._iniStructure = {
|
||||
"server_data": ["host", "port", "password"],
|
||||
"client_settings": ["name", "room", "playerPath"]
|
||||
}
|
||||
|
||||
#
|
||||
#Watch out for the method self._overrideConfigWithArgs when you're adding custom multi-word command line arguments
|
||||
#
|
||||
self._argparser = argparse.ArgumentParser(description=getMessage("en", "argument-description"),
|
||||
epilog='If no options supplied values from configuration file will be used')
|
||||
self._argparser.add_argument('--no-gui', action='store_true', help=getMessage("en", "nogui-argument"))
|
||||
self._argparser.add_argument('-a', '--host', metavar='hostname', type=str, help=getMessage("en", "host-argument"))
|
||||
self._argparser.add_argument('-n', '--name', metavar='username', type=str, help=getMessage("en", "name-argument"))
|
||||
self._argparser.add_argument('-d', '--debug', action='store_true', help=getMessage("en", "debug-argument"))
|
||||
self._argparser.add_argument('-g', '--force-gui-prompt', action='store_true', help=getMessage("en", "force-gui-prompt-argument"))
|
||||
self._argparser.add_argument('--no-store', action='store_true', help=getMessage("en", "no-store-argument"))
|
||||
self._argparser.add_argument('-r', '--room', metavar='room', type=str, nargs='?', help=getMessage("en", "room-argument"))
|
||||
self._argparser.add_argument('-p', '--password', metavar='password', type=str, nargs='?', help=getMessage("en", "password-argument"))
|
||||
self._argparser.add_argument('--player-path', metavar='path', type=str, help=getMessage("en", "player-path-argument"))
|
||||
self._argparser.add_argument('file', metavar='file', type=str, nargs='?', help=getMessage("en", "file-argument"))
|
||||
self._argparser.add_argument('_args', metavar='options', type=str, nargs='*', help=getMessage("en", "args-argument"))
|
||||
|
||||
def _validateArguments(self):
|
||||
for key in self._required:
|
||||
if(key == "playerPath"):
|
||||
if(self._isPlayerMPCAndValid(self._config["playerPath"])):
|
||||
self._config["playerType"] = "mpc"
|
||||
self.__addSpecialMPCFlags()
|
||||
elif(self._isMplayerPathAndValid(self._config["playerPath"])):
|
||||
self._config["playerType"] = "mplayer"
|
||||
else:
|
||||
raise InvalidConfigValue(getMessage("en", "player-path-error"))
|
||||
elif(key == "host"):
|
||||
self._config["host"], self._config["port"] = self._splitPortAndHost(self._config["host"])
|
||||
hostNotValid = (self._config["host"] == "" or self._config["host"] is None)
|
||||
portNotValid = (self._config["port"] == "" or self._config["port"] is None)
|
||||
if(hostNotValid or portNotValid):
|
||||
raise InvalidConfigValue(getMessage("en", "hostname-empty-error"))
|
||||
elif(self._config[key] == "" or self._config[key] is None):
|
||||
raise InvalidConfigValue(getMessage("en", "empty-error").format(key))
|
||||
|
||||
def _overrideConfigWithArgs(self, args):
|
||||
for key, val in vars(args).items():
|
||||
if(val):
|
||||
if(key == "force_gui_prompt"):
|
||||
key = "forceGuiPrompt"
|
||||
if(key == "no_store"):
|
||||
key = "noStore"
|
||||
if(key == "player_path"):
|
||||
key = "playerPath"
|
||||
if(key == "_args"):
|
||||
key = "playerArgs"
|
||||
if(key == "no_gui"):
|
||||
key = "noGui"
|
||||
self._config[key] = val
|
||||
|
||||
def _isPlayerMPCAndValid(self, path):
|
||||
if(os.path.isfile(path)):
|
||||
if(path[-10:] == 'mpc-hc.exe' or path[-12:] == 'mpc-hc64.exe'):
|
||||
return True
|
||||
if(os.path.isfile(path + "\\mpc-hc.exe")):
|
||||
path += "\\mpc-hc.exe"
|
||||
return True
|
||||
if(os.path.isfile(path + "\\mpc-hc64.exe")):
|
||||
path += "\\mpc-hc64.exe"
|
||||
return True
|
||||
return False
|
||||
|
||||
def __addSpecialMPCFlags(self):
|
||||
self._config['playerArgs'].extend(['/open', '/new'])
|
||||
|
||||
def _isMplayerPathAndValid(self, playerPath):
|
||||
if("mplayer" in playerPath):
|
||||
if os.access(playerPath, os.X_OK):
|
||||
return True
|
||||
for path in os.environ['PATH'].split(':'):
|
||||
path = os.path.join(os.path.realpath(path), playerPath)
|
||||
if os.access(path, os.X_OK):
|
||||
self._config['playerPath'] = path
|
||||
return True
|
||||
return False
|
||||
|
||||
def _splitPortAndHost(self, host):
|
||||
port = constants.DEFAULT_PORT if not self._config["port"] else self._config["port"]
|
||||
if(host):
|
||||
if ':' in host:
|
||||
host, port = host.split(':', 1)
|
||||
return host, int(port)
|
||||
|
||||
def _findWorkingDir(self):
|
||||
frozen = getattr(sys, 'frozen', '')
|
||||
if not frozen:
|
||||
path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||
elif frozen in ('dll', 'console_exe', 'windows_exe'):
|
||||
path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
|
||||
else:
|
||||
path = ""
|
||||
return path
|
||||
|
||||
def _checkForPortableFile(self):
|
||||
path = self._findWorkingDir()
|
||||
if(os.path.isfile(os.path.join(path, constants.DEFAULT_CONFIG_NAME))):
|
||||
return os.path.join(path, constants.DEFAULT_CONFIG_NAME)
|
||||
|
||||
def _getConfigurationFilePath(self):
|
||||
configFile = self._checkForPortableFile()
|
||||
if(not configFile):
|
||||
if(os.name <> 'nt'):
|
||||
configFile = os.path.join(os.getenv('HOME', '.'), constants.DEFAULT_CONFIG_NAME)
|
||||
else:
|
||||
configFile = os.path.join(os.getenv('APPDATA', '.'), constants.DEFAULT_CONFIG_NAME)
|
||||
return configFile
|
||||
|
||||
def _parseConfigFile(self, iniPath):
|
||||
parser = SafeConfigParser()
|
||||
parser.read(iniPath)
|
||||
for section, options in self._iniStructure.items():
|
||||
if(parser.has_section(section)):
|
||||
for option in options:
|
||||
if(parser.has_option(section, option)):
|
||||
self._config[option] = parser.get(section, option)
|
||||
|
||||
def _checkConfig(self):
|
||||
try:
|
||||
self._validateArguments()
|
||||
except InvalidConfigValue:
|
||||
try:
|
||||
for key, value in self._promptForMissingArguments().items():
|
||||
self._config[key] = value
|
||||
self._checkConfig()
|
||||
except:
|
||||
sys.exit()
|
||||
|
||||
def _promptForMissingArguments(self):
|
||||
if(self._config['noGui']):
|
||||
print getMessage("en", "arguments-missing-error")
|
||||
sys.exit()
|
||||
elif(GuiConfiguration):
|
||||
return GuiConfiguration(self._config).getProcessedConfiguration()
|
||||
|
||||
def __wasOptionChanged(self, parser, section, option):
|
||||
if (parser.has_option(section, option)):
|
||||
if (parser.get(section, option) != str(self._config[option])):
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
def _saveConfig(self, iniPath):
|
||||
changed = False
|
||||
if(self._config['noStore']):
|
||||
return
|
||||
parser = SafeConfigParser()
|
||||
parser.read(iniPath)
|
||||
for section, options in self._iniStructure.items():
|
||||
if(not parser.has_section(section)):
|
||||
parser.add_section(section)
|
||||
changed = True
|
||||
for option in options:
|
||||
if(self.__wasOptionChanged(parser, section, option)):
|
||||
changed = True
|
||||
parser.set(section, option, str(self._config[option]))
|
||||
if(changed):
|
||||
parser.write(file(iniPath, "w"))
|
||||
|
||||
def getConfiguration(self):
|
||||
iniPath = self._getConfigurationFilePath()
|
||||
self._parseConfigFile(iniPath)
|
||||
args = self._argparser.parse_args()
|
||||
self._overrideConfigWithArgs(args)
|
||||
if(self._config['forceGuiPrompt']):
|
||||
try:
|
||||
self._promptForMissingArguments()
|
||||
except:
|
||||
sys.exit()
|
||||
self._checkConfig()
|
||||
self._saveConfig(iniPath)
|
||||
return self._config
|
||||
|
||||
class ServerConfigurationGetter(object):
|
||||
def getConfiguration(self):
|
||||
self._prepareArgParser()
|
||||
self._args = self._argparser.parse_args()
|
||||
if(self._args.port == None):
|
||||
self._args.port = constants.DEFAULT_PORT
|
||||
return self._args
|
||||
|
||||
def _prepareArgParser(self):
|
||||
self._argparser = argparse.ArgumentParser(description=getMessage("en", "server-argument-description"),
|
||||
epilog=getMessage("en", "argument-epilog"))
|
||||
self._argparser.add_argument('--port', metavar='port', type=str, nargs='?', help=getMessage("en", "server-port-argument"))
|
||||
self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("en", "password-argument"))
|
||||
self._argparser.add_argument('--isolate-rooms', action='store_true', help=getMessage("en", "server-isolate-room-argument"))
|
||||
|
||||
@ -1,100 +1,101 @@
|
||||
import pygtk
|
||||
import os
|
||||
from syncplay import constants
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
gtk.set_interactive(False)
|
||||
import cairo, gio, pango, atk, pangocairo, gobject #@UnusedImport
|
||||
|
||||
class GuiConfiguration:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.closedAndNotSaved = False
|
||||
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
self.window.set_title("Syncplay Configuration")
|
||||
self.window.connect("delete_event", lambda w, e: self._windowClosed())
|
||||
vbox = gtk.VBox(False, 0)
|
||||
self.window.add(vbox)
|
||||
vbox.show()
|
||||
self._addLabeledEntries(config, vbox)
|
||||
|
||||
self.hostEntry.select_region(0, len(self.hostEntry.get_text()))
|
||||
button = gtk.Button(stock=gtk.STOCK_SAVE)
|
||||
button.connect("clicked", lambda w: self._saveDataAndLeave())
|
||||
vbox.pack_start(button, True, True, 0)
|
||||
button.set_flags(gtk.CAN_DEFAULT)
|
||||
button.grab_default()
|
||||
button.show()
|
||||
self.window.show()
|
||||
gtk.main()
|
||||
|
||||
def _windowClosed(self):
|
||||
self.window.destroy()
|
||||
gtk.main_quit()
|
||||
self.closedAndNotSaved = True
|
||||
|
||||
def _addLabeledEntries(self, config, vbox):
|
||||
if(config['host'] == None):
|
||||
host = ""
|
||||
elif(":" in config['host']):
|
||||
host = config['host']
|
||||
else:
|
||||
host = config['host']+":"+str(config['port'])
|
||||
|
||||
self.hostEntry = self._addLabeledEntryToVbox('Host: ', host, vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.userEntry = self._addLabeledEntryToVbox('Username: ', config['name'], vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.roomEntry = self._addLabeledEntryToVbox('Default room (optional): ', config['room'], vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.passEntry = self._addLabeledEntryToVbox('Server password (optional): ', config['password'], vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.mpcEntry = self._addLabeledEntryToVbox('Path to player executable: ', self._tryToFillUpMpcPath(config['playerPath']), vbox, lambda __, _: self._saveDataAndLeave())
|
||||
|
||||
def _tryToFillUpMpcPath(self, playerPath):
|
||||
if(playerPath == None):
|
||||
for path in constants.MPC_PATHS:
|
||||
if(os.path.isfile(path)):
|
||||
return path
|
||||
return playerPath
|
||||
|
||||
def getProcessedConfiguration(self):
|
||||
if(self.closedAndNotSaved):
|
||||
raise self.WindowClosed
|
||||
return self.config
|
||||
|
||||
def _saveDataAndLeave(self):
|
||||
self.config['host'] = self.hostEntry.get_text()
|
||||
self.config['name'] = self.userEntry.get_text()
|
||||
self.config['room'] = self.roomEntry.get_text()
|
||||
self.config['password'] = self.passEntry.get_text()
|
||||
self.config['playerPath'] = self.mpcEntry.get_text()
|
||||
self.window.destroy()
|
||||
gtk.main_quit()
|
||||
|
||||
def _addLabeledEntryToVbox(self, label, initialEntryValue, vbox, callback):
|
||||
hbox = gtk.HBox(False, 0)
|
||||
hbox.set_border_width(3)
|
||||
vbox.pack_start(hbox, False, False, 0)
|
||||
hbox.show()
|
||||
label_ = gtk.Label()
|
||||
label_.set_text(label)
|
||||
label_.set_alignment(xalign=0, yalign=0.5)
|
||||
hbox.pack_start(label_, False, False, 0)
|
||||
label_.show()
|
||||
entry = gtk.Entry()
|
||||
entry.connect("activate", callback, entry)
|
||||
if(initialEntryValue == None):
|
||||
initialEntryValue = ""
|
||||
entry.set_text(initialEntryValue)
|
||||
hbox.pack_end(entry, False, False, 0)
|
||||
entry.set_usize(200, -1)
|
||||
entry.show()
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox.add(hbox)
|
||||
hbox.show()
|
||||
return entry
|
||||
|
||||
class WindowClosed(Exception):
|
||||
def __init__(self):
|
||||
Exception.__init__(self)
|
||||
|
||||
|
||||
|
||||
|
||||
import pygtk
|
||||
import os
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
gtk.set_interactive(False)
|
||||
import cairo, gio, pango, atk, pangocairo, gobject #@UnusedImport
|
||||
|
||||
class GuiConfiguration:
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.closedAndNotSaved = False
|
||||
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
|
||||
self.window.set_title("Syncplay Configuration")
|
||||
self.window.connect("delete_event", lambda w, e: self._windowClosed())
|
||||
vbox = gtk.VBox(False, 0)
|
||||
self.window.add(vbox)
|
||||
vbox.show()
|
||||
self._addLabeledEntries(config, vbox)
|
||||
|
||||
self.hostEntry.select_region(0, len(self.hostEntry.get_text()))
|
||||
button = gtk.Button(stock=gtk.STOCK_SAVE)
|
||||
button.connect("clicked", lambda w: self._saveDataAndLeave())
|
||||
vbox.pack_start(button, True, True, 0)
|
||||
button.set_flags(gtk.CAN_DEFAULT)
|
||||
button.grab_default()
|
||||
button.show()
|
||||
self.window.show()
|
||||
gtk.main()
|
||||
|
||||
def _windowClosed(self):
|
||||
self.window.destroy()
|
||||
gtk.main_quit()
|
||||
self.closedAndNotSaved = True
|
||||
|
||||
def _addLabeledEntries(self, config, vbox):
|
||||
if(config['host'] == None):
|
||||
host = ""
|
||||
elif(":" in config['host']):
|
||||
host = config['host']
|
||||
else:
|
||||
host = config['host']+":"+str(config['port'])
|
||||
|
||||
self.hostEntry = self._addLabeledEntryToVbox(getMessage("en", "host-label"), host, vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.userEntry = self._addLabeledEntryToVbox(getMessage("en", "username-label"), config['name'], vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.roomEntry = self._addLabeledEntryToVbox(getMessage("en", "room-label"), config['room'], vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.passEntry = self._addLabeledEntryToVbox(getMessage("en", "password-label"), config['password'], vbox, lambda __, _: self._saveDataAndLeave())
|
||||
self.mpcEntry = self._addLabeledEntryToVbox(getMessage("en", "path-label"), self._tryToFillUpMpcPath(config['playerPath']), vbox, lambda __, _: self._saveDataAndLeave())
|
||||
|
||||
def _tryToFillUpMpcPath(self, playerPath):
|
||||
if(playerPath == None):
|
||||
for path in constants.MPC_PATHS:
|
||||
if(os.path.isfile(path)):
|
||||
return path
|
||||
return playerPath
|
||||
|
||||
def getProcessedConfiguration(self):
|
||||
if(self.closedAndNotSaved):
|
||||
raise self.WindowClosed
|
||||
return self.config
|
||||
|
||||
def _saveDataAndLeave(self):
|
||||
self.config['host'] = self.hostEntry.get_text()
|
||||
self.config['name'] = self.userEntry.get_text()
|
||||
self.config['room'] = self.roomEntry.get_text()
|
||||
self.config['password'] = self.passEntry.get_text()
|
||||
self.config['playerPath'] = self.mpcEntry.get_text()
|
||||
self.window.destroy()
|
||||
gtk.main_quit()
|
||||
|
||||
def _addLabeledEntryToVbox(self, label, initialEntryValue, vbox, callback):
|
||||
hbox = gtk.HBox(False, 0)
|
||||
hbox.set_border_width(3)
|
||||
vbox.pack_start(hbox, False, False, 0)
|
||||
hbox.show()
|
||||
label_ = gtk.Label()
|
||||
label_.set_text(label)
|
||||
label_.set_alignment(xalign=0, yalign=0.5)
|
||||
hbox.pack_start(label_, False, False, 0)
|
||||
label_.show()
|
||||
entry = gtk.Entry()
|
||||
entry.connect("activate", callback, entry)
|
||||
if(initialEntryValue == None):
|
||||
initialEntryValue = ""
|
||||
entry.set_text(initialEntryValue)
|
||||
hbox.pack_end(entry, False, False, 0)
|
||||
entry.set_usize(200, -1)
|
||||
entry.show()
|
||||
hbox = gtk.HBox(False, 0)
|
||||
vbox.add(hbox)
|
||||
hbox.show()
|
||||
return entry
|
||||
|
||||
class WindowClosed(Exception):
|
||||
def __init__(self):
|
||||
Exception.__init__(self)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,122 +1,123 @@
|
||||
from __future__ import print_function
|
||||
import threading
|
||||
import time
|
||||
import syncplay
|
||||
import os
|
||||
import re
|
||||
from syncplay import utils
|
||||
from syncplay import constants
|
||||
class ConsoleUI(threading.Thread):
|
||||
def __init__(self):
|
||||
self.promptMode = threading.Event()
|
||||
self.PromptResult = ""
|
||||
self.promptMode.set()
|
||||
self._syncplayClient = None
|
||||
threading.Thread.__init__(self, name="ConsoleUI")
|
||||
|
||||
def addClient(self, client):
|
||||
self._syncplayClient = client
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
data = raw_input()
|
||||
data = data.rstrip('\n\r')
|
||||
if(not self.promptMode.isSet()):
|
||||
self.PromptResult = data
|
||||
self.promptMode.set()
|
||||
elif(self._syncplayClient):
|
||||
self._executeCommand(data)
|
||||
|
||||
def promptFor(self, prompt=">", message=""):
|
||||
if message <> "":
|
||||
print(message)
|
||||
self.promptMode.clear()
|
||||
print(prompt, end='')
|
||||
self.promptMode.wait()
|
||||
return self.PromptResult
|
||||
|
||||
def showMessage(self, message, noTimestamp=False):
|
||||
if(os.name == "nt"):
|
||||
message = message.encode('ascii', 'replace')
|
||||
if(noTimestamp):
|
||||
print(message)
|
||||
else:
|
||||
print(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message)
|
||||
|
||||
def showDebugMessage(self, message):
|
||||
print(message)
|
||||
|
||||
def showErrorMessage(self, message):
|
||||
print("ERROR:\t" + message)
|
||||
|
||||
def _extractSign(self, m):
|
||||
if(m):
|
||||
if(m == "-"):
|
||||
return -1
|
||||
else:
|
||||
return 1
|
||||
else:
|
||||
return None
|
||||
|
||||
def _tryAdvancedCommands(self, data):
|
||||
o = re.match(constants.UI_OFFSET_REGEX, data)
|
||||
s = re.match(constants.UI_SEEK_REGEX, data)
|
||||
if(o):
|
||||
sign = self._extractSign(o.group('sign'))
|
||||
t = utils.parseTime(o.group('time'))
|
||||
if(t is None):
|
||||
return
|
||||
if (o.group('sign') == "/"):
|
||||
t = self._syncplayClient.getPlayerPosition() - t
|
||||
elif(sign):
|
||||
t = self._syncplayClient.getUserOffset() + sign * t
|
||||
self._syncplayClient.setUserOffset(t)
|
||||
return True
|
||||
elif s:
|
||||
sign = self._extractSign(s.group('sign'))
|
||||
t = utils.parseTime(s.group('time'))
|
||||
if(t is None):
|
||||
return
|
||||
if(sign):
|
||||
t = self._syncplayClient.getGlobalPosition() + sign * t
|
||||
self._syncplayClient.setPosition(t)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _executeCommand(self, data):
|
||||
command = re.match(constants.UI_COMMAND_REGEX, data)
|
||||
if(not command):
|
||||
return
|
||||
if(command.group('command') in constants.COMMANDS_UNDO):
|
||||
tmp_pos = self._syncplayClient.getPlayerPosition()
|
||||
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
|
||||
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
|
||||
elif (command.group('command') in constants.COMMANDS_LIST):
|
||||
self._syncplayClient.getUserList()
|
||||
elif (command.group('command') in constants.COMMANDS_PAUSE):
|
||||
self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
|
||||
elif (command.group('command') in constants.COMMANDS_ROOM):
|
||||
room = command.group('parameter')
|
||||
if room == None:
|
||||
if self._syncplayClient.userlist.currentUser.file:
|
||||
room = self._syncplayClient.userlist.currentUser.file["name"]
|
||||
else:
|
||||
room = self._syncplayClient.defaultRoom
|
||||
|
||||
self._syncplayClient.setRoom(room)
|
||||
self._syncplayClient.sendRoom()
|
||||
else:
|
||||
if(self._tryAdvancedCommands(data)):
|
||||
return
|
||||
if (command.group('command') not in constants.COMMANDS_HELP):
|
||||
self.showMessage("Unrecognized command")
|
||||
self.showMessage("Available commands:", True)
|
||||
self.showMessage("\tr [name] - change room", True)
|
||||
self.showMessage("\tl - show user list", True)
|
||||
self.showMessage("\tu - undo last seek", True)
|
||||
self.showMessage("\tp - toggle pause", True)
|
||||
self.showMessage("\t[s][+-]time - seek to the given value of time, if + or - is not specified it's absolute time in seconds or min:sec", True)
|
||||
self.showMessage("\th - this help", True)
|
||||
self.showMessage("Syncplay version: {}".format(syncplay.version), True)
|
||||
self.showMessage("More info available at: {}".format(syncplay.projectURL), True)
|
||||
|
||||
from __future__ import print_function
|
||||
import threading
|
||||
import time
|
||||
import syncplay
|
||||
import os
|
||||
import re
|
||||
from syncplay import utils
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
class ConsoleUI(threading.Thread):
|
||||
def __init__(self):
|
||||
self.promptMode = threading.Event()
|
||||
self.PromptResult = ""
|
||||
self.promptMode.set()
|
||||
self._syncplayClient = None
|
||||
threading.Thread.__init__(self, name="ConsoleUI")
|
||||
|
||||
def addClient(self, client):
|
||||
self._syncplayClient = client
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
data = raw_input()
|
||||
data = data.rstrip('\n\r')
|
||||
if(not self.promptMode.isSet()):
|
||||
self.PromptResult = data
|
||||
self.promptMode.set()
|
||||
elif(self._syncplayClient):
|
||||
self._executeCommand(data)
|
||||
|
||||
def promptFor(self, prompt=">", message=""):
|
||||
if message <> "":
|
||||
print(message)
|
||||
self.promptMode.clear()
|
||||
print(prompt, end='')
|
||||
self.promptMode.wait()
|
||||
return self.PromptResult
|
||||
|
||||
def showMessage(self, message, noTimestamp=False):
|
||||
if(os.name == "nt"):
|
||||
message = message.encode('ascii', 'replace')
|
||||
if(noTimestamp):
|
||||
print(message)
|
||||
else:
|
||||
print(time.strftime(constants.UI_TIME_FORMAT, time.localtime()) + message)
|
||||
|
||||
def showDebugMessage(self, message):
|
||||
print(message)
|
||||
|
||||
def showErrorMessage(self, message):
|
||||
print("ERROR:\t" + message)
|
||||
|
||||
def _extractSign(self, m):
|
||||
if(m):
|
||||
if(m == "-"):
|
||||
return -1
|
||||
else:
|
||||
return 1
|
||||
else:
|
||||
return None
|
||||
|
||||
def _tryAdvancedCommands(self, data):
|
||||
o = re.match(constants.UI_OFFSET_REGEX, data)
|
||||
s = re.match(constants.UI_SEEK_REGEX, data)
|
||||
if(o):
|
||||
sign = self._extractSign(o.group('sign'))
|
||||
t = utils.parseTime(o.group('time'))
|
||||
if(t is None):
|
||||
return
|
||||
if (o.group('sign') == "/"):
|
||||
t = self._syncplayClient.getPlayerPosition() - t
|
||||
elif(sign):
|
||||
t = self._syncplayClient.getUserOffset() + sign * t
|
||||
self._syncplayClient.setUserOffset(t)
|
||||
return True
|
||||
elif s:
|
||||
sign = self._extractSign(s.group('sign'))
|
||||
t = utils.parseTime(s.group('time'))
|
||||
if(t is None):
|
||||
return
|
||||
if(sign):
|
||||
t = self._syncplayClient.getGlobalPosition() + sign * t
|
||||
self._syncplayClient.setPosition(t)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _executeCommand(self, data):
|
||||
command = re.match(constants.UI_COMMAND_REGEX, data)
|
||||
if(not command):
|
||||
return
|
||||
if(command.group('command') in constants.COMMANDS_UNDO):
|
||||
tmp_pos = self._syncplayClient.getPlayerPosition()
|
||||
self._syncplayClient.setPosition(self._syncplayClient.playerPositionBeforeLastSeek)
|
||||
self._syncplayClient.playerPositionBeforeLastSeek = tmp_pos
|
||||
elif (command.group('command') in constants.COMMANDS_LIST):
|
||||
self._syncplayClient.getUserList()
|
||||
elif (command.group('command') in constants.COMMANDS_PAUSE):
|
||||
self._syncplayClient.setPaused(not self._syncplayClient.getPlayerPaused())
|
||||
elif (command.group('command') in constants.COMMANDS_ROOM):
|
||||
room = command.group('parameter')
|
||||
if room == None:
|
||||
if self._syncplayClient.userlist.currentUser.file:
|
||||
room = self._syncplayClient.userlist.currentUser.file["name"]
|
||||
else:
|
||||
room = self._syncplayClient.defaultRoom
|
||||
|
||||
self._syncplayClient.setRoom(room)
|
||||
self._syncplayClient.sendRoom()
|
||||
else:
|
||||
if(self._tryAdvancedCommands(data)):
|
||||
return
|
||||
if (command.group('command') not in constants.COMMANDS_HELP):
|
||||
self.showMessage(getMessage("en", "unrecognized-command-notification"))
|
||||
self.showMessage(getMessage("en", "commandlist-notification"), True)
|
||||
self.showMessage(getMessage("en", "commandlist-notification/room"), True)
|
||||
self.showMessage(getMessage("en", "commandlist-notification/list"), True)
|
||||
self.showMessage(getMessage("en", "commandlist-notification/undo"), True)
|
||||
self.showMessage(getMessage("en", "commandlist-notification/pause"), True)
|
||||
self.showMessage(getMessage("en", "commandlist-notification/seek"), True)
|
||||
self.showMessage(getMessage("en", "commandlist-notification/help"), True)
|
||||
self.showMessage("Syncplay version: {}".format(syncplay.version), True)
|
||||
self.showMessage("More info available at: {}".format(syncplay.projectURL), True)
|
||||
|
||||
|
||||
@ -1,76 +1,77 @@
|
||||
import time
|
||||
import re
|
||||
import datetime
|
||||
from syncplay import constants
|
||||
|
||||
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
|
||||
"""Retry calling the decorated function using an exponential backoff.
|
||||
|
||||
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
|
||||
original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
|
||||
|
||||
:param ExceptionToCheck: the exception to check. may be a tuple of
|
||||
excpetions to check
|
||||
:type ExceptionToCheck: Exception or tuple
|
||||
:param tries: number of times to try (not retry) before giving up
|
||||
:type tries: int
|
||||
:param delay: initial delay between retries in seconds
|
||||
:type delay: int
|
||||
:param backoff: backoff multiplier e.g. value of 2 will double the delay
|
||||
each retry
|
||||
:type backoff: int
|
||||
:param logger: logger to use. If None, print
|
||||
:type logger: logging.Logger instance
|
||||
"""
|
||||
def deco_retry(f):
|
||||
def f_retry(*args, **kwargs):
|
||||
mtries, mdelay = tries, delay
|
||||
try_one_last_time = True
|
||||
while mtries > 1:
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
try_one_last_time = False
|
||||
break
|
||||
except ExceptionToCheck, e:
|
||||
if logger:
|
||||
msg = "%s, Retrying in %d seconds..." % (str(e), mdelay)
|
||||
logger.warning(msg)
|
||||
time.sleep(mdelay)
|
||||
mtries -= 1
|
||||
mdelay *= backoff
|
||||
if try_one_last_time:
|
||||
return f(*args, **kwargs)
|
||||
return
|
||||
return f_retry # true decorator
|
||||
return deco_retry
|
||||
|
||||
def parseTime(timeStr):
|
||||
regex = re.compile(constants.PARSE_TIME_REGEX)
|
||||
parts = regex.match(timeStr)
|
||||
if not parts:
|
||||
return
|
||||
parts = parts.groupdict()
|
||||
time_params = {}
|
||||
for (name, param) in parts.iteritems():
|
||||
if param:
|
||||
if(name == "miliseconds"):
|
||||
time_params["microseconds"] = int(param) * 1000
|
||||
else:
|
||||
time_params[name] = int(param)
|
||||
return datetime.timedelta(**time_params).total_seconds()
|
||||
|
||||
def formatTime(timeInSeconds):
|
||||
timeInSeconds = round(timeInSeconds)
|
||||
weeks = timeInSeconds // 604800
|
||||
days = (timeInSeconds % 604800) // 86400
|
||||
hours = (timeInSeconds % 86400) // 3600
|
||||
minutes = (timeInSeconds % 3600) // 60
|
||||
seconds = timeInSeconds % 60
|
||||
if(weeks > 0):
|
||||
return '{0:.0f}w, {1:.0f}d, {2:02.0f}:{3:02.0f}:{4:02.0f}'.format(weeks, days, hours, minutes, seconds)
|
||||
elif(days > 0):
|
||||
return '{0:.0f}d, {1:02.0f}:{2:02.0f}:{3:02.0f}'.format(days, hours, minutes, seconds)
|
||||
elif(hours > 0):
|
||||
return '{0:02.0f}:{1:02.0f}:{2:02.0f}'.format(hours, minutes, seconds)
|
||||
else:
|
||||
return '{0:02.0f}:{1:02.0f}'.format(minutes, seconds)
|
||||
import time
|
||||
import re
|
||||
import datetime
|
||||
from syncplay import constants
|
||||
from syncplay.messages import getMessage
|
||||
|
||||
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
|
||||
"""Retry calling the decorated function using an exponential backoff.
|
||||
|
||||
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
|
||||
original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
|
||||
|
||||
:param ExceptionToCheck: the exception to check. may be a tuple of
|
||||
excpetions to check
|
||||
:type ExceptionToCheck: Exception or tuple
|
||||
:param tries: number of times to try (not retry) before giving up
|
||||
:type tries: int
|
||||
:param delay: initial delay between retries in seconds
|
||||
:type delay: int
|
||||
:param backoff: backoff multiplier e.g. value of 2 will double the delay
|
||||
each retry
|
||||
:type backoff: int
|
||||
:param logger: logger to use. If None, print
|
||||
:type logger: logging.Logger instance
|
||||
"""
|
||||
def deco_retry(f):
|
||||
def f_retry(*args, **kwargs):
|
||||
mtries, mdelay = tries, delay
|
||||
try_one_last_time = True
|
||||
while mtries > 1:
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
try_one_last_time = False
|
||||
break
|
||||
except ExceptionToCheck, e:
|
||||
if logger:
|
||||
msg = getMessage("en", "retrying-notification") % (str(e), mdelay)
|
||||
logger.warning(msg)
|
||||
time.sleep(mdelay)
|
||||
mtries -= 1
|
||||
mdelay *= backoff
|
||||
if try_one_last_time:
|
||||
return f(*args, **kwargs)
|
||||
return
|
||||
return f_retry # true decorator
|
||||
return deco_retry
|
||||
|
||||
def parseTime(timeStr):
|
||||
regex = re.compile(constants.PARSE_TIME_REGEX)
|
||||
parts = regex.match(timeStr)
|
||||
if not parts:
|
||||
return
|
||||
parts = parts.groupdict()
|
||||
time_params = {}
|
||||
for (name, param) in parts.iteritems():
|
||||
if param:
|
||||
if(name == "miliseconds"):
|
||||
time_params["microseconds"] = int(param) * 1000
|
||||
else:
|
||||
time_params[name] = int(param)
|
||||
return datetime.timedelta(**time_params).total_seconds()
|
||||
|
||||
def formatTime(timeInSeconds):
|
||||
timeInSeconds = round(timeInSeconds)
|
||||
weeks = timeInSeconds // 604800
|
||||
days = (timeInSeconds % 604800) // 86400
|
||||
hours = (timeInSeconds % 86400) // 3600
|
||||
minutes = (timeInSeconds % 3600) // 60
|
||||
seconds = timeInSeconds % 60
|
||||
if(weeks > 0):
|
||||
return '{0:.0f}w, {1:.0f}d, {2:02.0f}:{3:02.0f}:{4:02.0f}'.format(weeks, days, hours, minutes, seconds)
|
||||
elif(days > 0):
|
||||
return '{0:.0f}d, {1:02.0f}:{2:02.0f}:{3:02.0f}'.format(days, hours, minutes, seconds)
|
||||
elif(hours > 0):
|
||||
return '{0:02.0f}:{1:02.0f}:{2:02.0f}'.format(hours, minutes, seconds)
|
||||
else:
|
||||
return '{0:02.0f}:{1:02.0f}'.format(minutes, seconds)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user