diff --git a/buildPy2exe.py b/buildPy2exe.py index 3d7ef7b..fa428f9 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -68,6 +68,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^StartMenu $${LANG_ENGLISH} "Start Menu" LangString ^Desktop $${LANG_ENGLISH} "Desktop" LangString ^QuickLaunchBar $${LANG_ENGLISH} "Quick Launch Bar" + LangString ^AutomaticUpdates $${LANG_ENGLISH} "Check for updates automatically" LangString ^UninstConfig $${LANG_ENGLISH} "Delete configuration file." LangString ^SyncplayLanguage $${LANG_POLISH} "pl" @@ -88,6 +89,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^StartMenu $${LANG_RUSSIAN} "в меню Пуск" LangString ^Desktop $${LANG_RUSSIAN} "на рабочем столе" LangString ^QuickLaunchBar $${LANG_RUSSIAN} "в меню быстрого запуска" + LangString ^AutomaticUpdates $${LANG_RUSSIAN} "Проверять обновления автоматически"; TODO: Confirm Russian translation ("Check for updates automatically") LangString ^UninstConfig $${LANG_RUSSIAN} "Удалить файл настроек." LangString ^SyncplayLanguage $${LANG_GERMAN} "de" @@ -98,6 +100,7 @@ NSIS_SCRIPT_TEMPLATE = r""" LangString ^StartMenu $${LANG_GERMAN} "Startmenü" LangString ^Desktop $${LANG_GERMAN} "Desktop" LangString ^QuickLaunchBar $${LANG_GERMAN} "Schnellstartleiste" + LangString ^AutomaticUpdates $${LANG_GERMAN} "Automatisch nach Updates suchen"; TODO: Confirm German translation ("Check for updates automatically") LangString ^UninstConfig $${LANG_GERMAN} "Konfigurationsdatei löschen." ; Remove text to save space @@ -117,11 +120,13 @@ NSIS_SCRIPT_TEMPLATE = r""" Var Icon_Syncplay_Handle Var CheckBox_Associate Var CheckBox_VLC + Var CheckBox_AutomaticUpdates Var CheckBox_StartMenuShortcut Var CheckBox_DesktopShortcut Var CheckBox_QuickLaunchShortcut Var CheckBox_Associate_State Var CheckBox_VLC_State + Var CheckBox_AutomaticUpdates_State Var CheckBox_StartMenuShortcut_State Var CheckBox_DesktopShortcut_State Var CheckBox_QuickLaunchShortcut_State @@ -237,10 +242,10 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_CreateGroupBox} 1u 27u 264u 30u "$$(^DirSubText)" Pop $$GroupBox_DirSub - $${NSD_CreateLabel} 0u 111u 265u 8u "$$(^SpaceRequired)$$SizeMB" + $${NSD_CreateLabel} 0u 122u 132 8u "$$(^SpaceRequired)$$SizeMB" Pop $$Label_Size - $${NSD_CreateLabel} 0u 122u 265u 8u "$$(^SpaceAvailable)$$AvailibleSpaceGiB.$$AvailibleSpaceGB" + $${NSD_CreateLabel} 321u 122u 132 8u "$$(^SpaceAvailable)$$AvailibleSpaceGiB.$$AvailibleSpaceGB" Pop $$Label_Space $${NSD_CreateCheckBox} 8u 59u 187u 10u "$$(^Associate)" @@ -253,16 +258,20 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_CreateCheckBox} 8u 72u 250u 10u "$$(^VLC)" Pop $$CheckBox_VLC - $${NSD_CreateLabel} 8u 85u 187u 10u "$$(^Shortcut)" + $${NSD_CreateCheckBox} 8u 85u 250u 10u "$$(^AutomaticUpdates)" + Pop $$CheckBox_AutomaticUpdates + $${NSD_Check} $$CheckBox_AutomaticUpdates + + $${NSD_CreateLabel} 8u 98u 187u 10u "$$(^Shortcut)" Pop $$Label_Shortcut - $${NSD_CreateCheckbox} 8u 98u 60u 10u "$$(^StartMenu)" + $${NSD_CreateCheckbox} 8u 111u 60u 10u "$$(^StartMenu)" Pop $$CheckBox_StartMenuShortcut - $${NSD_CreateCheckbox} 78u 98u 70u 10u "$$(^Desktop)" + $${NSD_CreateCheckbox} 78u 111u 70u 10u "$$(^Desktop)" Pop $$CheckBox_DesktopShortcut - $${NSD_CreateCheckbox} 158u 98u 130u 10u "$$(^QuickLaunchBar)" + $${NSD_CreateCheckbox} 158u 111u 130u 10u "$$(^QuickLaunchBar)" Pop $$CheckBox_QuickLaunchShortcut $${If} $$CheckBox_Associate_State == $${BST_CHECKED} @@ -287,6 +296,10 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_Check} $$CheckBox_QuickLaunchShortcut $${EndIf} + $${If} $$CheckBox_AutomaticUpdates_State == $${BST_CHECKED} + $${NSD_Check} $$CheckBox_AutomaticUpdates + $${EndIf} + nsDialogs::Show $${NSD_FreeIcon} $$Icon_Syncplay_Handle @@ -297,6 +310,7 @@ NSIS_SCRIPT_TEMPLATE = r""" $${NSD_GetText} $$Text_Directory $$INSTDIR $${NSD_GetState} $$CheckBox_Associate $$CheckBox_Associate_State $${NSD_GetState} $$CheckBox_VLC $$CheckBox_VLC_State + $${NSD_GetState} $$CheckBox_AutomaticUpdates $$CheckBox_AutomaticUpdates_State $${NSD_GetState} $$CheckBox_StartMenuShortcut $$CheckBox_StartMenuShortcut_State $${NSD_GetState} $$CheckBox_DesktopShortcut $$CheckBox_DesktopShortcut_State $${NSD_GetState} $$CheckBox_QuickLaunchShortcut $$CheckBox_QuickLaunchShortcut_State @@ -444,6 +458,11 @@ NSIS_SCRIPT_TEMPLATE = r""" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Syncplay" "NoRepair" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Syncplay" "EstimatedSize" "$$SizeHex" WriteINIStr $$APPDATA\syncplay.ini general language $$(^SyncplayLanguage) + $${If} $$CheckBox_AutomaticUpdates_State == $${BST_CHECKED} + WriteINIStr $$APPDATA\syncplay.ini general CheckForUpdatesAutomatically "True" + $${Else} + WriteINIStr $$APPDATA\syncplay.ini general CheckForUpdatesAutomatically "False" + $${EndIf} FunctionEnd Function un.installConfirm @@ -625,7 +644,7 @@ guiIcons = ['resources/accept.png', 'resources/arrow_undo.png', 'resources/clock 'resources/eye.png', 'resources/comments.png', 'resources/cog_delete.png', 'resources/chevrons_right.png', 'resources/user_key.png', 'resources/lock.png', 'resources/key_go.png', 'resources/page_white_key.png', 'resources/tick.png', 'resources/lock_open.png', 'resources/cross_checkbox.png', 'resources/tick_checkbox.png', - 'resources/world_explore.png' + 'resources/world_explore.png', 'resources/application_get.png' ] resources = ["resources/icon.ico", "resources/syncplay.png"] resources.extend(guiIcons) diff --git a/resources/application_get.png b/resources/application_get.png new file mode 100644 index 0000000..28e41ea Binary files /dev/null and b/resources/application_get.png differ diff --git a/syncplay/__init__.py b/syncplay/__init__.py index e40e81d..e84a065 100644 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,3 +1,4 @@ version = '1.3.0' milestone = 'Chami' +release_number = '1' projectURL = 'http://syncplay.pl/' diff --git a/syncplay/client.py b/syncplay/client.py index 5bbaf88..00899b8 100644 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -548,6 +548,21 @@ class SyncplayClient(object): if self.controlpasswords.has_key(room): return self.controlpasswords[room] + def checkForUpdate(self): + try: + import urllib, syncplay, sys, messages, json + params = urllib.urlencode({'version': syncplay.version, 'milestone': syncplay.milestone, 'release_number': syncplay.release_number, + 'language': messages.messages["CURRENT"], 'platform': sys.platform}) + print params + + f = urllib.urlopen(constants.SYNCPLAY_UPDATE_URL.format(params)) + response = f.read() + response = response.replace("

","").replace("

","").replace("
","").replace("“","\"").replace("”","\"") # Fix Wordpress + response = json.loads(response) + return response["version-status"], response["version-message"] if response.has_key("version-message") else None, response["version-url"] if response.has_key("version-url") else None + except: + return "failed", getMessage("update-check-failed-notification").format(syncplay.version), constants.SYNCPLAY_DOWNLOAD_URL + class _WarningManager(object): def __init__(self, player, userlist, ui, client): self._client = client diff --git a/syncplay/constants.py b/syncplay/constants.py index c99355a..3e741f1 100644 --- a/syncplay/constants.py +++ b/syncplay/constants.py @@ -29,6 +29,7 @@ SHOW_DURATION_NOTIFICATION = True DEBUG_MODE = False #Changing these might be ok +AUTOMATIC_UPDATE_CHECK_FREQUENCY = 7 * 86400 # Days converted into seconds DEFAULT_REWIND_THRESHOLD = 4 MINIMUM_REWIND_THRESHOLD = 3 DEFAULT_FASTFORWARD_THRESHOLD = 5 @@ -157,3 +158,6 @@ CONFIG_NAME_MARKER = ":" CONFIG_VALUE_MARKER = "=" USERITEM_CONTROLLER_ROLE = 0 USERITEM_READY_ROLE = 1 + +SYNCPLAY_UPDATE_URL = u"http://syncplay.pl/checkforupdate?{}" # Params +SYNCPLAY_DOWNLOAD_URL = "http://syncplay.pl/download/" \ No newline at end of file diff --git a/syncplay/messages.py b/syncplay/messages.py index 0f3532e..3e4b0bf 100755 --- a/syncplay/messages.py +++ b/syncplay/messages.py @@ -57,6 +57,10 @@ en = { "userlist-file-notification" : "File", "controller-userlist-userflag" : "Controller", "ready-userlist-userflag" : "Ready", + + "update-check-failed-notification" : u"Could not automatically check whether Syncplay {} is up to date. Want to visit http://syncplay.pl/ to manually check for updates?", #Syncplay version + "syncplay-uptodate-notification" : u"Syncplay is up to date", + "syncplay-updateavailable-notification" : u"A new version of Syncplay is available. Do you want to download it?", "mplayer-file-required-notification" : "Syncplay using mplayer requires you to provide file when starting", "mplayer-file-required-notification/example" : "Usage example: syncplay [options] [url|path/]filename", @@ -78,6 +82,7 @@ en = { "gui-data-cleared-notification" : "Syncplay has cleared the path and window state data used by the GUI.", "language-changed-msgbox-label" : "Language will be changed when you run Syncplay.", + "promptforupdate-label" : u"Is it okay for Syncplay to automatically check for updates from time to time?", "vlc-version-mismatch": "Warning: You are running VLC version {}, but Syncplay is designed to run on VLC {} and above.", # VLC version, VLC min version "vlc-interface-version-mismatch": "Warning: You are running version {} of the Syncplay interface module for VLC, but Syncplay is designed to run with version {} and above.", # VLC interface version, VLC interface min version @@ -163,6 +168,7 @@ en = { "privacy-dontsend-option" : "Don't send", "filename-privacy-label" : "Filename information:", "filesize-privacy-label" : "File size information:", + "checkforupdatesautomatically-label" : "Check for updates automatically", "slowondesync-label" : "Slow down on minor desync (not supported on MPC-HC)", "rewindondesync-label" : "Rewind on major desync (highly recommended)", "fastforwardondesync-label" : "Fast-forward if lagging behind (recommended)", @@ -231,6 +237,7 @@ en = { "help-menu-label" : "&Help", "userguide-menu-label" : "Open user &guide", + "update-menu-label" : "Check for &update", "setoffset-msgbox-label" : "Set offset", "offsetinfo-msgbox-label" : "Offset (see http://syncplay.pl/guide/ for usage instructions):", @@ -265,6 +272,7 @@ en = { "privacy-sendraw-tooltip" : "Send this information without obfuscation. This is the default option with most functionality.", "privacy-sendhashed-tooltip" : "Send a hashed version of the information, making it less visible to other clients.", "privacy-dontsend-tooltip" : "Do not send this information to the server. This provides for maximum privacy.", + "checkforupdatesautomatically-tooltip" : "Regularly check with the Syncplay website to see whether a new version of Syncplay is available.", "slowondesync-tooltip" : "Reduce playback rate temporarily when needed to bring you back in sync with other viewers. Not supported on MPC-HC.", "dontslowdownwithme-tooltip" : "Means others do not get slowed down or rewinded if your playback is lagging. Useful for room controllers.", "pauseonleave-tooltip" : "Pause playback if you get disconnected or someone leaves from your room.", @@ -386,6 +394,10 @@ ru = { "userlist-file-notification" : u"File", # TODO: Translate into Russian (Файл?) "controller-userlist-userflag" : u"Controller", # TODO: Translate into Russian (this is to indicate a user is a controller in the ConsoleUI userlist) "ready-userlist-userflag" : u"Ready", # TODO: Translate into Russian (this is to indicate a user is ready to watch in the ConsoleUI userlist) + + "update-check-failed-notification" : u"Could not automatically check whether Syncplay {} is up to date. Want to visit http://syncplay.pl/ to manually check for updates?", #Syncplay version # TODO: Translate into Russian + "syncplay-uptodate-notification" : u"Syncplay is up to date", # TODO: Translate into Russian + "syncplay-updateavailable-notification" : u"A new version of Syncplay is available. Do you want to download it?", # TODO: Translate into Russian "mplayer-file-required-notification" : u"Для использования Syncplay с mplayer необходимо передать файл в качестве параметра", "mplayer-file-required-notification/example" : u"Пример использования: syncplay [options] [url|path/]filename", @@ -406,7 +418,8 @@ ru = { "more-info-notification" : u"Больше информации на {}", # projectURL "gui-data-cleared-notification" : u"Syncplay очистил путь и информацию о состоянии окна, использованного GUI.", - "language-changed-msgbox-label" : u"Language will be changed when you run Syncplay.", + "language-changed-msgbox-label" : u"Language will be changed when you run Syncplay.", # TODO: Translate into Russian + "promptforupdate-label" : u"Is it okay for Syncplay to automatically check for updates from time to time?", # TODO: Translate into Russian "vlc-version-mismatch" : u"Внимание: Вы используете VLC устаревшей версии {}. К сожалению, Syncplay способен работать с VLC {} и выше.", # VLC version, VLC min version "vlc-interface-version-mismatch" : u"Внимание: В используете модуль интерфейса Syncplay устаревшей версии {} для VLC. К сожалению, Syncplay способен работать с версией {} и выше.", # VLC interface version, VLC interface min version @@ -492,6 +505,7 @@ ru = { "privacy-dontsend-option" : u"не отпр.", "filename-privacy-label" : u"Имя файла:", "filesize-privacy-label" : u"Размер файла:", + "checkforupdatesautomatically-label" : u"Проверять обновления автоматически", # TODO: Confirm Russian translation "slowondesync-label" : u"Замедлять при небольших рассинхронизациях (не поддерживаетя в MPC-HC)", "rewindondesync-label" : u"Перемотка при больших рассинхронизациях (настоятельно рекомендуется)", "dontslowdownwithme-label" : u"Никогда не замедлять или перематывать видео другим", # TODO: Update new wording into Russian (should state "Experimental" in brackets at the end) @@ -560,6 +574,7 @@ ru = { "help-menu-label" : u"&Помощь", "userguide-menu-label" : u"&Руководство Пользователя", + "update-menu-label" : u"Check for &update", # TODO: Translate into Russian "setoffset-msgbox-label" : u"Установить смещение", "offsetinfo-msgbox-label" : u"Смещение (см. как использовать на http://syncplay.pl/guide/):", @@ -594,6 +609,7 @@ ru = { "privacy-sendraw-tooltip" : u"Отправляет эту информацию без шифрования. Рекомендуемая опция с наибольшей функциональностью.", "privacy-sendhashed-tooltip" : u"Отправляет хэш-сумму этой информации, делая ее невидимой для других пользователей.", "privacy-dontsend-tooltip" : u"Не отправлять эту информацию на сервер. Предоставляет наибольшую приватность.", + "checkforupdatesautomatically-tooltip" : u"Regularly check with the Syncplay website to see whether a new version of Syncplay is available.", # TODO: Translate into Russian "slowondesync-tooltip" : u"Временно уменьшить скорость воспроизведения в целях синхронизации с другими зрителями. Не поддерживается в MPC-HC.", "dontslowdownwithme-tooltip" : u"Ваши лаги не будут влиять на других зрителей.", "pauseonleave-tooltip" : u"Приостановить воспроизведение, если Вы покинули комнату или кто-то из зрителей отключился от сервера.", @@ -714,7 +730,11 @@ de = { "userlist-room-notification" : u"In Raum '{}':", # Room "userlist-file-notification" : u"File", # TODO: Translate into German (Datei?) "controller-userlist-userflag" : u"Controller", # TODO: Translate into German (this is to indicate a user is a controller in the ConsoleUI userlist) - "ready-userlist-userflag" : u"Ready", # TODO: Translate into Germany (this is to indicate a user is ready to watch in the ConsoleUI userlist) + "ready-userlist-userflag" : u"Ready", # TODO: Translate into German (this is to indicate a user is ready to watch in the ConsoleUI userlist) + + "update-check-failed-notification" : u"Could not automatically check whether Syncplay {} is up to date. Want to visit http://syncplay.pl/ to manually check for updates?", #Syncplay version # TODO: Translate into German + "syncplay-uptodate-notification" : u"Syncplay is up to date", # TODO: Translate into German + "syncplay-updateavailable-notification" : u"A new version of Syncplay is available. Do you want to download it?", # TODO: Translate into German "mplayer-file-required-notification" : u"Syncplay für mplayer benötigt eine Datei-Angabe beim Start", "mplayer-file-required-notification/example" : u"Anwendungsbeispiel: syncplay [optionen] [url|pfad/]Dateiname", @@ -736,6 +756,7 @@ de = { "gui-data-cleared-notification" : u"Syncplay hat die Pfad und Fensterdaten der Syncplay-GUI zurückgesetzt.", "language-changed-msgbox-label" : u"Die Sprache wird geändert, wenn du Syncplay neu startest.", + "promptforupdate-label" : u"Is it okay for Syncplay to automatically check for updates from time to time?", # TODO: Translate into German "vlc-version-mismatch": u"Warnung: Du nutzt VLC Version {}, aber Syncplay wurde für VLC ab Version {} entwickelt.", # VLC version, VLC min version "vlc-interface-version-mismatch": u"Warnung: Du nutzt Version {} des VLC-Syncplay Interface-Moduls, Syncplay benötigt aber mindestens Version {}.", # VLC interface version, VLC interface min version @@ -821,6 +842,7 @@ de = { "privacy-dontsend-option" : u"Nicht senden", "filename-privacy-label" : u"Dateiname:", "filesize-privacy-label" : u"Dateigröße:", + "checkforupdatesautomatically-label" : u"Automatisch nach Updates suche", # TODO: Confirm German translation "slowondesync-label" : u"Verlangsamen wenn nicht synchron (nicht unterstützt mit MPC-HC)", "dontslowdownwithme-label" : u"Nie verlangsamen oder andere zurückspulen (Experimentell)", "pauseonleave-label" : u"Pausieren wenn ein Benutzer austritt", @@ -887,6 +909,7 @@ de = { "help-menu-label" : u"&Hilfe", "userguide-menu-label" : u"&Benutzerhandbuch öffnen", + "update-menu-label" : u"Check for &update", # TODO: Translate into Russian "setoffset-msgbox-label" : u"Offset einstellen", "offsetinfo-msgbox-label" : u"Offset (siehe http://syncplay.pl/guide/ für eine Anleitung [Englisch]):", @@ -921,6 +944,7 @@ de = { "privacy-sendraw-tooltip" : u"Die Information im Klartext übertragen. Dies ist die Standard-Einstellung mit der besten Funktionalität.", "privacy-sendhashed-tooltip" : u"Die Informationen gehasht übertragen, um sie für andere Clients schwerer lesbar zu machen.", "privacy-dontsend-tooltip" : u"Diese Information nicht übertragen. Dies garantiert den größtmöglichen Datanschutz.", + "checkforupdatesautomatically-tooltip" : u"Regularly check with the Syncplay website to see whether a new version of Syncplay is available.", # TODO: Translate into German "slowondesync-tooltip" : u"Reduziert die Abspielgeschwindigkeit zeitweise, um die Synchronität zu den anderen Clients wiederherzustellen.", "rewindondesync-label" : u"Zurückspulen bei großer Zeitdifferenz (empfohlen)", "fastforwardondesync-label" : u"Vorspulen wenn das Video lagt (empfohlen)", diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index 89ce26b..adcb86a 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -47,6 +47,8 @@ class ConfigurationGetter(object): "readyAtStart": False, "clearGUIData": False, "language" : "", + "checkForUpdatesAutomatically" : None, + "lastCheckedForUpdates" : "", "resetConfig" : False, "showOSD" : True, "showOSDWarnings" : True, @@ -95,6 +97,9 @@ class ConfigurationGetter(object): "showContactInfo" , "showDurationNotification" ] + self._tristate = [ + "checkForUpdatesAutomatically" + ] self._numeric = [ "slowdownThreshold", @@ -106,7 +111,7 @@ class ConfigurationGetter(object): "server_data": ["host", "port", "password"], "client_settings": ["name", "room", "playerPath", "slowdownThreshold", "rewindThreshold", "fastforwardThreshold", "slowOnDesync", "rewindOnDesync", "fastforwardOnDesync", "dontSlowDownWithMe", "forceGuiPrompt", "filenamePrivacyMode", "filesizePrivacyMode", "pauseOnLeave", "readyAtStart"], "gui": ["showOSD", "showOSDWarnings", "showSlowdownOSD", "showDifferentRoomOSD", "showSameRoomOSD", "showNonControllerOSD", "showContactInfo" , "showDurationNotification"], - "general": ["language"] + "general": ["language", "checkForUpdatesAutomatically", "lastCheckedForUpdates"] } self._playerFactory = PlayerFactory() @@ -139,6 +144,14 @@ class ConfigurationGetter(object): elif self._config[key] == "False": self._config[key] = False + for key in self._tristate: + if self._config[key] == "True": + self._config[key] = True + elif self._config[key] == "False": + self._config[key] = False + elif self._config[key] == "None": + self._config[key] = None + for key in self._numeric: self._config[key] = float(self._config[key]) diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 9c1b0f1..adbf1b6 100644 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -1,8 +1,8 @@ from PySide import QtCore, QtGui -from PySide.QtCore import QSettings, Qt, QCoreApplication +from PySide.QtCore import QSettings, Qt, QCoreApplication, QUrl from PySide.QtGui import QApplication, QLineEdit, QCursor, QLabel, QCheckBox, QDesktopServices, QIcon, QImage, QButtonGroup, QRadioButton, QDoubleSpinBox from syncplay.players.playerFactory import PlayerFactory - +from datetime import datetime import os import sys from syncplay.messages import getMessage, getLanguages, setLanguage, getInitialLanguage @@ -35,6 +35,15 @@ class ConfigDialog(QtGui.QDialog): pressedclosebutton = False moreToggling = False + def automaticUpdatePromptCheck(self): + if self.automaticupdatesCheckbox.checkState() == Qt.PartiallyChecked and not self.nostoreCheckbox.isChecked(): + reply = QtGui.QMessageBox.question(self, "Syncplay", + getMessage("promptforupdate-label"), QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No) + if reply == QtGui.QMessageBox.Yes: + self.automaticupdatesCheckbox.setChecked(True) + else: + self.automaticupdatesCheckbox.setChecked(False) + def moreToggled(self): if self.moreToggling == False: self.moreToggling = True @@ -68,7 +77,7 @@ class ConfigDialog(QtGui.QDialog): self.runButton.setText(getMessage("storeandrun-label")) def openHelp(self): - self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/client/") + self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/client/")) def _isURL(self, path): if path is None: @@ -170,6 +179,17 @@ class ConfigDialog(QtGui.QDialog): if fileName: self.executablepathCombobox.setEditText(os.path.normpath(fileName)) + def loadLastUpdateCheckDate(self): + settings = QSettings("Syncplay", "Interface") + settings.beginGroup("Update") + self.lastCheckedForUpdates = settings.value("lastChecked", None) + if self.lastCheckedForUpdates: + if self.config["lastCheckedForUpdates"] is not None and self.config["lastCheckedForUpdates"] is not "": + if self.lastCheckedForUpdates > datetime.strptime(self.config["lastCheckedForUpdates"], "%Y-%m-%d %H:%M:%S.%f"): + self.config["lastCheckedForUpdates"] = str(self.lastCheckedForUpdates) + else: + self.config["lastCheckedForUpdates"] = str(self.lastCheckedForUpdates) + def loadMediaBrowseSettings(self): settings = QSettings("Syncplay", "MediaBrowseDialog") settings.beginGroup("MediaBrowseDialog") @@ -218,6 +238,9 @@ class ConfigDialog(QtGui.QDialog): self.saveMediaBrowseSettings() def _saveDataAndLeave(self): + self.automaticUpdatePromptCheck() + self.loadLastUpdateCheckDate() + self.processWidget(self, lambda w: self.saveValues(w)) if self.hostTextbox.text(): self.config['host'] = self.hostTextbox.text() if ":" in self.hostTextbox.text() else self.hostTextbox.text() + ":" + unicode(constants.DEFAULT_PORT) @@ -293,7 +316,12 @@ class ConfigDialog(QtGui.QDialog): inverted = True else: inverted = False - widget.setChecked(self.config[valueName] != inverted) + if self.config[valueName] is None: + widget.setTristate(True) + widget.setCheckState(Qt.PartiallyChecked) + widget.stateChanged.connect(lambda: widget.setTristate(False)) + else: + widget.setChecked(self.config[valueName] != inverted) elif isinstance(widget, QRadioButton): radioName, radioValue = valueName.split(constants.CONFIG_NAME_MARKER)[1].split(constants.CONFIG_VALUE_MARKER) if self.config[radioName] == radioValue: @@ -307,12 +335,15 @@ class ConfigDialog(QtGui.QDialog): return if isinstance(widget, QCheckBox) and widget.objectName(): - if valueName[:1] == constants.INVERTED_STATE_MARKER: - valueName = valueName[1:] - inverted = True + if widget.checkState() == Qt.PartiallyChecked: + self.config[valueName] = None else: - inverted = False - self.config[valueName] = widget.isChecked() != inverted + if valueName[:1] == constants.INVERTED_STATE_MARKER: + valueName = valueName[1:] + inverted = True + else: + inverted = False + self.config[valueName] = widget.isChecked() != inverted elif isinstance(widget, QRadioButton): radioName, radioValue = valueName.split(constants.CONFIG_NAME_MARKER)[1].split(constants.CONFIG_VALUE_MARKER) if widget.isChecked(): @@ -691,6 +722,9 @@ class ConfigDialog(QtGui.QDialog): self.filesizeprivacySendHashedOption.setObjectName("privacy-sendhashed" + constants.CONFIG_NAME_MARKER + "filesizePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_SENDHASHED_MODE) self.filesizeprivacyDontSendOption.setObjectName("privacy-dontsend" + constants.CONFIG_NAME_MARKER + "filesizePrivacyMode" + constants.CONFIG_VALUE_MARKER + constants.PRIVACY_DONTSEND_MODE) + self.automaticupdatesCheckbox = QCheckBox(getMessage("checkforupdatesautomatically-label")) + self.automaticupdatesCheckbox.setObjectName("checkForUpdatesAutomatically") + self.privacyLayout.addWidget(self.filenameprivacyLabel, 1, 0) self.privacyLayout.addWidget(self.filenameprivacySendRawOption, 1, 1, Qt.AlignLeft) self.privacyLayout.addWidget(self.filenameprivacySendHashedOption, 1, 2, Qt.AlignLeft) @@ -699,6 +733,7 @@ class ConfigDialog(QtGui.QDialog): self.privacyLayout.addWidget(self.filesizeprivacySendRawOption, 2, 1, Qt.AlignLeft) self.privacyLayout.addWidget(self.filesizeprivacySendHashedOption, 2, 2, Qt.AlignLeft) self.privacyLayout.addWidget(self.filesizeprivacyDontSendOption, 2, 3, Qt.AlignLeft) + self.privacyLayout.addWidget(self.automaticupdatesCheckbox, 3, 0, 1, 3, Qt.AlignLeft) self.privacyFrame.setLayout(self.privacyLayout) self.privacySettingsGroup.setLayout(self.privacyLayout) @@ -796,6 +831,10 @@ class ConfigDialog(QtGui.QDialog): settings.clear() settings = QSettings("Syncplay", "MainWindow") settings.clear() + settings = QSettings("Syncplay", "Interface") + settings.beginGroup("Update") + settings.setValue("lastChecked", None) + settings.endGroup() if not leaveMore: settings = QSettings("Syncplay", "MoreSettings") settings.clear() diff --git a/syncplay/ui/gui.py b/syncplay/ui/gui.py index c8f11af..06927c4 100644 --- a/syncplay/ui/gui.py +++ b/syncplay/ui/gui.py @@ -1,12 +1,14 @@ from PySide import QtGui -from PySide.QtCore import Qt, QSettings, QSize, QPoint +from PySide.QtCore import Qt, QSettings, QSize, QPoint, QUrl from syncplay import utils, constants, version from syncplay.messages import getMessage import sys import time +from datetime import datetime import re import os from syncplay.utils import formatTime, sameFilename, sameFilesize, sameFileduration, RoomPasswordProvider, formatSize +lastCheckedForUpdates = None class UserlistItemDelegate(QtGui.QStyledItemDelegate): def __init__(self): @@ -81,6 +83,7 @@ class MainWindow(QtGui.QMainWindow): self.hideMiscLabels() except (): pass + self.automaticUpdateCheck() def promptFor(self, prompt=">", message=""): # TODO: Prompt user @@ -428,11 +431,11 @@ class MainWindow(QtGui.QMainWindow): def openUserGuide(self): if sys.platform.startswith('linux'): - self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/linux/") + self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/linux/")) elif sys.platform.startswith('win'): - self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/windows/") + self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/windows/")) else: - self.QtGui.QDesktopServices.openUrl("http://syncplay.pl/guide/") + self.QtGui.QDesktopServices.openUrl(QUrl("http://syncplay.pl/guide/")) def drop(self): self.close() @@ -613,6 +616,9 @@ class MainWindow(QtGui.QMainWindow): window.userguideAction = window.helpMenu.addAction(QtGui.QIcon(self.resourcespath + 'help.png'), getMessage("userguide-menu-label")) window.userguideAction.triggered.connect(self.openUserGuide) + window.updateAction = window.helpMenu.addAction(QtGui.QIcon(self.resourcespath + 'application_get.png'), + getMessage("update-menu-label")) + window.updateAction.triggered.connect(self.userCheckForUpdates) window.menuBar.addMenu(window.helpMenu) window.mainLayout.setMenuBar(window.menuBar) @@ -652,6 +658,47 @@ class MainWindow(QtGui.QMainWindow): self.readyPushButton.setIcon(QtGui.QIcon(self.resourcespath + 'cross_checkbox.png')) self.readyPushButton.setText(getMessage("notready-guipushbuttonlabel")) + def automaticUpdateCheck(self): + if not self.config['checkForUpdatesAutomatically']: + return + if self.config['lastCheckedForUpdates']: + configLastChecked = datetime.strptime(self.config["lastCheckedForUpdates"], "%Y-%m-%d %H:%M:%S.%f") + if self.lastCheckedForUpdates is None or configLastChecked > self.lastCheckedForUpdates: + self.lastCheckedForUpdates = configLastChecked + currentDateTime = datetime.utcnow() + if self.lastCheckedForUpdates is None: + self.checkForUpdates() + else: + timeDelta = currentDateTime - self.lastCheckedForUpdates + if timeDelta.total_seconds() > constants.AUTOMATIC_UPDATE_CHECK_FREQUENCY: + self.checkForUpdates() + + def userCheckForUpdates(self): + self.checkForUpdates(userInitiated=True) + + def checkForUpdates(self, userInitiated=False): + self.lastCheckedForUpdates = datetime.utcnow() + updateStatus, updateMessage, updateURL = self._syncplayClient.checkForUpdate() + if updateMessage is None: + if updateStatus == "uptodate": + updateMessage = getMessage("syncplay-uptodate-notification") + elif updateStatus == "updateavailale": + updateMessage = getMessage("syncplay-updateavailable-notification") + else: + import syncplay + updateMessage = getMessage("update-check-failed-notification").format(syncplay.version) + if userInitiated == True: + updateURL = constants.SYNCPLAY_DOWNLOAD_URL + if updateURL is not None: + reply = QtGui.QMessageBox.question(self, "Syncplay", + updateMessage, QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No) + if reply == QtGui.QMessageBox.Yes: + self.QtGui.QDesktopServices.openUrl(QUrl(updateURL)) + elif userInitiated: + QtGui.QMessageBox.information(self, "Syncplay", updateMessage) + else: + self.showMessage(updateMessage) + def dragEnterEvent(self, event): data = event.mimeData() urls = data.urls() @@ -680,6 +727,10 @@ class MainWindow(QtGui.QMainWindow): settings.setValue("size", self.size()) settings.setValue("pos", self.pos()) settings.endGroup() + settings = QSettings("Syncplay", "Interface") + settings.beginGroup("Update") + settings.setValue("lastChecked", self.lastCheckedForUpdates) + settings.endGroup() def loadSettings(self): settings = QSettings("Syncplay", "MainWindow") @@ -687,6 +738,9 @@ class MainWindow(QtGui.QMainWindow): self.resize(settings.value("size", QSize(700, 500))) self.move(settings.value("pos", QPoint(200, 200))) settings.endGroup() + settings = QSettings("Syncplay", "Interface") + settings.beginGroup("Update") + self.lastCheckedForUpdates = settings.value("lastChecked", None) def __init__(self): super(MainWindow, self).__init__()