Merge pull request #215 from albertosottile/master

IPv6 support
This commit is contained in:
Alberto Sottile 2019-01-30 22:26:22 +01:00 committed by GitHub
commit bbc5ae0b7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 94 additions and 51 deletions

View File

@ -783,8 +783,8 @@ info = dict(
'py2exe': { 'py2exe': {
'dist_dir': OUT_DIR, 'dist_dir': OUT_DIR,
'packages': 'PySide2', 'packages': 'PySide2',
'includes': 'twisted, sys, encodings, datetime, os, time, math, liburl, ast, unicodedata, _ssl', 'includes': 'twisted, sys, encodings, datetime, os, time, math, liburl, ast, unicodedata, _ssl, win32pipe, win32file',
'excludes': 'venv, doctest, pdb, unittest, win32clipboard, win32file, win32pdh, win32security, win32trace, win32ui, winxpgui, win32pipe, win32process, Tkinter', 'excludes': 'venv, doctest, pdb, unittest, win32clipboard, win32pdh, win32security, win32trace, win32ui, winxpgui, win32process, Tkinter',
'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll', 'dll_excludes': 'msvcr71.dll, MSVCP90.dll, POWRPROF.dll',
'optimize': 2, 'optimize': 2,
'compressed': 1 'compressed': 1

View File

@ -1,5 +1,5 @@
version = '1.6.2' version = '1.6.3'
revision = '' revision = ' beta'
milestone = 'Yoitsu' milestone = 'Yoitsu'
release_number = '71' release_number = '72'
projectURL = 'https://syncplay.pl/' projectURL = 'https://syncplay.pl/'

View File

@ -11,8 +11,10 @@ import time
from copy import deepcopy from copy import deepcopy
from functools import wraps from functools import wraps
from twisted.internet.endpoints import HostnameEndpoint
from twisted.internet.protocol import ClientFactory from twisted.internet.protocol import ClientFactory
from twisted.internet import reactor, task, defer, threads from twisted.internet import reactor, task, defer, threads
from twisted.application.internet import ClientService
from syncplay import utils, constants, version from syncplay import utils, constants, version
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \ from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
@ -27,41 +29,14 @@ class SyncClientFactory(ClientFactory):
self._client = client self._client = client
self.retry = retry self.retry = retry
self._timesTried = 0 self._timesTried = 0
self.reconnecting = False
def buildProtocol(self, addr): def buildProtocol(self, addr):
self._timesTried = 0 self._timesTried = 0
return SyncClientProtocol(self._client) 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): def stopRetrying(self):
self._timesTried = self.retry self._client._reconnectingService.stopService()
self._client.ui.showErrorMessage(getMessage("disconnection-notification"))
class SyncplayClient(object): class SyncplayClient(object):
@ -725,16 +700,47 @@ class SyncplayClient(object):
reactor.callLater(0.1, self._playerClass.run, self, self._config['playerPath'], self._config['file'], self._config['playerArgs'], ) reactor.callLater(0.1, self._playerClass.run, self, self._config['playerPath'], self._config['file'], self._config['playerArgs'], )
self._playerClass = None self._playerClass = None
self.protocolFactory = SyncClientFactory(self) self.protocolFactory = SyncClientFactory(self)
if '[' in host:
host = host.strip('[]')
port = int(port) 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() reactor.run()
def stop(self, promptForAction=False): def stop(self, promptForAction=False):
if not self._running: if not self._running:
return return
self._running = False self._running = False
if self.protocolFactory:
self.protocolFactory.stopRetrying()
self.destroyProtocol() self.destroyProtocol()
if self._player: if self._player:
self._player.drop() self._player.drop()

View File

@ -16,6 +16,7 @@ de = {
"connection-failed-notification": "Verbindung zum Server fehlgeschlagen", "connection-failed-notification": "Verbindung zum Server fehlgeschlagen",
"connected-successful-notification": "Erfolgreich mit Server verbunden", "connected-successful-notification": "Erfolgreich mit Server verbunden",
"retrying-notification": "%s, versuche erneut in %d Sekunden...", # Seconds "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 "rewind-notification": "Zurückgespult wegen Zeitdifferenz mit {}", # User
"fastforward-notification": "Vorgespult wegen Zeitdifferenz mit {}", # User "fastforward-notification": "Vorgespult wegen Zeitdifferenz mit {}", # User

View File

@ -16,6 +16,7 @@ en = {
"connection-failed-notification": "Connection with server failed", "connection-failed-notification": "Connection with server failed",
"connected-successful-notification": "Successfully connected to server", "connected-successful-notification": "Successfully connected to server",
"retrying-notification": "%s, Retrying in %d seconds...", # Seconds "retrying-notification": "%s, Retrying in %d seconds...", # Seconds
"handshake-successful-notification": "Connection established with {} ({})",
"rewind-notification": "Rewinded due to time difference with {}", # User "rewind-notification": "Rewinded due to time difference with {}", # User
"fastforward-notification": "Fast-forwarded due to time difference with {}", # User "fastforward-notification": "Fast-forwarded due to time difference with {}", # User

View File

@ -16,6 +16,7 @@ it = {
"connection-failed-notification": "Connessione col server fallita", "connection-failed-notification": "Connessione col server fallita",
"connected-successful-notification": "Connessione al server effettuata con successo", "connected-successful-notification": "Connessione al server effettuata con successo",
"retrying-notification": "%s, Nuovo tentativo in %d secondi...", # Seconds "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 "rewind-notification": "Riavvolgo a causa della differenza temporale con {}", # User
"fastforward-notification": "Avanzamento rapido a causa della differenza temporale con {}", # User "fastforward-notification": "Avanzamento rapido a causa della differenza temporale con {}", # User

View File

@ -16,6 +16,7 @@ ru = {
"connection-failed-notification": "Не удалось подключиться к серверу", "connection-failed-notification": "Не удалось подключиться к серверу",
"connected-successful-notification": "Соединение с сервером установлено", "connected-successful-notification": "Соединение с сервером установлено",
"retrying-notification": "%s, следующая попытка через %d секунд(ы)...", # Seconds "retrying-notification": "%s, следующая попытка через %d секунд(ы)...", # Seconds
"handshake-successful-notification": "Connection established with {} ({})", # TODO: Translate
"rewind-notification": "Перемотано из-за разницы во времени с {}", # User "rewind-notification": "Перемотано из-за разницы во времени с {}", # User
"fastforward-notification": "Ускорено из-за разницы во времени с {}", # User "fastforward-notification": "Ускорено из-за разницы во времени с {}", # User

View File

@ -313,14 +313,32 @@ class ConfigurationGetter(object):
port = constants.DEFAULT_PORT if not self._config["port"] else self._config["port"] port = constants.DEFAULT_PORT if not self._config["port"] else self._config["port"]
if host: if host:
if ':' in host: if ':' in host:
host, port = host.split(':', 1) if host.count(':') == 1:
try: #IPv4 address or hostname, with port
port = int(port) host, port = host.rsplit(':', 1)
except ValueError:
try: try:
port = port.encode('ascii', 'ignore') port = int(port)
except: except ValueError:
port = "" 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 return host, port
def _checkForPortableFile(self): def _checkForPortableFile(self):

View File

@ -556,7 +556,7 @@ class ConfigDialog(QtWidgets.QDialog):
self.error = error self.error = error
if config['host'] is None: if config['host'] is None:
host = "" host = ""
elif ":" in config['host']: elif ":" in config['host'] and '[' not in config['host']:
host = config['host'] host = config['host']
else: else:
host = config['host'] + ":" + str(config['port']) host = config['host'] + ":" + str(config['port'])
@ -580,7 +580,7 @@ class ConfigDialog(QtWidgets.QDialog):
i += 1 i += 1
self.hostCombobox.setEditable(True) self.hostCombobox.setEditable(True)
self.hostCombobox.setEditText(host) self.hostCombobox.setEditText(host)
self.hostCombobox.setFixedWidth(165) self.hostCombobox.setFixedWidth(250)
self.hostLabel = QLabel(getMessage("host-label"), self) self.hostLabel = QLabel(getMessage("host-label"), self)
self.findServerButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + 'arrow_refresh.png'), getMessage("update-server-list-label")) self.findServerButton = QtWidgets.QPushButton(QtGui.QIcon(resourcespath + 'arrow_refresh.png'), getMessage("update-server-list-label"))
self.findServerButton.clicked.connect(self.updateServerList) self.findServerButton.clicked.connect(self.updateServerList)
@ -634,7 +634,7 @@ class ConfigDialog(QtWidgets.QDialog):
self.executablepathCombobox.setEditable(True) self.executablepathCombobox.setEditable(True)
self.executablepathCombobox.currentIndexChanged.connect(self.updateExecutableIcon) self.executablepathCombobox.currentIndexChanged.connect(self.updateExecutableIcon)
self.executablepathCombobox.setEditText(self._tryToFillPlayerPath(config['playerPath'], playerpaths)) self.executablepathCombobox.setEditText(self._tryToFillPlayerPath(config['playerPath'], playerpaths))
self.executablepathCombobox.setFixedWidth(250) self.executablepathCombobox.setFixedWidth(330)
self.executablepathCombobox.editTextChanged.connect(self.updateExecutableIcon) self.executablepathCombobox.editTextChanged.connect(self.updateExecutableIcon)
self.executablepathLabel = QLabel(getMessage("executable-path-label"), self) self.executablepathLabel = QLabel(getMessage("executable-path-label"), self)

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
#coding:utf8 #coding:utf8
import socket
import sys import sys
# libpath # libpath
@ -12,15 +13,27 @@ except AttributeError:
import warnings import warnings
warnings.warn("You must run Syncplay with Python 3.4 or newer!") 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 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__': if __name__ == '__main__':
argsGetter = ConfigurationGetter() argsGetter = ConfigurationGetter()
args = argsGetter.getConfiguration() args = argsGetter.getConfiguration()
reactor.listenTCP( dsp = DualStackPort(int(args.port),
int(args.port),
SyncFactory( SyncFactory(
args.port, args.port,
args.password, args.password,
@ -31,5 +44,7 @@ if __name__ == '__main__':
args.disable_chat, args.disable_chat,
args.max_chat_message_length, args.max_chat_message_length,
args.max_username_length, args.max_username_length,
args.stats_db_file)) args.stats_db_file),
interface='::')
dsp.startListening()
reactor.run() reactor.run()