Skip to content

Commit 39637b8

Browse files
scripts/python/app/NUT-Monitor-py2gtk2.in: propose a fix for reconnection modeled after PR #3523 for Python3 siblings [#3509]
Co-Authored-By: Programer <developer.sorli@gmail.com> Signed-off-by: Jim Klimov <jimklimov+nut@gmail.com>
1 parent 4b9209f commit 39637b8

2 files changed

Lines changed: 92 additions & 6 deletions

File tree

NEWS.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ https://github.com/networkupstools/nut/milestone/13
190190
* Fixed Qt tray tooltips to render in plain text, not rich text which
191191
ended up as literal HTML on KDE Plasma 6; now that rich text formatting
192192
is only used for main window status labels. [PR #3430]
193-
* Fixed Qt (Python 3) variants to handle automatic client reconnection
193+
* Fixed both Python 2 and 3 variants to handle automatic client reconnection
194194
(e.g. after system suspend/resume, network interruption, or NUT server
195195
restart). [issue #3509, PR #3523]
196196

scripts/python/app/NUT-Monitor-py2gtk2.in

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import gtk, gtk.glade, gobject
3636
import sys
37+
import socket
3738
import base64
3839
import os, os.path
3940
import stat
@@ -116,6 +117,7 @@ class interface :
116117
__ups_rw_vars = None
117118
__gui_thread = None
118119
__current_ups = None
120+
__reconnect_attempted = False
119121

120122
def __init__( self ) :
121123

@@ -389,8 +391,11 @@ class interface :
389391
def quit( self, widget=None ) :
390392
# If we are connected to an UPS, disconnect first...
391393
if self.__connected :
392-
self.gui_status_message( _("Disconnecting from device") )
393-
self.disconnect_from_ups()
394+
try:
395+
self.gui_status_message( _("Disconnecting from device") )
396+
self.disconnect_from_ups()
397+
except Exception:
398+
pass
394399

395400
gtk.main_quit()
396401

@@ -746,6 +751,7 @@ class interface :
746751
# Connect to the selected UPS using parameters (host,port,login,pass)
747752
def connect_to_ups( self, widget=None ) :
748753

754+
self.__reconnect_attempted = False
749755
host = self.__widgets["ups_host_entry"].get_text()
750756
port = int( self.__widgets["ups_port_entry"].get_value() )
751757
login = None
@@ -862,6 +868,7 @@ class gui_updater( threading.Thread ) :
862868
__parent_class = None
863869
__stop_thread = False
864870
was_online = True
871+
__reconnect_attempted = False
865872

866873
def __init__( self, parent_class ) :
867874
threading.Thread.__init__( self )
@@ -977,12 +984,91 @@ class gui_updater( threading.Thread ) :
977984
# Display UPS status as tooltip for tray icon
978985
self.__parent_class._interface__widgets["status_icon"].set_tooltip_markup( status_text )
979986

980-
except :
981-
self.__parent_class.gui_status_message( _("Error from '{0}' ({1})").format( ups, sys.exc_info()[1] ) )
982-
self.__parent_class.gui_status_notification( _("Error from '{0}'\n{1}").format( ups, sys.exc_info()[1] ), "warning.png" )
987+
except (socket.error, EOFError, PyNUT.PyNUTError) as e:
988+
# Connection error detected - attempt automatic reconnection
989+
if not self.__reconnect_attempted and self.__parent_class._interface__connected:
990+
self.__reconnect_attempted = True
991+
self.__parent_class.gui_status_message( _("Connection lost - attempting to reconnect...") )
992+
self.__parent_class.gui_status_notification( _("Connection lost"), "warning.png" )
993+
994+
# Wait for 2 seconds before attempting reconnection
995+
time.sleep( 2 )
996+
self.__attempt_reconnect()
997+
else:
998+
self.__parent_class.gui_status_message( _("Error from '{0}' ({1})").format( ups, sys.exc_info()[1] ) )
999+
self.__parent_class.gui_status_notification( _("Error from '{0}'\n{1}").format( ups, sys.exc_info()[1] ), "warning.png" )
9831000

9841001
time.sleep( 1 )
9851002

1003+
def __attempt_reconnect( self ) :
1004+
"""Attempt to reconnect to the UPS after a connection loss."""
1005+
if self.__stop_thread:
1006+
return
1007+
1008+
try:
1009+
# Get connection parameters from the parent class
1010+
host = self.__parent_class._interface__widgets["ups_host_entry"].get_text()
1011+
port = int(self.__parent_class._interface__widgets["ups_port_entry"].get_value())
1012+
login = None
1013+
password = None
1014+
1015+
if self.__parent_class._interface__widgets["ups_authentication_check"].get_active():
1016+
login = self.__parent_class._interface__widgets["ups_authentication_login"].get_text()
1017+
password = self.__parent_class._interface__widgets["ups_authentication_password"].get_text()
1018+
1019+
# Teardown old connection handler properly
1020+
old_handler = self.__parent_class._interface__ups_handler
1021+
if old_handler is not None:
1022+
try:
1023+
# Close the underlying telnet connection if it exists
1024+
if hasattr(old_handler, '_PyNUTClient__tn') and old_handler._PyNUTClient__tn is not None:
1025+
old_handler._PyNUTClient__tn.close()
1026+
elif hasattr(old_handler, 'tn') and old_handler.tn is not None:
1027+
old_handler.tn.close()
1028+
except Exception:
1029+
pass # Best effort cleanup
1030+
del old_handler
1031+
1032+
# Create a new connection handler
1033+
ups_handler = PyNUT.PyNUTClient(host=host, port=port, login=login, password=password)
1034+
1035+
# Verify the UPS is available
1036+
ups = self.__parent_class._interface__current_ups
1037+
srv_upses = ups_handler.GetUPSList()
1038+
1039+
if ups not in srv_upses:
1040+
self.__parent_class.gui_status_message( _("UPS '{0}' not found after reconnection").format(ups) )
1041+
self.__reconnect_attempted = False
1042+
return
1043+
1044+
if not ups_handler.CheckUPSAvailable(ups):
1045+
self.__parent_class.gui_status_message( _("UPS '{0}' is still not reachable").format(ups) )
1046+
self.__reconnect_attempted = False
1047+
return
1048+
1049+
# Connection successful - update the parent class
1050+
self.__parent_class._interface__ups_handler = ups_handler
1051+
self.__parent_class._interface__connected = True
1052+
self.__reconnect_attempted = False
1053+
1054+
# Refresh UPS data
1055+
commands = ups_handler.GetUPSCommands(ups)
1056+
self.__parent_class._interface__ups_commands = list(commands.keys())
1057+
self.__parent_class._interface__ups_commands.sort()
1058+
1059+
self.__parent_class._interface__ups_vars = ups_handler.GetUPSVars(ups)
1060+
self.__parent_class._interface__ups_rw_vars = ups_handler.GetRWVars(ups)
1061+
1062+
# Update the GUI (must be done on the main thread in GTK)
1063+
gobject.idle_add( self.__parent_class._interface__gui_update_ups_vars_view )
1064+
gobject.idle_add( self.__parent_class.change_status_icon, "on_line", False )
1065+
self.__parent_class.gui_status_message( _("Reconnected to '{0}' on {1}").format(ups, host) )
1066+
self.__parent_class.gui_status_notification( _("Reconnected to UPS"), "on_line.png" )
1067+
1068+
except (socket.error, EOFError, PyNUT.PyNUTError) as e:
1069+
self.__parent_class.gui_status_message( _("Reconnection failed: {0}").format(str(e)) )
1070+
self.__reconnect_attempted = False
1071+
9861072
def stop_thread( self ) :
9871073
self.__stop_thread = True
9881074

0 commit comments

Comments
 (0)