diff --git a/buildPy2exe.py b/buildPy2exe.py index 31d628d26..bbe17feff 100644 --- a/buildPy2exe.py +++ b/buildPy2exe.py @@ -783,8 +783,8 @@ def run(self): 'py2exe': { 'dist_dir': OUT_DIR, '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', + 'includes': 'twisted, sys, encodings, datetime, os, time, math, liburl, ast, unicodedata, _ssl, win32pipe, win32file', + 'excludes': 'venv, doctest, pdb, unittest, win32clipboard, win32pdh, win32security, win32trace, win32ui, winxpgui, win32process, Tkinter', 'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll', 'optimize': 2, 'compressed': 1 diff --git a/syncplay/__init__.py b/syncplay/__init__.py index cda75ca4f..7eefcf645 100755 --- a/syncplay/__init__.py +++ b/syncplay/__init__.py @@ -1,5 +1,5 @@ -version = '1.6.2' -revision = '' +version = '1.6.3' +revision = ' beta' milestone = 'Yoitsu' -release_number = '71' +release_number = '72' projectURL = 'https://syncplay.pl/' diff --git a/syncplay/client.py b/syncplay/client.py index 3bbb97b0e..0758e2ee2 100755 --- a/syncplay/client.py +++ b/syncplay/client.py @@ -11,8 +11,10 @@ from copy import deepcopy from functools import wraps +from twisted.internet.endpoints import HostnameEndpoint from twisted.internet.protocol import ClientFactory from twisted.internet import reactor, task, defer, threads +from twisted.application.internet import ClientService from syncplay import utils, constants, version from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \ @@ -27,41 +29,14 @@ def __init__(self, client, retry=constants.RECONNECT_RETRIES): self._client = client self.retry = retry self._timesTried = 0 - self.reconnecting = False def buildProtocol(self, addr): self._timesTried = 0 return SyncClientProtocol(self._client) - def startedConnecting(self, connector): - destination = connector.getDestination() - message = getMessage("connection-attempt-notification").format(destination.host, destination.port) - self._client.ui.showMessage(message) - - def clientConnectionLost(self, connector, reason): - if self._timesTried == 0: - self._client.onDisconnect() - if self._timesTried < self.retry: - self._timesTried += 1 - self._client.ui.showMessage(getMessage("reconnection-attempt-notification")) - self.reconnecting = True - reactor.callLater(0.1 * (2 ** min(self._timesTried, 5)), connector.connect) - else: - message = getMessage("disconnection-notification") - self._client.ui.showErrorMessage(message) - - def clientConnectionFailed(self, connector, reason): - if not self.reconnecting: - reactor.callLater(0.1, self._client.ui.showErrorMessage, getMessage("connection-failed-notification"), True) - reactor.callLater(0.1, self._client.stop, True) - else: - self.clientConnectionLost(connector, reason) - - def resetRetrying(self): - self._timesTried = 0 - def stopRetrying(self): - self._timesTried = self.retry + self._client._reconnectingService.stopService() + self._client.ui.showErrorMessage(getMessage("disconnection-notification")) class SyncplayClient(object): @@ -725,16 +700,47 @@ def start(self, host, port): reactor.callLater(0.1, self._playerClass.run, self, self._config['playerPath'], self._config['file'], self._config['playerArgs'], ) self._playerClass = None self.protocolFactory = SyncClientFactory(self) + if '[' in host: + host = host.strip('[]') port = int(port) - reactor.connectTCP(host, port, self.protocolFactory) + self._endpoint = HostnameEndpoint(reactor, host, port) + + def retry(retries): + self._lastGlobalUpdate = None + if retries == 0: + self.onDisconnect() + if retries > constants.RECONNECT_RETRIES: + reactor.callLater(0.1, self.ui.showErrorMessage, getMessage("connection-failed-notification"), + True) + reactor.callLater(0.1, self.stop, True) + return None + + self.ui.showMessage(getMessage("reconnection-attempt-notification")) + self.reconnecting = True + return(0.1 * (2 ** min(retries, 5))) + + self._reconnectingService = ClientService(self._endpoint, self.protocolFactory , retryPolicy=retry) + waitForConnection = self._reconnectingService.whenConnected(failAfterFailures=1) + self._reconnectingService.startService() + + def connectedNow(f): + hostIP = connectionHandle.result.transport.addr[0] + self.ui.showMessage(getMessage("handshake-successful-notification").format(host, hostIP)) + return + + def failed(f): + reactor.callLater(0.1, self.ui.showErrorMessage, getMessage("connection-failed-notification"), True) + reactor.callLater(0.1, self.stop, True) + + connectionHandle = waitForConnection.addCallbacks(connectedNow, failed) + message = getMessage("connection-attempt-notification").format(host, port) + self.ui.showMessage(message) reactor.run() def stop(self, promptForAction=False): if not self._running: return self._running = False - if self.protocolFactory: - self.protocolFactory.stopRetrying() self.destroyProtocol() if self._player: self._player.drop() diff --git a/syncplay/messages_de.py b/syncplay/messages_de.py index 978685b1e..e370015e9 100755 --- a/syncplay/messages_de.py +++ b/syncplay/messages_de.py @@ -16,6 +16,7 @@ "connection-failed-notification": "Verbindung zum Server fehlgeschlagen", "connected-successful-notification": "Erfolgreich mit Server verbunden", "retrying-notification": "%s, versuche erneut in %d Sekunden...", # Seconds + "handshake-successful-notification": "Connection established with {} ({})", # TODO: Translate "rewind-notification": "Zurückgespult wegen Zeitdifferenz mit {}", # User "fastforward-notification": "Vorgespult wegen Zeitdifferenz mit {}", # User diff --git a/syncplay/messages_en.py b/syncplay/messages_en.py index acf5c37b6..2cbf7259f 100755 --- a/syncplay/messages_en.py +++ b/syncplay/messages_en.py @@ -16,6 +16,7 @@ "connection-failed-notification": "Connection with server failed", "connected-successful-notification": "Successfully connected to server", "retrying-notification": "%s, Retrying in %d seconds...", # Seconds + "handshake-successful-notification": "Connection established with {} ({})", "rewind-notification": "Rewinded due to time difference with {}", # User "fastforward-notification": "Fast-forwarded due to time difference with {}", # User diff --git a/syncplay/messages_it.py b/syncplay/messages_it.py index 2ecff6803..cdc20a02e 100755 --- a/syncplay/messages_it.py +++ b/syncplay/messages_it.py @@ -16,6 +16,7 @@ "connection-failed-notification": "Connessione col server fallita", "connected-successful-notification": "Connessione al server effettuata con successo", "retrying-notification": "%s, Nuovo tentativo in %d secondi...", # Seconds + "handshake-successful-notification": "Connessione stabilita con {} ({})", "rewind-notification": "Riavvolgo a causa della differenza temporale con {}", # User "fastforward-notification": "Avanzamento rapido a causa della differenza temporale con {}", # User diff --git a/syncplay/messages_ru.py b/syncplay/messages_ru.py index 18eeebe59..b836abf55 100755 --- a/syncplay/messages_ru.py +++ b/syncplay/messages_ru.py @@ -16,6 +16,7 @@ "connection-failed-notification": "Не удалось подключиться к серверу", "connected-successful-notification": "Соединение с сервером установлено", "retrying-notification": "%s, следующая попытка через %d секунд(ы)...", # Seconds + "handshake-successful-notification": "Connection established with {} ({})", # TODO: Translate "rewind-notification": "Перемотано из-за разницы во времени с {}", # User "fastforward-notification": "Ускорено из-за разницы во времени с {}", # User diff --git a/syncplay/ui/ConfigurationGetter.py b/syncplay/ui/ConfigurationGetter.py index cc67d11b5..2f9a2c63c 100755 --- a/syncplay/ui/ConfigurationGetter.py +++ b/syncplay/ui/ConfigurationGetter.py @@ -313,14 +313,32 @@ def _splitPortAndHost(self, host): port = constants.DEFAULT_PORT if not self._config["port"] else self._config["port"] if host: if ':' in host: - host, port = host.split(':', 1) - try: - port = int(port) - except ValueError: + if host.count(':') == 1: + #IPv4 address or hostname, with port + host, port = host.rsplit(':', 1) try: - port = port.encode('ascii', 'ignore') - except: - port = "" + port = int(port) + except ValueError: + try: + port = port.encode('ascii', 'ignore') + except: + port = "" + else: + #IPv6 address + if ']' in host: + #IPv6 address in brackets + endBracket = host.index(']') + try: + #port explicitely indicated + port = int(host[endBracket+2:]) + except ValueError: + #no port after the bracket + pass + host = host[:endBracket+1] + else: + #IPv6 address with no port and no brackets + #add brackets to correctly store IPv6 addresses in configs + host = '[' + host + ']' return host, port def _checkForPortableFile(self): diff --git a/syncplay/ui/GuiConfiguration.py b/syncplay/ui/GuiConfiguration.py index 5ad81ae71..d64532154 100755 --- a/syncplay/ui/GuiConfiguration.py +++ b/syncplay/ui/GuiConfiguration.py @@ -556,7 +556,7 @@ def addBasicTab(self): self.error = error if config['host'] is None: host = "" - elif ":" in config['host']: + elif ":" in config['host'] and '[' not in config['host']: host = config['host'] else: host = config['host'] + ":" + str(config['port']) @@ -580,7 +580,7 @@ def addBasicTab(self): i += 1 self.hostCombobox.setEditable(True) self.hostCombobox.setEditText(host) - self.hostCombobox.setFixedWidth(165) + self.hostCombobox.setFixedWidth(250) self.hostLabel = QLabel(getMessage("host-label"), self) self.findServerButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + 'arrow_refresh.png'), getMessage("update-server-list-label")) self.findServerButton.clicked.connect(self.updateServerList) @@ -634,7 +634,7 @@ def addBasicTab(self): self.executablepathCombobox.setEditable(True) self.executablepathCombobox.currentIndexChanged.connect(self.updateExecutableIcon) self.executablepathCombobox.setEditText(self._tryToFillPlayerPath(config['playerPath'], playerpaths)) - self.executablepathCombobox.setFixedWidth(250) + self.executablepathCombobox.setFixedWidth(330) self.executablepathCombobox.editTextChanged.connect(self.updateExecutableIcon) self.executablepathLabel = QLabel(getMessage("executable-path-label"), self) diff --git a/syncplayServer.py b/syncplayServer.py index 28cfd1b7a..eaa9f348c 100755 --- a/syncplayServer.py +++ b/syncplayServer.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 #coding:utf8 +import socket import sys # libpath @@ -12,15 +13,27 @@ import warnings warnings.warn("You must run Syncplay with Python 3.4 or newer!") -from twisted.internet import reactor +from twisted.internet import reactor, tcp from syncplay.server import SyncFactory, ConfigurationGetter +class DualStackPort(tcp.Port): + + def __init__(self, port, factory, backlog=50, interface='', reactor=None): + tcp.Port.__init__(self, port, factory, backlog, interface, reactor) + + def createInternetSocket(self): + s = tcp.Port.createInternetSocket(self) + try: + s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + except: + pass + return s + if __name__ == '__main__': argsGetter = ConfigurationGetter() args = argsGetter.getConfiguration() - reactor.listenTCP( - int(args.port), + dsp = DualStackPort(int(args.port), SyncFactory( args.port, args.password, @@ -31,5 +44,7 @@ args.disable_chat, args.max_chat_message_length, args.max_username_length, - args.stats_db_file)) + args.stats_db_file), + interface='::') + dsp.startListening() reactor.run()