Finished cleanup

This commit is contained in:
Daniel Ahn 2018-07-20 13:49:13 -07:00
parent 4dea39a068
commit aa4f95e4be
12 changed files with 1734 additions and 1624 deletions

View File

@ -1,22 +1,27 @@
import ast
import collections
import hashlib import hashlib
import os.path import os.path
import time import random
import re import re
import sys import sys
import ast
import random
import threading import threading
import time
from copy import deepcopy
from functools import wraps
from twisted.internet.protocol import ClientFactory from twisted.internet.protocol import ClientFactory
from twisted.internet import reactor, task, defer, threads from twisted.internet import reactor, task, defer, threads
from functools import wraps
from copy import deepcopy
from syncplay.protocols import SyncClientProtocol
from syncplay import utils, constants, version from syncplay import utils, constants, version
from syncplay.utils import isMacOS
from syncplay.messages import getMissingStrings, getMessage
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \ from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
PRIVACY_HIDDENFILENAME PRIVACY_HIDDENFILENAME
import collections from syncplay.messages import getMissingStrings, getMessage
from syncplay.protocols import SyncClientProtocol
from syncplay.utils import isMacOS
class SyncClientFactory(ClientFactory): class SyncClientFactory(ClientFactory):
def __init__(self, client, retry=constants.RECONNECT_RETRIES): def __init__(self, client, retry=constants.RECONNECT_RETRIES):
self._client = client self._client = client
@ -58,6 +63,7 @@ class SyncClientFactory(ClientFactory):
def stopRetrying(self): def stopRetrying(self):
self._timesTried = self.retry self._timesTried = self.retry
class SyncplayClient(object): class SyncplayClient(object):
def __init__(self, playerClass, ui, config): def __init__(self, playerClass, ui, config):
constants.SHOW_OSD = config['showOSD'] constants.SHOW_OSD = config['showOSD']
@ -203,12 +209,19 @@ class SyncplayClient(object):
self._playerPosition = position self._playerPosition = position
self._playerPaused = paused self._playerPaused = paused
currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0 currentLength = self.userlist.currentUser.file["duration"] if self.userlist.currentUser.file else 0
if pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ if (
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD: pauseChange and paused and currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH
and abs(position - currentLength) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD
):
self.playlist.advancePlaylistCheck() self.playlist.advancePlaylistCheck()
elif pauseChange and "readiness" in self.serverFeatures and self.serverFeatures["readiness"]: elif pauseChange and "readiness" in self.serverFeatures and self.serverFeatures["readiness"]:
if currentLength == 0 or currentLength == -1 or\ if (
not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD): currentLength == 0 or currentLength == -1 or
not (
not self.playlist.notJustChangedPlaylist() and
abs(position - currentLength) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD
)
):
pauseChange = self._toggleReady(pauseChange, paused) pauseChange = self._toggleReady(pauseChange, paused)
if self._lastGlobalUpdate: if self._lastGlobalUpdate:
@ -304,7 +317,7 @@ class SyncplayClient(object):
self.setPosition(self.getGlobalPosition()) self.setPosition(self.getGlobalPosition())
self._player.setPaused(True) self._player.setPaused(True)
madeChangeOnPlayer = True madeChangeOnPlayer = True
if (self.lastLeftTime < time.time() - constants.OSD_DURATION) or (hideFromOSD == True): if (self.lastLeftTime < time.time() - constants.OSD_DURATION) or hideFromOSD:
self.ui.showMessage(getMessage("pause-notification").format(setBy), hideFromOSD) self.ui.showMessage(getMessage("pause-notification").format(setBy), hideFromOSD)
else: else:
self.ui.showMessage(getMessage("left-paused-notification").format(self.lastLeftUser, setBy), hideFromOSD) self.ui.showMessage(getMessage("left-paused-notification").format(self.lastLeftUser, setBy), hideFromOSD)
@ -351,9 +364,9 @@ class SyncplayClient(object):
self._lastGlobalUpdate = time.time() self._lastGlobalUpdate = time.time()
if doSeek: if doSeek:
madeChangeOnPlayer = self._serverSeeked(position, setBy) madeChangeOnPlayer = self._serverSeeked(position, setBy)
if diff > self._config['rewindThreshold'] and not doSeek and not self._config['rewindOnDesync'] == False: if diff > self._config['rewindThreshold'] and not doSeek and self._config['rewindOnDesync']:
madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy) madeChangeOnPlayer = self._rewindPlayerDueToTimeDifference(position, setBy)
if self._config['fastforwardOnDesync'] and (self.userlist.currentUser.canControl() == False or self._config['dontSlowDownWithMe'] == True): if self._config['fastforwardOnDesync'] and (not self.userlist.currentUser.canControl() or self._config['dontSlowDownWithMe']):
if diff < (constants.FASTFORWARD_BEHIND_THRESHOLD * -1) and not doSeek: if diff < (constants.FASTFORWARD_BEHIND_THRESHOLD * -1) and not doSeek:
if self.behindFirstDetected is None: if self.behindFirstDetected is None:
self.behindFirstDetected = time.time() self.behindFirstDetected = time.time()
@ -365,11 +378,11 @@ class SyncplayClient(object):
self.behindFirstDetected = time.time() + constants.FASTFORWARD_RESET_THRESHOLD self.behindFirstDetected = time.time() + constants.FASTFORWARD_RESET_THRESHOLD
else: else:
self.behindFirstDetected = None self.behindFirstDetected = None
if self._player.speedSupported and not doSeek and not paused and not self._config['slowOnDesync'] == False: if self._player.speedSupported and not doSeek and not paused and self._config['slowOnDesync']:
madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy) madeChangeOnPlayer = self._slowDownToCoverTimeDifference(diff, setBy)
if paused == False and pauseChanged: if not paused and pauseChanged:
madeChangeOnPlayer = self._serverUnpaused(setBy) madeChangeOnPlayer = self._serverUnpaused(setBy)
elif paused == True and pauseChanged: elif not paused and pauseChanged:
madeChangeOnPlayer = self._serverPaused(setBy) madeChangeOnPlayer = self._serverPaused(setBy)
return madeChangeOnPlayer return madeChangeOnPlayer
@ -566,11 +579,11 @@ class SyncplayClient(object):
constants.MAX_ROOM_NAME_LENGTH = self.serverFeatures["maxRoomNameLength"] constants.MAX_ROOM_NAME_LENGTH = self.serverFeatures["maxRoomNameLength"]
if self.serverFeatures["maxFilenameLength"] is not None: if self.serverFeatures["maxFilenameLength"] is not None:
constants.MAX_FILENAME_LENGTH = self.serverFeatures["maxFilenameLength"] constants.MAX_FILENAME_LENGTH = self.serverFeatures["maxFilenameLength"]
constants.MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = ["MaxChatMessageLength={}".format(constants.MAX_CHAT_MESSAGE_LENGTH), constants.MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = [
"MaxChatMessageLength={}".format(constants.MAX_CHAT_MESSAGE_LENGTH),
"inputPromptStartCharacter={}".format(constants.MPV_INPUT_PROMPT_START_CHARACTER), "inputPromptStartCharacter={}".format(constants.MPV_INPUT_PROMPT_START_CHARACTER),
"inputPromptEndCharacter={}".format(constants.MPV_INPUT_PROMPT_END_CHARACTER), "inputPromptEndCharacter={}".format(constants.MPV_INPUT_PROMPT_END_CHARACTER),
"backslashSubstituteCharacter={}".format( "backslashSubstituteCharacter={}".format(constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
self.ui.setFeatures(self.serverFeatures) self.ui.setFeatures(self.serverFeatures)
if self._player: if self._player:
self._player.setFeatures(self.serverFeatures) self._player.setFeatures(self.serverFeatures)
@ -586,7 +599,6 @@ class SyncplayClient(object):
else: else:
return None return None
def sendFile(self): def sendFile(self):
file_ = self.getSanitizedCurrentUserFile() file_ = self.getSanitizedCurrentUserFile()
if self._protocol and self._protocol.logged and file_: if self._protocol and self._protocol.logged and file_:
@ -762,7 +774,7 @@ class SyncplayClient(object):
from syncplay.ui.ConfigurationGetter import ConfigurationGetter from syncplay.ui.ConfigurationGetter import ConfigurationGetter
ConfigurationGetter().setConfigOption("sharedPlaylistEnabled", newState) ConfigurationGetter().setConfigOption("sharedPlaylistEnabled", newState)
self._config["sharedPlaylistEnabled"] = newState self._config["sharedPlaylistEnabled"] = newState
if oldState == False and newState == True: if not oldState and newState:
self.playlist.loadCurrentPlaylistIndex() self.playlist.loadCurrentPlaylistIndex()
def changeAutoplayState(self, newState): def changeAutoplayState(self, newState):
@ -773,7 +785,7 @@ class SyncplayClient(object):
oldAutoplayConditionsMet = self.autoplayConditionsMet() oldAutoplayConditionsMet = self.autoplayConditionsMet()
self.autoPlayThreshold = newThreshold self.autoPlayThreshold = newThreshold
newAutoplayConditionsMet = self.autoplayConditionsMet() newAutoplayConditionsMet = self.autoplayConditionsMet()
if oldAutoplayConditionsMet == False and newAutoplayConditionsMet == True: if not oldAutoplayConditionsMet and newAutoplayConditionsMet:
self.autoplayCheck() self.autoplayCheck()
def autoplayCheck(self): def autoplayCheck(self):
@ -799,9 +811,12 @@ class SyncplayClient(object):
def autoplayConditionsMet(self): def autoplayConditionsMet(self):
recentlyReset = (self.lastRewindTime is not None and abs(time.time() - self.lastRewindTime) < 10) and self._playerPosition < 3 recentlyReset = (self.lastRewindTime is not None and abs(time.time() - self.lastRewindTime) < 10) and self._playerPosition < 3
return self._playerPaused and (self.autoPlay or recentlyReset) and self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported()\ return (
and self.userlist.areAllUsersInRoomReady(requireSameFilenames=self._config["autoplayRequireSameFilenames"])\ self._playerPaused and (self.autoPlay or recentlyReset) and
self.userlist.currentUser.canControl() and self.userlist.isReadinessSupported()
and self.userlist.areAllUsersInRoomReady(requireSameFilenames=self._config["autoplayRequireSameFilenames"])
and ((self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold) or recentlyReset) and ((self.autoPlayThreshold and self.userlist.usersInRoomCount() >= self.autoPlayThreshold) or recentlyReset)
)
def autoplayTimerIsRunning(self): def autoplayTimerIsRunning(self):
return self.autoplayTimer.running return self.autoplayTimer.running
@ -1049,6 +1064,7 @@ class SyncplayClient(object):
elif not self._userlist.currentUser.isReady(): # CurrentUser should always be reminded they are set to not ready elif not self._userlist.currentUser.isReady(): # CurrentUser should always be reminded they are set to not ready
self.checkReadyStates() self.checkReadyStates()
class SyncplayUser(object): class SyncplayUser(object):
def __init__(self, username=None, room=None, file_=None): def __init__(self, username=None, room=None, file_=None):
self.ready = None self.ready = None
@ -1113,6 +1129,7 @@ class SyncplayUser(object):
def setFeatures(self, features): def setFeatures(self, features):
self._features = features self._features = features
class SyncplayUserlist(object): class SyncplayUserlist(object):
def __init__(self, ui, client): def __init__(self, ui, client):
self.currentUser = SyncplayUser() self.currentUser = SyncplayUser()
@ -1141,7 +1158,7 @@ class SyncplayUserlist(object):
showOnOSD = constants.SHOW_OSD_WARNINGS showOnOSD = constants.SHOW_OSD_WARNINGS
else: else:
showOnOSD = constants.SHOW_DIFFERENT_ROOM_OSD showOnOSD = constants.SHOW_DIFFERENT_ROOM_OSD
if constants.SHOW_NONCONTROLLER_OSD == False and self.canControl(username) == False: if not constants.SHOW_NONCONTROLLER_OSD and not self.canControl(username):
showOnOSD = False showOnOSD = False
hideFromOSD = not showOnOSD hideFromOSD = not showOnOSD
if not file_: if not file_:
@ -1258,18 +1275,22 @@ class SyncplayUserlist(object):
return False return False
for user in self._users.values(): for user in self._users.values():
if user.room == self.currentUser.room: if user.room == self.currentUser.room:
if user.isReadyWithFile() == False: if not user.isReadyWithFile():
return False return False
elif requireSameFilenames and\ elif (
(self.currentUser.file is None requireSameFilenames and
(
self.currentUser.file is None
or user.file is None or user.file is None
or not utils.sameFilename(self.currentUser.file['name'], user.file['name'])): or not utils.sameFilename(self.currentUser.file['name'], user.file['name'])
)
):
return False return False
return True return True
def areAllOtherUsersInRoomReady(self): def areAllOtherUsersInRoomReady(self):
for user in self._users.values(): for user in self._users.values():
if user.room == self.currentUser.room and user.isReadyWithFile() == False: if user.room == self.currentUser.room and not user.isReadyWithFile():
return False return False
return True return True
@ -1294,7 +1315,7 @@ class SyncplayUserlist(object):
if not self.currentUser.isReady(): if not self.currentUser.isReady():
notReady.append(self.currentUser.username) notReady.append(self.currentUser.username)
for user in self._users.values(): for user in self._users.values():
if user.room == self.currentUser.room and user.isReadyWithFile() == False: if user.room == self.currentUser.room and not user.isReadyWithFile():
notReady.append(user.username) notReady.append(user.username)
return ", ".join(notReady) return ", ".join(notReady)
@ -1394,6 +1415,7 @@ class SyncplayUserlist(object):
rooms = collections.OrderedDict(sorted(list(rooms.items()), key=lambda s: s[0].lower())) rooms = collections.OrderedDict(sorted(list(rooms.items()), key=lambda s: s[0].lower()))
return rooms return rooms
class UiManager(object): class UiManager(object):
def __init__(self, client, ui): def __init__(self, client, ui):
self._client = client self._client = client
@ -1429,7 +1451,8 @@ class UiManager(object):
self.__ui.showMessage(messageString) self.__ui.showMessage(messageString)
def showMessage(self, message, noPlayer=False, noTimestamp=False, OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL): def showMessage(self, message, noPlayer=False, noTimestamp=False, OSDType=constants.OSD_NOTIFICATION, mood=constants.MESSAGE_NEUTRAL):
if not noPlayer: self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood) if not noPlayer:
self.showOSDMessage(message, duration=constants.OSD_DURATION, OSDType=OSDType, mood=mood)
self.__ui.showMessage(message, noTimestamp) self.__ui.showMessage(message, noTimestamp)
def updateAutoPlayState(self, newState): def updateAutoPlayState(self, newState):
@ -1487,6 +1510,7 @@ class UiManager(object):
def drop(self): def drop(self):
self.__ui.drop() self.__ui.drop()
class SyncplayPlaylist(): class SyncplayPlaylist():
def __init__(self, client): def __init__(self, client):
self._client = client self._client = client
@ -1686,9 +1710,11 @@ class SyncplayPlaylist():
def advancePlaylistCheck(self): def advancePlaylistCheck(self):
position = self._client.getStoredPlayerPosition() position = self._client.getStoredPlayerPosition()
currentLength = self._client.userlist.currentUser.file["duration"] if self._client.userlist.currentUser.file else 0 currentLength = self._client.userlist.currentUser.file["duration"] if self._client.userlist.currentUser.file else 0
if currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\ if (
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD\ currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH and
and self.notJustChangedPlaylist(): abs(position - currentLength) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD and
self.notJustChangedPlaylist()
):
self.loadNextFileInPlaylist() self.loadNextFileInPlaylist()
def notJustChangedPlaylist(self): def notJustChangedPlaylist(self):
@ -1759,6 +1785,7 @@ class SyncplayPlaylist():
def _playlistBufferNeedsUpdating(self, newPlaylist): def _playlistBufferNeedsUpdating(self, newPlaylist):
return self._previousPlaylist != self._playlist and self._playlist != newPlaylist return self._previousPlaylist != self._playlist and self._playlist != newPlaylist
class FileSwitchManager(object): class FileSwitchManager(object):
def __init__(self, client): def __init__(self, client):
self._client = client self._client = client

View File

@ -1,6 +1,8 @@
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
from syncplay import ui from syncplay import ui
from syncplay.messages import getMessage from syncplay.messages import getMessage
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
class SyncplayClientManager(object): class SyncplayClientManager(object):
def run(self): def run(self):
@ -13,4 +15,3 @@ class SyncplayClientManager(object):
syncplayClient.start(config['host'], config['port']) syncplayClient.start(config['host'], config['port'])
else: else:
interface.showErrorMessage(getMessage("unable-to-start-client-error"), True) interface.showErrorMessage(getMessage("unable-to-start-client-error"), True)

View File

@ -16,7 +16,12 @@ SHOW_CONTACT_INFO = True # Displays dev contact details below list in GUI
SHOW_TOOLTIPS = True SHOW_TOOLTIPS = True
WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled) WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled)
FALLBACK_INITIAL_LANGUAGE = "en" FALLBACK_INITIAL_LANGUAGE = "en"
FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [['syncplay.pl:8995 (France)', 'syncplay.pl:8995'],['syncplay.pl:8996 (France)', 'syncplay.pl:8996'],['syncplay.pl:8997 (France)', 'syncplay.pl:8997'],['syncplay.pl:8998 (France)', 'syncplay.pl:8998'],['syncplay.pl:8999 (France)', 'syncplay.pl:8999']] FALLBACK_PUBLIC_SYNCPLAY_SERVERS = [
['syncplay.pl:8995 (France)', 'syncplay.pl:8995'],
['syncplay.pl:8996 (France)', 'syncplay.pl:8996'],
['syncplay.pl:8997 (France)', 'syncplay.pl:8997'],
['syncplay.pl:8998 (France)', 'syncplay.pl:8998'],
['syncplay.pl:8999 (France)', 'syncplay.pl:8999']]
PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH = 10 # Seconds PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH = 10 # Seconds
PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD = 5 # Seconds (only triggered if file is paused, e.g. due to EOF) PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD = 5 # Seconds (only triggered if file is paused, e.g. due to EOF)
@ -200,7 +205,11 @@ MPV_SYNCPLAYINTF_OPTIONS_TO_SEND = ["chatInputEnabled","chatInputFontFamily", "c
"chatTopMargin", "chatLeftMargin", "chatBottomMargin", "chatDirectInput", "chatTopMargin", "chatLeftMargin", "chatBottomMargin", "chatDirectInput",
"notificationTimeout", "alertTimeout", "chatTimeout", "chatOutputEnabled"] "notificationTimeout", "alertTimeout", "chatTimeout", "chatOutputEnabled"]
MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = ["MaxChatMessageLength={}".format(MAX_CHAT_MESSAGE_LENGTH),"inputPromptStartCharacter={}".format(MPV_INPUT_PROMPT_START_CHARACTER),"inputPromptEndCharacter={}".format(MPV_INPUT_PROMPT_END_CHARACTER),"backslashSubstituteCharacter={}".format(MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)] MPV_SYNCPLAYINTF_CONSTANTS_TO_SEND = [
"MaxChatMessageLength={}".format(MAX_CHAT_MESSAGE_LENGTH),
"inputPromptStartCharacter={}".format(MPV_INPUT_PROMPT_START_CHARACTER),
"inputPromptEndCharacter={}".format(MPV_INPUT_PROMPT_END_CHARACTER),
"backslashSubstituteCharacter={}".format(MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
# Note: Constants updated in client.py->checkForFeatureSupport # Note: Constants updated in client.py->checkForFeatureSupport
MPV_SYNCPLAYINTF_LANGUAGE_TO_SEND = ["mpv-key-tab-hint", "mpv-key-hint", "alphakey-mode-warning-first-line", "alphakey-mode-warning-second-line"] MPV_SYNCPLAYINTF_LANGUAGE_TO_SEND = ["mpv-key-tab-hint", "mpv-key-hint", "alphakey-mode-warning-first-line", "alphakey-mode-warning-second-line"]
VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek', VLC_SLAVE_ARGS = ['--extraintf=luaintf', '--lua-intf=syncplay', '--no-quiet', '--no-input-fast-seek',

View File

@ -14,6 +14,7 @@ messages = {
"CURRENT": None "CURRENT": None
} }
def getLanguages(): def getLanguages():
langList = {} langList = {}
for lang in messages: for lang in messages:
@ -21,9 +22,11 @@ def getLanguages():
langList[lang] = getMessage("LANGUAGE", lang) langList[lang] = getMessage("LANGUAGE", lang)
return langList return langList
def setLanguage(lang): def setLanguage(lang):
messages["CURRENT"] = lang messages["CURRENT"] = lang
def getMissingStrings(): def getMissingStrings():
missingStrings = "" missingStrings = ""
for lang in messages: for lang in messages:
@ -37,6 +40,7 @@ def getMissingStrings():
return missingStrings return missingStrings
def getInitialLanguage(): def getInitialLanguage():
import locale import locale
try: try:
@ -47,11 +51,13 @@ def getInitialLanguage():
initialLanguage = constants.FALLBACK_INITIAL_LANGUAGE initialLanguage = constants.FALLBACK_INITIAL_LANGUAGE
return initialLanguage return initialLanguage
def isValidLanguage(language): def isValidLanguage(language):
return language in messages return language in messages
def getMessage(type_, locale=None): def getMessage(type_, locale=None):
if constants.SHOW_TOOLTIPS == False: if not constants.SHOW_TOOLTIPS:
if "-tooltip" in type_: if "-tooltip" in type_:
return "" return ""

View File

@ -1,13 +1,16 @@
# coding:utf8 # coding:utf8
from twisted.protocols.basic import LineReceiver
import json import json
import syncplay
from functools import wraps
import time import time
from syncplay.messages import getMessage from functools import wraps
from twisted.protocols.basic import LineReceiver
import syncplay
from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION from syncplay.constants import PING_MOVING_AVERAGE_WEIGHT, CONTROLLED_ROOMS_MIN_VERSION, USER_READY_MIN_VERSION, SHARED_PLAYLIST_MIN_VERSION, CHAT_MIN_VERSION
from syncplay.messages import getMessage
from syncplay.utils import meetsMinVersion from syncplay.utils import meetsMinVersion
class JSONCommandProtocol(LineReceiver): class JSONCommandProtocol(LineReceiver):
def handleMessages(self, messages): def handleMessages(self, messages):
for message in messages.items(): for message in messages.items():
@ -102,9 +105,11 @@ class SyncClientProtocol(JSONCommandProtocol):
hello = {} hello = {}
hello["username"] = self._client.getUsername() hello["username"] = self._client.getUsername()
password = self._client.getPassword() password = self._client.getPassword()
if password: hello["password"] = password if password:
hello["password"] = password
room = self._client.getRoom() room = self._client.getRoom()
if room: hello["room"] = {"name" :room} if room:
hello["room"] = {"name": room}
hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server hello["version"] = "1.2.255" # Used so newer clients work on 1.2.X server
hello["realversion"] = syncplay.version hello["realversion"] = syncplay.version
hello["features"] = self._client.getFeatures() hello["features"] = self._client.getFeatures()
@ -160,7 +165,8 @@ class SyncClientProtocol(JSONCommandProtocol):
def sendRoomSetting(self, roomName, password=None): def sendRoomSetting(self, roomName, password=None):
setting = {} setting = {}
setting["name"] = roomName setting["name"] = roomName
if password: setting["password"] = password if password:
setting["password"] = password
self.sendSet({"room": setting}) self.sendSet({"room": setting})
def sendFileSetting(self, file_): def sendFileSetting(self, file_):
@ -231,7 +237,8 @@ class SyncClientProtocol(JSONCommandProtocol):
state["playstate"] = {} state["playstate"] = {}
state["playstate"]["position"] = position state["playstate"]["position"] = position
state["playstate"]["paused"] = paused state["playstate"]["paused"] = paused
if doSeek: state["playstate"]["doSeek"] = doSeek if doSeek:
state["playstate"]["doSeek"] = doSeek
state["ping"] = {} state["ping"] = {}
if latencyCalculation: if latencyCalculation:
state["ping"]["latencyCalculation"] = latencyCalculation state["ping"]["latencyCalculation"] = latencyCalculation
@ -255,6 +262,7 @@ class SyncClientProtocol(JSONCommandProtocol):
"password": password "password": password
} }
}) })
def handleChat(self, message): def handleChat(self, message):
username = message['username'] username = message['username']
userMessage = message['message'] userMessage = message['message']
@ -282,13 +290,13 @@ class SyncClientProtocol(JSONCommandProtocol):
} }
}) })
def handleError(self, error): def handleError(self, error):
self.dropWithError(error["message"]) self.dropWithError(error["message"])
def sendError(self, message): def sendError(self, message):
self.sendMessage({"Error": {"message": message}}) self.sendMessage({"Error": {"message": message}})
class SyncServerProtocol(JSONCommandProtocol): class SyncServerProtocol(JSONCommandProtocol):
def __init__(self, factory): def __init__(self, factory):
self._factory = factory self._factory = factory
@ -410,7 +418,8 @@ class SyncServerProtocol(JSONCommandProtocol):
hello["username"] = username hello["username"] = username
userIp = self.transport.getPeer().host userIp = self.transport.getPeer().host
room = self._watcher.getRoom() room = self._watcher.getRoom()
if room: hello["room"] = {"name": room.getName()} if room:
hello["room"] = {"name": room.getName()}
hello["version"] = clientVersion # Used so 1.2.X client works on newer server hello["version"] = clientVersion # Used so 1.2.X client works on newer server
hello["realversion"] = syncplay.version hello["realversion"] = syncplay.version
hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion) hello["motd"] = self._factory.getMotd(userIp, username, room, clientVersion)
@ -461,7 +470,6 @@ class SyncServerProtocol(JSONCommandProtocol):
} }
}) })
def sendSetReady(self, username, isReady, manuallyInitiated=True): def sendSetReady(self, username, isReady, manuallyInitiated=True):
self.sendSet({ self.sendSet({
"ready": { "ready": {
@ -556,7 +564,6 @@ class SyncServerProtocol(JSONCommandProtocol):
if self.serverIgnoringOnTheFly == 0 or forced: if self.serverIgnoringOnTheFly == 0 or forced:
self.sendMessage({"State": state}) self.sendMessage({"State": state})
def _extractStatePlaystateArguments(self, state): def _extractStatePlaystateArguments(self, state):
position = state["playstate"]["position"] if "position" in state["playstate"] else 0 position = state["playstate"]["position"] if "position" in state["playstate"] else 0
paused = state["playstate"]["paused"] if "paused" in state["playstate"] else None paused = state["playstate"]["paused"] if "paused" in state["playstate"] else None
@ -590,6 +597,7 @@ class SyncServerProtocol(JSONCommandProtocol):
def sendError(self, message): def sendError(self, message):
self.sendMessage({"Error": {"message": message}}) self.sendMessage({"Error": {"message": message}})
class PingService(object): class PingService(object):
def __init__(self): def __init__(self):

View File

@ -1,20 +1,26 @@
import argparse
import codecs
import hashlib import hashlib
import os
import random import random
import time
from string import Template
from twisted.internet import task, reactor from twisted.internet import task, reactor
from twisted.internet.protocol import Factory from twisted.internet.protocol import Factory
import syncplay import syncplay
from syncplay.protocols import SyncServerProtocol
import time
from syncplay import constants from syncplay import constants
from syncplay.messages import getMessage from syncplay.messages import getMessage
import codecs from syncplay.protocols import SyncServerProtocol
import os
from string import Template
import argparse
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText
class SyncFactory(Factory): class SyncFactory(Factory):
def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False,disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH, maxUsernameLength=constants.MAX_USERNAME_LENGTH): def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None,
disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH,
maxUsernameLength=constants.MAX_USERNAME_LENGTH):
self.isolateRooms = isolateRooms self.isolateRooms = isolateRooms
print(getMessage("welcome-server-notification").format(syncplay.version)) print(getMessage("welcome-server-notification").format(syncplay.version))
if password: if password:
@ -179,6 +185,7 @@ class SyncFactory(Factory):
else: else:
watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex()) watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex())
class RoomManager(object): class RoomManager(object):
def __init__(self): def __init__(self):
self._rooms = {} self._rooms = {}
@ -341,6 +348,7 @@ class Room(object):
def getPlaylistIndex(self): def getPlaylistIndex(self):
return self._playlistIndex return self._playlistIndex
class ControlledRoom(Room): class ControlledRoom(Room):
def __init__(self, name): def __init__(self, name):
Room.__init__(self, name) Room.__init__(self, name)
@ -389,6 +397,7 @@ class ControlledRoom(Room):
def getControllers(self): def getControllers(self):
return self._controllers return self._controllers
class Watcher(object): class Watcher(object):
def __init__(self, server, connector, name): def __init__(self, server, connector, name):
self._ready = None self._ready = None
@ -531,6 +540,7 @@ class Watcher(object):
return RoomPasswordProvider.isControlledRoom(self._room.getName()) \ return RoomPasswordProvider.isControlledRoom(self._room.getName()) \
and self._room.canControl(self) and self._room.canControl(self)
class ConfigurationGetter(object): class ConfigurationGetter(object):
def getConfiguration(self): def getConfiguration(self):
self._prepareArgParser() self._prepareArgParser()
@ -540,7 +550,8 @@ class ConfigurationGetter(object):
return args return args
def _prepareArgParser(self): def _prepareArgParser(self):
self._argparser = argparse.ArgumentParser(description=getMessage("server-argument-description"), self._argparser = argparse.ArgumentParser(
description=getMessage("server-argument-description"),
epilog=getMessage("server-argument-epilog")) epilog=getMessage("server-argument-epilog"))
self._argparser.add_argument('--port', metavar='port', type=str, nargs='?', help=getMessage("server-port-argument")) self._argparser.add_argument('--port', metavar='port', type=str, nargs='?', help=getMessage("server-port-argument"))
self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-argument")) self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-argument"))

View File

@ -1,35 +1,44 @@
import time
import re import ast
import datetime import datetime
import hashlib
import itertools
import random
import os
import platform
import re
import string
import subprocess
import sys
import time
import traceback
import unicodedata
import urllib.error
import urllib.parse
import urllib.request
from syncplay import constants from syncplay import constants
from syncplay.messages import getMessage from syncplay.messages import getMessage
import sys
import os
import itertools
import hashlib
import random
import string
import urllib.request, urllib.parse, urllib.error
import ast
import unicodedata
import platform
import subprocess
import traceback
folderSearchEnabled = True folderSearchEnabled = True
def isWindows(): def isWindows():
return sys.platform.startswith(constants.OS_WINDOWS) return sys.platform.startswith(constants.OS_WINDOWS)
def isLinux(): def isLinux():
return sys.platform.startswith(constants.OS_LINUX) return sys.platform.startswith(constants.OS_LINUX)
def isMacOS(): def isMacOS():
return sys.platform.startswith(constants.OS_MACOS) return sys.platform.startswith(constants.OS_MACOS)
def isBSD(): def isBSD():
return constants.OS_BSD in sys.platform or sys.platform.startswith(constants.OS_DRAGONFLY) return constants.OS_BSD in sys.platform or sys.platform.startswith(constants.OS_DRAGONFLY)
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None): def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
"""Retry calling the decorated function using an exponential backoff. """Retry calling the decorated function using an exponential backoff.
@ -71,6 +80,7 @@ def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
return f_retry # true decorator return f_retry # true decorator
return deco_retry return deco_retry
def parseTime(timeStr): def parseTime(timeStr):
regex = re.compile(constants.PARSE_TIME_REGEX) regex = re.compile(constants.PARSE_TIME_REGEX)
parts = regex.match(timeStr) parts = regex.match(timeStr)
@ -86,6 +96,7 @@ def parseTime(timeStr):
time_params[name] = int(param) time_params[name] = int(param)
return datetime.timedelta(**time_params).total_seconds() return datetime.timedelta(**time_params).total_seconds()
def formatTime(timeInSeconds, weeksAsTitles=True): def formatTime(timeInSeconds, weeksAsTitles=True):
if timeInSeconds < 0: if timeInSeconds < 0:
timeInSeconds = -timeInSeconds timeInSeconds = -timeInSeconds
@ -115,7 +126,8 @@ def formatTime(timeInSeconds, weeksAsTitles=True):
formattedTime = "{0:} (Title {1:.0f})".format(formattedTime, title) formattedTime = "{0:} (Title {1:.0f})".format(formattedTime, title)
return formattedTime return formattedTime
def formatSize (bytes, precise=False):
def formatSize(num_of_bytes, precise=False):
if bytes == 0: # E.g. when file size privacy is enabled if bytes == 0: # E.g. when file size privacy is enabled
return "???" return "???"
try: try:
@ -128,9 +140,11 @@ def formatSize (bytes, precise=False):
except: # E.g. when filesize is hashed except: # E.g. when filesize is hashed
return "???" return "???"
def isASCII(s): def isASCII(s):
return all(ord(c) < 128 for c in s) return all(ord(c) < 128 for c in s)
def findResourcePath(resourceName): def findResourcePath(resourceName):
if resourceName == "syncplay.lua": if resourceName == "syncplay.lua":
resourcePath = os.path.join(findWorkingDir(), "lua", "intf", "resources", resourceName) resourcePath = os.path.join(findWorkingDir(), "lua", "intf", "resources", resourceName)
@ -138,6 +152,7 @@ def findResourcePath(resourceName):
resourcePath = os.path.join(findWorkingDir(), "resources", resourceName) resourcePath = os.path.join(findWorkingDir(), "resources", resourceName)
return resourcePath return resourcePath
def findWorkingDir(): def findWorkingDir():
frozen = getattr(sys, 'frozen', '') frozen = getattr(sys, 'frozen', '')
if not frozen: if not frozen:
@ -153,15 +168,18 @@ def findWorkingDir():
path = "" path = ""
return path return path
def getResourcesPath(): def getResourcesPath():
if isWindows(): if isWindows():
return findWorkingDir() + "\\resources\\" return findWorkingDir() + "\\resources\\"
else: else:
return findWorkingDir() + "/resources/" return findWorkingDir() + "/resources/"
resourcespath = getResourcesPath() resourcespath = getResourcesPath()
posixresourcespath = findWorkingDir().replace("\\", "/") + "/resources/" posixresourcespath = findWorkingDir().replace("\\", "/") + "/resources/"
def getDefaultMonospaceFont(): def getDefaultMonospaceFont():
if platform.system() == "Windows": if platform.system() == "Windows":
return constants.DEFAULT_WINDOWS_MONOSPACE_FONT return constants.DEFAULT_WINDOWS_MONOSPACE_FONT
@ -170,15 +188,18 @@ def getDefaultMonospaceFont():
else: else:
return constants.FALLBACK_MONOSPACE_FONT return constants.FALLBACK_MONOSPACE_FONT
def limitedPowerset(s, minLength): def limitedPowerset(s, minLength):
return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s), minLength, -1)) return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s), minLength, -1))
def blackholeStdoutForFrozenWindow(): def blackholeStdoutForFrozenWindow():
if getattr(sys, 'frozen', '') == "windows_exe": if getattr(sys, 'frozen', '') == "windows_exe":
class Stderr(object): class Stderr(object):
softspace = 0 softspace = 0
_file = None _file = None
_error = None _error = None
def write(self, text, fname='.syncplay.log'): def write(self, text, fname='.syncplay.log'):
if self._file is None and self._error is None: if self._file is None and self._error is None:
if os.name != 'nt': if os.name != 'nt':
@ -190,21 +211,27 @@ def blackholeStdoutForFrozenWindow():
if self._file is not None: if self._file is not None:
self._file.write(text) self._file.write(text)
self._file.flush() self._file.flush()
def flush(self): def flush(self):
if self._file is not None: if self._file is not None:
self._file.flush() self._file.flush()
sys.stderr = Stderr() sys.stderr = Stderr()
del Stderr del Stderr
class Blackhole(object): class Blackhole(object):
softspace = 0 softspace = 0
def write(self, text): def write(self, text):
pass pass
def flush(self): def flush(self):
pass pass
sys.stdout = Blackhole() sys.stdout = Blackhole()
del Blackhole del Blackhole
def truncateText(unicodeText, maxLength): def truncateText(unicodeText, maxLength):
try: try:
unicodeText = unicodeText.decode('utf-8') unicodeText = unicodeText.decode('utf-8')
@ -216,6 +243,7 @@ def truncateText(unicodeText, maxLength):
pass pass
return "" return ""
def splitText(unicodeText, maxLength): def splitText(unicodeText, maxLength):
try: try:
unicodeText = unicodeText.decode('utf-8') unicodeText = unicodeText.decode('utf-8')
@ -231,6 +259,7 @@ def splitText(unicodeText, maxLength):
# Relate to file hashing / difference checking: # Relate to file hashing / difference checking:
def stripfilename(filename, stripURL): def stripfilename(filename, stripURL):
if filename: if filename:
try: try:
@ -247,6 +276,7 @@ def stripfilename(filename, stripURL):
else: else:
return "" return ""
def stripRoomName(RoomName): def stripRoomName(RoomName):
if RoomName: if RoomName:
try: try:
@ -256,6 +286,7 @@ def stripRoomName(RoomName):
else: else:
return "" return ""
def hashFilename(filename, stripURL=False): def hashFilename(filename, stripURL=False):
if isURL(filename): if isURL(filename):
stripURL = True stripURL = True
@ -267,9 +298,11 @@ def hashFilename(filename, stripURL = False):
filenameHash = hashlib.sha256(strippedFilename).hexdigest()[:12] filenameHash = hashlib.sha256(strippedFilename).hexdigest()[:12]
return filenameHash return filenameHash
def hashFilesize(size): def hashFilesize(size):
return hashlib.sha256(str(size).encode('utf-8')).hexdigest()[:12] return hashlib.sha256(str(size).encode('utf-8')).hexdigest()[:12]
def sameHashed(string1raw, string1hashed, string2raw, string2hashed): def sameHashed(string1raw, string1hashed, string2raw, string2hashed):
try: try:
if string1raw.lower() == string2raw.lower(): if string1raw.lower() == string2raw.lower():
@ -285,6 +318,7 @@ def sameHashed(string1raw, string1hashed, string2raw, string2hashed):
elif string1hashed == string2hashed: elif string1hashed == string2hashed:
return True return True
def sameFilename(filename1, filename2): def sameFilename(filename1, filename2):
try: try:
filename1 = filename1 filename1 = filename1
@ -302,6 +336,7 @@ def sameFilename (filename1, filename2):
else: else:
return False return False
def sameFilesize(filesize1, filesize2): def sameFilesize(filesize1, filesize2):
if filesize1 == 0 or filesize2 == 0: if filesize1 == 0 or filesize2 == 0:
return True return True
@ -310,6 +345,7 @@ def sameFilesize (filesize1, filesize2):
else: else:
return False return False
def sameFileduration(duration1, duration2): def sameFileduration(duration1, duration2):
if not constants.SHOW_DURATION_NOTIFICATION: if not constants.SHOW_DURATION_NOTIFICATION:
return True return True
@ -318,11 +354,13 @@ def sameFileduration (duration1, duration2):
else: else:
return False return False
def meetsMinVersion(version, minVersion): def meetsMinVersion(version, minVersion):
def versiontotuple(ver): def versiontotuple(ver):
return tuple(map(int, ver.split("."))) return tuple(map(int, ver.split(".")))
return versiontotuple(version) >= versiontotuple(minVersion) return versiontotuple(version) >= versiontotuple(minVersion)
def isURL(path): def isURL(path):
if path is None: if path is None:
return False return False
@ -331,22 +369,27 @@ def isURL(path):
else: else:
return False return False
def getPlayerArgumentsByPathAsArray(arguments, path): def getPlayerArgumentsByPathAsArray(arguments, path):
if arguments and not isinstance(arguments, str) and path in arguments: if arguments and not isinstance(arguments, str) and path in arguments:
return arguments[path] return arguments[path]
else: else:
return None return None
def getPlayerArgumentsByPathAsText(arguments, path): def getPlayerArgumentsByPathAsText(arguments, path):
argsToReturn = getPlayerArgumentsByPathAsArray(arguments, path) argsToReturn = getPlayerArgumentsByPathAsArray(arguments, path)
return " ".join(argsToReturn) if argsToReturn else "" return " ".join(argsToReturn) if argsToReturn else ""
def getListAsMultilineString(pathArray): def getListAsMultilineString(pathArray):
return "\n".join(pathArray) if pathArray else "" return "\n".join(pathArray) if pathArray else ""
def convertMultilineStringToList(multilineString): def convertMultilineStringToList(multilineString):
return str.split(multilineString, "\n") if multilineString else "" return str.split(multilineString, "\n") if multilineString else ""
def playlistIsValid(files): def playlistIsValid(files):
if len(files) > constants.PLAYLIST_MAX_ITEMS: if len(files) > constants.PLAYLIST_MAX_ITEMS:
return False return False
@ -354,6 +397,7 @@ def playlistIsValid(files):
return False return False
return True return True
def getDomainFromURL(URL): def getDomainFromURL(URL):
try: try:
URL = URL.split("//")[-1].split("/")[0] URL = URL.split("//")[-1].split("/")[0]
@ -363,6 +407,7 @@ def getDomainFromURL(URL):
except: except:
return None return None
def open_system_file_browser(path): def open_system_file_browser(path):
if isURL(path): if isURL(path):
return return
@ -374,6 +419,7 @@ def open_system_file_browser(path):
else: else:
subprocess.Popen(["xdg-open", path]) subprocess.Popen(["xdg-open", path])
def getListOfPublicServers(): def getListOfPublicServers():
try: try:
import urllib.request, urllib.parse, urllib.error, syncplay, sys import urllib.request, urllib.parse, urllib.error, syncplay, sys
@ -394,12 +440,13 @@ def getListOfPublicServers():
else: else:
raise IOError raise IOError
except: except:
if constants.DEBUG_MODE == True: if constants.DEBUG_MODE:
traceback.print_exc() traceback.print_exc()
raise raise
else: else:
raise IOError(getMessage("failed-to-load-server-list-error")) raise IOError(getMessage("failed-to-load-server-list-error"))
class RoomPasswordProvider(object): class RoomPasswordProvider(object):
CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$") CONTROLLED_ROOM_REGEX = re.compile("^\+(.*):(\w{12})$")
PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}") PASSWORD_REGEX = re.compile("[A-Z]{2}-\d{3}-\d{3}")
@ -433,6 +480,7 @@ class RoomPasswordProvider(object):
provisionalHash = hashlib.sha256(roomName + salt).hexdigest() provisionalHash = hashlib.sha256(roomName + salt).hexdigest()
return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper() return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper()
class RandomStringGenerator(object): class RandomStringGenerator(object):
@staticmethod @staticmethod
def generate_room_password(): def generate_room_password():
@ -458,6 +506,6 @@ class RandomStringGenerator(object):
def _get_random_numbers(quantity): def _get_random_numbers(quantity):
return ''.join(random.choice(string.digits) for _ in range(quantity)) return ''.join(random.choice(string.digits) for _ in range(quantity))
class NotControlledRoom(Exception): class NotControlledRoom(Exception):
pass pass