Question

I decided to make some modifications to the weather tray applet found here.

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 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
Was it helpful?

Solution

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().

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top