diff --git a/.appveyor.yml b/.appveyor.yml index 4b12967..bfdd1b1 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,7 +1,7 @@ environment: MINICONDA: "C:\\Miniconda" - PYTHON: "C:\\Python34" - PYTHON_VERSION: 3.4 + PYTHON: "C:\\Python35" + PYTHON_VERSION: 3.5 PYTHON_ARCH: 32 platform: x86 @@ -15,11 +15,13 @@ init: - python --version - python -m pip install -U pip setuptools wheel - pip install -U pypiwin32==219 - - pip install pyside - pip install twisted - - pip install py2exe - pip install zope.interface - - type nul > C:\Python34\lib\site-packages\zope\__init__.py + - type nul > %PYTHON%\lib\site-packages\zope\__init__.py + - curl -L https://bintray.com/alby128/Syncplay/download_file?file_path=py2exe-0.9.2.2-py33.py34.py35-none-any.whl -o py2exe-0.9.2.2-py33.py34.py35-none-any.whl + - pip install py2exe-0.9.2.2-py33.py34.py35-none-any.whl + - del py2exe-0.9.2.2-py33.py34.py35-none-any.whl + - pip install PySide2==5.11.1 - pip freeze install: diff --git a/.travis.yml b/.travis.yml index 5c9dd37..b876108 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,11 +18,12 @@ before_install: - python3 --version - which pip3 - pip3 --version -- brew install qt -- curl -L https://bintray.com/alby128/Syncplay/download_file?file_path=PySide2-5.11.0-5.11.0-cp36-cp36m-macosx_10_11_x86_64.whl -o PySide2-5.11.0-5.11.0-cp36-cp36m-macosx_10_11_x86_64.whl -- pip3 install PySide2-5.11.0-5.11.0-cp36-cp36m-macosx_10_11_x86_64.whl -- ln -s /usr/local/lib/python3.6/site-packages/PySide2/libpyside2.cpython-36m-darwin.5.11.dylib /usr/local/lib/ -- ln -s /usr/local/lib/python3.6/site-packages/PySide2/libshiboken2.cpython-36m-darwin.5.11.dylib /usr/local/lib/ +- curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/a57d0deab976cd8dee32404abe73f63cc5fbd60d/Formula/qt.rb +- brew install ./qt.rb +- curl -L https://bintray.com/alby128/Syncplay/download_file?file_path=PySide2-5.11.1-5.11.1-cp35.cp36.cp37-abi3-macosx_10_11_x86_64.whl -o PySide2-5.11.1-5.11.1-cp35.cp36.cp37-abi3-macosx_10_11_x86_64.whl +- pip3 install PySide2-5.11.1-5.11.1-cp35.cp36.cp37-abi3-macosx_10_11_x86_64.whl +- ln -s /usr/local/lib/python3.6/site-packages/PySide2/libpyside2.abi3.5.11.dylib /usr/local/lib/ +- ln -s /usr/local/lib/python3.6/site-packages/PySide2/libshiboken2.abi3.5.11.dylib /usr/local/lib/ - python3 -c "from PySide2 import __version__; print(__version__)" - python3 -c "from PySide2.QtCore import __version__; print(__version__)" - cd py2app @@ -31,9 +32,7 @@ before_install: - python3 -c "from py2app.recipes import pyside2" install: -- pip3 install twisted appnope -- pip3 install -U https://github.com/requests/requests/zipball/master - +- pip3 install twisted appnope requests before_deploy: - pip3 install dmgbuild diff --git a/GNUmakefile b/GNUmakefile index be70750..29842fa 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -61,7 +61,7 @@ u-common: client: -mkdir -p $(BIN_PATH) cp syncplayClient.py $(BIN_PATH)/syncplay - sed -i -e 's%# libpath%site.addsitedir\("${PREFIX}/lib/syncplay"\)%' $(BIN_PATH)/syncplay + sed -i -e '/# libpath/ a\import site\nsite.addsitedir\("${PREFIX}/lib/syncplay"\)' $(BIN_PATH)/syncplay chmod 755 $(BIN_PATH)/syncplay cp syncplayClient.py $(LIB_PATH)/syncplay/ cp resources/syncplay.desktop $(APP_SHORTCUT_PATH)/ @@ -87,7 +87,7 @@ u-client: server: -mkdir -p $(BIN_PATH) cp syncplayServer.py $(BIN_PATH)/syncplay-server - sed -i -e 's%# libpath%site.addsitedir\("${PREFIX}/lib/syncplay"\)%' $(BIN_PATH)/syncplay-server + sed -i -e '/# libpath/ a\import site\nsite.addsitedir\("${PREFIX}/lib/syncplay"\)' $(BIN_PATH)/syncplay-server chmod 755 $(BIN_PATH)/syncplay-server cp syncplayServer.py $(LIB_PATH)/syncplay/ cp resources/syncplay-server.desktop $(APP_SHORTCUT_PATH)/ diff --git a/bintray.json b/bintray.json index eb4f953..5fb36fd 100644 --- a/bintray.json +++ b/bintray.json @@ -5,7 +5,7 @@ "subject": "syncplay" }, "version": { - "name": "v1.5.6" + "name": "v1.6.0" }, "files": [ { diff --git a/buildPy2exe.py b/buildPy2exe.py old mode 100755 new mode 100644 index cd8e694..e4a122d --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -15,7 +15,6 @@ import sys # import warnings # warnings.warn("You must build Syncplay with Python 2.7!") - import os import subprocess from string import Template @@ -34,7 +33,6 @@ if missingStrings is not None and missingStrings is not "": import warnings warnings.warn("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings)) - def get_nsis_path(): bin_name = "makensis.exe" from winreg import HKEY_LOCAL_MACHINE as HKLM @@ -49,7 +47,6 @@ def get_nsis_path(): except WindowsError: return bin_name - NSIS_COMPILE = get_nsis_path() OUT_DIR = "syncplay_v{}".format(syncplay.version) @@ -619,7 +616,6 @@ NSIS_SCRIPT_TEMPLATE = r""" SectionEnd """ - class NSISScript(object): def create(self): fileList, totalSize = self.getBuildDirContents(OUT_DIR) @@ -676,17 +672,57 @@ class NSISScript(object): delete.append('RMdir "$INSTDIR\\{}"'.format(file_)) return "\n".join(delete) +def pruneUnneededLibraries(): + from pathlib import Path + cwd = os.getcwd() + libDir = cwd + '\\' + OUT_DIR + '\\lib\\' + unneededModules = ['PySide2.Qt3D*', 'PySide2.QtAxContainer.pyd', 'PySide2.QtCharts.pyd', 'PySide2.QtConcurrent.pyd', + 'PySide2.QtDataVisualization.pyd', 'PySide2.QtHelp.pyd', 'PySide2.QtLocation.pyd', + 'PySide2.QtMultimedia.pyd', 'PySide2.QtMultimediaWidgets.pyd', 'PySide2.QtOpenGL.pyd', + 'PySide2.QtPositioning.pyd', 'PySide2.QtPrintSupport.pyd', 'PySide2.QtQml.pyd', + 'PySide2.QtQuick.pyd', 'PySide2.QtQuickWidgets.pyd', 'PySide2.QtScxml.pyd', 'PySide2.QtSensors.pyd', + 'PySide2.QtSql.pyd', 'PySide2.QtSvg.pyd', 'PySide2.QtTest.pyd', 'PySide2.QtTextToSpeech.pyd', + 'PySide2.QtUiTools.pyd', 'PySide2.QtWebChannel.pyd', 'PySide2.QtWebSockets.pyd', + 'PySide2.QtWinExtras.pyd', 'PySide2.QtXml.pyd', 'PySide2.QtXmlPatterns.pyd'] + unneededLibs = ['Qt53D*', 'Qt5Charts.dll', 'Qt5Concurrent.dll', 'Qt5DataVisualization.dll', 'Qt5Gamepad.dll', 'Qt5Help.dll', + 'Qt5Location.dll', 'Qt5Multimedia.dll', 'Qt5MultimediaWidgets.dll', 'Qt5OpenGL.dll', 'Qt5Positioning.dll', + 'Qt5PrintSupport.dll', 'Qt5Quick.dll', 'Qt5QuickWidgets.dll', 'Qt5Scxml.dll', 'Qt5Sensors.dll', 'Qt5Sql.dll', + 'Qt5Svg.dll', 'Qt5Test.dll', 'Qt5TextToSpeech.dll', 'Qt5WebChannel.dll', 'Qt5WebSockets.dll', 'Qt5WinExtras.dll', + 'Qt5Xml.dll', 'Qt5XmlPatterns.dll'] + windowsDLL = ['MSVCP140.dll', 'VCRUNTIME140.dll'] + deleteList = unneededModules + unneededLibs + windowsDLL + deleteList.append('api-*') + for filename in deleteList: + for p in Path(libDir).glob(filename): + p.unlink() + +def copyQtPlugins(paths): + import shutil + from PySide2 import QtCore + basePath = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.PluginsPath) + basePath = basePath.replace('/', '\\') + destBase = os.getcwd() + '\\' + OUT_DIR + for elem in paths: + elemDir, elemName = os.path.split(elem) + source = basePath + '\\' + elem + dest = destBase + '\\' + elem + destDir = destBase + '\\' + elemDir + os.makedirs(destDir, exist_ok=True) + shutil.copy(source, dest) class build_installer(py2exe): def run(self): py2exe.run(self) + print('*** deleting unnecessary libraries and modules ***') + pruneUnneededLibraries() + print('*** copying qt plugins ***') + copyQtPlugins(qt_plugins) script = NSISScript() script.create() - print("*** compiling the NSIS setup script***") + print("*** compiling the NSIS setup script ***") script.compile() print("*** DONE ***") - guiIcons = [ 'resources/accept.png', 'resources/arrow_undo.png', 'resources/clock_go.png', 'resources/control_pause_blue.png', 'resources/cross.png', 'resources/door_in.png', @@ -720,6 +756,8 @@ resources = [ resources.extend(guiIcons) intf_resources = ["resources/lua/intf/syncplay.lua"] +qt_plugins = ['platforms\\qwindows.dll', 'styles\\qwindowsvistastyle.dll'] + common_info = dict( name='Syncplay', version=syncplay.version, @@ -738,11 +776,12 @@ info = dict( console=['syncplayServer.py'], # *** If you wish to make the Syncplay client use console mode (for --no-gui to work) then comment out the above two lines and uncomment the following line: # console=['syncplayServer.py', {"script":"syncplayClient.py", "icon_resources":[(1, "resources\\icon.ico")], 'dest_base': "Syncplay"}], + options={ 'py2exe': { 'dist_dir': OUT_DIR, - 'packages': 'PySide.QtUiTools', - 'includes': 'twisted, sys, encodings, datetime, os, time, math, PySide, liburl, ast, unicodedata, _ssl', + 'packages': 'PySide2', + 'includes': 'twisted, sys, encodings, datetime, os, time, math, liburl, ast, unicodedata, _ssl', 'excludes': 'venv, doctest, pdb, unittest, win32clipboard, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process, Tkinter', 'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll', 'optimize': 2, @@ -754,5 +793,5 @@ info = dict( cmdclass={"py2exe": build_installer}, ) -sys.argv.extend(['py2exe', '-p win32com ', '-i twisted.web.resource', '-i PySide.QtCore', '-i PySide.QtGui']) -setup(**info) +sys.argv.extend(['py2exe', '-p win32com ', '-i twisted.web.resource', '-p PySide2']) +setup(**info) \ No newline at end of file diff --git a/syncplay/__init__.py b/syncplay/__init__.py index 561ff5f..d729808 100755 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,5 +1,5 @@ -version = '1.5.6' -revision = ' beta' +version = '1.6.1' +revision = '' milestone = 'Yoitsu' -release_number = '66' +release_number = '70' projectURL = 'https://syncplay.pl/' diff --git a/syncplay/constants.py b/syncplay/constants.py index 9cad6c0..76fdd17 100755 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -9,7 +9,7 @@ MPLAYER_OSD_LEVEL = 1 UI_TIME_FORMAT = "[%X] " CONFIG_NAMES = [".syncplay", "syncplay.ini"] # Syncplay searches first to last DEFAULT_CONFIG_NAME = "syncplay.ini" -RECENT_CLIENT_THRESHOLD = "1.5.5" # This and higher considered 'recent' clients (no warnings) +RECENT_CLIENT_THRESHOLD = "1.6.0" # This and higher considered 'recent' clients (no warnings) WARN_OLD_CLIENTS = True # Use MOTD to inform old clients to upgrade LIST_RELATIVE_CONFIGS = True # Print list of relative configs loaded SHOW_CONTACT_INFO = True # Displays dev contact details below list in GUI @@ -53,6 +53,7 @@ DIFFERENT_DURATION_THRESHOLD = 2.5 PROTOCOL_TIMEOUT = 12.5 RECONNECT_RETRIES = 999 SERVER_STATE_INTERVAL = 1 +SERVER_STATS_SNAPSHOT_INTERVAL = 3600 WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1 AUTOPLAY_DELAY = 3.0 DO_NOT_RESET_POSITION_THRESHOLD = 1.0 diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 1dcab6d..978685b 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -116,6 +116,7 @@ de = { "empty-error": "{} darf nicht leer sein", # Configuration "media-player-error": "Player-Fehler: \"{}\"", # Error line "unable-import-gui-error": "Konnte die GUI-Bibliotheken nicht importieren. PySide muss installiert sein, damit die grafische Oberfläche funktioniert.", + "unable-import-twisted-error": "Could not import Twisted. Please install Twisted v12.1.0 or later.", #To do: translate "arguments-missing-error": "Notwendige Argumente fehlen, siehe --help", @@ -434,7 +435,8 @@ de = { "server-motd-argument": "Pfad zur Datei, von der die Nachricht des Tages geladen wird", "server-chat-argument": "Should chat be disabled?", # TODO: Translate "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # TODO: Translate - "server-maxusernamelength-argument": "Maximum number of charactrs in a username (default is {})", # TODO: Translate + "server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", # TODO: Translate + "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", # TODO: Translate "server-messed-up-motd-unescaped-placeholders": "Die Nachricht des Tages hat unmaskierte Platzhalter. Alle $-Zeichen sollten verdoppelt werden ($$).", "server-messed-up-motd-too-long": "Die Nachricht des Tages ist zu lang - Maximal {} Zeichen, aktuell {}.", diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index f22015b..acf5c37 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -115,6 +115,7 @@ en = { "empty-error": "{} can't be empty", # Configuration "media-player-error": "Media player error: \"{}\"", # Error line "unable-import-gui-error": "Could not import GUI libraries. If you do not have PySide installed then you will need to install it for the GUI to work.", + "unable-import-twisted-error": "Could not import Twisted. Please install Twisted v12.1.0 or later.", "arguments-missing-error": "Some necessary arguments are missing, refer to --help", @@ -437,8 +438,9 @@ en = { "server-disable-ready-argument": "disable readiness feature", "server-motd-argument": "path to file from which motd will be fetched", "server-chat-argument": "Should chat be disabled?", - "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters - "server-maxusernamelength-argument": "Maximum number of charactrs in a username (default is {})", + "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # Default number of characters + "server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", + "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", "server-messed-up-motd-unescaped-placeholders": "Message of the Day has unescaped placeholders. All $ signs should be doubled ($$).", "server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.", diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 8904733..2ecff68 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -115,6 +115,7 @@ it = { "empty-error": "Il campo {} non può esssere vuoto", # Configuration "media-player-error": "Errore media player: \"{}\"", # Error line "unable-import-gui-error": "Non è possibile importare le librerie di interfaccia grafica. Hai bisogno di PySide per poter utilizzare l'interfaccia grafica.", + "unable-import-twisted-error": "Non è possibile importare Twisted. Si prega di installare Twisted v12.1. o superiore.", "arguments-missing-error": "Alcuni argomenti obbligatori non sono stati trovati. Fai riferimento a --help", @@ -437,8 +438,9 @@ it = { "server-disable-ready-argument": "disabilita la funzionalità \"pronto\"", "server-motd-argument": "percorso del file da cui verrà letto il messaggio del giorno", "server-chat-argument": "abilita o disabilita la chat", - "server-chat-maxchars-argument": "Numero massimo di caratteri in un messaggio di chat (default è {})", # Default number of characters - "server-maxusernamelength-argument": "Maximum number of charactrs in a username (default is {})", # TODO: Translate + "server-chat-maxchars-argument": "Numero massimo di caratteri in un messaggio di chat (default è {})", # Default number of characters + "server-maxusernamelength-argument": "Numero massimo di caratteri in un nome utente (default è {})", + "server-stats-db-file-argument": "Abilita la raccolta dei dati statistici nel file SQLite indicato", "server-messed-up-motd-unescaped-placeholders": "Il messaggio del giorno ha dei caratteri non 'escaped'. Tutti i simboli $ devono essere doppi ($$).", "server-messed-up-motd-too-long": "Il messaggio del giorno è troppo lungo - numero massimo di caratteri è {}, {} trovati.", diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 9daecb6..18eeebe 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -116,6 +116,7 @@ ru = { "empty-error": "{} не может быть пустым.", # Configuration "media-player-error": "Ошибка проигрывателя: \"{}\"", # Error line "unable-import-gui-error": "Невозможно импортировать библиотеки GUI (графического интерфейса). Необходимо установить PySide, иначе графический интерфейс не будет работать.", + "unable-import-twisted-error": "Could not import Twisted. Please install Twisted v12.1.0 or later.", #To do: translate "arguments-missing-error": "Некоторые необходимые аргументы отсутствуют, обратитесь к --help", @@ -440,9 +441,10 @@ ru = { "server-motd-argument": "путь к файлу, из которого будет извлекаться MOTD-сообщение", "server-chat-argument": "Should chat be disabled?", # TODO: Translate "server-chat-maxchars-argument": "Maximum number of characters in a chat message (default is {})", # TODO: Translate - "server-maxusernamelength-argument": "Maximum number of charactrs in a username (default is {})", # TODO: Translate - "server-messed-up-motd-unescaped-placeholders": "MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", - "server-messed-up-motd-too-long": "MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", + "server-maxusernamelength-argument": "Maximum number of characters in a username (default is {})", # TODO: Translate + "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", # TODO: Translate + "server-messed-up-motd-unescaped-placeholders" : "MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", + "server-messed-up-motd-too-long" : "MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", # Server errors "unknown-command-server-error": "Неизвестная команда: {}", # message @@ -460,7 +462,7 @@ ru = { "cannot-add-unsafe-path-error": "Не удалось автоматически переключиться на {}, потому что ссылка не соответствует доверенным сайтам. Её можно включить вручную, дважны кливнув по ссылке в списке воспроизведения. Добавить доверенный сайт можно в выпадающем меню 'Дополнительно' или просто кликнув по ссылке правой кнопкой мыши.", # Filename "sharedplaylistenabled-label": "Включить общий список воспроизведения", "removefromplaylist-menu-label": "Удалить", - "shufflepremaininglaylist-menuu-label": "Shuffle remaining playlist", # Was: Перемешать список # TODO: Translate + "shufflepremaininglaylist-menu-label": "Shuffle remaining playlist", # Was: Перемешать список # TODO: Translate "shuffleentireplaylist-menu-label": "Shuffle entire playlist", # TODO: Translate "undoplaylist-menu-label": "Отменить последнее действие", "addfilestoplaylist-menu-label": "Добавить файлы в очередь", diff --git a/syncplay/server.py b/syncplay/server.py index 4b8cdaa..d62d8d5 100755 --- a/syncplay/server.py +++ b/syncplay/server.py @@ -1,4 +1,3 @@ - import argparse import codecs import hashlib @@ -7,6 +6,7 @@ import random import time from string import Template +from twisted.enterprise import adbapi from twisted.internet import task, reactor from twisted.internet.protocol import Factory @@ -18,12 +18,14 @@ from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomString class SyncFactory(Factory): - def __init__(self, password='', motdFilePath=None, isolateRooms=False, salt=None, + def __init__(self, port='', password='', motdFilePath=None, isolateRooms=False, salt=None, disableReady=False, disableChat=False, maxChatMessageLength=constants.MAX_CHAT_MESSAGE_LENGTH, - maxUsernameLength=constants.MAX_USERNAME_LENGTH): + maxUsernameLength=constants.MAX_USERNAME_LENGTH, statsDbFile=None): self.isolateRooms = isolateRooms print(getMessage("welcome-server-notification").format(syncplay.version)) + self.port = port if password: + password = password.encode('utf-8') password = hashlib.md5(password).hexdigest() self.password = password if salt is None: @@ -39,6 +41,13 @@ class SyncFactory(Factory): self._roomManager = RoomManager() else: self._roomManager = PublicRoomManager() + if statsDbFile is not None: + self._statsDbHandle = DBManager(statsDbFile) + self._statsRecorder = StatsRecorder(self._statsDbHandle, self._roomManager) + statsDelay = 5*(int(self.port)%10 + 1) + self._statsRecorder.startRecorder(statsDelay) + else: + self._statsDbHandle = None def buildProtocol(self, addr): return SyncServerProtocol(self) @@ -186,6 +195,55 @@ class SyncFactory(Factory): watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex()) +class StatsRecorder(object): + def __init__(self, dbHandle, roomManager): + self._dbHandle = dbHandle + self._roomManagerHandle = roomManager + + def startRecorder(self, delay): + try: + self._dbHandle.connect() + reactor.callLater(delay, self._scheduleClientSnapshot) + except: + print("--- Error in initializing the stats database. Server Stats not enabled. ---") + + def _scheduleClientSnapshot(self): + self._clientSnapshotTimer = task.LoopingCall(self._runClientSnapshot) + self._clientSnapshotTimer.start(constants.SERVER_STATS_SNAPSHOT_INTERVAL) + + def _runClientSnapshot(self): + try: + snapshotTime = int(time.time()) + rooms = self._roomManagerHandle.exportRooms() + for room in rooms.values(): + for watcher in room.getWatchers(): + self._dbHandle.addVersionLog(snapshotTime, watcher.getVersion()) + except: + pass + + +class DBManager(object): + def __init__(self, dbpath): + self._dbPath = dbpath + self._connection = None + + def __del__(self): + if self._connection is not None: + self._connection.close() + + def connect(self): + self._connection = adbapi.ConnectionPool("sqlite3", self._dbPath, check_same_thread=False) + self._createSchema() + + def _createSchema(self): + initQuery = 'create table if not exists clients_snapshots (snapshot_time integer, version string)' + self._connection.runQuery(initQuery) + + def addVersionLog(self, timestamp, version): + content = (timestamp, version, ) + self._connection.runQuery("INSERT INTO clients_snapshots VALUES (?, ?)", content) + + class RoomManager(object): def __init__(self): self._rooms = {} @@ -244,6 +302,9 @@ class RoomManager(object): while username.lower() in allnames: username += '_' return username + + def exportRooms(self): + return self._rooms class PublicRoomManager(RoomManager): @@ -554,11 +615,12 @@ class ConfigurationGetter(object): 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")) + self._argparser.add_argument('--password', metavar='password', type=str, nargs='?', help=getMessage("server-password-argument"), default=os.environ.get('SYNCPLAY_PASSWORD')) self._argparser.add_argument('--isolate-rooms', action='store_true', help=getMessage("server-isolate-room-argument")) self._argparser.add_argument('--disable-ready', action='store_true', help=getMessage("server-disable-ready-argument")) self._argparser.add_argument('--disable-chat', action='store_true', help=getMessage("server-chat-argument")) - self._argparser.add_argument('--salt', metavar='salt', type=str, nargs='?', help=getMessage("server-salt-argument")) + self._argparser.add_argument('--salt', metavar='salt', type=str, nargs='?', help=getMessage("server-salt-argument"), default=os.environ.get('SYNCPLAY_SALT')) self._argparser.add_argument('--motd-file', metavar='file', type=str, nargs='?', help=getMessage("server-motd-argument")) self._argparser.add_argument('--max-chat-message-length', metavar='maxChatMessageLength', type=int, nargs='?', help=getMessage("server-chat-maxchars-argument").format(constants.MAX_CHAT_MESSAGE_LENGTH)) self._argparser.add_argument('--max-username-length', metavar='maxUsernameLength', type=int, nargs='?', help=getMessage("server-maxusernamelength-argument").format(constants.MAX_USERNAME_LENGTH)) + self._argparser.add_argument('--stats-db-file', metavar='file', type=str, nargs='?', help=getMessage("server-stats-db-file-argument")) diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 8af06f4..cc67d11 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -352,7 +352,7 @@ class ConfigurationGetter(object): def _getXdgConfigHome(self): path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) if not os.path.isdir(path): - os.mkdir(path, 0o755) + os.mkdir(path, 0o700) return path def _parseConfigFile(self, iniPath, createConfig=True): @@ -473,7 +473,7 @@ class ConfigurationGetter(object): self._argparser.add_argument('-p', '--password', metavar='password', type=str, nargs='?', help=getMessage("password-argument")) self._argparser.add_argument('--player-path', metavar='path', type=str, help=getMessage("player-path-argument")) self._argparser.add_argument('--language', metavar='language', type=str, help=getMessage("language-argument")) - self._argparser.add_argument('file', metavar='file', type=lambda s: str(s, 'utf8'), nargs='?', help=getMessage("file-argument")) + self._argparser.add_argument('file', metavar='file', type=str, nargs='?', help=getMessage("file-argument")) self._argparser.add_argument('--clear-gui-data', action='store_true', help=getMessage("clear-gui-data-argument")) self._argparser.add_argument('-v', '--version', action='store_true', help=getMessage("version-argument")) self._argparser.add_argument('_args', metavar='options', type=str, nargs='*', help=getMessage("args-argument")) @@ -496,6 +496,11 @@ class ConfigurationGetter(object): import appnope appnope.nope() except ImportError: + try: + from twisted.trial import unittest + except: + print(getMessage("unable-import-twisted-error")) + sys.exit() print(getMessage("unable-import-gui-error")) self._config['noGui'] = True if self._config['file'] and self._config['file'][:2] == "--": diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index e656518..05acea3 100755 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -972,12 +972,7 @@ class MainWindow(QtWidgets.QMainWindow): self.updatingPlaylist = True for URI in URIsToAdd: URI = URI.rstrip() - try: - URI = URI.encode('utf-8') - except UnicodeDecodeError: - pass URI = urllib.parse.unquote(URI) - URI = URI.decode('utf-8') if URI != "": self.addStreamToPlaylist(URI) self.updatingPlaylist = False diff --git a/syncplay/utils.py b/syncplay/utils.py index b9eafa8..50a1a24 100755 --- a/syncplay/utils.py +++ b/syncplay/utils.py @@ -476,8 +476,10 @@ class RoomPasswordProvider(object): @staticmethod def _computeRoomHash(roomName, password, salt): roomName = roomName.encode('utf8') - salt = hashlib.sha256(salt).hexdigest() - provisionalHash = hashlib.sha256(roomName + salt).hexdigest() + salt = salt.encode('utf8') + password = password.encode('utf8') + salt = hashlib.sha256(salt).hexdigest().encode('utf8') + provisionalHash = hashlib.sha256(roomName + salt).hexdigest().encode('utf8') return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper() diff --git a/syncplayServer.py b/syncplayServer.py index 3aa9866..28cfd1b 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -22,11 +22,14 @@ if __name__ == '__main__': reactor.listenTCP( int(args.port), SyncFactory( + args.port, args.password, args.motd_file, args.isolate_rooms, args.salt, args.disable_ready, args.disable_chat, - args.max_chat_message_length)) + args.max_chat_message_length, + args.max_username_length, + args.stats_db_file)) reactor.run()