Skip to content

Commit 3986ca7

Browse files
committed
Add manual reconnect button to File menu
Resolves issues where users had to quit and restart Syncplay after connection times out.
1 parent db95264 commit 3986ca7

File tree

3 files changed

+59
-5
lines changed

3 files changed

+59
-5
lines changed

syncplay/client.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -871,9 +871,8 @@ def start(self, host, port):
871871
self._clientSupportsTLS = False
872872

873873
def retry(retries):
874-
self._lastGlobalUpdate = None
875-
self.ui.setSSLMode(False)
876-
self.playlistMayNeedRestoring = True
874+
# Use shared state reset method
875+
self._performRetryStateReset()
877876
if retries == 0:
878877
self.onDisconnect()
879878
if retries > constants.RECONNECT_RETRIES:
@@ -882,8 +881,6 @@ def retry(retries):
882881
reactor.callLater(0.1, self.stop, True)
883882
return None
884883

885-
self.ui.showMessage(getMessage("reconnection-attempt-notification"))
886-
self.reconnecting = True
887884
return(0.1 * (2 ** min(retries, 5)))
888885

889886
self._reconnectingService = ClientService(self._endpoint, self.protocolFactory, retryPolicy=retry)
@@ -920,6 +917,42 @@ def stop(self, promptForAction=False):
920917
if promptForAction:
921918
self.ui.promptFor(getMessage("enter-to-exit-prompt"))
922919

920+
def _performRetryStateReset(self):
921+
"""
922+
Shared method to reset connection state for both automatic and manual retries.
923+
This contains the common logic from the original retry function.
924+
"""
925+
self._lastGlobalUpdate = None
926+
self.ui.setSSLMode(False)
927+
self.playlistMayNeedRestoring = True
928+
self.ui.showMessage(getMessage("reconnection-attempt-notification"))
929+
self.reconnecting = True
930+
931+
def manualReconnect(self):
932+
"""
933+
Trigger a manual reconnection by forcing the retry mechanism.
934+
This performs the same steps as the automatic retry function.
935+
"""
936+
if not self._running or not hasattr(self, '_reconnectingService'):
937+
self.ui.showErrorMessage(getMessage("connection-failed-notification"))
938+
return
939+
940+
from twisted.internet import reactor
941+
942+
def performReconnect():
943+
# Apply the shared state reset logic
944+
self._performRetryStateReset()
945+
946+
# Stop current service and restart it to trigger reconnection
947+
if self._reconnectingService and self._reconnectingService.running:
948+
self._reconnectingService.stopService()
949+
950+
# Restart the service to trigger a reconnection attempt
951+
self._reconnectingService.startService()
952+
953+
# Use callLater for threading purposes as suggested
954+
reactor.callLater(0.1, performReconnect)
955+
923956
def requireServerFeature(featureRequired):
924957
def requireServerFeatureDecorator(f):
925958
@wraps(f)

syncplay/messages_en.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525

2626
"connection-attempt-notification": "Attempting to connect to {}:{}", # Port, IP
2727
"reconnection-attempt-notification": "Connection with server lost, attempting to reconnect",
28+
"reconnect-menu-triggered-notification": "Manual reconnect initiated - will attempt fresh connection to {}:{} in 2 seconds...",
29+
"reconnect-failed-no-host-error": "Cannot reconnect: no server information available",
30+
"reconnect-failed-no-port-error": "Cannot reconnect: invalid server configuration",
31+
"reconnect-failed-error": "Reconnection failed: {}",
2832
"disconnection-notification": "Disconnected from server",
2933
"connection-failed-notification": "Connection with server failed",
3034
"connected-successful-notification": "Successfully connected to server",
@@ -334,6 +338,7 @@
334338
"setmediadirectories-menu-label": "Set media &directories",
335339
"loadplaylistfromfile-menu-label": "&Load playlist from file",
336340
"saveplaylisttofile-menu-label": "&Save playlist to file",
341+
"reconnect-menu-label": "&Reconnect to server",
337342
"exit-menu-label": "E&xit",
338343
"advanced-menu-label": "&Advanced",
339344
"window-menu-label": "&Window",

syncplay/ui/gui.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,19 @@ def pause(self):
10071007
self._syncplayClient.setPaused(True)
10081008

10091009
@needsClient
1010+
def reconnectToServer(self):
1011+
"""
1012+
Trigger a manual reconnection using the client's built-in retry mechanism.
1013+
This is simpler and more reliable than doing a complete restart.
1014+
"""
1015+
try:
1016+
if self._syncplayClient:
1017+
self._syncplayClient.manualReconnect()
1018+
else:
1019+
self.showErrorMessage(getMessage("connection-failed-notification"))
1020+
except Exception as e:
1021+
self.showErrorMessage(getMessage("reconnect-failed-error").format(str(e)))
1022+
10101023
def exitSyncplay(self):
10111024
self._syncplayClient.stop()
10121025

@@ -1715,6 +1728,9 @@ def populateMenubar(self, window):
17151728
getMessage("setmediadirectories-menu-label"))
17161729
window.openAction.triggered.connect(self.openSetMediaDirectoriesDialog)
17171730

1731+
window.reconnectAction = window.fileMenu.addAction(getMessage("reconnect-menu-label"))
1732+
window.reconnectAction.triggered.connect(self.reconnectToServer)
1733+
17181734
window.exitAction = window.fileMenu.addAction(getMessage("exit-menu-label"))
17191735
if isMacOS():
17201736
window.exitAction.setMenuRole(QtWidgets.QAction.QuitRole)

0 commit comments

Comments
 (0)