Merge pull request #3 from Syncplay/master

Update with Master
This commit is contained in:
Daniel Ahn 2019-01-09 21:45:57 -08:00 committed by GitHub
commit bb5d53cc35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 170 additions and 54 deletions

View File

@ -1,7 +1,7 @@
environment: environment:
MINICONDA: "C:\\Miniconda" MINICONDA: "C:\\Miniconda"
PYTHON: "C:\\Python34" PYTHON: "C:\\Python35"
PYTHON_VERSION: 3.4 PYTHON_VERSION: 3.5
PYTHON_ARCH: 32 PYTHON_ARCH: 32
platform: x86 platform: x86
@ -15,11 +15,13 @@ init:
- python --version - python --version
- python -m pip install -U pip setuptools wheel - python -m pip install -U pip setuptools wheel
- pip install -U pypiwin32==219 - pip install -U pypiwin32==219
- pip install pyside
- pip install twisted - pip install twisted
- pip install py2exe
- pip install zope.interface - 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 - pip freeze
install: install:

View File

@ -18,11 +18,12 @@ before_install:
- python3 --version - python3 --version
- which pip3 - which pip3
- pip3 --version - pip3 --version
- brew install qt - curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/a57d0deab976cd8dee32404abe73f63cc5fbd60d/Formula/qt.rb
- 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 - brew install ./qt.rb
- pip3 install PySide2-5.11.0-5.11.0-cp36-cp36m-macosx_10_11_x86_64.whl - 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
- ln -s /usr/local/lib/python3.6/site-packages/PySide2/libpyside2.cpython-36m-darwin.5.11.dylib /usr/local/lib/ - 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/libshiboken2.cpython-36m-darwin.5.11.dylib /usr/local/lib/ - 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 import __version__; print(__version__)"
- python3 -c "from PySide2.QtCore import __version__; print(__version__)" - python3 -c "from PySide2.QtCore import __version__; print(__version__)"
- cd py2app - cd py2app
@ -31,9 +32,7 @@ before_install:
- python3 -c "from py2app.recipes import pyside2" - python3 -c "from py2app.recipes import pyside2"
install: install:
- pip3 install twisted appnope - pip3 install twisted appnope requests
- pip3 install -U https://github.com/requests/requests/zipball/master
before_deploy: before_deploy:
- pip3 install dmgbuild - pip3 install dmgbuild

View File

@ -61,7 +61,7 @@ u-common:
client: client:
-mkdir -p $(BIN_PATH) -mkdir -p $(BIN_PATH)
cp syncplayClient.py $(BIN_PATH)/syncplay 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 chmod 755 $(BIN_PATH)/syncplay
cp syncplayClient.py $(LIB_PATH)/syncplay/ cp syncplayClient.py $(LIB_PATH)/syncplay/
cp resources/syncplay.desktop $(APP_SHORTCUT_PATH)/ cp resources/syncplay.desktop $(APP_SHORTCUT_PATH)/
@ -87,7 +87,7 @@ u-client:
server: server:
-mkdir -p $(BIN_PATH) -mkdir -p $(BIN_PATH)
cp syncplayServer.py $(BIN_PATH)/syncplay-server 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 chmod 755 $(BIN_PATH)/syncplay-server
cp syncplayServer.py $(LIB_PATH)/syncplay/ cp syncplayServer.py $(LIB_PATH)/syncplay/
cp resources/syncplay-server.desktop $(APP_SHORTCUT_PATH)/ cp resources/syncplay-server.desktop $(APP_SHORTCUT_PATH)/

View File

@ -5,7 +5,7 @@
"subject": "syncplay" "subject": "syncplay"
}, },
"version": { "version": {
"name": "v1.5.6" "name": "v1.6.0"
}, },
"files": [ "files": [
{ {

59
buildPy2exe.py Executable file → Normal file
View File

@ -15,7 +15,6 @@ import sys
# import warnings # import warnings
# warnings.warn("You must build Syncplay with Python 2.7!") # warnings.warn("You must build Syncplay with Python 2.7!")
import os import os
import subprocess import subprocess
from string import Template from string import Template
@ -34,7 +33,6 @@ if missingStrings is not None and missingStrings is not "":
import warnings import warnings
warnings.warn("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings)) warnings.warn("MISSING/UNUSED STRINGS DETECTED:\n{}".format(missingStrings))
def get_nsis_path(): def get_nsis_path():
bin_name = "makensis.exe" bin_name = "makensis.exe"
from winreg import HKEY_LOCAL_MACHINE as HKLM from winreg import HKEY_LOCAL_MACHINE as HKLM
@ -49,7 +47,6 @@ def get_nsis_path():
except WindowsError: except WindowsError:
return bin_name return bin_name
NSIS_COMPILE = get_nsis_path() NSIS_COMPILE = get_nsis_path()
OUT_DIR = "syncplay_v{}".format(syncplay.version) OUT_DIR = "syncplay_v{}".format(syncplay.version)
@ -619,7 +616,6 @@ NSIS_SCRIPT_TEMPLATE = r"""
SectionEnd SectionEnd
""" """
class NSISScript(object): class NSISScript(object):
def create(self): def create(self):
fileList, totalSize = self.getBuildDirContents(OUT_DIR) fileList, totalSize = self.getBuildDirContents(OUT_DIR)
@ -676,17 +672,57 @@ class NSISScript(object):
delete.append('RMdir "$INSTDIR\\{}"'.format(file_)) delete.append('RMdir "$INSTDIR\\{}"'.format(file_))
return "\n".join(delete) 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): class build_installer(py2exe):
def run(self): def run(self):
py2exe.run(self) py2exe.run(self)
print('*** deleting unnecessary libraries and modules ***')
pruneUnneededLibraries()
print('*** copying qt plugins ***')
copyQtPlugins(qt_plugins)
script = NSISScript() script = NSISScript()
script.create() script.create()
print("*** compiling the NSIS setup script***") print("*** compiling the NSIS setup script ***")
script.compile() script.compile()
print("*** DONE ***") print("*** DONE ***")
guiIcons = [ guiIcons = [
'resources/accept.png', 'resources/arrow_undo.png', 'resources/clock_go.png', 'resources/accept.png', 'resources/arrow_undo.png', 'resources/clock_go.png',
'resources/control_pause_blue.png', 'resources/cross.png', 'resources/door_in.png', 'resources/control_pause_blue.png', 'resources/cross.png', 'resources/door_in.png',
@ -720,6 +756,8 @@ resources = [
resources.extend(guiIcons) resources.extend(guiIcons)
intf_resources = ["resources/lua/intf/syncplay.lua"] intf_resources = ["resources/lua/intf/syncplay.lua"]
qt_plugins = ['platforms\\qwindows.dll', 'styles\\qwindowsvistastyle.dll']
common_info = dict( common_info = dict(
name='Syncplay', name='Syncplay',
version=syncplay.version, version=syncplay.version,
@ -738,11 +776,12 @@ info = dict(
console=['syncplayServer.py'], 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: # *** 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"}], # console=['syncplayServer.py', {"script":"syncplayClient.py", "icon_resources":[(1, "resources\\icon.ico")], 'dest_base': "Syncplay"}],
options={ options={
'py2exe': { 'py2exe': {
'dist_dir': OUT_DIR, 'dist_dir': OUT_DIR,
'packages': 'PySide.QtUiTools', 'packages': 'PySide2',
'includes': 'twisted, sys, encodings, datetime, os, time, math, PySide, liburl, ast, unicodedata, _ssl', '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', 'excludes': 'venv, doctest, pdb, unittest, win32clipboard, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process, Tkinter',
'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll', 'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll',
'optimize': 2, 'optimize': 2,
@ -754,5 +793,5 @@ info = dict(
cmdclass={"py2exe": build_installer}, cmdclass={"py2exe": build_installer},
) )
sys.argv.extend(['py2exe', '-p win32com ', '-i twisted.web.resource', '-i PySide.QtCore', '-i PySide.QtGui']) sys.argv.extend(['py2exe', '-p win32com ', '-i twisted.web.resource', '-p PySide2'])
setup(**info) setup(**info)

View File

@ -1,5 +1,5 @@
version = '1.5.6' version = '1.6.1'
revision = ' beta' revision = ''
milestone = 'Yoitsu' milestone = 'Yoitsu'
release_number = '66' release_number = '70'
projectURL = 'https://syncplay.pl/' projectURL = 'https://syncplay.pl/'

View File

@ -9,7 +9,7 @@ MPLAYER_OSD_LEVEL = 1
UI_TIME_FORMAT = "[%X] " UI_TIME_FORMAT = "[%X] "
CONFIG_NAMES = [".syncplay", "syncplay.ini"] # Syncplay searches first to last CONFIG_NAMES = [".syncplay", "syncplay.ini"] # Syncplay searches first to last
DEFAULT_CONFIG_NAME = "syncplay.ini" 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 WARN_OLD_CLIENTS = True # Use MOTD to inform old clients to upgrade
LIST_RELATIVE_CONFIGS = True # Print list of relative configs loaded LIST_RELATIVE_CONFIGS = True # Print list of relative configs loaded
SHOW_CONTACT_INFO = True # Displays dev contact details below list in GUI 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 PROTOCOL_TIMEOUT = 12.5
RECONNECT_RETRIES = 999 RECONNECT_RETRIES = 999
SERVER_STATE_INTERVAL = 1 SERVER_STATE_INTERVAL = 1
SERVER_STATS_SNAPSHOT_INTERVAL = 3600
WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1 WARNING_OSD_MESSAGES_LOOP_INTERVAL = 1
AUTOPLAY_DELAY = 3.0 AUTOPLAY_DELAY = 3.0
DO_NOT_RESET_POSITION_THRESHOLD = 1.0 DO_NOT_RESET_POSITION_THRESHOLD = 1.0

View File

@ -116,6 +116,7 @@ de = {
"empty-error": "{} darf nicht leer sein", # Configuration "empty-error": "{} darf nicht leer sein", # Configuration
"media-player-error": "Player-Fehler: \"{}\"", # Error line "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-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", "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-motd-argument": "Pfad zur Datei, von der die Nachricht des Tages geladen wird",
"server-chat-argument": "Should chat be disabled?", # TODO: Translate "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-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-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 {}.", "server-messed-up-motd-too-long": "Die Nachricht des Tages ist zu lang - Maximal {} Zeichen, aktuell {}.",

View File

@ -115,6 +115,7 @@ en = {
"empty-error": "{} can't be empty", # Configuration "empty-error": "{} can't be empty", # Configuration
"media-player-error": "Media player error: \"{}\"", # Error line "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-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", "arguments-missing-error": "Some necessary arguments are missing, refer to --help",
@ -437,8 +438,9 @@ en = {
"server-disable-ready-argument": "disable readiness feature", "server-disable-ready-argument": "disable readiness feature",
"server-motd-argument": "path to file from which motd will be fetched", "server-motd-argument": "path to file from which motd will be fetched",
"server-chat-argument": "Should chat be disabled?", "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-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-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-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.", "server-messed-up-motd-too-long": "Message of the Day is too long - maximum of {} chars, {} given.",

View File

@ -115,6 +115,7 @@ it = {
"empty-error": "Il campo {} non può esssere vuoto", # Configuration "empty-error": "Il campo {} non può esssere vuoto", # Configuration
"media-player-error": "Errore media player: \"{}\"", # Error line "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-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", "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-disable-ready-argument": "disabilita la funzionalità \"pronto\"",
"server-motd-argument": "percorso del file da cui verrà letto il messaggio del giorno", "server-motd-argument": "percorso del file da cui verrà letto il messaggio del giorno",
"server-chat-argument": "abilita o disabilita la chat", "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-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-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-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.", "server-messed-up-motd-too-long": "Il messaggio del giorno è troppo lungo - numero massimo di caratteri è {}, {} trovati.",

View File

@ -116,6 +116,7 @@ ru = {
"empty-error": "{} не может быть пустым.", # Configuration "empty-error": "{} не может быть пустым.", # Configuration
"media-player-error": "Ошибка проигрывателя: \"{}\"", # Error line "media-player-error": "Ошибка проигрывателя: \"{}\"", # Error line
"unable-import-gui-error": "Невозможно импортировать библиотеки GUI (графического интерфейса). Необходимо установить PySide, иначе графический интерфейс не будет работать.", "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", "arguments-missing-error": "Некоторые необходимые аргументы отсутствуют, обратитесь к --help",
@ -440,9 +441,10 @@ ru = {
"server-motd-argument": "путь к файлу, из которого будет извлекаться MOTD-сообщение", "server-motd-argument": "путь к файлу, из которого будет извлекаться MOTD-сообщение",
"server-chat-argument": "Should chat be disabled?", # TODO: Translate "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-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-messed-up-motd-unescaped-placeholders": "MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).", "server-stats-db-file-argument": "Enable server stats using the SQLite db file provided", # TODO: Translate
"server-messed-up-motd-too-long": "MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).", "server-messed-up-motd-unescaped-placeholders" : "MOTD-сообщение содержит неэкранированные спец.символы. Все знаки $ должны быть продублированы ($$).",
"server-messed-up-motd-too-long" : "MOTD-сообщение слишком длинное: максимальная длина - {} символ(ов), текущая длина - {} символ(ов).",
# Server errors # Server errors
"unknown-command-server-error": "Неизвестная команда: {}", # message "unknown-command-server-error": "Неизвестная команда: {}", # message
@ -460,7 +462,7 @@ ru = {
"cannot-add-unsafe-path-error": "Не удалось автоматически переключиться на {}, потому что ссылка не соответствует доверенным сайтам. Её можно включить вручную, дважны кливнув по ссылке в списке воспроизведения. Добавить доверенный сайт можно в выпадающем меню 'Дополнительно' или просто кликнув по ссылке правой кнопкой мыши.", # Filename "cannot-add-unsafe-path-error": "Не удалось автоматически переключиться на {}, потому что ссылка не соответствует доверенным сайтам. Её можно включить вручную, дважны кливнув по ссылке в списке воспроизведения. Добавить доверенный сайт можно в выпадающем меню 'Дополнительно' или просто кликнув по ссылке правой кнопкой мыши.", # Filename
"sharedplaylistenabled-label": "Включить общий список воспроизведения", "sharedplaylistenabled-label": "Включить общий список воспроизведения",
"removefromplaylist-menu-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 "shuffleentireplaylist-menu-label": "Shuffle entire playlist", # TODO: Translate
"undoplaylist-menu-label": "Отменить последнее действие", "undoplaylist-menu-label": "Отменить последнее действие",
"addfilestoplaylist-menu-label": "Добавить файлы в очередь", "addfilestoplaylist-menu-label": "Добавить файлы в очередь",

View File

@ -1,4 +1,3 @@
import argparse import argparse
import codecs import codecs
import hashlib import hashlib
@ -7,6 +6,7 @@ import random
import time import time
from string import Template from string import Template
from twisted.enterprise import adbapi
from twisted.internet import task, reactor from twisted.internet import task, reactor
from twisted.internet.protocol import Factory from twisted.internet.protocol import Factory
@ -18,12 +18,14 @@ from syncplay.utils import RoomPasswordProvider, NotControlledRoom, RandomString
class SyncFactory(Factory): 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, 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 self.isolateRooms = isolateRooms
print(getMessage("welcome-server-notification").format(syncplay.version)) print(getMessage("welcome-server-notification").format(syncplay.version))
self.port = port
if password: if password:
password = password.encode('utf-8')
password = hashlib.md5(password).hexdigest() password = hashlib.md5(password).hexdigest()
self.password = password self.password = password
if salt is None: if salt is None:
@ -39,6 +41,13 @@ class SyncFactory(Factory):
self._roomManager = RoomManager() self._roomManager = RoomManager()
else: else:
self._roomManager = PublicRoomManager() 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): def buildProtocol(self, addr):
return SyncServerProtocol(self) return SyncServerProtocol(self)
@ -186,6 +195,55 @@ class SyncFactory(Factory):
watcher.setPlaylistIndex(room.getName(), room.getPlaylistIndex()) 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): class RoomManager(object):
def __init__(self): def __init__(self):
self._rooms = {} self._rooms = {}
@ -244,6 +302,9 @@ class RoomManager(object):
while username.lower() in allnames: while username.lower() in allnames:
username += '_' username += '_'
return username return username
def exportRooms(self):
return self._rooms
class PublicRoomManager(RoomManager): class PublicRoomManager(RoomManager):
@ -554,11 +615,12 @@ class ConfigurationGetter(object):
description=getMessage("server-argument-description"), 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"), 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('--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-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('--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('--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-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('--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"))

View File

@ -352,7 +352,7 @@ class ConfigurationGetter(object):
def _getXdgConfigHome(self): def _getXdgConfigHome(self):
path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
if not os.path.isdir(path): if not os.path.isdir(path):
os.mkdir(path, 0o755) os.mkdir(path, 0o700)
return path return path
def _parseConfigFile(self, iniPath, createConfig=True): 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('-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('--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('--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('--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('-v', '--version', action='store_true', help=getMessage("version-argument"))
self._argparser.add_argument('_args', metavar='options', type=str, nargs='*', help=getMessage("args-argument")) self._argparser.add_argument('_args', metavar='options', type=str, nargs='*', help=getMessage("args-argument"))
@ -496,6 +496,11 @@ class ConfigurationGetter(object):
import appnope import appnope
appnope.nope() appnope.nope()
except ImportError: except ImportError:
try:
from twisted.trial import unittest
except:
print(getMessage("unable-import-twisted-error"))
sys.exit()
print(getMessage("unable-import-gui-error")) print(getMessage("unable-import-gui-error"))
self._config['noGui'] = True self._config['noGui'] = True
if self._config['file'] and self._config['file'][:2] == "--": if self._config['file'] and self._config['file'][:2] == "--":

View File

@ -972,12 +972,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.updatingPlaylist = True self.updatingPlaylist = True
for URI in URIsToAdd: for URI in URIsToAdd:
URI = URI.rstrip() URI = URI.rstrip()
try:
URI = URI.encode('utf-8')
except UnicodeDecodeError:
pass
URI = urllib.parse.unquote(URI) URI = urllib.parse.unquote(URI)
URI = URI.decode('utf-8')
if URI != "": if URI != "":
self.addStreamToPlaylist(URI) self.addStreamToPlaylist(URI)
self.updatingPlaylist = False self.updatingPlaylist = False

View File

@ -476,8 +476,10 @@ class RoomPasswordProvider(object):
@staticmethod @staticmethod
def _computeRoomHash(roomName, password, salt): def _computeRoomHash(roomName, password, salt):
roomName = roomName.encode('utf8') roomName = roomName.encode('utf8')
salt = hashlib.sha256(salt).hexdigest() salt = salt.encode('utf8')
provisionalHash = hashlib.sha256(roomName + salt).hexdigest() 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() return hashlib.sha1(provisionalHash + salt + password).hexdigest()[:12].upper()

View File

@ -22,11 +22,14 @@ if __name__ == '__main__':
reactor.listenTCP( reactor.listenTCP(
int(args.port), int(args.port),
SyncFactory( SyncFactory(
args.port,
args.password, args.password,
args.motd_file, args.motd_file,
args.isolate_rooms, args.isolate_rooms,
args.salt, args.salt,
args.disable_ready, args.disable_ready,
args.disable_chat, args.disable_chat,
args.max_chat_message_length)) args.max_chat_message_length,
args.max_username_length,
args.stats_db_file))
reactor.run() reactor.run()