~ubuntu-branches/ubuntu/precise/indicator-weather/precise-updates

« back to all changes in this revision

Viewing changes to .pc/fix_flurries.patch/bin/indicator-weather

  • Committer: Package Import Robot
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2012-07-30 17:44:36 UTC
  • Revision ID: package-import@ubuntu.com-20120730174436-3tgnus8h89t3w17j
Tags: 11.11.28-0ubuntu1.1
* debian/patches/dont_block_on_sun_times.patch:
 - If Earthtools.org is down, report 'Unknown' rather than
   blocking on sunset and sunrise times (LP: #964365).
* debian/patches/fix_flurries.patch:
 - Fixes flurries condition being marked as 'Unknown' (LP: #928596).
* debian/patches/fix_pid_checking.patch:
 - Correctly preform .pid file matching so we can restart after
   a crash (LP: #926433).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# -*- coding: utf-8 -*-
 
3
### BEGIN LICENSE
 
4
# Copyright (C) 2010 Sebastian MacDonald Sebas310@gmail.com
 
5
# Copyright (C) 2010 Mehdi Rejraji mehd36@gmail.com
 
6
# Copyright (C) 2011 Vadim Rutkovsky roignac@gmail.com
 
7
# This program is free software: you can redistribute it and/or modify it
 
8
# under the terms of the GNU General Public License version 3, as published
 
9
# by the Free Software Foundation.
 
10
#
 
11
# This program is distributed in the hope that it will be useful, but
 
12
# WITHOUT ANY WARRANTY; without even the implied warranties of
 
13
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 
14
# PURPOSE.  See the GNU General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License along
 
17
# with this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
### END LICENSE
 
19
 
 
20
try:
 
21
    from gi.repository import Gio
 
22
except ImportError:
 
23
    pass
 
24
import sys, os, shutil, tempfile
 
25
import gtk, pygtk, gobject, pynotify
 
26
pygtk.require('2.0')
 
27
import appindicator
 
28
import urllib2, urllib
 
29
from urllib import urlencode
 
30
import re
 
31
import locale
 
32
from xml.dom.minidom import parseString
 
33
import datetime
 
34
import dbus
 
35
import time
 
36
import traceback
 
37
import types
 
38
# Will be used for humidex
 
39
#import math
 
40
import commands, threading
 
41
import logging, logging.handlers
 
42
import pywapi
 
43
 
 
44
import gettext
 
45
from gettext import gettext as _
 
46
from gettext import ngettext as __
 
47
gettext.textdomain('indicator-weather')
 
48
 
 
49
# Add project root directory (enable symlink, and trunk execution).
 
50
PROJECT_ROOT_DIRECTORY = os.path.abspath(
 
51
    os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
 
52
 
 
53
if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'indicator_weather'))
 
54
    and PROJECT_ROOT_DIRECTORY not in sys.path):
 
55
    sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
 
56
    os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY) # for subprocesses
 
57
 
 
58
VERSION = "11.11.28 'Cloudy 9'"
 
59
 
 
60
from indicator_weather.helpers import *
 
61
 
 
62
INFO_TYPE            = 'type'
 
63
INFO_SETTING         = 'setting'
 
64
 
 
65
class Settings:
 
66
    """ Class to read/write settings """
 
67
    db = None
 
68
    BASE_KEY             = 'apps.indicators.weather'
 
69
    WEATHER_KEY          = 'weather'
 
70
    LOCATIONS_KEY        = 'locations'
 
71
    INDICATOR_DISPLAY    = 'show_label'
 
72
    NOTIFICATIONS        = 'notif'
 
73
    WEATHER_SOURCE       = 'data_source'
 
74
    REFRESH_RATE         = 'refresh_rate'
 
75
    METRIC_SYSTEM        = 'unit'
 
76
    WIND_UNIT            = 'wind'
 
77
    PLACECHOSEN          = 'placechosen'
 
78
    PLACES               = 'places'
 
79
 
 
80
    # info dict
 
81
    # INFO_TYPE    : Python type
 
82
    # INFO_SETTING : dconf setting name
 
83
    INFO = {
 
84
        INDICATOR_DISPLAY : {
 
85
            INFO_TYPE : types.IntType,
 
86
            INFO_SETTING : 'indicator-display'
 
87
        },
 
88
        NOTIFICATIONS : {
 
89
            INFO_TYPE : types.StringType,
 
90
            INFO_SETTING : 'notifications'
 
91
        },
 
92
        WEATHER_SOURCE : {
 
93
            INFO_TYPE : types.StringType,
 
94
            INFO_SETTING : 'weather-source'
 
95
        },
 
96
        REFRESH_RATE : {
 
97
            INFO_TYPE : types.IntType,
 
98
            INFO_SETTING : 'refresh-rate'
 
99
        },
 
100
        METRIC_SYSTEM : {
 
101
            INFO_TYPE : types.StringType,
 
102
            INFO_SETTING : 'metric-system'
 
103
        },
 
104
        WIND_UNIT : {
 
105
            INFO_TYPE : types.StringType,
 
106
            INFO_SETTING : 'wind-unit'
 
107
        },
 
108
        PLACECHOSEN : {
 
109
            INFO_TYPE : types.IntType,
 
110
            INFO_SETTING: 'placechosen'
 
111
        },
 
112
        PLACES : {
 
113
            INFO_TYPE : types.ListType,
 
114
            INFO_SETTING: 'places'
 
115
        },
 
116
    }
 
117
 
 
118
    # Open the DB
 
119
    def prepare_settings_store(self):
 
120
        log.debug("Settings: preparing settings store")
 
121
        try:
 
122
            self.db = Gio.Settings.new(self.BASE_KEY)
 
123
        except Exception as e:
 
124
            log.debug("Settings: exception occurred while opening settings:\n %s" % str(e))
 
125
 
 
126
    # Make sure autostart file is installed. Inspired by GTG.
 
127
    def check_autostart(self):
 
128
        autostart_dir = os.path.join(os.path.expanduser("~"),".config/autostart/")
 
129
        autostart_file = "indicator-weather.desktop"
 
130
        autostart_path = os.path.join(autostart_dir, autostart_file)
 
131
        if not os.path.isfile(autostart_path):
 
132
            #Look for the desktop file
 
133
            desktop_file_path = None
 
134
            desktop_file_directories = ["/usr/share/applications",
 
135
                                        "/usr/local/share/applications"]
 
136
            this_directory = os.path.dirname(os.path.abspath(__file__))
 
137
            for path in desktop_file_directories:
 
138
                fullpath = os.path.normpath(os.path.join(this_directory, path, \
 
139
                                                        autostart_file))
 
140
                if os.path.isfile(fullpath):
 
141
                    desktop_file_path = fullpath
 
142
                    break
 
143
            #If we have found the desktop file, we make a link to in in
 
144
            # autostart_path.
 
145
            if desktop_file_path:
 
146
                if not os.path.exists(autostart_dir):
 
147
                    os.mkdir(autostart_dir)
 
148
                if os.path.isdir(autostart_dir):
 
149
                    log.debug("Installing autostart file.")
 
150
                    os.symlink(desktop_file_path, autostart_path)
 
151
 
 
152
    # Get a value of the setting
 
153
    def get_value(self, setting, return_id = False):
 
154
        log.debug("Settings: getting value for %s" % setting)
 
155
        setting_name = Settings.INFO[setting][INFO_SETTING]
 
156
        try:
 
157
            setting_type = Settings.INFO[setting][INFO_TYPE]
 
158
            get_func = {
 
159
                types.IntType:     self.db.get_int,
 
160
                types.StringType:  self.db.get_string,
 
161
                types.BooleanType: self.db.get_boolean,
 
162
                types.ListType:    self.db.get_string,
 
163
                types.DictType:    self.db.get_string,
 
164
                types.NoneType:    self.db.get_value,
 
165
            }[setting_type]
 
166
            return get_func(setting_name)
 
167
        except:
 
168
            self.log.debug("Settings: can't find value for %s" % setting)
 
169
            return None
 
170
 
 
171
    # Set a setting value
 
172
    def set_value(self, setting, value):
 
173
 
 
174
        value = '' if value is None else value
 
175
        value = str(value) if type(value) is types.ListType else value
 
176
        log.debug("Settings: setting '%s'='%s'" % (setting, value))
 
177
 
 
178
        setting_name = Settings.INFO[setting][INFO_SETTING]
 
179
        try:
 
180
            setting_type = Settings.INFO[setting][INFO_TYPE]
 
181
            set_func = {
 
182
                types.IntType:     self.db.set_int,
 
183
                types.StringType:  self.db.set_string,
 
184
                types.BooleanType: self.db.set_boolean,
 
185
                types.ListType:    self.db.set_string,
 
186
                types.DictType:    self.db.set_string,
 
187
                types.NoneType:    self.db.set_value,
 
188
            }[setting_type]
 
189
            set_func(setting_name, value)
 
190
        except:
 
191
            log.debug( \
 
192
                "Settings: schema for '%s' not found, aborting" % setting)
 
193
 
 
194
    # Get cached weather by location code.
 
195
    # If return_id is True, only document id is returned, otherwise - full weather data
 
196
    def get_weather(self, location_code, return_id = False):
 
197
        log.debug("Settings: getting cached weather for %s" % \
 
198
            location_code)
 
199
        try:
 
200
            cached_weather_string = self.db.get_string(self.WEATHER_KEY)
 
201
            cached_weather = {} if cached_weather_string == ''\
 
202
                else eval(cached_weather_string)
 
203
            if location_code in cached_weather.keys():
 
204
                return str(cached_weather[location_code])
 
205
            else:
 
206
                log.debug(\
 
207
                    "Settings: can't find value for %s" % location_code)
 
208
                return None
 
209
        except:
 
210
            log.debug("Settings: can't find %s setting" % WEATHER_KEY)
 
211
            return None
 
212
 
 
213
    # Save weather info in cache for specific location
 
214
    def save_weather(self, weather, location_code):
 
215
 
 
216
        record = {
 
217
            "label"    : weather.get_temperature(needs_rounding=True),
 
218
            "condition": weather.get_condition_label(),
 
219
            "icon"     : weather.get_icon_name(),
 
220
            "temper"   : weather.get_temperature_label(),
 
221
            "humidex"  : weather.get_humidex_label(),
 
222
            "humidity" : weather.get_humidity_label(),
 
223
            "wind"     : weather.get_wind_label(),
 
224
            "sunrise"  : weather.get_sunrise_label(),
 
225
            "sunset"   : weather.get_sunset_label()
 
226
        }
 
227
        log.debug("Settings: setting '%s'='%s'" % (location_code, record))
 
228
 
 
229
        try:
 
230
            cached_weather_string = self.db.get_string(self.WEATHER_KEY)
 
231
            cached_weather = {} if cached_weather_string == ''\
 
232
                else eval(cached_weather_string)
 
233
            cached_weather[location_code] = record
 
234
            cached_weather_string = str(cached_weather)
 
235
            self.db.set_string(self.WEATHER_KEY, cached_weather_string)
 
236
        except:
 
237
            log.debug(\
 
238
                "Settings: schema for '%s' not found, aborting" % setting)
 
239
 
 
240
    # Get location details by location code
 
241
    # If return_id is True, only document id is returned, otherwise - full location data
 
242
    def get_location_details(self, location_code, return_id = False):
 
243
        try:
 
244
            locations_string = self.db.get_string(self.LOCATIONS_KEY)
 
245
            locations = {} if locations_string == ''\
 
246
                else eval(locations_string)
 
247
            if location_code in locations.keys():
 
248
                return str(locations[location_code])
 
249
            else:
 
250
                log.debug(\
 
251
                    "Settings: can't find value for %s" % location_code)
 
252
                return None
 
253
        except:
 
254
            log.debug("Settings: can't find location details for %s" % \
 
255
                location_code)
 
256
            return None
 
257
 
 
258
    # Save location details
 
259
    def save_location_details(self, location_details, location_code):
 
260
        log.debug("Settings: setting '%s'='%s'" %\
 
261
            (location_code, location_details))
 
262
 
 
263
        try:
 
264
            locations_string = self.db.get_string(self.LOCATIONS_KEY)
 
265
            locations = {} if locations_string == ''\
 
266
                else eval(locations_string)
 
267
            locations[location_code] = location_details
 
268
            locations_string = str(locations)
 
269
            self.db.set_string(self.LOCATIONS_KEY, locations_string)
 
270
        except:
 
271
            pass
 
272
 
 
273
class MetricSystem:
 
274
    """ Class with available metric systems units """
 
275
    SI = 1
 
276
    IMPERIAL = 2
 
277
 
 
278
class WindUnits:
 
279
    """ Class for available wind unit systems """
 
280
    MPS = 1
 
281
    MPH = 2
 
282
    BEAUFORT = 3
 
283
    KPH = 4
 
284
    KNOTS = 5
 
285
 
 
286
class WeatherDataSource:
 
287
    """ Class for available weather data sources """
 
288
    GOOGLE = 1
 
289
    YAHOO = 2
 
290
 
 
291
class Location:
 
292
    """ Data object to store location details """
 
293
 
 
294
    # Initialize an object with a label
 
295
    def __init__(self, metric_system, wind_unit, location_details = None):
 
296
        self.metric_system = metric_system
 
297
        self.wind_unit = wind_unit
 
298
        self.location_details = location_details
 
299
 
 
300
    # Convert coordinate for google
 
301
    def convert_coordinate_for_google(self, value):
 
302
       value = float(value) * 1e6
 
303
       return int(round(value))
 
304
 
 
305
    # Get necessary location details by its GeoNames details
 
306
    def prepare_location(self, geonames_details):
 
307
        self.location_details = {}
 
308
        self.location_details['full name'] = geonames_details[0]
 
309
        self.location_details['latitude'] = geonames_details[2]
 
310
        self.location_details['longitude'] = geonames_details[3]
 
311
        self.prepare_location_for_google(geonames_details)
 
312
        self.prepare_location_for_yahoo(geonames_details)
 
313
        #TODO: Get noaa id from geonames service
 
314
        self.location_details['noaa id'] = "woot"
 
315
 
 
316
        # check mandatory attributes
 
317
        if not hasattr(self, 'location_code') or \
 
318
                'latitude' not in self.location_details or \
 
319
                'longitude' not in self.location_details:
 
320
            return False
 
321
 
 
322
        # check that we have at least one supported data source
 
323
        if 'google id' not in self.location_details and \
 
324
                'yahoo id' not in self.location_details:
 
325
            log.error(("Location '%s'" %
 
326
                self.location_details['full name'])) + \
 
327
                "is not supported by current data sources"
 
328
            return False
 
329
 
 
330
        return True
 
331
 
 
332
    def prepare_location_for_google(self, geonames_details):
 
333
        # Format latitude and longitude for Google needs
 
334
        try:
 
335
            lat = self.convert_coordinate_for_google(geonames_details[2])
 
336
            lon = self.convert_coordinate_for_google(geonames_details[3])
 
337
            self.location_details['google id'] = ",,,%s,%s" % (lat, lon)
 
338
 
 
339
        except Exception, e:
 
340
            log.error(e)
 
341
 
 
342
    def prepare_location_for_yahoo(self, geonames_details):
 
343
        # Get location details in english for Yahoo
 
344
        baseurl = 'http://api.geonames.org/getJSON'
 
345
        params = {'geonameId': geonames_details[1], 'username': 'indicatorweather'}
 
346
        url = '?'.join((baseurl, urlencode(params)))
 
347
        log.debug("Location: Get GeoNames location details, url %s" % url)
 
348
        try:
 
349
            city = eval(urllib2.urlopen(url).read())
 
350
            if 'adminName1' in city:
 
351
                displayed_city_name = "%s, %s, %s" % (city['name'], city['adminName1'], city['countryName'])
 
352
            elif 'name' in city:
 
353
                displayed_city_name = "%s, %s" % (city['name'], city['countryName'])
 
354
            else:
 
355
                log.error("Location: Cannot find GeoNames info for code %s Full Response:\n %s" % (geonames_details[1], str(city)))
 
356
                return
 
357
 
 
358
            # Get YAHOO WOEID by english name of location
 
359
            baseurl = 'http://where.yahooapis.com/geocode'
 
360
            params = {'location': displayed_city_name, 'appid': 'mOawLd4s', 'flags': 'J'}
 
361
            url = '?'.join((baseurl, urlencode(params)))
 
362
            log.debug("Location: Get Yahoo WOEID, url %s" % url)
 
363
            f = urllib2.urlopen(url)
 
364
            s=f.read()
 
365
            null = None
 
366
            yahoo_woeid_result = eval(s)
 
367
            if (yahoo_woeid_result['ResultSet']['Error'] != 0) and  (yahoo_woeid_result['ResultSet']['Results'] != None):
 
368
                log.error("Location: Yahoo woeid return error. Full response:\n %s" % str(yahoo_woeid_result))
 
369
                return
 
370
            else:
 
371
                woeid = yahoo_woeid_result['ResultSet']['Results'][0]['woeid']
 
372
                self.location_code = woeid
 
373
                log.debug("Location: woeid is %s" % woeid)
 
374
 
 
375
                # Get old Yahoo id by woeid
 
376
                url = 'http://weather.yahooapis.com/forecastrss?w=%s' % woeid
 
377
                log.debug("Location: Get Yahoo RSS ID, url %s" % url)
 
378
                f = urllib2.urlopen(url)
 
379
                s=f.read()
 
380
                parsed = parseString(s)
 
381
                #TODO: Add a try-catch for empty guid_value
 
382
                guid = parsed.getElementsByTagName("guid")
 
383
                if len(guid) > 0:
 
384
                    guid_value = guid[0].firstChild.nodeValue
 
385
                    p = re.compile('([^_]*)_')
 
386
                    m = p.match(guid_value)
 
387
                    if m:
 
388
                        self.location_details['yahoo id'] = m.group(1)
 
389
                        log.debug("Location: yahoo id is %s" % self.location_details['yahoo id'])
 
390
                    else:
 
391
                        log.error("Location: Can't find yahoo id via woeid. Full response:\n %s" % guid_value)
 
392
                        return
 
393
                else:
 
394
                    log.error("Location: Can't guid in yahoo RSS response. Full response:\n %s" % s)
 
395
                    return
 
396
 
 
397
        except urllib2.URLError:
 
398
            log.error("Location: error reaching url '%s'" % url)
 
399
 
 
400
        except Exception, e:
 
401
            log.error(e)
 
402
 
 
403
    # Return lcoation code and location details
 
404
    def export_location_details(self):
 
405
        return (self.location_code, self.location_details)
 
406
 
 
407
    # Get fresh weather data and store it to weather object
 
408
    def update_weather_data(self, source):
 
409
        # gather existing source keys
 
410
        valid_source = None
 
411
        loc_ids = {}
 
412
 
 
413
        SOURCES = {
 
414
            WeatherDataSource.GOOGLE : ("google id", "Google"),
 
415
            WeatherDataSource.YAHOO : ("yahoo id", "Yahoo"),
 
416
        }
 
417
 
 
418
        for source_id in SOURCES.keys():
 
419
            if SOURCES[source_id][0] in self.location_details:
 
420
                loc_ids[source_id] = SOURCES[source_id][0]
 
421
 
 
422
        # try with the default source
 
423
        if source in loc_ids:
 
424
            valid_source = source
 
425
            log.debug(("Location: default weather source '%s' " +
 
426
                "chosen for '%s'") % (SOURCES[valid_source][1],
 
427
                self.location_details['label']))
 
428
 
 
429
        # try with the first alternative
 
430
        elif len(loc_ids.keys()):
 
431
            valid_source = loc_ids.keys()[0]
 
432
            log.debug(("Location: non default weather source '%s' " +
 
433
                "chosen for '%s'") % (SOURCES[valid_source][1],
 
434
                self.location_details['label']))
 
435
 
 
436
        if valid_source is None:
 
437
            log.error(("Location: no valid weather source can be " +
 
438
                "chosen for '%s'") % (
 
439
                self.location_details['label']))
 
440
            self.weather = None
 
441
        else:
 
442
            self.weather = Weather(
 
443
                self.location_details[loc_ids[valid_source]],
 
444
                valid_source, self.metric_system, self.wind_unit,
 
445
                self.location_details['latitude'],
 
446
                self.location_details['longitude'])
 
447
 
 
448
class Forecast:
 
449
    """ Class to get forecast information """
 
450
 
 
451
    # Initialize a class with metric system, wind units, location and current user locale
 
452
    def __init__ (self, units, lat, lon, locale):
 
453
        self.metric_system = units
 
454
        self.lat = self.convert_coordinate_for_google(lat)
 
455
        self.lon = self.convert_coordinate_for_google(lon)
 
456
        self.locale = locale
 
457
 
 
458
    # Convert coordinate for google
 
459
    def convert_coordinate_for_google(self, value):
 
460
       value = float(value) * 1e6
 
461
       return int(round(value))
 
462
 
 
463
    # Get and store forecast data. For now using Google only
 
464
    def prepare_forecast_data(self):
 
465
        self.daysofweek = []
 
466
        self.icons = []
 
467
        self.conditions = []
 
468
        self.error_message = None
 
469
        try:
 
470
            # Generate a fake location by current coordinates
 
471
            location_name = ",,,%s,%s" % (self.lat, self.lon)
 
472
            self.forecast = pywapi.get_weather_from_google (location_name, hl = self.locale)
 
473
            self.unitsystem = self.forecast['forecast_information']['unit_system']
 
474
 
 
475
            for forecast in self.forecast['forecasts']:
 
476
                self.daysofweek.append(forecast["day_of_week"])
 
477
                forecast_icon = str(forecast["icon"])
 
478
                if "/ig/images/weather/" in forecast_icon:
 
479
                    self.icons.append(forecast_icon.split("/ig/images/weather/")[-1].split(".gif")[0])
 
480
                elif "http://g0.gstatic.com/images/icons/onebox" in forecast_icon:
 
481
                    self.icons.append(forecast_icon.split("http://g0.gstatic.com/images/icons/onebox/weather_")[-1].split("-40.gif")[0])
 
482
                self.conditions.append(forecast["condition"])
 
483
            self.error_message = None
 
484
 
 
485
        except urllib2.URLError:
 
486
            log.error("Forecast: error reading forecast for %s" % location_name)
 
487
        except KeyError:
 
488
            log.error("Forecast: returned empty forecast %s" % location_name)
 
489
            self.error_message = _('Unknown error occurred while picking up weather data')
 
490
 
 
491
    # Parse high values for forecast data
 
492
    def get_forecast_data(self):
 
493
        self.highdata = []
 
494
        self.lowdata = []
 
495
 
 
496
        if not hasattr(self, 'unitsystem'):
 
497
            return None
 
498
 
 
499
        if ((self.unitsystem == 'SI') and (self.metric_system == MetricSystem.SI)) or ((self.unitsystem == 'US') and (self.metric_system == MetricSystem.IMPERIAL)):
 
500
            #correct scale selected
 
501
            for forecast in self.forecast['forecasts']:
 
502
                self.highdata.append(forecast["high"])
 
503
                self.lowdata.append(forecast["low"])
 
504
 
 
505
        elif ((self.unitsystem == 'SI') and (self.metric_system == MetricSystem.IMPERIAL)):
 
506
            #convert from SI to imperial
 
507
            for forecast in self.forecast['forecasts']:
 
508
                self.highdata.append(int(((int(forecast["high"])*9)/5)+32))
 
509
                self.lowdata.append(int(((int(forecast["low"])*9)/5)+32))
 
510
 
 
511
        elif ((self.unitsystem == 'US') and (self.metric_system == MetricSystem.SI)):
 
512
            #convert from imperial to SI
 
513
            for forecast in self.forecast['forecasts']:
 
514
                self.highdata.append(int((((int(forecast["high"]))-32)*5)/9))
 
515
                self.lowdata.append(int((((int(forecast["low"]))-32)*5)/9))
 
516
 
 
517
        return (self.highdata, self.lowdata)
 
518
 
 
519
    # Parse a list of days of week with forecast data
 
520
    def get_forecast_daysofweek(self):
 
521
        return self.daysofweek
 
522
 
 
523
    # Parse icons for forecast data
 
524
    def get_forecast_icons(self):
 
525
        return self.icons
 
526
 
 
527
    # Parse conditions for forecast data
 
528
    def get_forecast_conditions(self):
 
529
        return self.conditions
 
530
 
 
531
class Weather:
 
532
    """
 
533
    Data object to parse weather data with unit convertion
 
534
    """
 
535
 
 
536
    #Available conditions by google icon
 
537
    #Format: Google icon name: (day icon, night icon, is a severe weather condition)
 
538
    #Reference: http://www.blindmotion.com/2009/03/google-weather-api-images/
 
539
    _GoogleConditions = {
 
540
        "sunny"            : ( "weather-clear",      "weather-clear-night",      False),
 
541
        "mostly_sunny"     : ( "weather-clear",      "weather-clear-night",      False),
 
542
        "partlycloudy"     : ( "weather-few-clouds", "weather-few-clouds-night", False),
 
543
        "partly_cloudy"    : ( "weather-few-clouds", "weather-few-clouds-night", False),
 
544
        "windy"            : ( "weather-few-clouds", "weather-few-clouds-night", False),
 
545
        "cloudy"           : ( "weather-clouds",     "weather-clouds-night",     False),
 
546
        "mostlycloudy"     : ( "weather-overcast",   "weather-overcast",         False),
 
547
        "mostly_cloudy"    : ( "weather-overcast",   "weather-overcast",         False),
 
548
        "overcast"         : ( "weather-overcast",   "weather-overcast",         False),
 
549
        "rain"             : ( "weather-showers",    "weather-showers",          False),
 
550
        "chanceofrain"     : ( "weather-showers",    "weather-showers",          False),
 
551
        "chance_of_rain"   : ( "weather-showers",    "weather-showers",          False),
 
552
        "heavyrain"        : ( "weather-showers",    "weather-showers",          False),
 
553
        "drizzle"          : ( "weather-showers",    "weather-showers",          False),
 
554
        "sleet"            : ( "weather-snow",       "weather-snow",             False),
 
555
        "rain_snow"        : ( "weather-snow",       "weather-snow",             False),
 
556
        "rainsnow"         : ( "weather-snow",       "weather-snow",             False),
 
557
        "snow"             : ( "weather-snow",       "weather-snow",             False),
 
558
        "chanceofsnow"     : ( "weather-snow",       "weather-snow",             False),
 
559
        "chance_of_snow"   : ( "weather-snow",       "weather-snow",             False),
 
560
        "heavysnow"        : ( "weather-snow",       "weather-snow",             False),
 
561
        "icy"              : ( "weather-snow",       "weather-snow",             False),
 
562
        "snowflurries"     : ( "weather-snow",       "weather-snow",             False),
 
563
        "dust"             : ( "weather-fog",        "weather-fog",              False),
 
564
        "fog"              : ( "weather-fog",        "weather-fog",              False),
 
565
        "smoke"            : ( "weather-fog",        "weather-fog",              False),
 
566
        "haze"             : ( "weather-fog",        "weather-fog",              False),
 
567
        "mist"             : ( "weather-fog",        "weather-fog",              False),
 
568
        "thunderstorm"     : ( "weather-storm",      "weather-storm",            True),
 
569
        "chance_of_storm"  : ( "weather-storm",      "weather-storm",            True),
 
570
        "thunderstorms"    : ( "weather-storm",      "weather-storm",            True),
 
571
        "scatteredshowers" : ( "weather-showers-scattered", "weather-showers-scattered", True),
 
572
        "scatteredthunderstorms" : ( "weather-storm",       "weather-storm",             True),
 
573
    }
 
574
 
 
575
    #Available conditions by yahoo condition code
 
576
    #Format: condition code: (day icon, night icon, is a severe weather condition, localized condition name)
 
577
    _YahooConditions = {
 
578
        '0' : ("weather-storm",             "weather-storm",            True,  _("Tornado")),
 
579
        '1' : ("weather-storm",             "weather-storm",            True,  _("Tropical storm")),
 
580
        '2' : ("weather-storm",             "weather-storm",            True,  _("Hurricane")),
 
581
        '3' : ("weather-storm",             "weather-storm",            True,  _("Severe thunderstorms")),
 
582
        '4' : ("weather-storm",             "weather-storm",            True,  _("Thunderstorms")),
 
583
        '5' : ("weather-snow",              "weather-snow",             False, _("Mixed rain and snow")),
 
584
        # Use American meaning of sleet - see http://en.wikipedia.org/wiki/Sleet
 
585
        '6' : ("weather-showers",           "weather-showers",          False, _("Mixed rain and sleet")),
 
586
        '7' : ("weather-snow",              "weather-snow",             False, _("Mixed snow and sleet")),
 
587
        '8' : ("weather-showers",           "weather-showers",          False, _("Freezing drizzle")),
 
588
        '9' : ("weather-showers",           "weather-showers",          False, _("Drizzle")),
 
589
        '10': ("weather-snow",              "weather-snow",             False, _("Freezing rain")),
 
590
        '11': ("weather-showers",           "weather-showers",          False, _("Showers")),
 
591
        '12': ("weather-showers",           "weather-showers",          False, _("Showers")),
 
592
        '13': ("weather-snow",              "weather-snow",             False, _("Snow flurries")),
 
593
        '14': ("weather-snow",              "weather-snow",             False, _("Light snow showers")),
 
594
        '15': ("weather-snow",              "weather-snow",             False, _("Blowing snow")),
 
595
        '16': ("weather-snow",              "weather-snow",             False, _("Snow")),
 
596
        '17': ("weather-showers",           "weather-showers",          False, _("Hail")),
 
597
        '18': ("weather-snow",              "weather-snow",             False, _("Sleet")),
 
598
        '19': ("weather-fog",               "weather-fog",              False, _("Dust")),
 
599
        '20': ("weather-fog",               "weather-fog",              False, _("Foggy")),
 
600
        '21': ("weather-fog",               "weather-fog",              False, _("Haze")),
 
601
        '22': ("weather-fog",               "weather-fog",              False, _("Smoky")),
 
602
        '23': ("weather-clear",             "weather-clear-night",      False, _("Blustery")),
 
603
        '24': ("weather-clear",             "weather-clear-night",      False, _("Windy")),
 
604
        '25': ("weather-clear",             "weather-clear-night",      False, _("Cold")),
 
605
        '26': ("weather-clouds",            "weather-clouds-night",     False, _("Cloudy")),
 
606
        '27': ("weather-clouds",            "weather-clouds-night",     False, _("Mostly cloudy")),
 
607
        '28': ("weather-clouds",            "weather-clouds-night",     False, _("Mostly cloudy")),
 
608
        '29': ("weather-few-clouds",        "weather-few-clouds-night", False, _("Partly cloudy")),
 
609
        '30': ("weather-few-clouds",        "weather-few-clouds-night", False, _("Partly cloudy")),
 
610
        '31': ("weather-clear",             "weather-clear-night",      False, _("Clear")),
 
611
        '32': ("weather-clear",             "weather-clear-night",      False, _("Sunny")),
 
612
        '33': ("weather-clear",             "weather-clear-night",      False, _("Fair")),
 
613
        '34': ("weather-clear",             "weather-clear-night",      False, _("Fair")),
 
614
        '35': ("weather-showers-scattered", "weather-showers-scattered",False, _("Mixed rain and hail")),
 
615
        '36': ("weather-clear",             "weather-clear-night",      False, _("Hot")),
 
616
        '37': ("weather-storm",             "weather-storm",            True,  _("Isolated thunderstorms")),
 
617
        '38': ("weather-storm",             "weather-storm",            True,  _("Scattered thunderstorms")),
 
618
        '39': ("weather-storm",             "weather-storm",            True,  _("Scattered thunderstorms")),
 
619
        '40': ("weather-showers-scattered", "weather-showers-scattered",False, _("Scattered showers")),
 
620
        '41': ("weather-snow",              "weather-snow",             False, _("Heavy snow")),
 
621
        '42': ("weather-snow",              "weather-snow",             False, _("Scattered snow showers")),
 
622
        '43': ("weather-snow",              "weather-snow",             False, _("Heavy snow")),
 
623
        '44': ("weather-few-clouds",        "weather-few-clouds-night", False, _("Partly cloudy")),
 
624
        '45': ("weather-storm",             "weather-storm",            True,  _("Thundershowers")),
 
625
        '46': ("weather-snow",              "weather-snow",             False, _("Snow showers")),
 
626
        '47': ("weather-storm",             "weather-storm",            True,  _("Isolated thundershowers")),
 
627
        '3200': (False,                     False,                      False, _("Unknown condition"))
 
628
    }
 
629
 
 
630
    # Initialize and get fresh data
 
631
    def __init__(self, location_id, weather_datasource, metric_system, wind_unit, lat, lon):
 
632
        self.__weather_datasource = weather_datasource
 
633
        self.__metric_system = metric_system
 
634
        self._wind_unit = wind_unit
 
635
        self.__current_condition = None
 
636
        self.__lat = lat
 
637
        self.__lon = lon
 
638
 
 
639
        # Get data from Google
 
640
        if self.__weather_datasource == WeatherDataSource.GOOGLE:
 
641
            # Get data in english locale, then - switch back
 
642
            self.__report = pywapi.get_weather_from_google (location_id, hl = 'en')
 
643
            # Get data in original locale for condition name
 
644
            self.__localized_report = pywapi.get_weather_from_google (location_id, hl = locale_name)
 
645
 
 
646
            if 'current_conditions' not in self.__report.keys():
 
647
                log.error("Weather: could not get Google weather condition from report")
 
648
                log.error("Weather: got data '%s'" % str(self.__report))
 
649
                self.__current_condition = (False, False, False, _("Unknown condition"))
 
650
 
 
651
            if 'current_conditions' not in self.__localized_report.keys():
 
652
                log.error("Weather: could not get Google weather condition from localized report")
 
653
                log.error("Weather: got data '%s'" % str(self.__localized_report))
 
654
                self.__current_condition = (False, False, False, _("Unknown condition"))
 
655
 
 
656
            if 'icon' in self.__report['current_conditions'].keys():
 
657
                icon_path = self.__report['current_conditions']['icon']
 
658
                if '/ig/images/weather/' in icon_path:
 
659
                    icon_name = icon_path.replace('/ig/images/weather/', '').replace('.gif', '')
 
660
                elif 'http://g0.gstatic.com/images/icons/onebox' in icon_path:
 
661
                    icon_name = icon_path.replace('http://g0.gstatic.com/images/icons/onebox/weather_', '').replace('-40.gif', '')
 
662
                else:
 
663
                    icon_name = icon_path
 
664
            else:
 
665
                log.error("Weather: could not get weather icon from report")
 
666
                log.error("Weather: got data '%s'" % str(self.__report['current_conditions']))
 
667
                icon_name = ""
 
668
 
 
669
            self.__current_condition = self._GoogleConditions.get(icon_name)
 
670
            if self.__current_condition == None:
 
671
                log.error("ExtendedForecast: unknown Google weather condition '%s'" % icon_name)
 
672
                self.__current_condition = (False, False, False, _("Unknown condition"))
 
673
 
 
674
        # Get data from Yahoo
 
675
        if self.__weather_datasource == WeatherDataSource.YAHOO:
 
676
            self.__report = pywapi.get_weather_from_yahoo (location_id, 'imperial')
 
677
            self.__localized_report = self.__report
 
678
            if 'condition' not in self.__report.keys():
 
679
                log.error("Weather: could not get Yahoo weather condition from report")
 
680
                log.error("Weather: got data '%s'" % str(self.__report))
 
681
                self.__current_condition = (False, False, False, _("Unknown condition"))
 
682
 
 
683
            elif 'code' in self.__report['condition'].keys():
 
684
                icon_name = self.__report['condition']['code']
 
685
                self.__current_condition = self._YahooConditions.get(icon_name)
 
686
 
 
687
            else:
 
688
                icon_name = ""
 
689
                log.error("Weather: could not get icon name from Yahoo report")
 
690
                log.error("Weather: got data '%s'" % str(self.__report['condition']))
 
691
                self.__current_condition = (False, False, False, _("Unknown condition"))
 
692
 
 
693
        log.debug("Weather: current condition: '%s', '%s'" % (icon_name, str(self.__current_condition)))
 
694
        #Prepare sunrise/sunset data
 
695
        self.get_sun_data()
 
696
 
 
697
    #Get sunrise/sunset times, calculate whether it is night already
 
698
    def get_sun_data(self):
 
699
        self.__night = False
 
700
        self.__sunrise_t = None
 
701
        self.__sunset_t = None
 
702
        # Grab local datetime and the daylight saving status (1/0)
 
703
        # from earthtools.org
 
704
        url = 'http://www.earthtools.org/timezone-1.1/%s/%s' % \
 
705
            (self.__lat, self.__lon)
 
706
        try:
 
707
            f = urllib2.urlopen(url)
 
708
            s = f.read()
 
709
            parsed = parseString(s)
 
710
            localtime = parsed.getElementsByTagName(
 
711
                "isotime")[0].firstChild.nodeValue
 
712
            dst = parsed.getElementsByTagName(
 
713
                "dst")[0].firstChild.nodeValue
 
714
            # strip timezone info
 
715
            localtime = datetime.datetime.strptime(localtime.rsplit(' ',1)[0],
 
716
                '%Y-%m-%d %H:%M:%S')
 
717
            dst = 1 if dst == "True" else 0
 
718
 
 
719
        except urllib2.URLError:
 
720
            log.error("Weather: error reaching url '%s'" % url)
 
721
            return
 
722
 
 
723
        # Grab sunrise/sunset from earthtools.org
 
724
        url = 'http://www.earthtools.org/sun/%s/%s/%s/%s/99/%s' % \
 
725
            (self.__lat, self.__lon, localtime.day, localtime.month, dst)
 
726
        try:
 
727
            f = urllib2.urlopen(url)
 
728
            s=f.read()
 
729
            parsed = parseString(s)
 
730
            sunrise = parsed.getElementsByTagName("morning")[0].getElementsByTagName("sunrise")[0].firstChild.nodeValue
 
731
            sunset  = parsed.getElementsByTagName("evening")[0].getElementsByTagName("sunset")[0].firstChild.nodeValue
 
732
            self.__sunrise_t = datetime.datetime.strptime(sunrise, '%H:%M:%S').time()
 
733
            self.__sunset_t = datetime.datetime.strptime(sunset, '%H:%M:%S').time()
 
734
        except urllib2.URLError:
 
735
            log.error("Weather: error reaching url '%s'" % url)
 
736
            return
 
737
 
 
738
        # Calculate, whether it is night or day
 
739
        if localtime.time()<self.__sunrise_t or localtime.time()>self.__sunset_t:
 
740
            self.__night = True
 
741
        else:
 
742
            self.__night = False
 
743
        log.debug("Weather: got localtime " +
 
744
            "%s, dst %s, sunrise '%s', sunset '%s', night = %s" % (
 
745
            localtime, dst, self.__sunrise_t, self.__sunset_t, self.__night))
 
746
 
 
747
    # Return True, if weather condition is severe
 
748
    def condition_is_severe(self):
 
749
        if self.__current_condition != None:
 
750
            log.debug("Weather: got severe condition '%s'" % self.__current_condition[2])
 
751
            return self.__current_condition[2]
 
752
        else:
 
753
            log.error("Weather: condition is not set while condition severity check")
 
754
            return False;
 
755
 
 
756
    # Get associated icon name
 
757
    def get_icon_name(self):
 
758
        if self.__current_condition != None:
 
759
            if self.__night:
 
760
                log.debug("Weather: night, show '%s' icon" % self.__current_condition[1])
 
761
                return self.__current_condition[1]
 
762
            else:
 
763
                log.debug("Weather: day, show '%s' icon" % self.__current_condition[0])
 
764
                return self.__current_condition[0]
 
765
        else:
 
766
            log.error("Weather: return 'offline' icon due to empty condition")
 
767
            return False
 
768
 
 
769
    # Get condition text
 
770
    def get_condition_label(self):
 
771
        if self.__weather_datasource == WeatherDataSource.GOOGLE:
 
772
            if 'condition' in self.__localized_report['current_conditions'].keys():
 
773
                condition = self.__localized_report['current_conditions']['condition']
 
774
            else:
 
775
                condition = _("Unknown condition")
 
776
        if self.__weather_datasource == WeatherDataSource.YAHOO:
 
777
            condition = self.__current_condition[3]
 
778
        return condition
 
779
 
 
780
    # Get humidity label
 
781
    def get_humidity_label(self):
 
782
        humidity = "%s: ---%%" % (_("Humidity"))
 
783
        if self.__weather_datasource == WeatherDataSource.GOOGLE \
 
784
            and 'humidity' in self.__localized_report['current_conditions']:
 
785
            humidity = self.__localized_report['current_conditions']['humidity']
 
786
        if self.__weather_datasource == WeatherDataSource.YAHOO \
 
787
            and 'humidity' in self.__localized_report['atmosphere']:
 
788
            humidity = "%s: %s%%" % (_("Humidity"), self.__localized_report['atmosphere']['humidity'])
 
789
        return humidity
 
790
 
 
791
    # Get dew point - using in humidex calculation
 
792
    #TODO: Update with NOAA
 
793
    def get_dew_point_label(self):
 
794
        if self.__weather_datasource == WeatherDataSource.GOOGLE or self.__weather_datasource == WeatherDataSource.YAHOO:
 
795
            # Not returned by Google and Yahoo
 
796
            return None
 
797
 
 
798
    # Get pressure label
 
799
    def get_pressure_label(self):
 
800
        if self.__weather_datasource == WeatherDataSource.GOOGLE:
 
801
            # TODO: Empty for Google, use NOAA data?
 
802
            value = "---"
 
803
            unit = ""
 
804
        if self.__weather_datasource == WeatherDataSource.YAHOO \
 
805
            and 'pressure' in self.__localized_report['atmosphere'].keys() \
 
806
            and 'pressure' in self.__localized_report['units'].keys():
 
807
            value = self.__localized_report['atmosphere']['pressure']
 
808
            unit = self.__localized_report['units']['pressure']
 
809
        return "%s: %s %s" % (_("Pressure"), value, units)
 
810
 
 
811
    # Get temperature with units value - doesn't include 'Temperature' label
 
812
    def get_temperature(self, needs_rounding = False):
 
813
        _value = "---"
 
814
        _unit = ""
 
815
        if self.__weather_datasource == WeatherDataSource.GOOGLE:
 
816
            if (self.__metric_system == MetricSystem.SI) \
 
817
            and 'temp_c' in self.__report['current_conditions'].keys():
 
818
                _value = self.__report['current_conditions']['temp_c']
 
819
                _unit  = "˚C"
 
820
            elif 'temp_f' in self.__report['current_conditions'].keys():
 
821
                _value = self.__report['current_conditions']['temp_f']
 
822
                _unit  = "˚F"
 
823
        if self.__weather_datasource == WeatherDataSource.YAHOO:
 
824
            if (self.__metric_system == MetricSystem.SI) \
 
825
            and 'temp' in self.__report['condition'].keys():
 
826
                _value = NumberFormatter.format_float(
 
827
                    ((float(self.__report['condition']['temp']) - 32) * 5/9), 1)
 
828
                _unit  = "˚C"
 
829
            else:
 
830
                _value = self.__report['condition']['temp']
 
831
                _unit  = "˚F"
 
832
        # round the value if required
 
833
        if needs_rounding and _value != "---":
 
834
            _value = NumberFormatter.format_float(locale.atof(_value), 0)
 
835
        return ("%s %s" % (_value, _unit))
 
836
 
 
837
    # Get temperature label
 
838
    def get_temperature_label(self):
 
839
        return "%s: %s" % (_("Temperature"), self.get_temperature())
 
840
 
 
841
    # Get humidex parameter
 
842
    def get_humidex_label(self):
 
843
        if self.__weather_datasource == WeatherDataSource.GOOGLE or self.__weather_datasource == WeatherDataSource.YAHOO:
 
844
            #Empty for Yahoo and Google
 
845
            return None
 
846
        #TODO: Update with NOAA data
 
847
        #dewPoint=2
 
848
        #temp_c = 1
 
849
        #self.vapour_pressure = 6.11 * math.exp(5417.7530 * ( (1/273.16) - (1/(dewPoint+273.16))))
 
850
        #self.humidex = temp_c + (0.5555)*(self.vapour_pressure  - 10.0);
 
851
        #return ("%s: %.1f" % (_("Humidex"), self.humidex)).replace(".0", "")
 
852
 
 
853
    # Get wind label
 
854
    def get_wind_label(self):
 
855
        if self.__weather_datasource == WeatherDataSource.GOOGLE:
 
856
            # Convert units picked up from Google and replace units with currently configured
 
857
            if 'wind_condition' in self.__localized_report['current_conditions'].keys():
 
858
                localized_wind_info = self.__localized_report['current_conditions']['wind_condition'].split(' ')
 
859
                wind_direction = localized_wind_info[1]
 
860
                wind_info = self.__report['current_conditions']['wind_condition'].split(' ')
 
861
                wind_speed = wind_info[3]
 
862
            else:
 
863
                return _("Unknown")
 
864
 
 
865
        if self.__weather_datasource == WeatherDataSource.YAHOO:
 
866
            # Create a similar to Google wind_info structure from Yahoo data
 
867
            wind_direction = "%s (%s˚)" % (self.get_wind_direction(self.__localized_report['wind']['direction']), self.__localized_report['wind']['direction'])
 
868
            wind_speed = self.__localized_report['wind']['speed']
 
869
            wind_units = self.__localized_report['units']['speed']
 
870
            localized_wind_info = [_("Wind") + ":", wind_direction, wind_speed, wind_units]
 
871
 
 
872
        # Parse Wind_direction - convert to selected scale
 
873
        if (self._wind_unit == WindUnits.MPH):
 
874
            _value = float(wind_speed)
 
875
            _unit  = __("mph", "mph", _value)
 
876
        if (self._wind_unit == WindUnits.MPS):
 
877
            _value = float(wind_speed) * 0.44704
 
878
            _unit  = __("m/s", "m/s", _value)
 
879
        if (self._wind_unit == WindUnits.BEAUFORT):
 
880
            _value = self.get_beaufort_from_mph(float(wind_speed))
 
881
            _unit  = ""
 
882
        if (self._wind_unit == WindUnits.KPH):
 
883
            _value = float(wind_speed) * 1.609344
 
884
            _unit  = __("km/h", "km/h", _value)
 
885
        if (self._wind_unit == WindUnits.KNOTS):
 
886
            _value = float(wind_speed) * 0.868976241900648
 
887
            _unit  = __("knot", "knots", _value)
 
888
 
 
889
        # Join wind_info data in a label
 
890
        localized_wind_info[len(localized_wind_info)-1] = _unit
 
891
        localized_wind_info[len(localized_wind_info)-2] = \
 
892
            NumberFormatter.format_float(_value, 1)
 
893
        return "%s %s %s %s" % (localized_wind_info[0], localized_wind_info[1], \
 
894
            localized_wind_info[2], localized_wind_info[3])
 
895
 
 
896
    # Get sunrise label
 
897
    def get_sunrise_label(self):
 
898
        return "%s: %s" % (_("Sunrise"), TimeFormatter.format_time(self.__sunrise_t))
 
899
 
 
900
    # Get sunset label
 
901
    def get_sunset_label(self):
 
902
        return "%s: %s" % (_("Sunset"), TimeFormatter.format_time(self.__sunset_t))
 
903
 
 
904
 
 
905
    # Additional functions
 
906
    # Convert wind direction from degrees to localized direction
 
907
    def get_wind_direction(self, degrees):
 
908
        try:
 
909
            degrees = int(degrees)
 
910
        except ValueError:
 
911
            return ''
 
912
 
 
913
        if degrees < 23 or degrees >= 338:
 
914
            #Short wind direction - north
 
915
            return _('N')
 
916
        elif degrees < 68:
 
917
            return _('NE')
 
918
        elif degrees < 113:
 
919
            return _('E')
 
920
        elif degrees < 158:
 
921
            return _('SE')
 
922
        elif degrees < 203:
 
923
            return _('S')
 
924
        elif degrees < 248:
 
925
            return _('SW')
 
926
        elif degrees < 293:
 
927
            return _('W')
 
928
        elif degrees < 338:
 
929
            return _('NW')
 
930
 
 
931
    # Convert mph to Beufort scale
 
932
    def get_beaufort_from_mph(self, value):
 
933
        if value < 1:
 
934
            return 0
 
935
        elif value < 4:
 
936
            return 1
 
937
        elif value < 8:
 
938
            return 2
 
939
        elif value < 13:
 
940
            return 3
 
941
        elif value < 18:
 
942
            return 4
 
943
        elif value < 25:
 
944
            return 5
 
945
        elif value < 27:
 
946
            return 6
 
947
        elif value < 39:
 
948
            return 7
 
949
        elif value < 47:
 
950
            return 8
 
951
        elif value < 89:
 
952
            return 9
 
953
        elif value < 64:
 
954
            return 10
 
955
        elif value < 73:
 
956
            return 11
 
957
        elif value >= 73:
 
958
            return 12
 
959
 
 
960
class indicator_weather(threading.Thread):
 
961
    """ Indicator class """
 
962
    last_update_time = None
 
963
 
 
964
    # Settings values
 
965
    # Formats: setting value, object name (for preferences dialog), value assigned (optional)
 
966
    metric_systems = { 'S': ('si',       MetricSystem.SI),
 
967
                       'I': ('imperial', MetricSystem.IMPERIAL)}
 
968
 
 
969
    weather_sources = { 'G': ('google', WeatherDataSource.GOOGLE),
 
970
                        'Y': ('yahoo',  WeatherDataSource.YAHOO)}
 
971
 
 
972
    notifications = {'N': 'nonotif',
 
973
                     'O': 'notifsevere',
 
974
                     'A': 'notifall'}
 
975
 
 
976
    wind_systems = {'mps':      ("mps",      WindUnits.MPS),
 
977
                    'mph':      ("mph",      WindUnits.MPH),
 
978
                    'kph':      ("kph",      WindUnits.KPH),
 
979
                    'beaufort': ("beaufort", WindUnits.BEAUFORT),
 
980
                    'knots':    ("knots",    WindUnits.KNOTS)}
 
981
 
 
982
    # Initializing and reading settings
 
983
    def __init__(self):
 
984
        log.debug("Indicator: creating")
 
985
        threading.Thread.__init__(self)
 
986
        self.main_icon = os.path.join
 
987
        self.winder = appindicator.Indicator ("indicator-weather", "weather-indicator", appindicator.CATEGORY_OTHER)
 
988
        self.winder.set_status (appindicator.STATUS_ACTIVE)
 
989
        self.winder.set_attention_icon ("weather-indicator-error")
 
990
 
 
991
        self.menu_update_lock = threading.Lock()
 
992
        self.refreshed_minutes_ago = -1
 
993
        monitor_upower(self.on_system_sleep, self.on_system_resume, log)
 
994
 
 
995
        log.debug("Indicator: reading settings")
 
996
        self.settings = Settings()
 
997
        self.settings.check_autostart()
 
998
        self.settings.prepare_settings_store()
 
999
        self.rate  = self.settings.get_value("refresh_rate")
 
1000
        self.unit  = self.settings.get_value("unit")
 
1001
        self.notif = self.settings.get_value("notif")
 
1002
        self.wind  = self.settings.get_value("wind")
 
1003
        self.source = self.settings.get_value("data_source")
 
1004
        self.placechosen = self.settings.get_value("placechosen")
 
1005
        self.places = str(self.settings.get_value("places"))
 
1006
        self.show_label = self.settings.get_value("show_label")
 
1007
 
 
1008
        log.debug("Preferences: got settings: rate=%s, unit=%s, notif=%s, wind=%s, placechosen=%s, places=%s" %
 
1009
                (self.rate, self.unit, self.notif, self.wind, self.placechosen, self.places))
 
1010
 
 
1011
        #Setting default values
 
1012
        self.metric_system = MetricSystem.SI
 
1013
        self.wind_unit = WindUnits.MPH
 
1014
        self.place = None
 
1015
        self.menu = None
 
1016
        self.condition = None
 
1017
        self.icon = None
 
1018
 
 
1019
        #Parsing settings
 
1020
        # Metric system
 
1021
        if self.unit in (False, None):
 
1022
            default_value = 'S'
 
1023
            log.debug("Indicator: could not parse unit, setting to %s" % default_value)
 
1024
            self.settings.set_value("unit", default_value)
 
1025
            self.unit = default_value
 
1026
        self.metric_system = self.metric_systems[self.unit][1]
 
1027
 
 
1028
        # Notification
 
1029
        if self.notif in (False, None):
 
1030
            default_value = 'N'
 
1031
            log.debug("Indicator: could not parse notif, setting to %s" % default_value)
 
1032
            self.settings.set_value("notif", default_value)
 
1033
            self.notif = default_value
 
1034
 
 
1035
        # Wind units
 
1036
        if self.wind in (False, None):
 
1037
            default_value = 'mph'
 
1038
            log.debug("Indicator: could not parse notif, setting to %s" % default_value)
 
1039
            self.settings.set_value("wind", default_value)
 
1040
            self.wind = default_value
 
1041
        self.wind_unit = self.wind_systems[self.wind][1]
 
1042
 
 
1043
        # Show label in indicator?
 
1044
        self.show_label = True if self.show_label == 1 else False
 
1045
 
 
1046
        # Weather source
 
1047
        if self.source in (False, None):
 
1048
            default_value = 'Y'
 
1049
            log.debug("Indicator: could not parse data source, setting to %s" % default_value)
 
1050
            self.settings.set_value("data_source", default_value)
 
1051
            self.source = default_value
 
1052
        self.weather_source = self.weather_sources[self.source][1]
 
1053
 
 
1054
        # Rate
 
1055
        if self.rate in (False, None):
 
1056
            default_value = 15
 
1057
            log.debug("Indicator: could not parse rate, setting to %s" % str(default_value))
 
1058
            self.settings.set_value("refresh_rate", default_value)
 
1059
            self.rate = default_value
 
1060
 
 
1061
        # Place chosen
 
1062
        if self.placechosen == None:
 
1063
            log.debug("Indicator: could not parse placechosen, setting to 0")
 
1064
            self.settings.set_value("placechosen", 0)
 
1065
            self.placechosen = 0
 
1066
        else:
 
1067
            self.placechosen = int(self.placechosen)
 
1068
 
 
1069
        # Places list
 
1070
        if self.places in (False, None, '', '[]', "['']"):
 
1071
            log.debug("Indicator: could not parse places")
 
1072
            self.menu_noplace()
 
1073
        else:
 
1074
            self.places = eval(self.places)
 
1075
            if self.placechosen >= len(self.places):
 
1076
                self.placechosen = 0
 
1077
            self.place = self.places[self.placechosen]
 
1078
            self.location_details = self.settings.get_location_details(self.place[0])
 
1079
            if self.location_details in (False, None, '', '[]', "['']"):
 
1080
                log.debug("Indicator: could not parse current location details")
 
1081
                self.menu_noplace()
 
1082
            else:
 
1083
                self.location_details = eval(self.location_details)
 
1084
                self.menu_normal()
 
1085
                self.update_weather()
 
1086
 
 
1087
    # Set a label of indicator
 
1088
    def update_label(self, label):
 
1089
        if (hasattr(self.winder, 'set_label')):
 
1090
            log.debug("Indicator: update_label: setting label to '%s'" % label)
 
1091
            self.previous_label_value = label
 
1092
            self.winder.set_label(label) if self.show_label else self.winder.set_label(" ")
 
1093
            self.winder.set_status(appindicator.STATUS_ATTENTION)
 
1094
            self.winder.set_status(appindicator.STATUS_ACTIVE)
 
1095
 
 
1096
    # Show a menu if no places specified
 
1097
    def menu_noplace(self):
 
1098
        log.debug("Indicator: making a menu for no places")
 
1099
        menu_noplace = gtk.Menu()
 
1100
 
 
1101
        setup = gtk.MenuItem(_("Set Up Weather..."))
 
1102
        setup.connect("activate", self.prefs)
 
1103
        setup.show()
 
1104
        menu_noplace.append(setup)
 
1105
 
 
1106
        quit = gtk.ImageMenuItem(gtk.STOCK_QUIT)
 
1107
        quit.connect("activate", self.quit)
 
1108
        quit.show()
 
1109
        menu_noplace.append(quit)
 
1110
 
 
1111
        self.winder.set_menu(menu_noplace)
 
1112
        self.winder.set_icon(os.path.join(PROJECT_ROOT_DIRECTORY, "share/indicator-weather/media/icon.png"))
 
1113
        self.winder.set_status(appindicator.STATUS_ATTENTION)
 
1114
        self.winder.set_status(appindicator.STATUS_ACTIVE)
 
1115
 
 
1116
    # Show menu with data
 
1117
    def menu_normal(self):
 
1118
        log.debug("Indicator: menu_normal: filling in a menu for found places")
 
1119
        self.menu = gtk.Menu()
 
1120
 
 
1121
    ##City
 
1122
        self.city_show = gtk.MenuItem()
 
1123
        self.city_show.set_sensitive(True)
 
1124
        self.menu.append(self.city_show)
 
1125
        self.city_show.show()
 
1126
 
 
1127
    ##Condition
 
1128
        self.cond_show = gtk.MenuItem()
 
1129
        self.cond_show.set_sensitive(True)
 
1130
        self.cond_show.show()
 
1131
        self.menu.append(self.cond_show)
 
1132
 
 
1133
    ##Temperature
 
1134
        self.temp_show = gtk.MenuItem()
 
1135
        self.temp_show.set_sensitive(True)
 
1136
        self.temp_show.show()
 
1137
        self.menu.append(self.temp_show)
 
1138
 
 
1139
    ##Humidex
 
1140
        self.humidex_show = gtk.MenuItem()
 
1141
        self.humidex_show.set_sensitive(True)
 
1142
        self.humidex_show.show()
 
1143
        self.menu.append(self.humidex_show)
 
1144
 
 
1145
    ##Humidity
 
1146
        self.humid_show = gtk.MenuItem()
 
1147
        self.humid_show.set_sensitive(True)
 
1148
        self.humid_show.show()
 
1149
        self.menu.append(self.humid_show)
 
1150
 
 
1151
    ##Wind
 
1152
        self.wind_show = gtk.MenuItem()
 
1153
        self.wind_show.set_sensitive(True)
 
1154
        self.wind_show.show()
 
1155
        self.menu.append(self.wind_show)
 
1156
 
 
1157
    ##Sunrise
 
1158
        self.sunrise_show = gtk.MenuItem()
 
1159
        self.sunrise_show.set_sensitive(True)
 
1160
        self.sunrise_show.show()
 
1161
        self.menu.append(self.sunrise_show)
 
1162
 
 
1163
    ##Sunset
 
1164
        self.sunset_show = gtk.MenuItem()
 
1165
        self.sunset_show.set_sensitive(True)
 
1166
        self.sunset_show.show()
 
1167
        self.menu.append(self.sunset_show)
 
1168
 
 
1169
    ##Cities
 
1170
        if len(self.places) != 1:
 
1171
            ##Breaker
 
1172
            breaker = gtk.SeparatorMenuItem()
 
1173
            breaker.show()
 
1174
            self.menu.append(breaker)
 
1175
 
 
1176
            log.debug("Indicator: menu_normal: adding first location menu item '%s'" % self.places[0][1])
 
1177
            loco1 = gtk.RadioMenuItem(None, self.places[0][1])
 
1178
            if self.placechosen == 0:
 
1179
                loco1.set_active(True)
 
1180
            loco1.connect("toggled", self.on_city_changed)
 
1181
            loco1.show()
 
1182
            self.menu.append(loco1)
 
1183
            for place in self.places[1:]:
 
1184
                log.debug("Indicator: menu_normal: adding location menu item '%s'" % place[1])
 
1185
                loco = gtk.RadioMenuItem(loco1, place[1])
 
1186
                if self.places.index(place) == self.placechosen:
 
1187
                    loco.set_active(True)
 
1188
                loco.connect("toggled", self.on_city_changed)
 
1189
                loco.show()
 
1190
                self.menu.append(loco)
 
1191
 
 
1192
    ##Breaker
 
1193
        breaker = gtk.SeparatorMenuItem()
 
1194
        breaker.show()
 
1195
        self.menu.append(breaker)
 
1196
 
 
1197
        self.refresh_show = gtk.MenuItem()
 
1198
        #label will be set later
 
1199
        self.refresh_show.connect("activate", self.update_weather)
 
1200
        self.refresh_show.show()
 
1201
        self.menu.append(self.refresh_show)
 
1202
 
 
1203
        ext_show = gtk.MenuItem(_("Forecast"))
 
1204
        ext_show.connect("activate", self.extforecast)
 
1205
        ext_show.show()
 
1206
        self.menu.append(ext_show)
 
1207
 
 
1208
    ##Preferences
 
1209
        prefs_show = gtk.MenuItem(_("Preferences..."))
 
1210
        prefs_show.connect("activate", self.prefs)
 
1211
        prefs_show.show()
 
1212
        self.menu.append(prefs_show)
 
1213
 
 
1214
    ##About
 
1215
        about_show = gtk.MenuItem(_("About..."))
 
1216
        about_show.connect("activate", self.about)
 
1217
        about_show.show()
 
1218
        self.menu.append(about_show)
 
1219
 
 
1220
    ##Quit
 
1221
        quit = gtk.ImageMenuItem(gtk.STOCK_QUIT)
 
1222
        quit.connect("activate", self.quit)
 
1223
        quit.show()
 
1224
        self.menu.append(quit)
 
1225
 
 
1226
        self.winder.set_menu(self.menu)
 
1227
        self.update_label(" ")
 
1228
 
 
1229
    # Another city has been selected from radiobutton
 
1230
    def on_city_changed(self,widget):
 
1231
        if widget.get_active():
 
1232
            for place in self.places:
 
1233
                if (place[1] == widget.get_label()):
 
1234
                    log.debug("Indicator: new location selected: %s" % self.places.index(place))
 
1235
                    self.placechosen = self.places.index(place)
 
1236
                    break
 
1237
 
 
1238
            if self.placechosen >= len(self.places):
 
1239
                self.placechosen = 0
 
1240
            self.place = self.places[self.placechosen]
 
1241
            self.location_details = self.settings.get_location_details(self.place[0])
 
1242
            if self.location_details in (False, None, '', '[]', "['']"):
 
1243
                log.debug("Indicator: could not parse location details for placechosen='%s'" % self.placechosen)
 
1244
                self.menu_noplace()
 
1245
            else:
 
1246
                self.location_details = eval(self.location_details)
 
1247
            self.settings.set_value("placechosen", self.placechosen)
 
1248
            self.update_weather(False)
 
1249
 
 
1250
    def on_system_sleep(self):
 
1251
        """
 
1252
        Callback from UPower that system suspends/hibernates
 
1253
        """
 
1254
        # store time
 
1255
        self.sleep_time = datetime.datetime.now()
 
1256
        log.debug("Indicator: system goes to sleep at %s" % self.sleep_time)
 
1257
        # remove gobject timeouts
 
1258
        if hasattr(self, "refresh_id"):
 
1259
            gobject.source_remove(self.refresh_id)
 
1260
        if hasattr(self, "rate_id"):
 
1261
            gobject.source_remove(self.rate_id)
 
1262
 
 
1263
    def on_system_resume(self):
 
1264
        """
 
1265
        Callback from UPower that system resumes
 
1266
        """
 
1267
        now = datetime.datetime.now()
 
1268
        log.debug("Indicator: system resumes at %s" % now)
 
1269
        # if we have places set
 
1270
        if isinstance(self.places, types.ListType) and len(self.places)>0:
 
1271
            td = now - self.sleep_time
 
1272
            mins_elapsed = td.days/24*60 + td.seconds/60
 
1273
            # update refresh label
 
1274
            if self.refreshed_minutes_ago >= 0:
 
1275
                mins_elapsed += self.refreshed_minutes_ago
 
1276
                self.update_refresh_label(mins_elapsed)
 
1277
            # check if we need to update the weather now or to reschedule the update
 
1278
            min_diff = int(self.rate) - mins_elapsed
 
1279
            if min_diff > 0:
 
1280
                self.schedule_weather_update(min_diff)
 
1281
            else:
 
1282
                self.update_weather()
 
1283
 
 
1284
    # Schedule weather update
 
1285
    def schedule_weather_update(self, rate_override = None):
 
1286
        if hasattr(self, "rate_id"):
 
1287
            gobject.source_remove(self.rate_id)
 
1288
        if rate_override:
 
1289
            self.rate_id = gobject.timeout_add(
 
1290
                int(rate_override) * 60000, self.update_weather)
 
1291
        else:
 
1292
            self.rate_id = gobject.timeout_add(
 
1293
                int(self.rate) * 60000, self.update_weather)
 
1294
 
 
1295
    # Schedule weather update
 
1296
    def schedule_refresh_label_update(self):
 
1297
        if hasattr(self, "refresh_id"):
 
1298
            gobject.source_remove(self.refresh_id)
 
1299
        self.refresh_id = gobject.timeout_add(60000, self.update_refresh_label)
 
1300
 
 
1301
    # Update 'Refresh' label with time since last successful data refresh
 
1302
    def update_refresh_label(self, reset_minutes = None):
 
1303
        if reset_minutes is not None:
 
1304
            self.refreshed_minutes_ago = reset_minutes
 
1305
        else:
 
1306
            self.refreshed_minutes_ago += 1
 
1307
        self.set_refresh_label()
 
1308
        self.schedule_refresh_label_update()
 
1309
        return False
 
1310
 
 
1311
    def set_refresh_label(self, refreshing=False):
 
1312
        if refreshing:
 
1313
            refresh_label=_("Refreshing, please wait")
 
1314
        elif self.refreshed_minutes_ago < 0:
 
1315
            refresh_label=_("Refresh")
 
1316
        elif self.refreshed_minutes_ago == 0:
 
1317
            refresh_label="%s (%s)" % (_("Refresh"), _("just now"))
 
1318
        else:
 
1319
            refresh_label = "%s (%s)" % (_("Refresh"), _("%d min. ago") % self.refreshed_minutes_ago)
 
1320
        self.refresh_show.set_label(refresh_label)
 
1321
 
 
1322
    # Load weather data from cache and display its values
 
1323
    def show_cached_weather(self):
 
1324
        try:
 
1325
            self.menu_update_lock.acquire(True)
 
1326
            self.previous_condition = None
 
1327
            cached_weather = self.settings.get_weather(self.places[self.placechosen][0])
 
1328
            if cached_weather is not None:
 
1329
                cached_weather = eval(cached_weather)
 
1330
                log.debug("Indicator: loading weather from cache for %s" % self.places[self.placechosen])
 
1331
                self.menu_normal()
 
1332
                self.set_refresh_label(True)
 
1333
                self.icon = cached_weather['icon']
 
1334
                if (self.icon == False):
 
1335
                    self.winder.set_icon(os.path.join(PROJECT_ROOT_DIRECTORY, "share/indicator-weather/media/icon_unknown_condition.png"))
 
1336
                else:
 
1337
                    self.winder.set_icon(self.icon)
 
1338
 
 
1339
                self.city_show.set_label(self.places[self.placechosen][1])
 
1340
                self.previous_condition = cached_weather['condition']
 
1341
                self.cond_show.set_label(cached_weather['condition'])
 
1342
                self.temp_show.set_label(cached_weather['temper'])
 
1343
                if cached_weather['humidex'] != None:
 
1344
                    self.humidex_show.set_label(cached_weather['humidex'])
 
1345
                else:
 
1346
                    self.humidex_show.destroy()
 
1347
                self.humid_show.set_label(cached_weather['humidity'])
 
1348
                self.wind_show.set_label(cached_weather['wind'])
 
1349
                self.sunrise_show.set_label(cached_weather['sunrise'])
 
1350
                self.sunset_show.set_label(cached_weather['sunset'])
 
1351
                self.update_label(cached_weather['label'])
 
1352
                self.winder.set_status(appindicator.STATUS_ATTENTION)
 
1353
                self.winder.set_status(appindicator.STATUS_ACTIVE)
 
1354
 
 
1355
        except Exception, e:
 
1356
            log.error(e)
 
1357
            log.debug(traceback.format_exc(e))
 
1358
 
 
1359
        self.menu_update_lock.release()
 
1360
 
 
1361
    # Get fresh weather data
 
1362
    def get_new_weather_data(self, notif = True):
 
1363
 
 
1364
        # get weather and catch any exception
 
1365
        weather = None
 
1366
        try:
 
1367
            weather = self.get_weather()
 
1368
 
 
1369
        except urllib2.URLError, e:
 
1370
            weather = None
 
1371
            log.error("Indicator: networking error: %s" % e)
 
1372
 
 
1373
        except Exception, e:
 
1374
            weather = None
 
1375
            log.error(e)
 
1376
            log.debug(traceback.format_exc(e))
 
1377
 
 
1378
        try:
 
1379
            # wait until cacher finishes
 
1380
            log.debug("Indicator: updateWeather: waiting for 'Cacher' thread to terminate")
 
1381
            self.menu_update_lock.acquire(True)
 
1382
            self.menu_update_lock.release()
 
1383
 
 
1384
            if weather is None:
 
1385
                # remove the "Refreshing" status
 
1386
                self.set_refresh_label()
 
1387
                # No data returned - leave cached data to be displayed
 
1388
                log.error("Indicator: updateWeather: could not get weather, leaving cached data")
 
1389
                # Repeat an attempt in one minute
 
1390
                self.schedule_weather_update(1)
 
1391
                return
 
1392
 
 
1393
            # Fill in menu with data
 
1394
            log.debug("Indicator: updateWeather: got condition '%s', icon '%s'" % (self.condition, self.icon))
 
1395
            self.condition = weather.get_condition_label()
 
1396
            self.icon = weather.get_icon_name()
 
1397
            log.debug("Indicator: fill in menu with params: city='%s', temp='%s', humid='%s', wind='%s', sunrise='%s', sunset='%s', puretemp=%s" % (self.places[self.placechosen][1], weather.get_temperature_label(), weather.get_humidity_label(), weather.get_wind_label(), weather.get_sunrise_label(), weather.get_sunset_label(), weather.get_temperature()))
 
1398
 
 
1399
            self.menu_normal()
 
1400
            self.update_refresh_label(0)
 
1401
            self.city_show.set_label(self.places[self.placechosen][1])
 
1402
            self.cond_show.set_label(self.condition)
 
1403
            self.temp_show.set_label(weather.get_temperature_label())
 
1404
            if (weather.get_humidex_label() != None):
 
1405
                self.humidex_show.set_label(weather.get_humidex_label())
 
1406
            else:
 
1407
                self.humidex_show.destroy()
 
1408
            self.humid_show.set_label(weather.get_humidity_label())
 
1409
            self.wind_show.set_label(weather.get_wind_label())
 
1410
            self.sunrise_show.set_label(weather.get_sunrise_label())
 
1411
            self.sunset_show.set_label(weather.get_sunset_label())
 
1412
 
 
1413
            # Saving cached data, unless correct icon is supplied
 
1414
            if (self.icon == False):
 
1415
                self.winder.set_icon(os.path.join(PROJECT_ROOT_DIRECTORY, "share/indicator-weather/media/icon_unknown_condition.png"))
 
1416
            else:
 
1417
                self.winder.set_icon(self.icon)
 
1418
                self.settings.save_weather(weather, self.places[self.placechosen][0])
 
1419
            self.update_label(weather.get_temperature(needs_rounding=True))
 
1420
 
 
1421
            # Notify user, if notifications are enabled
 
1422
            if self.condition != self.previous_condition and self.notif == 'U':
 
1423
                # Weather condition has changed
 
1424
                log.debug("Indicator: updateWeather: weather has changed, notify")
 
1425
                self.notify(self.condition, self.icon)
 
1426
            if self.notif == 'S' and weather.condition_is_severe():
 
1427
                # Severe weather condition notification
 
1428
                log.debug("Indicator: updateWeather: severe condition notification")
 
1429
                self.notify(self.condition, self.icon, severe=True)
 
1430
 
 
1431
            self.previous_condition = self.condition
 
1432
 
 
1433
        except Exception, e:
 
1434
            log.error(e)
 
1435
            log.debug(traceback.format_exc(e))
 
1436
 
 
1437
        self.schedule_weather_update()
 
1438
 
 
1439
    # Update weather
 
1440
    def update_weather(self, notif=True, widget=None):
 
1441
        log.debug("Indicator: updateWeather: updating weather for %s" % self.places[self.placechosen])
 
1442
        # First, display cached data
 
1443
        threading.Thread(target=self.show_cached_weather, name='Cache').start()
 
1444
        # Then, start a new thread with real data pickup
 
1445
        threading.Thread(target=self.get_new_weather_data, name='Fetcher').start()
 
1446
 
 
1447
    # Get current weather for selected location
 
1448
    def get_weather(self):
 
1449
        log.debug("Indicator: getWeather for location '%s'" % self.location_details['full name'])
 
1450
        self.current_location = Location(self.metric_system, self.wind_unit, self.location_details)
 
1451
        log.debug("Indicator: getWeather: updating weather report")
 
1452
        self.current_location.update_weather_data(self.weather_source)
 
1453
        return self.current_location.weather
 
1454
 
 
1455
    # Show notification to user
 
1456
    def notify(self,conditon,icon,severe=False):
 
1457
        log.debug("Indicator: Notify on weather condition, severe=%s, condition=%s, icon=%s" % (severe, self.condition, icon))
 
1458
        if severe:
 
1459
                        n = pynotify.Notification (_("Severe weather alert"),
 
1460
                self.condition,
 
1461
                icon)
 
1462
        else:
 
1463
                    n = pynotify.Notification (self.condition, "", icon)
 
1464
        n.show ()
 
1465
 
 
1466
    # Menu callbacks
 
1467
    # Open Preferences dialog
 
1468
    def prefs(self, widget):
 
1469
        log.debug("Indicator: open Preferences")
 
1470
        if ((not hasattr(self, 'prefswindow')) or (not self.prefswindow.get_visible())):
 
1471
            self.prefswindow = PreferencesDialog()
 
1472
            self.prefswindow.show()
 
1473
 
 
1474
    def about(self, widget):
 
1475
        log.debug("Indicator: open About dialog")
 
1476
        self.aboutdialog = gtk.AboutDialog()
 
1477
        self.aboutdialog.set_name(_("Weather Indicator"))
 
1478
        self.aboutdialog.set_version(VERSION)
 
1479
 
 
1480
        ifile = open(os.path.join(PROJECT_ROOT_DIRECTORY, "share/doc/indicator-weather/AUTHORS"), "r")
 
1481
        self.aboutdialog.set_copyright(ifile.read().replace('\x0c', ''))
 
1482
        ifile.close()
 
1483
 
 
1484
        ifile = open(os.path.join(PROJECT_ROOT_DIRECTORY, "share/common-licenses/GPL-3"), "r")
 
1485
        self.aboutdialog.set_license(ifile.read().replace('\x0c', ''))
 
1486
        ifile.close()
 
1487
 
 
1488
        self.aboutdialog.set_website("https://launchpad.net/weather-indicator")
 
1489
        self.aboutdialog.set_translator_credits(_("translator-credits"))
 
1490
        logo_path = os.path.join(PROJECT_ROOT_DIRECTORY, "share/indicator-weather/media/icon.png")
 
1491
        self.aboutdialog.set_logo(gtk.gdk.pixbuf_new_from_file(logo_path))
 
1492
 
 
1493
 
 
1494
        self.aboutdialog.connect("response", self.about_close)
 
1495
        self.aboutdialog.show()
 
1496
 
 
1497
    def about_close(self, widget, event=None):
 
1498
        log.debug("Indicator: closing About dialog")
 
1499
        self.aboutdialog.destroy()
 
1500
 
 
1501
    # Open Extended forecast window
 
1502
    def extforecast(self, widget):
 
1503
        log.debug("Indicator: open Forecast")
 
1504
        if ((not hasattr(self, 'forecastwd')) or (not self.forecastwd.get_visible())):
 
1505
            self.forecastwd = ExtendedForecast()
 
1506
            self.forecastwd.show()
 
1507
 
 
1508
    # Quit the applet
 
1509
    def quit(self, widget, data=None):
 
1510
        log.debug("Indicator: Quitting")
 
1511
        gtk.main_quit()
 
1512
 
 
1513
class PreferencesDialog(gtk.Dialog):
 
1514
    """ Class for preferences dialog """
 
1515
    __gtype_name__ = "PreferencesDialog"
 
1516
 
 
1517
    # Creating a new preferences dialog
 
1518
    def __new__(cls):
 
1519
        log.debug("Preferences: creating")
 
1520
        builder = get_builder('PreferencesDialog')
 
1521
        new_object = builder.get_object("preferences_dialog")
 
1522
        new_object.finish_initializing(builder)
 
1523
        return new_object
 
1524
 
 
1525
    # Fill in preferences dialog with currect data
 
1526
    def finish_initializing(self, builder):
 
1527
        log.debug("Preferences: finishing initialization")
 
1528
        log.debug("Preferences: got settings: unit=%s, notif=%s, wind=%s, rate=%s, source=%s" %
 
1529
                (wi.unit, wi.notif, wi.wind, wi.rate, wi.source))
 
1530
        self.builder = builder
 
1531
 
 
1532
        # Set correct wind_unit using dictionary of wind value and object name
 
1533
        self.builder.get_object(wi.metric_systems[wi.unit][0]).set_active(True)
 
1534
        self.builder.get_object(wi.notifications[wi.notif]).set_active(True)
 
1535
        self.builder.get_object(wi.wind_systems[wi.wind][0]).set_active(True)
 
1536
        self.builder.get_object(wi.weather_sources[wi.source][0]).set_active(True)
 
1537
        self.builder.get_object('show_label').set_active(wi.show_label)
 
1538
        self.builder.get_object('show_label').set_visible(hasattr(wi.winder, 'set_label'))
 
1539
        self.builder.get_object('rate').set_value(float(wi.rate))
 
1540
 
 
1541
        log.debug("Preferences: Loading places")
 
1542
        if wi.places != None:
 
1543
            for place in wi.places:
 
1544
                if len(place)>1:
 
1545
                    log.debug("Preferences: Places: got (%s, %s)" % (place[1], place[0]))
 
1546
                    newplace = list()
 
1547
                    newplace.append(place[1])
 
1548
                    newplace.append(place[0])
 
1549
                    newplace.append(wi.settings.get_location_details(place[0]))
 
1550
 
 
1551
                    self.builder.get_object('citieslist').append(newplace)
 
1552
                    self.builder.get_object('ok_button').set_sensitive(True)
 
1553
 
 
1554
        self.builder.connect_signals(self)
 
1555
 
 
1556
    # 'Remove' clicked - remove location from list
 
1557
    #TODO: Update settings object
 
1558
    def on_remove_location(self, widget):
 
1559
        selection = self.builder.get_object('location_list').get_selection()
 
1560
        model, iter = selection.get_selected()
 
1561
        if iter != None:
 
1562
            log.debug("Preferences: Removing location %s (code %s)" % (model[iter][0], model[iter][1]))
 
1563
            model.remove(iter)
 
1564
 
 
1565
        if (self.builder.get_object('citieslist').get_iter_first() == None):
 
1566
            self.builder.get_object('ok_button').set_sensitive(False)
 
1567
 
 
1568
    # 'Add' clicked - create a new Assistant
 
1569
    def on_add_location(self, widget):
 
1570
        log.debug("Preferences: Add location clicked")
 
1571
        if ((not hasattr(self, 'assistant')) or (not self.assistant.get_visible())):
 
1572
            self.assistant = Assistant()
 
1573
            self.assistant.show()
 
1574
 
 
1575
    # 'OK' clicked - save settings
 
1576
    def ok(self, widget, data=None):
 
1577
        log.debug("Preferences: Saving settings")
 
1578
        need_to_update_weather = False
 
1579
        need_to_update_indicator = False
 
1580
 
 
1581
        #Show label near icon
 
1582
        new_show_label = self.builder.get_object('show_label').get_active()
 
1583
        if (wi.show_label != new_show_label):
 
1584
            wi.show_label = new_show_label
 
1585
            wi.settings.set_value("show_label", new_show_label)
 
1586
            need_to_update_weather = False
 
1587
            need_to_update_indicator = True
 
1588
            log.debug("Preferences: Show Label changed to '%s'" % wi.show_label)
 
1589
 
 
1590
        # Metric systems
 
1591
        for k in wi.metric_systems.keys():
 
1592
            if self.builder.get_object(wi.metric_systems[k][0]).get_active():
 
1593
                new_unit = k
 
1594
                new_metric_system = wi.metric_systems[k][1]
 
1595
 
 
1596
        if (wi.unit != new_unit):
 
1597
            wi.unit = new_unit
 
1598
            wi.metric_system = new_metric_system
 
1599
            wi.settings.set_value("unit", wi.unit)
 
1600
            need_to_update_weather = True
 
1601
            log.debug("Preferences: Unit changed to '%s'" % wi.unit)
 
1602
 
 
1603
        # Notifications
 
1604
        for k in wi.notifications.keys():
 
1605
            if self.builder.get_object(wi.notifications[k]).get_active():
 
1606
                new_notification  = k
 
1607
 
 
1608
        if (wi.notif != new_notification):
 
1609
            wi.notif = new_notification
 
1610
            wi.settings.set_value("notif", wi.notif)
 
1611
            need_to_update_weather = True
 
1612
            log.debug("Preferences: Notifications changed to '%s'" % wi.notif)
 
1613
 
 
1614
        # Wind Units
 
1615
        for k in wi.wind_systems.keys():
 
1616
            if self.builder.get_object(wi.wind_systems[k][0]).get_active():
 
1617
                new_wind_unit   = k
 
1618
                new_wind_system = wi.wind_systems[k][1]
 
1619
 
 
1620
        if (wi.wind != new_wind_unit):
 
1621
            wi.wind = new_wind_unit
 
1622
            wi.wind_unit = new_wind_system
 
1623
            wi.settings.set_value("wind", wi.wind)
 
1624
            need_to_update_weather = True
 
1625
            log.debug("Preferences: Wind Unit changed to '%s'" % wi.wind)
 
1626
 
 
1627
        # Weather source
 
1628
        for k in wi.weather_sources.keys():
 
1629
            if self.builder.get_object(wi.weather_sources[k][0]).get_active():
 
1630
                new_source   = k
 
1631
                new_weather_source = wi.weather_sources[k][1]
 
1632
 
 
1633
        if (wi.source != new_source):
 
1634
            wi.source = new_source
 
1635
            wi.weather_source = new_weather_source
 
1636
            wi.settings.set_value("data_source", wi.source)
 
1637
            need_to_update_weather = True
 
1638
            log.debug("Preferences: Weather Source changed to '%s'" % wi.source)
 
1639
 
 
1640
        # Rate
 
1641
        if int(self.builder.get_object('rate').get_value()) != wi.rate:
 
1642
            wi.settings.set_value("refresh_rate", int(self.builder.get_object('rate').get_value()))
 
1643
            wi.rate = int(self.builder.get_object('rate').get_value())
 
1644
            log.debug("Preferences: Rate changed to '%s'" % wi.rate)
 
1645
            wi.schedule_weather_update()
 
1646
 
 
1647
        # Get places from location list
 
1648
        newplaces = list()
 
1649
        item = self.builder.get_object('citieslist').get_iter_first()
 
1650
        while (item != None):
 
1651
            newplace = list()
 
1652
            newplace.append(self.builder.get_object('citieslist').get_value (item, 1))
 
1653
            newplace.append(self.builder.get_object('citieslist').get_value (item, 0))
 
1654
            newplaces.append(newplace)
 
1655
            item = self.builder.get_object('citieslist').iter_next(item)
 
1656
 
 
1657
        # If places have changed - update weather data
 
1658
        if newplaces != wi.places:
 
1659
            wi.places = newplaces
 
1660
            log.debug("Preferences: Places changed to '%s'" % str(wi.places))
 
1661
            wi.settings.set_value("places", str(wi.places))
 
1662
            if (type(wi.place) != None) and (wi.place in wi.places):
 
1663
                wi.placechosen = wi.places.index(wi.place)
 
1664
            else:
 
1665
                wi.placechosen = 0
 
1666
                wi.place = wi.places[0]
 
1667
            log.debug("Preferences: Place Chosen changed to '%s'" % wi.placechosen)
 
1668
            wi.settings.set_value("placechosen", wi.placechosen)
 
1669
            wi.location_details = eval(wi.settings.get_location_details(wi.place[0]))
 
1670
            wi.menu_normal()
 
1671
            wi.set_refresh_label()
 
1672
            need_to_update_weather = True
 
1673
 
 
1674
        if need_to_update_weather:
 
1675
            wi.update_weather(False)
 
1676
 
 
1677
        if need_to_update_indicator:
 
1678
            wi.update_label(wi.previous_label_value)
 
1679
 
 
1680
        self.destroy()
 
1681
 
 
1682
    # 'Cancel' click - forget all changes
 
1683
    def cancel(self, widget, data=None):
 
1684
        log.debug("Preferences: Cancelling")
 
1685
        self.destroy()
 
1686
 
 
1687
class ExtendedForecast(gtk.Window):
 
1688
    """ Class for forecast window """
 
1689
    __gtype_name__ = "ExtendedForecast"
 
1690
 
 
1691
    # Create forecast
 
1692
    def __new__(cls):
 
1693
        log.debug("ExtendedForecast: creating")
 
1694
        builder = get_builder('ExtendedForecast')
 
1695
        new_object = builder.get_object("extended_forecast")
 
1696
        new_object.finish_initializing(builder)
 
1697
        return new_object
 
1698
 
 
1699
    # Fill in forecast parameters
 
1700
    def finish_initializing(self, builder):
 
1701
        log.debug("ExtendedForecast: finishing initialization")
 
1702
        self.builder = builder
 
1703
        self.builder.connect_signals(self)
 
1704
 
 
1705
        # Get forecast data using Forecast object
 
1706
        log.debug("ExtendedForecast: chosen place: %s (code %s)" % (wi.places[wi.placechosen][1], wi.places[wi.placechosen][0]))
 
1707
        self.builder.get_object('extended_forecast').set_title("%s %s" % (_('Weather Forecast for '), wi.places[wi.placechosen][1]))
 
1708
        log.debug("ExtendedForecast: getting forecast data")
 
1709
        forecast = Forecast(wi.metric_system, wi.current_location.location_details['latitude'], wi.current_location.location_details['longitude'], locale_name)
 
1710
        forecast.prepare_forecast_data()
 
1711
        if forecast.error_message != None:
 
1712
            #Error occurred while getting forecast data
 
1713
            self.builder.get_object('connection_error').set_text("%s" % forecast.error_message)
 
1714
            self.builder.get_object('connection_error').set_visible(True)
 
1715
            self.builder.get_object('hbox1').set_visible(False)
 
1716
        else:
 
1717
            daysofweek = forecast.get_forecast_daysofweek()
 
1718
            forecast_data = forecast.get_forecast_data()
 
1719
            if forecast_data == None:
 
1720
                # Forecast data unavailable - hide elements and show 'connection_error' label
 
1721
                self.builder.get_object('connection_error').set_visible(True);
 
1722
                self.builder.get_object('hbox1').set_visible(False);
 
1723
                self.builder.get_object('hseparator1').set_visible(False);
 
1724
                return
 
1725
            (highdata, lowdata) = forecast_data
 
1726
            icons      = forecast.get_forecast_icons()
 
1727
            conditions = forecast.get_forecast_conditions()
 
1728
 
 
1729
            log.debug("ExtendedForecast: parsing forecast data")
 
1730
            # Create labels for each weekday
 
1731
            self.builder.get_object('day1lbl').set_label('<big>%s</big>' % daysofweek[0].capitalize())
 
1732
            self.builder.get_object('day2lbl').set_label('<big>%s</big>' % daysofweek[1].capitalize())
 
1733
            self.builder.get_object('day3lbl').set_label('<big>%s</big>' % daysofweek[2].capitalize())
 
1734
            self.builder.get_object('day4lbl').set_label('<big>%s</big>' % daysofweek[3].capitalize())
 
1735
 
 
1736
            # Fill in icons
 
1737
            for i in xrange(1,5):
 
1738
                # Get icon name from dictionary in Weather object for Google icons
 
1739
                try:
 
1740
                    conds = Weather._GoogleConditions.get(icons[i-1])
 
1741
                    if conds != None:
 
1742
                        google_icon = conds[0]
 
1743
                    else:
 
1744
                        log.error("ExtendedForecast: unknown Google weather condition '%s'" % icons[i-1])
 
1745
                        log.error(forecast.forecast)
 
1746
                        google_icon = 'weather-indicator-unknown'
 
1747
                    self.builder.get_object('day%simage' % str(i)).set_from_icon_name(google_icon,gtk.ICON_SIZE_BUTTON)
 
1748
                except IndexError:
 
1749
                     log.error("ExtendedForecast: Google didn't return condition for %s days" % i-1)
 
1750
                     log.error(forecast.forecast)
 
1751
 
 
1752
            # Fill in condition labels
 
1753
            for i in xrange(1,5):
 
1754
                if conditions[i-1] != '':
 
1755
                    condition = conditions[i-1]
 
1756
                else:
 
1757
                    condition = _("Unknown condition")
 
1758
                self.builder.get_object('day%scond' % str(i)).set_label(condition)
 
1759
 
 
1760
            # Fill in High and Low temperatures
 
1761
            if wi.metric_system == MetricSystem.SI:
 
1762
                tempunit = '°C'
 
1763
            else:
 
1764
                tempunit = '°F'
 
1765
            for i in xrange(1,5):
 
1766
                label = "%s: %s%s" % (_('High'), highdata[i-1],tempunit)
 
1767
                self.builder.get_object('day%stemphigh' % str(i)).set_label(label)
 
1768
                label = "%s: %s%s" % (_('Low'), lowdata[i-1],tempunit)
 
1769
                self.builder.get_object('day%stemplow' % str(i)).set_label(label)
 
1770
 
 
1771
    # Closing forecast window
 
1772
    def close(self, widget, data=None):
 
1773
        log.debug("ExtendedForecast: closing window")
 
1774
        self.destroy()
 
1775
 
 
1776
    def on_destroy(self, widget):
 
1777
        pass
 
1778
 
 
1779
class Assistant(gtk.Assistant):
 
1780
    """ Class for a wizard, which helps to add a new location in location list """
 
1781
    __gtype_name__ = "Assistant"
 
1782
 
 
1783
    # Create new object
 
1784
    def __new__(cls):
 
1785
        log.debug("Assistant: creating new Assistance instance")
 
1786
        builder = get_builder('Assistant')
 
1787
        new_object = builder.get_object("assistant")
 
1788
        new_object.finish_initializing(builder)
 
1789
        return new_object
 
1790
 
 
1791
    # Finish UI initialization - prepare combobox
 
1792
    def finish_initializing(self, builder):
 
1793
        log.debug("Assistant: finishing initialization")
 
1794
        self.builder = builder
 
1795
        self.builder.connect_signals(self)
 
1796
        self.assistant = self.builder.get_object("assistant")
 
1797
        self.assistant.set_page_complete(self.builder.get_object("label"),True)
 
1798
        self.assistant.set_page_complete(self.builder.get_object("review"),True)
 
1799
 
 
1800
        # Set up combobox
 
1801
        log.debug("Assistant: setting up location combobox")
 
1802
        self.store = gtk.ListStore(str, str, str, str, str)
 
1803
        self.location_input_combo = self.builder.get_object("combolocations")
 
1804
        self.location_input_combo.set_model(self.store)
 
1805
        self.location_input_combo.set_text_column(0)
 
1806
        self.location_entry = self.builder.get_object("entrylocation")
 
1807
        self.place_selected = None
 
1808
        self.location = None
 
1809
 
 
1810
        self.assistant.set_forward_page_func(self.next_page)
 
1811
 
 
1812
    # 'Get cities' button clicked - get suggested cities list
 
1813
    def on_get_city_names(self, widget):
 
1814
        new_text = self.location_entry.get_text()
 
1815
        log.debug("Assistant: looking for location '%s'" % new_text)
 
1816
        try:
 
1817
            # Clear up exising suggestions
 
1818
            self.store.clear()
 
1819
            # Get suggested city names from GeoNames DB in native locale
 
1820
            new_text = urllib.quote(new_text)
 
1821
            url = 'http://api.geonames.org/searchJSON?q=%s&featureClass=P&maxRows=10&lang=%s&username=indicatorweather' % (new_text, locale_name)
 
1822
            cities = eval(urllib2.urlopen(url).read())
 
1823
            for city in cities['geonames']:
 
1824
                # Create a full city name, consisting of city name, administrative areas names and country name
 
1825
                if 'adminName2' in city:
 
1826
                    displayed_city_name = "%s, %s, %s, %s" % (city['name'], city['adminName1'], city['adminName1'], city['countryName'])
 
1827
                elif 'adminName1' in city:
 
1828
                    displayed_city_name = "%s, %s, %s" % (city['name'], city['adminName1'], city['countryName'])
 
1829
                else:
 
1830
                    displayed_city_name = "%s, %s" % (city['name'], city['countryName'])
 
1831
                self.store.append([displayed_city_name, str(city['geonameId']), str(city['lat']), str(city['lng']), str(city['name'])])
 
1832
                self.location_input_combo.popup()
 
1833
        except urllib2.URLError:
 
1834
            log.error("Assistant: error reaching url '%s'" % url)
 
1835
 
 
1836
    # A city is selected from suggested list
 
1837
    def on_select_city(self, entry):
 
1838
        if self.location_input_combo.get_active() != -1:
 
1839
            self.place_selected = self.store[self.location_input_combo.get_active()]
 
1840
            self.assistant.set_page_complete(self.builder.get_object("placeinput"),True)
 
1841
        else:
 
1842
            self.place_selected = None
 
1843
            self.location = None
 
1844
            self.assistant.set_page_complete(self.builder.get_object("placeinput"), False)
 
1845
 
 
1846
    # Create a location object out of a selected location
 
1847
    def next_page(self, current_page):
 
1848
        log.debug("Assistant: moved to page %s" % current_page)
 
1849
        if (self.assistant.get_current_page() == 0) and not self.location and self.place_selected:
 
1850
            # Label input page
 
1851
            log.debug("Assistant: Page %s: got location with code %s" % (current_page, self.place_selected[1]))
 
1852
            self.location = Location(wi.metric_system, wi.wind_unit)
 
1853
            if self.location.prepare_location(self.place_selected):
 
1854
                log.debug("Assistant: Page %s: City %s found" % (current_page, self.place_selected[0]))
 
1855
                # Set a short city name as default label
 
1856
                self.builder.get_object("entrylbl").set_text(self.place_selected[4])
 
1857
            else:
 
1858
                log.error("Assistant: Page %s: City with code %s was NOT found" % (current_page, self.place_selected[0]))
 
1859
                return 3
 
1860
        elif self.assistant.get_current_page() == 1:
 
1861
            # Confirmation page
 
1862
            lbl = self.builder.get_object("entrylbl").get_text()
 
1863
            log.debug("Assistant: Page %s: City label is %s" % (current_page, lbl))
 
1864
            # If empty label was input, set label to short city name
 
1865
            if lbl == '':
 
1866
                log.debug("Assistant: Page %s: Empty label found, setting lbl to short name - %s" % (current_page, self.place_selected[4]))
 
1867
                lbl = self.place_selected[4]
 
1868
            self.location.location_details['label'] = lbl
 
1869
            self.builder.get_object("lbl3").set_label(_('Label:'))
 
1870
            self.builder.get_object("labellbl").set_label('<b>%s</b>' % lbl)
 
1871
            self.builder.get_object("placelbl").set_label('<b>%s</b>' % self.place_selected[0])
 
1872
 
 
1873
        return self.assistant.get_current_page() + 1
 
1874
 
 
1875
    # 'Cancel' clicked
 
1876
    def on_cancel(self,widget):
 
1877
        log.debug("Assistant: Cancelled")
 
1878
        self.destroy()
 
1879
 
 
1880
    # 'Apply' clicked - save location details, add an entry in a location list
 
1881
    def on_apply(self,widget):
 
1882
        (location_code, location_details) = self.location.export_location_details()
 
1883
        log.debug("Assistant: Apply: adding location ('%s', '%s')" % (self.location.location_details['label'], location_code))
 
1884
        newplace = list()
 
1885
        newplace.append(self.location.location_details['label'])
 
1886
        newplace.append(str(location_code))
 
1887
        newplace.append(str(location_details))
 
1888
        wi.settings.save_location_details(eval(str(location_details)), str(location_code))
 
1889
        wi.prefswindow.builder.get_object('citieslist').append(newplace)
 
1890
        # Enable 'OK' button in Preferences
 
1891
        wi.prefswindow.builder.get_object('ok_button').set_sensitive(True)
 
1892
        self.hide()
 
1893
 
 
1894
class SingleInstance(object):
 
1895
    """ Class to ensure, that single instance of the applet is run for each user """
 
1896
 
 
1897
    # Initialize, specifying a path to store pids
 
1898
    def __init__(self, pidPath):
 
1899
        self.pidPath=pidPath
 
1900
        # See if pidFile exists
 
1901
        if os.path.exists(pidPath):
 
1902
            log.debug("SingleInstance: pid file %s exists" % pidPath)
 
1903
            # Make sure it is not a "stale" pidFile
 
1904
            pid=open(pidPath, 'r').read().strip()
 
1905
            # Check list of running pids, if not running it is stale so overwrite
 
1906
            pidRunning = commands.getoutput('ls /proc | grep %s' % pid)
 
1907
            log.debug("SingleInstance: pid running %s" % pidRunning)
 
1908
            self.lasterror = True if pidRunning else False
 
1909
        else:
 
1910
            self.lasterror = False
 
1911
 
 
1912
        if not self.lasterror:
 
1913
            log.debug("SingleInstance: writing new pid %s" % str(os.getpid()))
 
1914
            # Create a temp file, copy it to pidPath and remove temporary file
 
1915
            (fp, temp_path)=tempfile.mkstemp()
 
1916
            try:
 
1917
                os.fdopen(fp, "w+b").write(str(os.getpid()))
 
1918
                shutil.copy(temp_path, pidPath)
 
1919
                os.unlink(temp_path)
 
1920
            except Exception as e:
 
1921
                log.error("SingleInstance: exception while renaming '%s' to '%s':\n %s" % (temp_path, pidPath, str(e)))
 
1922
 
 
1923
    def is_already_running(self):
 
1924
        return self.lasterror
 
1925
 
 
1926
    def __del__(self):
 
1927
        if not self.lasterror:
 
1928
            log.debug("SingleInstance: deleting %s" % self.pidPath)
 
1929
            os.unlink(self.pidPath)
 
1930
 
 
1931
def main():
 
1932
    gtk.main()
 
1933
    return 0
 
1934
 
 
1935
if __name__ == "__main__":
 
1936
    #Enable and configure logs
 
1937
    global log
 
1938
    cachedir = os.environ.get('XDG_CACHE_HOME','').strip()
 
1939
    if not cachedir:
 
1940
        cachedir = os.path.expanduser("~/.cache")
 
1941
    log_filename = os.path.join(cachedir, "indicator-weather.log")
 
1942
    log = logging.getLogger('IndicatorWeather')
 
1943
    log.propagate = False
 
1944
    log.setLevel(logging.DEBUG)
 
1945
    log_handler = logging.handlers.RotatingFileHandler(log_filename, maxBytes=1024*1024, backupCount=5)
 
1946
    log_formatter = logging.Formatter("[%(threadName)s] %(asctime)s - %(levelname)s - %(message)s")
 
1947
    log_handler.setFormatter(log_formatter)
 
1948
    log.addHandler(log_handler)
 
1949
 
 
1950
    log.info("------------------------------")
 
1951
    log.info("Started Weather Indicator from %s" % PROJECT_ROOT_DIRECTORY)
 
1952
    log.info("Weather Indicator version %s" % VERSION)
 
1953
 
 
1954
    # Single instance stuff for weather indicator
 
1955
    myapp = SingleInstance("/tmp/indicator-weather-%d.pid" % os.getuid())
 
1956
    # check is another instance of same program running
 
1957
    if myapp.is_already_running():
 
1958
        log.info("Another instance of this program is already running")
 
1959
        sys.exit(_("Another instance of this program is already running"))
 
1960
 
 
1961
    # Set http proxy support
 
1962
    ProxyMonitor.monitor_proxy(log)
 
1963
    # Use date-time format as in indicator-datetime
 
1964
    TimeFormatter.monitor_indicator_datetime(log)
 
1965
 
 
1966
    # not running, safe to continue...
 
1967
    gtk.gdk.threads_init()
 
1968
    gtk.gdk.threads_enter()
 
1969
    # Remember locale name
 
1970
    global locale_name
 
1971
    locale_name = locale.getlocale()[0]
 
1972
    if locale_name is not None:
 
1973
        locale_name = locale_name.split('_')[0]
 
1974
    else:
 
1975
        locale.setlocale(locale.LC_ALL, 'C') # use default (C) locale
 
1976
        locale_name = "en"
 
1977
    wi = indicator_weather()
 
1978
    main()
 
1979
    gtk.gdk.threads_leave()