Restart gobject.timeout_add_seconds counter after a socket.error
Question: I decided to make some modifications to the weather tray applet **[found
here](http://heap.zloduch.cz/software/scripts/weatherboy)**.

After many tests, I found that **update_tray()** stops updating after my
computer spends some time on hibernation. Only updating it manually works.

After examining the code, I found the line responsible for the update is this:

    
    
    gobject.timeout_add_seconds(self.args.delta * 60, self.update_tray)
    

The **[gobject documentation](http://www.pygtk.org/pygtk2reference/gobject-
functions.html#function-gobject--timeout-add)** says that _this function is
called repeatedly until it returns FALSE, at which point the timeout is
automatically destroyed and the function will not be called again._ But it
seems the counter is destroyed because the system clock changes so suddenly.

I'd like to know how can I get the timer restarted when this problem happens.
Here is the complete code:

    
    
    #!/usr/bin/env python2
    # -*- coding: utf-8 -*-
    #
    #  Released under the terms of the GNU GPLv2.
    #       
    
    import gtk
    import gobject
    import time
    import webbrowser
    from urllib2 import urlopen, URLError
    from argparse import ArgumentParser
    from xml.dom import minidom
    
    parser = ArgumentParser(description='Simple weather applet', epilog='Written by Roman Komkov and updated at 06.06.2012.\nPlease, report bugs to <r.komkov@gmail.com>')
    parser.add_argument('-l', '--location', required=True, metavar='WOEID', help='location WOEID (more on http://developer.yahoo.com/weather/)')
    parser.add_argument('-u', '--units', choices=['c','f'], default='c', metavar='c|f', help='units to display')
    parser.add_argument('-d', '--delta', default='10', type=int, metavar='N', help='timeout in minutes between next weather data query')
    parser.add_argument('-a', '--advanced', action = 'store_true', default=False, help='Advanced tooltip')
    
    class Api:
        def __init__(self, location, units):
            self.params = (location,units)
            self.url = 'http://xml.weather.yahoo.com/forecastrss?w=%s&u=%s'
            self.namespace = 'http://xml.weather.yahoo.com/ns/rss/1.0'
            self.website = 'http://weather.yahoo.com/'
    
            self.codes = { #code:icon name
                '0':'weather-severe-alert',
                '1':'weather-severe-alert',
                '2':'weather-severe-alert',
                '3':'weather-severe-alert',
                '4':'weather-storm',
                '5':'weather-snow-rain',
                '6':'weather-snow-rain',
                '7':'weather-snow',
                '8':'weather-freezing-rain',
                '9':'weather-fog',
                '10':'weather-freezing-rain',
                '11':'weather-showers',
                '12':'weather-showers',
                '13':'weather-snow',
                '14':'weather-snow',
                '15':'weather-snow',
                '16':'weather-snow',
                '17':'weather-snow',
                '18':'weather-snow',
                '19':'weather-fog',
                '20':'weather-fog',
                '21':'weather-fog',
                '22':'weather-fog',
                '23':'weather-few-clouds',
                '24':'weather-few-clouds',
                '25':'weather-few-clouds',
                '26':'weather-overcast',
                '27':'weather-clouds-night',
                '28':'weather-clouds',
                '29':'weather-few-clouds-night',
                '30':'weather-few-clouds',
                '31':'weather-clear-night',
                '32':'weather-clear',
                '33':'weather-clear-night',
                '34':'weather-clear',
                '35':'weather-snow-rain',
                '36':'weather-clear',
                '37':'weather-storm',
                '38':'weather-storm',
                '39':'weather-storm',
                '40':'weather-showers-scattered',
                '41':'weather-snow',
                '42':'weather-snow',
                '43':'weather-snow',
                '44':'weather-few-clouds',
                '45':'weather-storm',
                '46':'weather-snow',
                '47':'weather-storm',
                '3200':'stock-unknown'
            }
        def conv_direction(self, value):
            value = int(value)
            if value >= 0 and value < 45:
                return u'\u2191 (N)'
            elif value >= 45 and value < 90:
                return u'\u2197 (NE)'
            elif value >= 90 and value < 135:
                return u'\u2192 (E)'
            elif value >= 135 and value < 180:
                return u'\u2198 (SE)'
            elif value >= 180 and value < 225:
                return u'\u2193 (S)'
            elif value >= 225 and value < 270:
                return u'\u2199 (SW)'
            elif value >= 270 and value < 315:
                return u'\u2190 (W)'
            elif value >= 315 and value < 360:
                return u'\u2196 (NW)'
            else:
                return u'\u2191 (N)'
    
        def get_data(self):
            try:
                url = self.url % self.params
                dom = minidom.parse(urlopen(url))
                units_node = dom.getElementsByTagNameNS(self.namespace, 'units')[0]
                units = {'temperature': units_node.getAttribute('temperature'),
                        'distance': units_node.getAttribute('distance'),
                        'pressure': units_node.getAttribute('pressure'),
                        'speed': units_node.getAttribute('speed')}
                forecasts = []
                for node in dom.getElementsByTagNameNS(self.namespace, 'forecast'):
                    forecasts.append({
                        'date': node.getAttribute('date'),
                        'low': node.getAttribute('low')+u'\u00B0 '+units['temperature'],
                        'high': node.getAttribute('high')+u'\u00B0 '+units['temperature'],
                        'condition': node.getAttribute('text'),
                        'icon': self.codes.get(node.getAttribute('code'))
                    })
                condition = dom.getElementsByTagNameNS(self.namespace, 'condition')[0]
                location = dom.getElementsByTagNameNS(self.namespace, 'location')[0]
                wind = dom.getElementsByTagNameNS(self.namespace, 'wind')[0]
                atmosphere = dom.getElementsByTagNameNS(self.namespace, 'atmosphere')[0]
                return {
                    'current_condition': condition.getAttribute('text'),
                    'current_icon': self.codes.get(condition.getAttribute('code')),
                    'current_temp': condition.getAttribute('temp')+u'\u00B0 '+units['temperature'],
                    'extra':{
                    'wind': {'direction':self.conv_direction(wind.getAttribute('direction')),
                                'speed':wind.getAttribute('speed')+' '+units['speed']},
                    'atmosphere': {'humidity':atmosphere.getAttribute('humidity')+'%',
                                    'visibility':atmosphere.getAttribute('visibility')+' '+units['distance'],
                                    'pressure':atmosphere.getAttribute('pressure')+' '+units['pressure']}},
                    'forecasts': forecasts,
                    'location' : {'city' : location.getAttribute('city'),'country' : location.getAttribute('country')}
                }
            except URLError, ex:
                return None
    
    class MainApp:
        def __init__(self,args):
            self.args = args
            self.weather = None
            self.tooltip = None
            self.tray = gtk.StatusIcon()
            self.tray.connect('popup-menu', self.on_right_click)
            self.tray.connect('activate', self.on_left_click)
            self.tray.set_has_tooltip(True)
            if self.args.advanced:
                self.tray.connect('query-tooltip', self.on_tooltip_advanced)
            self.api = Api(self.args.location, self.args.units)
            self.update_tray()
            gobject.timeout_add_seconds(self.args.delta * 60, self.update_tray)
    
        def on_tooltip_advanced(self, widget, x, y, keyboard_mode, tooltip):
            #if self.tooltip:
                #tooltip.set_text(self.tooltip)
            if self.weather:
                weather = self.weather
                tooltip_text = '%s\n%s' % (self.weather['current_temp'],self.weather['current_condition'])
                vbox = gtk.VBox()
                header = gtk.Label()
                header.set_markup('<u><b>'+self.weather['location']['city']+', '+self.weather['location']['country']+'</b></u>')
                header.set_alignment(1.0, 0.5)
                separator_h = gtk.HSeparator()
                hbox = gtk.HBox()
                now_image = gtk.Image()
                now_image.set_padding(0,5)
                now_image.set_pixel_size(48)
                now_image.set_from_icon_name(weather['current_icon'],48)
                now_label = gtk.Label()
                now_label.set_markup('<b>'+tooltip_text+'</b>')
                now_label.set_padding(5,5)
                table = gtk.Table(columns=2, homogeneous=False)
                u = 0
                l = 1
                for k,v in self.weather['extra'].iteritems():
                    h_label = gtk.Label()
                    h_label.set_markup('<b>'+k+'</b>')
                    h_label.set_alignment(0.0, 0.5)
                    h_label.set_padding(5,0)
                    table.attach(h_label,0,1,u,l)
                    for i,j in v.iteritems():
                        u +=1
                        l +=1
                        k_label = gtk.Label(i)
                        k_label.set_alignment(0.0, 0.5)
                        v_label = gtk.Label(j)
                        v_label.set_alignment(0.0, 0.5)
                        table.attach(k_label,0,1,u,l)
                        table.attach(v_label,1,2,u,l)
                    u +=1
                    l +=1
    
                hbox.pack_start(now_image, False, False, 0)
                hbox.pack_start(now_label, False, False, 0)
                vbox.pack_start(header, True, False, 0)
                vbox.pack_start(separator_h, False, False, 0)
                vbox.pack_start(hbox, False, False, 0)
                vbox.pack_start(table, False, False, 0)
                vbox.show_all()
                tooltip.set_custom(vbox)
            else:
                tooltip.set_text('Connection error!')
            return True
    
        def on_refresh(self,widget):
            self.update_tray()
    
        def on_right_click(self, icon, event_button, event_time):
            menu = gtk.Menu()
            refresh = gtk.MenuItem('Refresh')
            refresh.show()
            refresh.connect('activate', self.on_refresh)
            quit = gtk.MenuItem('Quit')
            quit.show()
            quit.connect('activate', gtk.main_quit)
            menu.append(refresh)
            menu.append(quit)
            menu.popup(None, None, gtk.status_icon_position_menu,
                        event_button, event_time, self.tray)
    
        def on_left_click(self, widget):
            webbrowser.open(self.api.website)
    
        def update_tray(self):
            self.weather = self.api.get_data()
            if self.weather != None:
                self.tray.set_from_icon_name(self.weather['current_icon'])
                if not self.args.advanced:
                    tooltip_text =  '%s / %s' % (self.weather['current_temp'],self.weather['current_condition'])
                    self.tray.set_tooltip_markup(tooltip_text)
            else:
                if not self.args.advanced:
                    self.tray.set_tooltip_text('Connection error!')
                self.tray.set_from_stock('gtk-dialog-error')
            return True
    
    if __name__ == "__main__":
        try:
            args = parser.parse_args()
            MainApp(args)
            gtk.main()
        except KeyboardInterrupt:
            pass
    

As requested by @J.F.Sebastian, here is a smaller example of the code:

    
    
    #!/usr/bin/env python2
    # -*- coding: utf-8 -*-
    
    import gtk, gobject, time
    from datetime import datetime
    
    class MainApp:
        def __init__(self):
            self.update_tray()
            gobject.timeout_add_seconds(15 * 60, self.update_tray)
    
        def update_tray(self):
            print time.strftime('%c')
            return True
    
    if __name__ == "__main__":
        try:
            MainApp()
            gtk.main()
        except KeyboardInterrupt:
            pass
    

**UPDATE:** I set it to update every minute, hibernated and, when returned,
the script kept working. After 10 minutes I got a **socket.error** and it just
didn't update anymore. I thought the problem wasn't with the internet
connection, because I couldn't reproduce it by just unplugging the cable. Now,
my question is: how can I restart the counter upon finding this error, or
avoid this error, since **get_data already has a`try:` statement?**

    
    
    Traceback (most recent call last):
      File "weatherboy", line 333, in update_tray
        self.weather = self.api.get_data()
      File "weatherboy", line 145, in get_data
        dom = minidom.parse(urlopen(url))
      File "/usr/lib/python2.7/urllib2.py", line 127, in urlopen
        return _opener.open(url, data, timeout)
      File "/usr/lib/python2.7/urllib2.py", line 401, in open
        response = self._open(req, data)
      File "/usr/lib/python2.7/urllib2.py", line 419, in _open
        '_open', req)
      File "/usr/lib/python2.7/urllib2.py", line 379, in _call_chain
        result = func(*args)
      File "/usr/lib/python2.7/urllib2.py", line 1211, in http_open
        return self.do_open(httplib.HTTPConnection, req)
      File "/usr/lib/python2.7/urllib2.py", line 1184, in do_open
        r = h.getresponse(buffering=True)
      File "/usr/lib/python2.7/httplib.py", line 1034, in getresponse
        response.begin()
      File "/usr/lib/python2.7/httplib.py", line 407, in begin
        version, status, reason = self._read_status()
      File "/usr/lib/python2.7/httplib.py", line 365, in _read_status
        line = self.fp.readline()
      File "/usr/lib/python2.7/socket.py", line 447, in readline
        data = self._sock.recv(self._rbufsize)
    socket.error: [Errno 104] Connection reset by peer
    


Answer: `update_tray()` should return True if you want it to be called. Unhandled
`socket.error` prevents that. You could use `except EnvironmentError` instead
of `except URLError` to catch wider range of errors inside `get_data()`.

To ignore other kind of errors, you could add `try: ... except Exception:
log_it()` to `update_tray()`.

