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 os.path
import time
import random
import re
import sys
import ast
import random
import threading
import time
from copy import deepcopy
from functools import wraps
from twisted.internet.protocol import ClientFactory
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.utils import isMacOS
from syncplay.messages import getMissingStrings, getMessage
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
PRIVACY_HIDDENFILENAME
import collections
from syncplay.messages import getMissingStrings, getMessage
from syncplay.protocols import SyncClientProtocol
from syncplay.utils import isMacOS
class SyncClientFactory(ClientFactory):
def __init__(self, client, retry=constants.RECONNECT_RETRIES):
self._client = client
@ -58,6 +63,7 @@ class SyncClientFactory(ClientFactory):
def stopRetrying(self):
self._timesTried = self.retry
class SyncplayClient(object):
def __init__(self, playerClass, ui, config):
constants.SHOW_OSD = config['showOSD']
@ -203,12 +209,19 @@ class SyncplayClient(object):
self._playerPosition = position
self._playerPaused = paused
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\
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD:
if (
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()
elif pauseChange and "readiness" in self.serverFeatures and self.serverFeatures["readiness"]:
if currentLength == 0 or currentLength == -1 or\
not (not self.playlist.notJustChangedPlaylist() and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD):
if (
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)
if self._lastGlobalUpdate:
@ -304,7 +317,7 @@ class SyncplayClient(object):
self.setPosition(self.getGlobalPosition())
self._player.setPaused(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)
else:
self.ui.showMessage(getMessage("left-paused-notification").format(self.lastLeftUser, setBy), hideFromOSD)
@ -351,9 +364,9 @@ class SyncplayClient(object):
self._lastGlobalUpdate = time.time()
if doSeek:
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)
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 self.behindFirstDetected is None:
self.behindFirstDetected = time.time()
@ -365,11 +378,11 @@ class SyncplayClient(object):
self.behindFirstDetected = time.time() + constants.FASTFORWARD_RESET_THRESHOLD
else:
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)
if paused == False and pauseChanged:
if not paused and pauseChanged:
madeChangeOnPlayer = self._serverUnpaused(setBy)
elif paused == True and pauseChanged:
elif not paused and pauseChanged:
madeChangeOnPlayer = self._serverPaused(setBy)
return madeChangeOnPlayer
@ -566,11 +579,11 @@ class SyncplayClient(object):
constants.MAX_ROOM_NAME_LENGTH = self.serverFeatures["maxRoomNameLength"]
if self.serverFeatures["maxFilenameLength"] is not None:
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),
"inputPromptEndCharacter={}".format(constants.MPV_INPUT_PROMPT_END_CHARACTER),
"backslashSubstituteCharacter={}".format(
constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
"backslashSubstituteCharacter={}".format(constants.MPV_INPUT_BACKSLASH_SUBSTITUTE_CHARACTER)]
self.ui.setFeatures(self.serverFeatures)
if self._player:
self._player.setFeatures(self.serverFeatures)
@ -586,7 +599,6 @@ class SyncplayClient(object):
else:
return None
def sendFile(self):
file_ = self.getSanitizedCurrentUserFile()
if self._protocol and self._protocol.logged and file_:
@ -762,7 +774,7 @@ class SyncplayClient(object):
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
ConfigurationGetter().setConfigOption("sharedPlaylistEnabled", newState)
self._config["sharedPlaylistEnabled"] = newState
if oldState == False and newState == True:
if not oldState and newState:
self.playlist.loadCurrentPlaylistIndex()
def changeAutoplayState(self, newState):
@ -773,7 +785,7 @@ class SyncplayClient(object):
oldAutoplayConditionsMet = self.autoplayConditionsMet()
self.autoPlayThreshold = newThreshold
newAutoplayConditionsMet = self.autoplayConditionsMet()
if oldAutoplayConditionsMet == False and newAutoplayConditionsMet == True:
if not oldAutoplayConditionsMet and newAutoplayConditionsMet:
self.autoplayCheck()
def autoplayCheck(self):
@ -799,9 +811,12 @@ class SyncplayClient(object):
def autoplayConditionsMet(self):
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()\
and self.userlist.areAllUsersInRoomReady(requireSameFilenames=self._config["autoplayRequireSameFilenames"])\
return (
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)
)
def autoplayTimerIsRunning(self):
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
self.checkReadyStates()
class SyncplayUser(object):
def __init__(self, username=None, room=None, file_=None):
self.ready = None
@ -1113,6 +1129,7 @@ class SyncplayUser(object):
def setFeatures(self, features):
self._features = features
class SyncplayUserlist(object):
def __init__(self, ui, client):
self.currentUser = SyncplayUser()
@ -1141,7 +1158,7 @@ class SyncplayUserlist(object):
showOnOSD = constants.SHOW_OSD_WARNINGS
else:
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
hideFromOSD = not showOnOSD
if not file_:
@ -1258,18 +1275,22 @@ class SyncplayUserlist(object):
return False
for user in self._users.values():
if user.room == self.currentUser.room:
if user.isReadyWithFile() == False:
if not user.isReadyWithFile():
return False
elif requireSameFilenames and\
(self.currentUser.file is None
elif (
requireSameFilenames and
(
self.currentUser.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 True
def areAllOtherUsersInRoomReady(self):
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 True
@ -1294,7 +1315,7 @@ class SyncplayUserlist(object):
if not self.currentUser.isReady():
notReady.append(self.currentUser.username)
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)
return ", ".join(notReady)
@ -1394,6 +1415,7 @@ class SyncplayUserlist(object):
rooms = collections.OrderedDict(sorted(list(rooms.items()), key=lambda s: s[0].lower()))
return rooms
class UiManager(object):
def __init__(self, client, ui):
self._client = client
@ -1429,7 +1451,8 @@ class UiManager(object):
self.__ui.showMessage(messageString)
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)
def updateAutoPlayState(self, newState):
@ -1487,6 +1510,7 @@ class UiManager(object):
def drop(self):
self.__ui.drop()
class SyncplayPlaylist():
def __init__(self, client):
self._client = client
@ -1686,9 +1710,11 @@ class SyncplayPlaylist():
def advancePlaylistCheck(self):
position = self._client.getStoredPlayerPosition()
currentLength = self._client.userlist.currentUser.file["duration"] if self._client.userlist.currentUser.file else 0
if currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH\
and abs(position - currentLength ) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD\
and self.notJustChangedPlaylist():
if (
currentLength > constants.PLAYLIST_LOAD_NEXT_FILE_MINIMUM_LENGTH and
abs(position - currentLength) < constants.PLAYLIST_LOAD_NEXT_FILE_TIME_FROM_END_THRESHOLD and
self.notJustChangedPlaylist()
):
self.loadNextFileInPlaylist()
def notJustChangedPlaylist(self):
@ -1759,6 +1785,7 @@ class SyncplayPlaylist():
def _playlistBufferNeedsUpdating(self, newPlaylist):
return self._previousPlaylist != self._playlist and self._playlist != newPlaylist
class FileSwitchManager(object):
def __init__(self, client):
self._client = client

View File

@ -1,6 +1,8 @@
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
from syncplay import ui
from syncplay.messages import getMessage
from syncplay.ui.ConfigurationGetter import ConfigurationGetter
class SyncplayClientManager(object):
def run(self):
@ -13,4 +15,3 @@ class SyncplayClientManager(object):
syncplayClient.start(config['host'], config['port'])
else:
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
WARN_ABOUT_MISSING_STRINGS = False # (If debug mode is enabled)
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_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",
"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
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',

View File

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

View File

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

View File

@ -1,20 +1,26 @@
import argparse
import codecs
import hashlib
import os
import random
import time
from string import Template
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
from syncplay.messages import getMessage
import codecs
import os
from string import Template
import argparse
from syncplay.protocols import SyncServerProtocol
from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomStringGenerator, meetsMinVersion, playlistIsValid, truncateText
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
print(getMessage("welcome-server-notification").format(syncplay.version))
if password:
@ -179,6 +185,7 @@ class SyncFactory(Factory):
else:
watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex())
class RoomManager(object):
def __init__(self):
self._rooms = {}
@ -341,6 +348,7 @@ class Room(object):
def getPlaylistIndex(self):
return self._playlistIndex
class ControlledRoom(Room):
def __init__(self, name):
Room.__init__(self, name)
@ -389,6 +397,7 @@ class ControlledRoom(Room):
def getControllers(self):
return self._controllers
class Watcher(object):
def __init__(self, server, connector, name):
self._ready = None
@ -531,6 +540,7 @@ class Watcher(object):
return RoomPasswordProvider.isControlledRoom(self._room.getName()) \
and self._room.canControl(self)
class ConfigurationGetter(object):
def getConfiguration(self):
self._prepareArgParser()
@ -540,7 +550,8 @@ class ConfigurationGetter(object):
return args
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"))
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"))

View File

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