Skip to content

Commit

Permalink
Merge pull request #215 from albertosottile/master
Browse files Browse the repository at this point in the history
IPv6 support
  • Loading branch information
albertosottile committed Jan 30, 2019
2 parents 8fde98e + 43486e9 commit bbc5ae0
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 51 deletions.
4 changes: 2 additions & 2 deletions buildPy2exe.py
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions 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/'
70 changes: 38 additions & 32 deletions syncplay/client.py
Expand Up @@ -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, \
Expand All @@ -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):
Expand Down Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions syncplay/messages_de.py
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions syncplay/messages_en.py
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions syncplay/messages_it.py
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions syncplay/messages_ru.py
Expand Up @@ -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
Expand Down
32 changes: 25 additions & 7 deletions syncplay/ui/ConfigurationGetter.py
Expand Up @@ -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):
Expand Down
6 changes: 3 additions & 3 deletions syncplay/ui/GuiConfiguration.py
Expand Up @@ -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'])
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
23 changes: 19 additions & 4 deletions syncplayServer.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
#coding:utf8

import socket
import sys

# libpath
Expand All @@ -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,
Expand All @@ -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()

0 comments on commit bbc5ae0

Please sign in to comment.