Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions syncplay/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,9 +871,8 @@ def start(self, host, port):
self._clientSupportsTLS = False

def retry(retries):
self._lastGlobalUpdate = None
self.ui.setSSLMode(False)
self.playlistMayNeedRestoring = True
# Use shared state reset method
self._performRetryStateReset()
if retries == 0:
self.onDisconnect()
if retries > constants.RECONNECT_RETRIES:
Expand All @@ -882,8 +881,6 @@ def retry(retries):
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)
Expand Down Expand Up @@ -920,6 +917,42 @@ def stop(self, promptForAction=False):
if promptForAction:
self.ui.promptFor(getMessage("enter-to-exit-prompt"))

def _performRetryStateReset(self):
"""
Shared method to reset connection state for both automatic and manual retries.
This contains the common logic from the original retry function.
"""
self._lastGlobalUpdate = None
self.ui.setSSLMode(False)
self.playlistMayNeedRestoring = True
self.ui.showMessage(getMessage("reconnection-attempt-notification"))
self.reconnecting = True

def manualReconnect(self):
"""
Trigger a manual reconnection by forcing the retry mechanism.
This performs the same steps as the automatic retry function.
"""
if not self._running or not hasattr(self, '_reconnectingService'):
self.ui.showErrorMessage(getMessage("connection-failed-notification"))
return

from twisted.internet import reactor

def performReconnect():
# Apply the shared state reset logic
self._performRetryStateReset()

# Stop current service and restart it to trigger reconnection
if self._reconnectingService and self._reconnectingService.running:
self._reconnectingService.stopService()

# Restart the service to trigger a reconnection attempt
self._reconnectingService.startService()

# Use callLater for threading purposes as suggested
reactor.callLater(0.1, performReconnect)

def requireServerFeature(featureRequired):
def requireServerFeatureDecorator(f):
@wraps(f)
Expand Down
5 changes: 5 additions & 0 deletions syncplay/messages_en.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@

"connection-attempt-notification": "Attempting to connect to {}:{}", # Port, IP
"reconnection-attempt-notification": "Connection with server lost, attempting to reconnect",
"reconnect-menu-triggered-notification": "Manual reconnect initiated - will attempt fresh connection to {}:{} in 2 seconds...",
"reconnect-failed-no-host-error": "Cannot reconnect: no server information available",
"reconnect-failed-no-port-error": "Cannot reconnect: invalid server configuration",
"reconnect-failed-error": "Reconnection failed: {}",
"disconnection-notification": "Disconnected from server",
"connection-failed-notification": "Connection with server failed",
"connected-successful-notification": "Successfully connected to server",
Expand Down Expand Up @@ -334,6 +338,7 @@
"setmediadirectories-menu-label": "Set media &directories",
"loadplaylistfromfile-menu-label": "&Load playlist from file",
"saveplaylisttofile-menu-label": "&Save playlist to file",
"reconnect-menu-label": "&Reconnect to server",
"exit-menu-label": "E&xit",
"advanced-menu-label": "&Advanced",
"window-menu-label": "&Window",
Expand Down
16 changes: 16 additions & 0 deletions syncplay/ui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,19 @@ def pause(self):
self._syncplayClient.setPaused(True)

@needsClient
def reconnectToServer(self):
"""
Trigger a manual reconnection using the client's built-in retry mechanism.
This is simpler and more reliable than doing a complete restart.
"""
try:
if self._syncplayClient:
self._syncplayClient.manualReconnect()
else:
self.showErrorMessage(getMessage("connection-failed-notification"))
except Exception as e:
self.showErrorMessage(getMessage("reconnect-failed-error").format(str(e)))

def exitSyncplay(self):
self._syncplayClient.stop()

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

window.reconnectAction = window.fileMenu.addAction(getMessage("reconnect-menu-label"))
window.reconnectAction.triggered.connect(self.reconnectToServer)

window.exitAction = window.fileMenu.addAction(getMessage("exit-menu-label"))
if isMacOS():
window.exitAction.setMenuRole(QtWidgets.QAction.QuitRole)
Expand Down