66
by m-buck
* Updated Dutch (nl) translation based on update from Mark Coumans |
1 |
#!/usr/bin/env python2
|
2 |
# -*- coding: utf-8 -*-
|
|
3 |
###############################################################################
|
|
4 |
# conkyForecast.py is a (not so) simple (anymore) python script to gather
|
|
5 |
# details of the current weather for use in conky.
|
|
6 |
#
|
|
7 |
# Author: Kaivalagi
|
|
8 |
# Created: 13/04/2008
|
|
9 |
||
10 |
from datetime import datetime, timedelta, tzinfo |
|
11 |
from optparse import OptionParser |
|
12 |
import sys |
|
13 |
import os |
|
14 |
import socket |
|
15 |
import urllib2 |
|
16 |
import gettext |
|
17 |
import locale |
|
18 |
import re |
|
19 |
import codecs |
|
20 |
import traceback |
|
21 |
import time |
|
22 |
import json |
|
23 |
||
24 |
# not sure on these, might create more trouble, but here goes...
|
|
25 |
reload(sys) |
|
26 |
sys.setdefaultencoding('utf-8') |
|
27 |
||
28 |
# cPickle is a pickle class implemented in C - so its faster
|
|
29 |
# in case its not available, use regular pickle
|
|
30 |
try: |
|
31 |
import cPickle as pickle |
|
32 |
except ImportError: |
|
33 |
import pickle |
|
34 |
||
35 |
app_name = "conkyForecast" |
|
36 |
app_path = os.path.dirname(os.path.abspath(__file__)) |
|
37 |
module_name = __file__.replace(os.path.dirname (__file__) + "/", "").replace(".pyc","").replace(".py", "") |
|
38 |
||
39 |
# default to standard locale translation
|
|
40 |
domain = __file__.replace(os.path.dirname (__file__) + "/", "").replace(".py", "") |
|
41 |
localedirectory = os.path.dirname(os.path.abspath(__file__)) + "/locale" |
|
42 |
gettext.bindtextdomain(domain, localedirectory) |
|
43 |
gettext.textdomain(domain) |
|
44 |
gettext.install(domain) |
|
45 |
||
46 |
class CommandLineParser: |
|
47 |
||
48 |
parser = None |
|
49 |
||
50 |
def __init__(self): |
|
51 |
||
52 |
self.parser = OptionParser() |
|
53 |
self.parser.add_option("-C", "--config", dest="config", default="~/.conkyForecastWU.config", type="string", metavar="FILE", help=u"[default: %default] The path to the configuration file, allowing multiple config files to be used.") |
|
54 |
self.parser.add_option("-l", "--location", dest="location", type="string", metavar="CODE", help=u"location code for weather data [default set in config]. Normally in the form \"COUNTRY/CITY\"") |
|
55 |
self.parser.add_option("-d", "--datatype", dest="datatype", default="HT", type="string", metavar="DATATYPE", help=u"[default: %default] The data type options are: DW (Day of Week), WI (Weather Icon Path), LT (Forecast:Low Temp,Current:Feels Like Temp), HT (Forecast:High Temp,Current:Current Temp), CT (Conditions Text), PC (Precipitation Chance), HM (Humidity), VI (Visibility), WD (Wind Direction), WA (Wind Angle - in degrees), WS (Wind Speed), WG (Wind Gusts), BF (Bearing Font), BI (Bearing Icon Path), BS (Bearing font with Speed), CN (City Name), CO (Country), OB (Observatory), BR (Barometer Reading), BD (Barometer Description), UI (UV Index), UT (UV Text), DP (Dew Point), LU (Last Update at weather.com), LF (Last Fetch from weather.com). Not applicable at command line when using templates.") |
|
56 |
self.parser.add_option("-s", "--startday", dest="startday", type="int", metavar="NUMBER", help=u"define the starting day number, if omitted current conditions are output. Not applicable at command line when using templates.") |
|
57 |
self.parser.add_option("-e", "--endday", dest="endday", type="int", metavar="NUMBER", help=u"define the ending day number, if omitted only starting day data is output. Not applicable at command line when using templates.") |
|
58 |
self.parser.add_option("-S", "--spaces", dest="spaces", type="int", default=1, metavar="NUMBER", help=u"[default: %default] Define the number of spaces between ranged output. Not applicable at command line when using templates.") |
|
59 |
self.parser.add_option("-t", "--template", dest="template", type="string", metavar="FILE", help=u"define a template file to generate output in one call. A displayable item in the file is in the form [--datatype=HT --startday=1]. The following are possible options within each item: --location,--datatype,--startday,--endday,--night,--shortweekday,--imperial,--beaufort,--metrespersecond,--hideunits,--hidedegreesymbol,--spaces,--minuteshide. Note that the short forms of the options are not supported! If any of these options is set from the commandline, it sets the default value of the option for all template items.") |
|
60 |
self.parser.add_option("-L", "--locale", dest="locale", type="string", help=u"override the system locale for language output (bg=bulgarian, cs=czech, de=german, es=spanish, en=english, es=spanish, fj=fijian, fr=french, it=italian, nl=dutch, pl=polish, ro=romanian, sk=slovak, more to come)") |
|
61 |
self.parser.add_option("-i", "--imperial", dest="imperial", default=False, action="store_true", help=u"request imperial units, if omitted output is in metric.") |
|
62 |
self.parser.add_option("-b", "--beaufort", dest="beaufort", default=False, action="store_true", help=u"request beaufort scale for wind speeds, if omitted output is either metric/imperial.") |
|
63 |
self.parser.add_option("-M", "--metrespersecond", dest="metrespersecond", default=False, action="store_true", help=u"request metres per second for wind speeds, if omitted output is either metric/imperial.") |
|
64 |
self.parser.add_option("-n", "--night", dest="night", default=False, action="store_true", help=u"switch output to night data, if omitted day output will be output.") |
|
65 |
self.parser.add_option("-w", "--shortweekday", dest="shortweekday", default=False, action="store_true", help=u"Shorten the day of week data type to 3 characters.") |
|
66 |
self.parser.add_option("-u", "--hideunits", dest="hideunits", default=False, action="store_true", help=u"Hide units such as mph or C, degree symbols (°) are still shown.") |
|
67 |
self.parser.add_option("-x", "--hidedegreesymbol", dest="hidedegreesymbol", default=False, action="store_true", help=u"Hide the degree symbol used with temperature output, this is only valid if used in conjunction with --hideunits.") |
|
68 |
self.parser.add_option("-m", "--minuteshide", dest="minuteshide", type="int", metavar="NUMBER", help=u"Works only with LU and LF. If present, hides the date part of the LU or LF timestamp if the day of the timestamp is today. The time part is also hidden, if the timestamp is older than minutes specified in this argument. If set to 0, the time part is always shown. If set to -1, the value EXPIRY_MINUTES from the config file is used.") |
|
69 |
self.parser.add_option("-c", "--centeredwidth", dest="centeredwidth", type="int", metavar="WIDTH", help=u"If used the output will be centered in a string of the set width, padded out with spaces, if the output width is greater than the setting it will be truncated") |
|
70 |
self.parser.add_option("-r", "--refetch", dest="refetch", default=False, action="store_true", help=u"Fetch data regardless of data expiry.") |
|
71 |
self.parser.add_option("-v", "--verbose", dest="verbose", default=False, action="store_true", help=u"Request verbose output, not a good idea when running through conky!") |
|
72 |
self.parser.add_option("-V", "--version", dest="version", default=False, action="store_true", help=u"Displays the version of the script.") |
|
73 |
self.parser.add_option("--errorlogfile", dest="errorlogfile", type="string", metavar="FILE", help=u"If a filepath is set, the script appends errors to the filepath.") |
|
74 |
self.parser.add_option("--infologfile", dest="infologfile", type="string", metavar="FILE", help=u"If a filepath is set, the script appends info to the filepath.") |
|
75 |
||
76 |
def parse_args(self): |
|
77 |
(options, args) = self.parser.parse_args() |
|
78 |
return (options, args) |
|
79 |
||
80 |
def print_help(self): |
|
81 |
return self.parser.print_help() |
|
82 |
||
83 |
# N.B: The below class member values are defaults and should be left alone, they
|
|
84 |
# are there to provide a working script if the script is called without all
|
|
85 |
# the expected input. Any issues raised where these values have been
|
|
86 |
# changed will get the simple "put the .py file back to it's original
|
|
87 |
# state" reply
|
|
88 |
class ForecastConfig: |
|
89 |
CACHE_FOLDERPATH = "/tmp/" |
|
90 |
CONNECTION_TIMEOUT = 5 |
|
91 |
EXPIRY_MINUTES = 30 |
|
92 |
TIME_FORMAT = "%H:%M" |
|
93 |
DATE_FORMAT = "%Y-%m-%d" |
|
94 |
LOCALE = "" # with no setting the default locale of the system is used |
|
95 |
XOAP_PARTNER_ID = "" # need config with correct partner id |
|
96 |
XOAP_LICENCE_KEY = "" # need config"N/A" with correct licence key |
|
97 |
DEFAULT_LOCATION = "UK/Norwich" |
|
98 |
MAXIMUM_DAYS_FORECAST = 7 |
|
99 |
AUTO_NIGHT = False |
|
100 |
BASE_WU_JSON_URL = "http://api.wunderground.com/api/<WU_LICENCE_KEY>/geolookup/conditions/forecast/q/<LOCATION>.json" |
|
101 |
PROXY_HOST = None |
|
102 |
PROXY_PORT = 8080 |
|
103 |
PROXY_USERNAME = None |
|
104 |
PROXY_PASSWORD = None |
|
105 |
||
106 |
||
107 |
class ForecastDataset: |
|
108 |
def __init__(self, last_update, day_of_week, low, high, condition_url, condition_text, precip, humidity, wind_dir, wind_dir_numeric, wind_speed, wind_gusts, timezone, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country): |
|
109 |
self.last_update = last_update |
|
110 |
self.day_of_week = day_of_week |
|
111 |
self.low = low |
|
112 |
self.high = high |
|
113 |
self.condition_url = condition_url |
|
114 |
self.condition_text = condition_text |
|
115 |
self.precip = precip |
|
116 |
self.humidity = humidity |
|
117 |
self.wind_dir = wind_dir |
|
118 |
self.wind_dir_numeric = wind_dir_numeric |
|
119 |
self.wind_speed = wind_speed |
|
120 |
self.wind_gusts = wind_gusts |
|
121 |
self.timezone = timezone |
|
122 |
self.bar_read = bar_read |
|
123 |
self.bar_desc = bar_desc |
|
124 |
self.uv_index = uv_index |
|
125 |
self.uv_text = uv_text |
|
126 |
self.dew_point = dew_point |
|
127 |
self.observatory = observatory |
|
128 |
self.visibility = visibility |
|
129 |
self.city = city |
|
130 |
self.country = country |
|
131 |
||
132 |
class ForecastLocation: |
|
133 |
timestamp = None |
|
134 |
||
135 |
def __init__(self, current, day, night): |
|
136 |
self.current = current |
|
137 |
self.day = day |
|
138 |
self.night = night |
|
139 |
self.timestamp = datetime.today() |
|
140 |
||
141 |
def outdated(self, mins): |
|
142 |
if datetime.today() > self.timestamp + timedelta(minutes=mins): |
|
143 |
return True |
|
144 |
else: |
|
145 |
return False |
|
146 |
||
147 |
# start ignoring translations required at runtime
|
|
148 |
def _(text): return text |
|
149 |
||
150 |
class ForecastText: |
|
151 |
||
152 |
# translatable dictionaries
|
|
153 |
conditions_text = { |
|
154 |
"0": _(u"Tornado"), |
|
155 |
"1": _(u"Tropical Storm"), |
|
156 |
"2": _(u"Hurricane"), |
|
157 |
"3": _(u"Severe Thunderstorms"), |
|
158 |
"4": _(u"Thunderstorms"), |
|
159 |
"5": _(u"Mixed Rain and Snow"), |
|
160 |
"6": _(u"Mixed Rain and Sleet"), |
|
161 |
"7": _(u"Mixed Precipitation"), |
|
162 |
"8": _(u"Freezing Drizzle"), |
|
163 |
"9": _(u"Drizzle"), |
|
164 |
"10": _(u"Freezing Rain"),"N/A" |
|
165 |
"11": _(u"Light Rain"), |
|
166 |
"12": _(u"Rain"), |
|
167 |
"13": _(u"Snow Flurries"), |
|
168 |
"14": _(u"Light Snow Showers"), |
|
169 |
"15": _(u"Drifting Snow"), |
|
170 |
"16": _(u"Snow"), |
|
171 |
"17": _(u"Hail"), |
|
172 |
"18": _(u"Sleet"), |
|
173 |
"19": _(u"Dust"), |
|
174 |
"20": _(u"Fog"), |
|
175 |
"21": _(u"Haze"), |
|
176 |
"22": _(u"Smoke"), |
|
177 |
"23": _(u"Blustery"), |
|
178 |
"24": _(u"Windy"), |
|
179 |
"25": _(u"N/A"), |
|
180 |
"26": _(u"Cloudy"), |
|
181 |
"27": _(u"Mostly Cloudy"), |
|
182 |
"28": _(u"Mostly Cloudy"), |
|
183 |
"29": _(u"Partly Cloudy"), |
|
184 |
"30": _(u"Partly Cloudy"), |
|
185 |
"31": _(u"Clear"), |
|
186 |
"32": _(u"Clear"), |
|
187 |
"33": _(u"Fair"), |
|
188 |
"34": _(u"Fair"), |
|
189 |
"35": _(u"Mixed Rain and Hail"), |
|
190 |
"36": _(u"Hot"), |
|
191 |
"37": _(u"Isolated Thunderstorms"), |
|
192 |
"38": _(u"Scattered Thunderstorms"), |
|
193 |
"39": _(u"Scattered Showers"), |
|
194 |
"40": _(u"Heavy Rain"), |
|
195 |
"41": _(u"Scattered Snow Showers"), |
|
196 |
"42": _(u"Heavy Snow"), |
|
197 |
"43": _(u"Heavy Snow"), |
|
198 |
"44": _(u"N/A"), |
|
199 |
"45": _(u"Scattered Showers"), |
|
200 |
"46": _(u"Snow Showers"), |
|
201 |
"47": _(u"Isolated Thunderstorms"), |
|
202 |
"na": _(u"N/A"), |
|
203 |
"-": _(u"N/A") |
|
204 |
}
|
|
205 |
||
206 |
day_of_week_short = { |
|
207 |
"Today": _(u"Now"), |
|
208 |
"Monday": _(u"Mon"), |
|
209 |
"Tuesday": _(u"Tue"), |
|
210 |
"Wednesday": _(u"Wed"), |
|
211 |
"Thursday": _(u"Thu"), |
|
212 |
"Friday": _(u"Fri"), |
|
213 |
"Saturday": _(u"Sat"), |
|
214 |
"Sunday": _(u"Sun") |
|
215 |
}
|
|
216 |
||
217 |
# this now returns ascii code
|
|
218 |
bearing_arrow_font = { |
|
219 |
"S": 0x31,"N/A" |
|
220 |
"SSW": 0x32, |
|
221 |
"SW": 0x33, |
|
222 |
"WSW": 0x34, |
|
223 |
"W": 0x35, |
|
224 |
"WNW": 0x36, |
|
225 |
"NW": 0x37, |
|
226 |
"NNW": 0x38, |
|
227 |
"N": 0x39, |
|
228 |
"NNE": 0x3a, |
|
229 |
"NE": 0x3b, |
|
230 |
"ENE": 0x3c, |
|
231 |
"E": 0x3d, |
|
232 |
"ESE": 0x3e, |
|
233 |
"SE": 0x3f, |
|
234 |
"SSE": 0x40, |
|
235 |
}
|
|
236 |
||
237 |
bearing_icon = { |
|
238 |
"calm": "00", |
|
239 |
"VAR": "01", |
|
240 |
"S": "05", |
|
241 |
"SSW": "06", |
|
242 |
"SW": "07", |
|
243 |
"WSW": "08", |
|
244 |
"W": "09", |
|
245 |
"WNW": "10", |
|
246 |
"NW": "11", |
|
247 |
"NNW": "12", |
|
248 |
"N": "13", |
|
249 |
"NNE": "14", |
|
250 |
"NE": "15", |
|
251 |
"ENE": "16", |
|
252 |
"E": "17", |
|
253 |
"ESE": "18", |
|
254 |
"SE": "19", |
|
255 |
"SSE": "20" |
|
256 |
}
|
|
257 |
||
258 |
# some general things...
|
|
259 |
general = { |
|
260 |
"n/a": _(u"n/a"), |
|
261 |
'N/A': _(u"N/A"), |
|
262 |
'Not Available': _(u"Not Available"), |
|
263 |
'unknown': _(u"unknown"), |
|
264 |
'NONE': _(u"NONE"), |
|
265 |
'day': _(u"day"), |
|
266 |
'night': _(u"night") |
|
267 |
}
|
|
268 |
||
269 |
# UV index ...
|
|
270 |
UV_index = { |
|
271 |
"Extreme": _(u"Extreme"), |
|
272 |
"Very high": _(u"Very High"), |
|
273 |
"High": _(u"High"), |
|
274 |
"Moderate": (u"Moderate"), |
|
275 |
"Low": _(u"Low") |
|
276 |
}
|
|
277 |
||
278 |
# tendencies used for barometric pressure
|
|
279 |
bar_pressure = { |
|
280 |
"Very Low": _(u"Very Low"), |
|
281 |
"Moderate": _(u"Moderate"), |
|
282 |
"rising": _(u"rising"), |
|
283 |
"falling": _(u"falling"), |
|
284 |
"steady": _(u"steady"), |
|
285 |
"calm": _(u"calm") |
|
286 |
}
|
|
287 |
||
288 |
||
289 |
# wind directions long
|
|
290 |
wind_directions_long = { |
|
291 |
"East": _(u"East"), |
|
292 |
"East Northeast": _(u"East Northeast"), |
|
293 |
"East Southeast": _(u"East Southeast"), |
|
294 |
"North": _(u"North"), |
|
295 |
"Northeast": _(u"Northeast"), |
|
296 |
"North Northeast": _(u"North Northeast"), |
|
297 |
"North Northwest": _(u"North Northwest"), |
|
298 |
"Northwest": _(u"Northwest"), |
|
299 |
"South": _(u"South"), |
|
300 |
"Souteast": _(u"Southeast"), |
|
301 |
"South Southeast": _(u"South Southeast"),"N/A" |
|
302 |
"South Southwest": _(u"South Southwest"), |
|
303 |
"Southwest": _(u"Southwest"), |
|
304 |
"variable": _(u"variable"), |
|
305 |
"West": _(u"West"), |
|
306 |
"West Northwest": _(u"West Northwest"), |
|
307 |
"West Southwest": _(u"West Southwest") |
|
308 |
}
|
|
309 |
||
310 |
wind_directions_short = { |
|
311 |
"E": _(u"E"), |
|
312 |
"ENE": _(u"ENE"), |
|
313 |
"ESE": _(u"ESE"), |
|
314 |
"N": _(u"N"), |
|
315 |
"NE": _(u"NE"), |
|
316 |
"NNE": _(u"NNE"), |
|
317 |
"NNW": _(u"NNW"), |
|
318 |
"NW": _(u"NW"), |
|
319 |
"S": _(u"S"), |
|
320 |
"SE": _(u"SE"), |
|
321 |
"SSE": _(u"SSE"), |
|
322 |
"SSW": _(u"SSW"), |
|
323 |
"SW": _(u"SW"), |
|
324 |
"W": _(u"W"), |
|
325 |
"WNW": _(u"WNW"), |
|
326 |
"WSW": _(u"WSW") |
|
327 |
}
|
|
328 |
||
329 |
days_of_week = { |
|
330 |
"Today": _(u"Today"), |
|
331 |
"Monday": _(u"Monday"), |
|
332 |
"Tuesday":_(u"Tuesday"), |
|
333 |
"Wednesday":_(u"Wednesday"), |
|
334 |
"Thursday": _(u"Thursday"), |
|
335 |
"Friday": _(u"Friday"),"N/A" |
|
336 |
"Saturday": _(u"Saturday"), |
|
337 |
"Sunday": _(u"Sunday") |
|
338 |
}
|
|
339 |
||
340 |
# end ignoring translations
|
|
341 |
del _ |
|
342 |
||
343 |
class ForecastInfo: |
|
344 |
||
345 |
# design time variables
|
|
346 |
options = None |
|
347 |
config = None |
|
348 |
forecast_data = {} |
|
349 |
# a list of locations for which an attempt was made to load them
|
|
350 |
# locations in this list are not loaded again (if there was an error,
|
|
351 |
# this makes sure it doesn't reapeat over and over)
|
|
352 |
loaded_locations = [] |
|
353 |
error = "" |
|
354 |
errorfound = False |
|
355 |
||
356 |
# design time settings
|
|
357 |
CACHE_FILENAME = ".conkyForecast-<LOCATION>.cache" |
|
358 |
CONDITION_IMAGE_FILENAME = ".conkyForecast-WI-<CONDITION>.gif" |
|
359 |
||
360 |
def __init__(self, options): |
|
361 |
||
362 |
self.options = options |
|
363 |
||
364 |
self.loadConfigData() |
|
365 |
||
366 |
# setup timeout for connections
|
|
367 |
# TODO: seems like this doesn't work in all cases..
|
|
368 |
socket.setdefaulttimeout(self.config.CONNECTION_TIMEOUT) |
|
369 |
||
370 |
# set the locale
|
|
371 |
if self.options.locale == None: |
|
372 |
if self.config.LOCALE == "": |
|
373 |
self.options.locale = locale.getdefaultlocale()[0][0:2] |
|
374 |
else: |
|
375 |
self.options.locale = self.config.LOCALE |
|
376 |
||
377 |
self.logInfo("Locale set to " + self.options.locale) |
|
378 |
||
379 |
# if not the default "en" locale, configure the i18n language translation
|
|
380 |
if self.options.locale != "en": |
|
381 |
||
382 |
self.logInfo("Looking for translation file for '%s' under %s" % (self.options.locale, localedirectory)) |
|
383 |
||
384 |
if gettext.find(domain, localedirectory, languages=[self.options.locale]) != None: |
|
385 |
self.logInfo("Translation file found for '%s'" % self.options.locale) |
|
386 |
||
387 |
try: |
|
388 |
trans = gettext.translation(domain, localedirectory, languages=[self.options.locale]) |
|
389 |
trans.install(unicode=True) |
|
390 |
self.logInfo("Translation installed for '%s'" % self.options.locale) |
|
391 |
||
392 |
except Exception, e: |
|
393 |
self.logError("Unable to load translation for '%s' %s" % (self.options.locale, e.__str__())) |
|
394 |
else: |
|
395 |
self.logInfo("Translation file not found for '%s', defaulting to 'en'" % self.options.locale) |
|
396 |
self.options.locale = "en" |
|
397 |
||
398 |
# setup location code if not set
|
|
399 |
if self.options.location == None: |
|
400 |
self.options.location = self.config.DEFAULT_LOCATION |
|
401 |
||
402 |
# setup a proxy if defined
|
|
403 |
if self.config.PROXY_HOST != None: |
|
404 |
if self.config.PROXY_USERNAME != None and self.config.PROXY_PASSWORD != None: |
|
405 |
self.logInfo("Setting up proxy '%s:%d', with username and password"%(self.config.PROXY_HOST,self.config.PROXY_PORT)) |
|
406 |
proxyurl = "http://%s:%s@%s:%d"%(self.config.PROXY_USERNAME,self.config.PROXY_PASSWORD,self.config.PROXY_HOST,self.config.PROXY_PORT) |
|
407 |
else: |
|
408 |
self.logInfo("Setting up proxy '%s:%d', without username and password"%(self.config.PROXY_HOST,self.config.PROXY_PORT)) |
|
409 |
proxyurl = "http://%s:%d"%(self.config.PROXY_HOST,self.config.PROXY_PORT) |
|
410 |
||
411 |
try: |
|
412 |
proxy_support = urllib2.ProxyHandler({"http" : proxyurl}) |
|
413 |
opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler) |
|
414 |
urllib2.install_opener(opener) |
|
415 |
except Exception, e: |
|
416 |
self.logError("Unable to setup proxy: %s"%e.__str__()) |
|
417 |
else: |
|
418 |
self.logInfo("Not using a proxy as none is defined") |
|
419 |
||
420 |
# Check if the location is loaded, if not, load it. If it can't be loaded, there was an error
|
|
421 |
if not self.checkAndLoad(self.options.location): |
|
422 |
self.logError("Failed to load the location cache") |
|
423 |
||
424 |
def loadConfigData(self): |
|
425 |
try: |
|
426 |
# load .conkyForecast.config from the options setting
|
|
427 |
configfilepath = os.path.expanduser(self.options.config) |
|
428 |
||
429 |
if os.path.exists(configfilepath): |
|
430 |
||
431 |
self.config = ForecastConfig() |
|
432 |
||
433 |
#load the file
|
|
434 |
fileinput = open(configfilepath) |
|
435 |
lines = fileinput.read().split("\n") |
|
436 |
fileinput.close() |
|
437 |
||
438 |
for line in lines: |
|
439 |
line = line.strip() |
|
440 |
if len(line) > 0 and line[0:1] != "#": # ignore commented lines or empty ones |
|
441 |
||
442 |
splitpos = line.find("=") |
|
443 |
name = line[:splitpos-1].strip().upper() # config setting name on the left of = |
|
444 |
value = line[splitpos+1:].split("#")[0].strip() |
|
445 |
||
446 |
if len(value) > 0: |
|
447 |
if name == "CACHE_FOLDERPATH": |
|
448 |
self.config.CACHE_FOLDERPATH = value |
|
449 |
elif name == "CONNECTION_TIMEOUT": |
|
450 |
try: # NITE: removed the isNumeric() check in favor of this..its more effective and lets the user know something is wrong |
|
451 |
self.config.CONNECTION_TIMEOUT = int(value) |
|
452 |
except: |
|
453 |
self.logError("Invalid value of config option CONNECTION_TIMEOUT: " + value) |
|
454 |
elif name == "EXPIRY_MINUTES": |
|
455 |
try: |
|
456 |
self.config.EXPIRY_MINUTES = int(value) |
|
457 |
except: |
|
458 |
self.logError("Invalid value of config option EXPIRY_MINUTES: " + value) |
|
459 |
elif name == "TIME_FORMAT": |
|
460 |
self.config.TIME_FORMAT = value |
|
461 |
elif name == "DATE_FORMAT": |
|
462 |
self.config.DATE_FORMAT = value |
|
463 |
elif name == "LOCALE": |
|
464 |
self.config.LOCALE = value |
|
465 |
elif name == "WU_LICENCE_KEY": |
|
466 |
self.config.WU_LICENCE_KEY = value |
|
467 |
elif name == "DEFAULT_LOCATION": |
|
468 |
self.config.DEFAULT_LOCATION = value |
|
469 |
elif name == "MAXIMUM_DAYS_FORECAST": |
|
470 |
self.config.MAXIMUM_DAYS_FORECAST = int(value) |
|
471 |
elif name == "AUTO_NIGHT": |
|
472 |
self.config.AUTO_NIGHT = self.parseBoolString(value) |
|
473 |
elif name == "BASE_WU_JSON_URL": |
|
474 |
self.config.BASE_WU_JSON_URL = value |
|
475 |
elif name == "PROXY_HOST": |
|
476 |
self.config.PROXY_HOST = value |
|
477 |
elif name == "PROXY_PORT": |
|
478 |
self.config.PROXY_PORT = int(value) |
|
479 |
elif name == "PROXY_USERNAME": |
|
480 |
self.config.PROXY_USERNAME = value |
|
481 |
elif name == "PROXY_PASSWORD": |
|
482 |
self.config.PROXY_PASSWORD = value |
|
483 |
else: |
|
484 |
self.logError("Unknown option in config file: " + name) |
|
485 |
||
486 |
if self.options.verbose == True: |
|
487 |
print >> sys.stdout,"*** CONFIG OPTIONS:" |
|
488 |
print >> sys.stdout," CACHE_FOLDERPATH:", self.config.CACHE_FOLDERPATH |
|
489 |
print >> sys.stdout," CONNECTION_TIMEOUT:", self.config.CONNECTION_TIMEOUT |
|
490 |
print >> sys.stdout," EXPIRY_MINUTES:", self.config.EXPIRY_MINUTES |
|
491 |
print >> sys.stdout," TIME_FORMAT:", self.config.TIME_FORMAT |
|
492 |
print >> sys.stdout," DATE_FORMAT:", self.config.DATE_FORMAT |
|
493 |
print >> sys.stdout," LOCALE:", self.config.LOCALE |
|
494 |
print >> sys.stdout," WU_LICENCE_KEY:", self.config.WU_LICENCE_KEY |
|
495 |
print >> sys.stdout," DEFAULT_LOCATION:", self.config.DEFAULT_LOCATION |
|
496 |
print >> sys.stdout," MAXIMUM_DAYS_FORECAST:", self.config.MAXIMUM_DAYS_FORECAST |
|
497 |
print >> sys.stdout," BASE_WU_JSON_URL:", self.config.BASE_WU_JSON_URL |
|
498 |
||
499 |
else: |
|
500 |
self.logError("Config data file %s not found, using defaults (Registration info is needed though)" % configfilepath) |
|
501 |
||
502 |
except Exception, e: |
|
503 |
self.logError("Error while loading config data, using defaults (Registration info is needed though): " + e.__str__()) |
|
504 |
||
505 |
||
506 |
def checkAndLoad(self, location): |
|
507 |
||
508 |
# if the location was not loaded before (or attempted to load)
|
|
509 |
if not location in self.loaded_locations: |
|
510 |
# add it to the list so it doesn't get loaded again (or attempted to load)
|
|
511 |
self.loaded_locations.append(location) |
|
512 |
||
513 |
# define CACHE_FILEPATH based on cache folder and location code
|
|
514 |
CACHE_FILEPATH = os.path.join(self.config.CACHE_FOLDERPATH, self.CACHE_FILENAME.replace("<LOCATION>", location.replace("/","-"))) |
|
515 |
||
516 |
if not self.forecast_data.has_key(location): |
|
517 |
if os.path.exists(CACHE_FILEPATH): |
|
518 |
try: |
|
519 |
self.logInfo("Loading cache file " + CACHE_FILEPATH) |
|
520 |
file = open(CACHE_FILEPATH, 'rb') |
|
521 |
self.forecast_data[location] = pickle.load(file) |
|
522 |
file.close() |
|
523 |
except Exception, e: |
|
524 |
self.logError("Unable to read the cache file %s: %s" % (CACHE_FILEPATH, e.__str__())) |
|
525 |
#TODO: get to the bottom of failure to load pickled cache file, is this a 2.7.1 issue?
|
|
526 |
self.logInfo("Deleting cache file due to loading issues, it will be prepared again") |
|
527 |
os.remove(CACHE_FILEPATH) |
|
528 |
#return False
|
|
529 |
||
530 |
# check the data in the dictionary and update if outdated
|
|
531 |
# if there was an update, store the new data in the cache file
|
|
532 |
if self.checkAndUpdate(location) == True: |
|
533 |
try: |
|
534 |
self.logInfo("Saving updated cache file " + CACHE_FILEPATH) |
|
535 |
file = open(CACHE_FILEPATH, 'wb') |
|
536 |
pickle.dump(self.forecast_data[location], file) |
|
537 |
file.close() |
|
538 |
except Exception, e: |
|
539 |
self.logError("Unable to save cache file %s: %s" % (CACHE_FILEPATH, e.__str__())) |
|
540 |
return False |
|
541 |
||
542 |
# if the location is still not in cache, print an error and return false to writeOutput()
|
|
543 |
if self.forecast_data.has_key(location): |
|
544 |
return True |
|
545 |
else: |
|
546 |
self.logError("Location %s is not in cache." % self.options.location) |
|
547 |
return False |
|
548 |
||
549 |
||
550 |
def checkAndUpdate(self, location): |
|
551 |
# if the location is outdated or the refetch is forced..
|
|
552 |
if not self.forecast_data.has_key(location) or \ |
|
553 |
self.forecast_data[location].outdated(self.config.EXPIRY_MINUTES) or \ |
|
554 |
self.options.refetch == True: |
|
555 |
||
556 |
# obtain current conditions data from xoap service
|
|
557 |
try: |
|
558 |
url = self.config.BASE_WU_JSON_URL.replace("<LOCATION>",location).replace("<WU_LICENCE_KEY>",self.config.WU_LICENCE_KEY) |
|
559 |
||
560 |
self.logInfo("Fetching weather data from " + url) |
|
561 |
||
562 |
usock = urllib2.urlopen(url) |
|
563 |
jsondata = usock.read() |
|
564 |
usock.close() |
|
565 |
||
566 |
except Exception, e: |
|
567 |
self.logError("Server connection error: " + e.__str__()) |
|
568 |
return False |
|
569 |
||
570 |
else: |
|
571 |
# interrogate weather data
|
|
572 |
try: |
|
573 |
# parse the XML
|
|
574 |
self.weatherdic = json.loads(jsondata) |
|
575 |
||
576 |
current = self.weatherdic["current_observation"] |
|
577 |
||
578 |
city = current["display_location"]["city"] |
|
579 |
country = current["display_location"]["country"] |
|
580 |
latitude = current["display_location"]["latitude"] |
|
581 |
longitude = current["display_location"]["longitude"] |
|
582 |
||
583 |
observatory = current["observation_location"]["city"] |
|
584 |
timezone = current["local_tz_short"] |
|
585 |
||
586 |
condition_url = current["icon_url"] |
|
587 |
condition_text = current["weather"] |
|
588 |
||
589 |
current_temp = str(current["temp_c"]) |
|
590 |
||
591 |
||
592 |
last_update = current["observation_time_rfc822"] |
|
593 |
||
594 |
day_of_week = "Today" |
|
595 |
current_temp_feels = str(current["windchill_c"]) |
|
596 |
||
597 |
precip = _(u"N/A") |
|
598 |
wind_direction = current["wind_dir"] |
|
599 |
wind_direction_numeric = str(current["wind_degrees"]) |
|
600 |
wind_speed = str(current["wind_mph"]) |
|
601 |
wind_gusts = str(current["wind_gust_mph"]) |
|
602 |
humidity = current["relative_humidity"] |
|
603 |
||
604 |
bar_read = str(current["pressure_mb"]) |
|
605 |
bar_desc = current["pressure_trend"] |
|
606 |
uv_index = str(current["heat_index_c"]) |
|
607 |
uv_text = current["heat_index_string"] |
|
608 |
dew_point = str(current["dewpoint_c"]) |
|
609 |
visibility = str(current["visibility_km"]) |
|
610 |
||
611 |
current_forecast_data = ForecastDataset(last_update, day_of_week, current_temp_feels, current_temp, condition_url, condition_text, precip, humidity, wind_direction, wind_direction_numeric, wind_speed, wind_gusts, timezone, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country) |
|
612 |
||
613 |
bar_read = _(u"N/A") |
|
614 |
bar_desc = _(u"N/A") |
|
615 |
visibility = _(u"N/A") |
|
616 |
uv_index = _(u"N/A") |
|
617 |
uv_text = _(u"N/A") |
|
618 |
dew_point = _(u"N/A") |
|
619 |
||
620 |
day_forecast_data_list = [] |
|
621 |
night_forecast_data_list = [] |
|
622 |
||
623 |
forecastlist = self.weatherdic["forecast"]["simpleforecast"]["forecastday"] |
|
624 |
for forecastday in forecastlist: |
|
625 |
last_update = time.strftime('%Y-%m-%d %I:%M:%S %p %Z',time.localtime(float(forecastday['date']['epoch']))) |
|
626 |
daynumber = forecastday["period"] |
|
627 |
day_of_week = forecastday["date"]["weekday"] |
|
628 |
high_temp = str(forecastday["high"]["celsius"]) |
|
629 |
low_temp = str(forecastday["low"]["celsius"]) |
|
630 |
||
631 |
condition_url = forecastday["icon_url"] |
|
632 |
condition_text = forecastday["conditions"] |
|
633 |
||
634 |
precip = str(forecastday["pop"]) |
|
635 |
wind_speed = _(u"N/A") |
|
636 |
wind_gusts = _(u"N/A") |
|
637 |
wind_direction_numeric = _(u"N/A") |
|
638 |
wind_direction = _(u"N/A") |
|
639 |
humidity = _(u"N/A") |
|
640 |
||
641 |
||
642 |
day_forecast_data = ForecastDataset(last_update, day_of_week, low_temp, high_temp, condition_url, condition_text, precip, humidity, wind_direction, wind_direction_numeric, wind_speed, wind_gusts, timezone, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country) |
|
643 |
day_forecast_data_list.append(day_forecast_data) |
|
644 |
||
645 |
# no night data available at the mo....so just populating with day data
|
|
646 |
night_forecast_data = ForecastDataset(last_update, day_of_week, low_temp, high_temp, condition_url, condition_text, precip, humidity, wind_direction, wind_direction_numeric, wind_speed, wind_gusts, timezone, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country) |
|
647 |
night_forecast_data_list.append(night_forecast_data) |
|
648 |
||
649 |
self.forecast_data[location] = ForecastLocation(current_forecast_data, day_forecast_data_list, night_forecast_data_list) |
|
650 |
||
651 |
return True |
|
652 |
||
653 |
except Exception, e: |
|
654 |
self.logError("Error reading weather data: " + e.__str__()) |
|
655 |
return False |
|
656 |
||
657 |
def getTimestampOutput(self, timestamp, minuteshide): |
|
658 |
# minuteshide:
|
|
659 |
# None = disabled
|
|
660 |
# -1 = hide days and use config.EXPIRY_MINUTES
|
|
661 |
# 0 = hide days and always show hours
|
|
662 |
||
663 |
output = u"" |
|
664 |
||
665 |
today = datetime.today() |
|
666 |
days = today.day - timestamp.day |
|
667 |
if days or minuteshide == None: |
|
668 |
output += timestamp.strftime(self.config.DATE_FORMAT) |
|
669 |
||
670 |
if minuteshide == -1: |
|
671 |
minuteshide = self.config.EXPIRY_MINUTES |
|
672 |
||
673 |
delta = today - timestamp |
|
674 |
if days or minuteshide == None or minuteshide == 0 or delta.seconds > minuteshide * 60: |
|
675 |
if (len(output) > 0): |
|
676 |
output += " " |
|
677 |
output += timestamp.strftime(self.config.TIME_FORMAT) |
|
678 |
||
679 |
return output |
|
680 |
||
681 |
||
682 |
def getDatatypeFromSet(self, location, datatype, set, shortweekday, imperial, beaufort, metrespersecond, tempunit, speedunit, distanceunit, pressureunit, minuteshide, centeredwidth): |
|
683 |
output = u"" |
|
684 |
||
685 |
try: |
|
686 |
if datatype == "LU": |
|
687 |
output = set.last_update.strip() |
|
688 |
elif datatype == "LF": |
|
689 |
output = self.getTimestampOutput(self.forecast_data[location].timestamp, minuteshide) |
|
690 |
elif datatype == "DW": |
|
691 |
if shortweekday == True: |
|
692 |
output = _(ForecastText.day_of_week_short[set.day_of_week]) |
|
693 |
else: |
|
694 |
output = _(set.day_of_week) |
|
695 |
elif datatype == "WI": |
|
696 |
output = self.getImageSrcForCondition(set.condition_url, set.condition_text) |
|
697 |
elif datatype == "LT": |
|
698 |
if self.isNumeric(set.low) == True: |
|
699 |
if imperial == True: |
|
700 |
string = self.convertCelsiusToFahrenheit(set.low) |
|
701 |
else: |
|
702 |
string = set.low |
|
703 |
string = string + tempunit |
|
704 |
else: |
|
705 |
string = _(set.low) |
|
706 |
output = string |
|
707 |
elif datatype == "HT": |
|
708 |
if self.isNumeric(set.high) == True: |
|
709 |
if imperial == True: |
|
710 |
string = self.convertCelsiusToFahrenheit(set.high) |
|
711 |
else: |
|
712 |
string = set.high |
|
713 |
string = string + tempunit |
|
714 |
else: |
|
715 |
string = _(set.high) |
|
716 |
output = string |
|
717 |
elif datatype == "CT": |
|
718 |
output = _(set.condition_text) |
|
719 |
elif datatype == "PC": |
|
720 |
if self.isNumeric(set.precip) == True: |
|
721 |
string = set.precip + u"%" |
|
722 |
else: |
|
723 |
string = _(set.precip) |
|
724 |
output = string |
|
725 |
elif datatype == "HM": |
|
726 |
if self.isNumeric(set.humidity) == True: |
|
727 |
string = set.humidity + u"%" |
|
728 |
else: |
|
729 |
string = _(set.humidity) |
|
730 |
output = string |
|
731 |
elif datatype == "WD": |
|
732 |
output = _(set.wind_dir) |
|
733 |
elif datatype == "BF": |
|
734 |
if set.wind_speed.lower() == "calm": |
|
735 |
output = chr(0x25) |
|
736 |
else: |
|
737 |
if (set.wind_dir == "VAR"): |
|
738 |
output = chr(0x22) # 2nd level var arrow |
|
739 |
else: |
|
740 |
try: |
|
741 |
# for the old datatype, add 0x10, that makes the output in the A-P range,
|
|
742 |
# which is the 2nd level arrow
|
|
743 |
output = chr(ForecastText.bearing_arrow_font[set.wind_dir] + 0x10) |
|
744 |
except KeyError: |
|
745 |
# if the value wasn't found in ForecastText.bearing_arrow_font, use space
|
|
746 |
output = "-" |
|
747 |
elif datatype == "BS": |
|
748 |
if set.wind_speed.lower() == "calm": |
|
749 |
output = chr(0x25) |
|
750 |
elif self.isNumeric(set.wind_speed) == True: |
|
751 |
if (set.wind_dir == "VAR"): |
|
752 |
output = chr(0x21 + self.getWindLevel(set.wind_speed)) |
|
753 |
else: |
|
754 |
try: |
|
755 |
output = chr(ForecastText.bearing_arrow_font[set.wind_dir] + self.getWindLevel(set.wind_speed) * 0x10) |
|
756 |
except KeyError: |
|
757 |
# if the value wasn't found in ForecastText.bearing_arrow_font, use N/A
|
|
758 |
output = "-" |
|
759 |
else: |
|
760 |
try: |
|
761 |
# if the speed is not "calm" but also not a number, add 0x10
|
|
762 |
# that makes the output in the A-P range, the 2nd level arrow
|
|
763 |
output = chr(ForecastText.bearing_arrow_font[set.wind_dir] + 0x10) |
|
764 |
except KeyError: |
|
765 |
# if the value wasn't found in ForecastText.bearing_arrow_font, use N/A
|
|
766 |
output = "-" |
|
767 |
elif datatype == "BI": |
|
768 |
if set.wind_speed.lower() == "calm": |
|
769 |
output = self.getImagePathForBearing(ForecastText.bearing_icon["calm"]) |
|
770 |
elif self.isNumeric(set.wind_speed) == True: |
|
771 |
if (set.wind_dir == "VAR"): |
|
772 |
output = self.getImagePathForBearing(int(ForecastText.bearing_icon[set.wind_dir]) + self.getWindLevel(set.wind_speed)) |
|
773 |
else: |
|
774 |
try: |
|
775 |
output = self.getImagePathForBearing(int(ForecastText.bearing_icon[set.wind_dir]) + self.getWindLevel(set.wind_speed)*16) |
|
776 |
except KeyError: |
|
777 |
# if the value wasn't found in ForecastText.bearing_icon, use calm code
|
|
778 |
output = self.getImagePathForBearing(ForecastText.bearing_icon["calm"]) |
|
779 |
||
780 |
elif datatype == "WA": |
|
781 |
output = _(set.wind_dir_numeric) |
|
782 |
elif datatype == "WS": |
|
783 |
if self.isNumeric(set.wind_speed) == True: |
|
784 |
if beaufort == True: |
|
785 |
string = self.convertMPHtoBeaufort(set.wind_speed) |
|
786 |
elif metrespersecond == True: |
|
787 |
string = self.convertMPHtoMS(set.wind_speed) |
|
788 |
elif imperial == True: |
|
789 |
string = set.wind_speed |
|
790 |
else: |
|
791 |
string = self.convertMilesToKilometres(set.wind_speed) |
|
792 |
||
793 |
string = string + speedunit |
|
794 |
else: |
|
795 |
string = _(set.wind_speed.lower()) |
|
796 |
output = string |
|
797 |
elif datatype == "WG": |
|
798 |
if self.isNumeric(set.wind_gusts) == True: |
|
799 |
if beaufort == True: |
|
800 |
string = self.convertMPHtoBeaufort(set.wind_gusts) |
|
801 |
elif metrespersecond == True: |
|
802 |
string = self.convertMPHtoMS(set.wind_gusts) |
|
803 |
elif imperial == True: |
|
804 |
string = set.wind_gusts |
|
805 |
else: |
|
806 |
string = self.convertMilesToKilometres(set.wind_gusts) |
|
807 |
||
808 |
string = string + speedunit |
|
809 |
else: |
|
810 |
string = _(set.wind_gusts) # need to define translations |
|
811 |
output = string |
|
812 |
elif datatype == "BR": |
|
813 |
if self.isNumeric(set.bar_read) == True: |
|
814 |
if imperial == True: |
|
815 |
string = self.convertMillibarsToInches(set.bar_read,2) |
|
816 |
else: |
|
817 |
string = set.bar_read |
|
818 |
string = string + pressureunit |
|
819 |
else: |
|
820 |
string = _(set.bar_read) |
|
821 |
output = string |
|
822 |
elif datatype == "BD": |
|
823 |
output = _(set.bar_desc) # need to define translations |
|
824 |
elif datatype == "UI": |
|
825 |
output = _(set.uv_index) |
|
826 |
elif datatype == "UT": |
|
827 |
output = _(set.uv_text) |
|
828 |
elif datatype == "DP": |
|
829 |
if self.isNumeric(set.dew_point) == True: |
|
830 |
if imperial == True: |
|
831 |
string = self.convertCelsiusToFahrenheit(set.dew_point) |
|
832 |
else: |
|
833 |
string = set.dew_point |
|
834 |
string = string + tempunit |
|
835 |
else: |
|
836 |
string = _(set.dew_point) |
|
837 |
output = string |
|
838 |
elif datatype == "OB": |
|
839 |
output = set.observatory |
|
840 |
elif datatype == "VI": |
|
841 |
if self.isNumeric(set.visibility) == True: |
|
842 |
if imperial == True: |
|
843 |
string = self.convertKilometresToMiles(set.visibility,1) |
|
844 |
else: |
|
845 |
string = set.visibility |
|
846 |
string = string + distanceunit |
|
847 |
else: |
|
848 |
string = _(set.visibility) |
|
849 |
output = string |
|
850 |
elif datatype == "CN": |
|
851 |
output = set.city |
|
852 |
elif datatype == "CO": |
|
853 |
output = set.country |
|
854 |
else: |
|
855 |
self.logError("Unknown datatype requested: " + datatype) |
|
856 |
||
857 |
except KeyError, e: |
|
858 |
self.logError("Unknown value %s encountered for datatype '%s'! Please report this!" % (e.__str__(), datatype)) |
|
859 |
||
860 |
# set the width if it is set, either left trimming or centering the text in spaces as requested
|
|
861 |
if centeredwidth != None and self.isNumeric(centeredwidth) == True: |
|
862 |
if centeredwidth < len(output): |
|
863 |
output = output[:centeredwidth] |
|
864 |
else: |
|
865 |
output = output.center(int(centeredwidth)) |
|
866 |
||
867 |
return output |
|
868 |
||
869 |
||
870 |
def getDatasetOutput(self, location, datatype, startday, endday, night, shortweekday, imperial, beaufort, metrespersecond, hideunits, hidedegreesymbol, spaces, minuteshide, centeredwidth): |
|
871 |
||
872 |
output = u"" |
|
873 |
||
874 |
# Check if the location is loaded, if not, load it. If it can't be loaded, there was an error
|
|
875 |
if not self.checkAndLoad(location): |
|
876 |
self.logError("Failed to load the location cache") |
|
877 |
return u"" |
|
878 |
||
879 |
# define current units for output
|
|
880 |
if hideunits == False: |
|
881 |
if imperial == False: |
|
882 |
tempunit = _(u"°C") |
|
883 |
speedunit = _(u"kph") |
|
884 |
distanceunit = _(u"km") |
|
885 |
pressureunit = _(u"mb") |
|
886 |
else: |
|
887 |
tempunit = _(u"°F") |
|
888 |
speedunit = _(u"mph") |
|
889 |
distanceunit = _(u"m") |
|
890 |
pressureunit = _(u"in") |
|
891 |
||
892 |
# override speed units if beaufort selected
|
|
893 |
if beaufort == True: |
|
894 |
speedunit = u"" |
|
895 |
||
896 |
if metrespersecond == True: |
|
897 |
speedunit = u"m/s" |
|
898 |
else: |
|
899 |
# remove degree symbol if not required
|
|
900 |
if hidedegreesymbol == False: |
|
901 |
tempunit = u"°" |
|
902 |
else: |
|
903 |
tempunit = u"" |
|
904 |
||
905 |
speedunit = u"" |
|
906 |
distanceunit = u"" |
|
907 |
pressureunit = u"" |
|
908 |
||
909 |
if startday == None: |
|
910 |
output += self.getDatatypeFromSet(location, datatype, self.forecast_data[location].current, shortweekday, imperial, beaufort, metrespersecond, tempunit, speedunit, distanceunit, pressureunit, minuteshide, centeredwidth) |
|
911 |
else: # forecast data |
|
912 |
||
913 |
# ensure startday and enday are within the forecast limit
|
|
914 |
||
915 |
if startday < 0: |
|
916 |
startday = 0 |
|
917 |
self.logError("--startday set beyond forecast limit, reset to minimum of 0") |
|
918 |
elif startday > self.config.MAXIMUM_DAYS_FORECAST: |
|
919 |
startday = self.config.MAXIMUM_DAYS_FORECAST |
|
920 |
self.logError("--startday set beyond forecast limit, reset to maximum of " + str(self.config.MAXIMUM_DAYS_FORECAST)) |
|
921 |
||
922 |
if endday == None: # if no endday was set use startday |
|
923 |
endday = startday |
|
924 |
elif endday < 0: |
|
925 |
endday = 0 |
|
926 |
self.logError("--endday set beyond forecast limit, reset to minimum of 0") |
|
927 |
elif endday > self.config.MAXIMUM_DAYS_FORECAST: |
|
928 |
endday = self.config.MAXIMUM_DAYS_FORECAST |
|
929 |
self.logError("--endday set beyond forecast limit, reset to maximum of " + str(self.config.MAXIMUM_DAYS_FORECAST)) |
|
930 |
||
931 |
for daynumber in range(startday, endday + 1): |
|
932 |
||
933 |
# if AUTO_NIGHT config is true then handle N/A output, by using the night option between 2pm and 2am, when the startday = 0.
|
|
934 |
if self.config.AUTO_NIGHT == True and daynumber == 0: |
|
935 |
now = datetime.now() |
|
936 |
hour = now.hour |
|
937 |
if hour > 13 or hour < 2: |
|
938 |
night = True |
|
939 |
||
940 |
if night == True: |
|
941 |
output += self.getDatatypeFromSet(location, datatype, self.forecast_data[location].night[daynumber], shortweekday, imperial, beaufort, metrespersecond, tempunit, speedunit, distanceunit, pressureunit, minuteshide, centeredwidth) |
|
942 |
else: |
|
943 |
output += self.getDatatypeFromSet(location, datatype, self.forecast_data[location].day[daynumber], shortweekday, imperial, beaufort, metrespersecond, tempunit, speedunit, distanceunit, pressureunit, minuteshide, centeredwidth) |
|
944 |
||
945 |
if daynumber != endday: |
|
946 |
output += self.getSpaces(spaces) |
|
947 |
||
948 |
return output |
|
949 |
||
950 |
def getTemplateItemOutput(self, template_text): |
|
951 |
||
952 |
# keys to template data
|
|
953 |
LOCATION_KEY = "location" |
|
954 |
DATATYPE_KEY = "datatype" |
|
955 |
STARTDAY_KEY = "startday" |
|
956 |
ENDDAY_KEY = "endday" |
|
957 |
NIGHT_KEY = "night" |
|
958 |
SHORTWEEKDAY_KEY = "shortweekday" |
|
959 |
IMPERIAL_KEY = "imperial" |
|
960 |
BEAUFORT_KEY = "beaufort" |
|
961 |
METRESPERSECOND_KEY = "metrespersecond" |
|
962 |
HIDEUNITS_KEY = "hideunits" |
|
963 |
HIDEDEGREESYMBOL_KEY = "hidedegreesymbol" |
|
964 |
SPACES_KEY = "spaces" |
|
965 |
MINUTESHIDE_KEY = "minuteshide" |
|
966 |
CENTEREDWIDTH_KEY = "centeredwidth" |
|
967 |
||
968 |
location = self.options.location |
|
969 |
datatype = self.options.datatype |
|
970 |
startday = self.options.startday |
|
971 |
endday = self.options.endday |
|
972 |
night = self.options.night |
|
973 |
shortweekday = self.options.shortweekday |
|
974 |
imperial = self.options.imperial |
|
975 |
beaufort = self.options.beaufort |
|
976 |
metrespersecond = self.options.metrespersecond |
|
977 |
hideunits = self.options.hideunits |
|
978 |
hidedegreesymbol = self.options.hidedegreesymbol |
|
979 |
spaces = self.options.spaces |
|
980 |
minuteshide = self.options.minuteshide |
|
981 |
centeredwidth = self.options.centeredwidth |
|
982 |
||
983 |
for option in template_text.split('--'): |
|
984 |
if len(option) == 0 or option.isspace(): |
|
985 |
continue
|
|
986 |
||
987 |
# not using split here...it can't assign both key and value in one call, this should be faster
|
|
988 |
x = option.find('=') |
|
989 |
if (x != -1): |
|
990 |
key = option[:x].strip() |
|
991 |
value = option[x + 1:].strip() |
|
992 |
if value == "": |
|
993 |
value = None |
|
994 |
else: |
|
995 |
key = option.strip() |
|
996 |
value = None |
|
997 |
||
998 |
try: |
|
999 |
if key == LOCATION_KEY: |
|
1000 |
location = value |
|
1001 |
elif key == DATATYPE_KEY: |
|
1002 |
datatype = value |
|
1003 |
elif key == STARTDAY_KEY: |
|
1004 |
startday = int(value) |
|
1005 |
elif key == ENDDAY_KEY: |
|
1006 |
endday = int(value) |
|
1007 |
elif key == NIGHT_KEY: |
|
1008 |
night = True |
|
1009 |
elif key == SHORTWEEKDAY_KEY: |
|
1010 |
shortweekday = True |
|
1011 |
elif key == IMPERIAL_KEY: |
|
1012 |
imperial = True |
|
1013 |
elif key == BEAUFORT_KEY: |
|
1014 |
beaufort = True |
|
1015 |
elif key == METRESPERSECOND_KEY: |
|
1016 |
metrespersecond = True |
|
1017 |
elif key == HIDEUNITS_KEY: |
|
1018 |
hideunits = True |
|
1019 |
elif key == HIDEDEGREESYMBOL_KEY: |
|
1020 |
hidedegreesymbol = True |
|
1021 |
elif key == SPACES_KEY: |
|
1022 |
spaces = int(value) |
|
1023 |
elif key == MINUTESHIDE_KEY: |
|
1024 |
if value != None: |
|
1025 |
minuteshide = int(value) |
|
1026 |
else: |
|
1027 |
minuteshide = -1 |
|
1028 |
elif key == CENTEREDWIDTH_KEY: |
|
1029 |
centeredwidth = value |
|
1030 |
else: |
|
1031 |
self.logError("Unknown template option: " + option) |
|
1032 |
||
1033 |
except (TypeError, ValueError): |
|
1034 |
self.logError("Cannot convert option argument to number: " + option) |
|
1035 |
return u"" |
|
1036 |
||
1037 |
#REMOVED
|
|
1038 |
# Check if the location is loaded, if not, load it. If it can't be loaded, there was an error
|
|
1039 |
#if not self.checkAndLoad(location):
|
|
1040 |
# self.logError("Failed to load the location cache")
|
|
1041 |
# return u""
|
|
1042 |
||
1043 |
if datatype != None: |
|
1044 |
return self.getDatasetOutput(location, datatype, startday, endday, night, shortweekday, imperial, beaufort, metrespersecond, hideunits, hidedegreesymbol, spaces, minuteshide, centeredwidth) |
|
1045 |
else: |
|
1046 |
self.logError("Template item does not have datatype defined") |
|
1047 |
return u"" |
|
1048 |
||
1049 |
def getOutputFromTemplate(self, template): |
|
1050 |
output = u"" |
|
1051 |
end = False |
|
1052 |
a = 0 |
|
1053 |
||
1054 |
# a and b are indexes in the template string
|
|
1055 |
# moving from left to right the string is processed
|
|
1056 |
# b is index of the opening bracket and a of the closing bracket
|
|
1057 |
# everything between b and a is a template that needs to be parsed
|
|
1058 |
while not end: |
|
1059 |
b = template.find('[', a) |
|
1060 |
||
1061 |
if b == -1: |
|
1062 |
b = len(template) |
|
1063 |
end = True |
|
1064 |
||
1065 |
# if there is something between a and b, append it straight to output
|
|
1066 |
if b > a: |
|
1067 |
output += template[a : b] |
|
1068 |
# check for the escape char (if we are not at the end)
|
|
1069 |
if template[b - 1] == '\\' and not end: |
|
1070 |
# if its there, replace it by the bracket
|
|
1071 |
output = output[:-1] + '[' |
|
1072 |
# skip the bracket in the input string and continue from the beginning
|
|
1073 |
a = b + 1 |
|
1074 |
continue
|
|
1075 |
||
1076 |
if end: |
|
1077 |
break
|
|
1078 |
||
1079 |
a = template.find(']', b) |
|
1080 |
||
1081 |
if a == -1: |
|
1082 |
self.logError("Missing terminal bracket (]) for a template item") |
|
1083 |
return u"" |
|
1084 |
||
1085 |
# if there is some template text...
|
|
1086 |
if a > b + 1: |
|
1087 |
output += self.getTemplateItemOutput(template[b + 1 : a]) |
|
1088 |
||
1089 |
a = a + 1 |
|
1090 |
||
1091 |
return output |
|
1092 |
||
1093 |
def writeOutput(self): |
|
1094 |
||
1095 |
if self.options.template != None: |
|
1096 |
#load the file
|
|
1097 |
try: |
|
1098 |
fileinput = codecs.open(os.path.expanduser(self.options.template), encoding='utf-8') |
|
1099 |
template = fileinput.read() |
|
1100 |
fileinput.close() |
|
1101 |
except Exception, e: |
|
1102 |
self.logError("Error loading template file: " + e.__str__()) |
|
1103 |
else: |
|
1104 |
output = self.getOutputFromTemplate(template) |
|
1105 |
else: |
|
1106 |
||
1107 |
output = self.getDatasetOutput(self.options.location, self.options.datatype, self.options.startday, self.options.endday, self.options.night, self.options.shortweekday, self.options.imperial, self.options.beaufort, self.options.metrespersecond, self.options.hideunits, self.options.hidedegreesymbol, self.options.spaces, self.options.minuteshide, self.options.centeredwidth) |
|
1108 |
||
1109 |
print output.encode("utf-8") |
|
1110 |
||
1111 |
def logInfo(self, text): |
|
1112 |
if self.options.verbose == True: |
|
1113 |
print >> sys.stdout, "INFO: " + text |
|
1114 |
||
1115 |
if self.options.infologfile != None: |
|
1116 |
datetimestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
1117 |
fileoutput = open(self.options.infologfile, "ab") |
|
1118 |
fileoutput.write(datetimestamp+" INFO: "+text+"\n") |
|
1119 |
fileoutput.close() |
|
1120 |
||
1121 |
def logError(self, text): |
|
1122 |
print >> sys.stderr, "ERROR: " + text |
|
1123 |
||
1124 |
if self.options.errorlogfile != None: |
|
1125 |
datetimestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
1126 |
fileoutput = open(self.options.errorlogfile, "ab") |
|
1127 |
fileoutput.write(datetimestamp+" ERROR: "+text+"\n") |
|
1128 |
fileoutput.close() |
|
1129 |
||
1130 |
def getText(self, parentNode, name): |
|
1131 |
try: |
|
1132 |
node = parentNode.getElementsByTagName(name)[0] |
|
1133 |
except IndexError: |
|
1134 |
raise Exception, "Data element <%s> not present under <%s>" % (name, parentNode.tagName) |
|
1135 |
||
1136 |
rc = "" |
|
1137 |
for child in node.childNodes: |
|
1138 |
if child.nodeType == child.TEXT_NODE: |
|
1139 |
rc = rc + child.data |
|
1140 |
return rc |
|
1141 |
||
1142 |
def getChild(self, parentNode, name, index = 0): |
|
1143 |
try: |
|
1144 |
return parentNode.getElementsByTagName(name)[index] |
|
1145 |
except IndexError: |
|
1146 |
raise Exception, "Data element <%s> is not present under <%s> (index %i)" % (name, parentNode.tagName, index) |
|
1147 |
||
1148 |
def getSpaces(self, spaces=1): |
|
1149 |
string = u"" |
|
1150 |
for dummy in range(0, spaces): |
|
1151 |
string = string + u" " |
|
1152 |
return string |
|
1153 |
||
1154 |
def isNumeric(self, string): |
|
1155 |
try: |
|
1156 |
dummy = float(string) |
|
1157 |
return True |
|
1158 |
except: |
|
1159 |
return False |
|
1160 |
||
1161 |
def parseBoolString(self, string): |
|
1162 |
#return string[0].upper()=="T"
|
|
1163 |
||
1164 |
if string is True or string is False: |
|
1165 |
return string |
|
1166 |
string = str(string).strip().lower() |
|
1167 |
return not string in ['false','f','n','0',''] |
|
1168 |
||
1169 |
def convertCelsiusToFahrenheit(self, temp, dp=0): |
|
1170 |
value = ((float(temp) * 9.0) / 5.0) + 32 |
|
1171 |
if dp == 0: |
|
1172 |
return str(int(round(value,dp))) # lose the dp |
|
1173 |
else: |
|
1174 |
return str(round(value,dp)) |
|
1175 |
||
1176 |
def convertKilometresToMiles(self, dist, dp=0): |
|
1177 |
value = float(dist) * 0.621371192 |
|
1178 |
if dp == 0: |
|
1179 |
return str(int(round(value,dp))) # lose the dp |
|
1180 |
else: |
|
1181 |
return str(round(value,dp)) |
|
1182 |
||
1183 |
def convertMilesToKilometres(self, dist, dp=0): |
|
1184 |
value = float(dist) * 1.609344001 |
|
1185 |
if dp == 0: |
|
1186 |
return str(int(round(value,dp))) # lose the dp |
|
1187 |
else: |
|
1188 |
return str(round(value,dp)) |
|
1189 |
||
1190 |
def convertKPHtoBeaufort(self, kph, dp=0): |
|
1191 |
value = pow(float(kph) * 0.332270069, 2.0 / 3.0) |
|
1192 |
if dp == 0: |
|
1193 |
return str(int(round(value,dp))) # lose the dp |
|
1194 |
else: |
|
1195 |
return str(round(value,dp)) |
|
1196 |
||
1197 |
def convertMPHtoBeaufort(self, mph, dp=0): |
|
1198 |
value = pow(float(self.convertMilesToKilometres(mph)) * 0.332270069, 2.0 / 3.0) |
|
1199 |
if dp == 0: |
|
1200 |
return str(int(round(value,dp))) # lose the dp |
|
1201 |
else: |
|
1202 |
return str(round(value,dp)) |
|
1203 |
||
1204 |
def convertKPHtoMS(self, kph, dp=0): |
|
1205 |
value = float(kph) * 0.27777778 |
|
1206 |
if dp == 0: |
|
1207 |
return str(int(round(value,dp))) # lose the dp |
|
1208 |
else: |
|
1209 |
return str(round(value,dp)) |
|
1210 |
||
1211 |
def convertMPHtoMS(self, mph, dp=0): |
|
1212 |
value = float(self.convertMilesToKilometres(mph)) * 0.27777778 |
|
1213 |
if dp == 0: |
|
1214 |
return str(int(round(value,dp))) # lose the dp |
|
1215 |
else: |
|
1216 |
return str(round(value,dp)) |
|
1217 |
||
1218 |
def convertMillibarsToInches(self,mb,dp=0): |
|
1219 |
value = float(mb)/33.8582 |
|
1220 |
if dp == 0: |
|
1221 |
return str(int(round(value,dp))) # lose the dp |
|
1222 |
else: |
|
1223 |
return str(round(value,dp)) |
|
1224 |
||
1225 |
def getWindLevel(self, speed): |
|
1226 |
beaufort = int(self.convertMPHtoBeaufort(speed)) |
|
1227 |
if beaufort < 4: |
|
1228 |
return 0 |
|
1229 |
elif beaufort < 7: |
|
1230 |
return 1 |
|
1231 |
elif beaufort < 10: |
|
1232 |
return 2 |
|
1233 |
else: |
|
1234 |
return 3 |
|
1235 |
||
1236 |
def getFormattedTimeFromSeconds(self,seconds,showseconds=False): |
|
1237 |
time = int(seconds) |
|
1238 |
hours, time = divmod(time, 60*60) |
|
1239 |
minutes, seconds = divmod(time, 60) |
|
1240 |
||
1241 |
if showseconds == True: |
|
1242 |
output = "%02d:%02d:%02d"%(hours, minutes, seconds) |
|
1243 |
else: |
|
1244 |
output = "%02d:%02d"%(hours, minutes) |
|
1245 |
||
1246 |
return output |
|
1247 |
||
1248 |
def getImageSrcForCondition(self, url, condition): |
|
1249 |
imagesrc = "" |
|
1250 |
imgfilepath = os.path.join(self.config.CACHE_FOLDERPATH, self.CONDITION_IMAGE_FILENAME.replace("<CONDITION>",condition.replace(" ","-"))) |
|
1251 |
||
1252 |
if os.path.exists(imgfilepath) == False: |
|
1253 |
||
1254 |
try: |
|
1255 |
self.logInfo("Fetching image from " + imagesrc) |
|
1256 |
||
1257 |
usock = urllib2.urlopen(url) |
|
1258 |
img = usock.read() |
|
1259 |
except Exception, e: |
|
1260 |
self.logError("Error downloading the image file: " + e.__str__()+"\n"+traceback.format_exc()) |
|
1261 |
else: |
|
1262 |
# save the image and contruct an image tag
|
|
1263 |
||
1264 |
imgfile = open(imgfilepath,'wb') |
|
1265 |
imgfile.write(img) |
|
1266 |
self.logInfo("Saved image to " + imgfilepath) |
|
1267 |
||
1268 |
finally: |
|
1269 |
usock.close() |
|
1270 |
imgfile.close() |
|
1271 |
||
1272 |
return imgfilepath |
|
1273 |
||
1274 |
def getImagePathForBearing(self, bearingcode): |
|
1275 |
#TODO: Once gif supported properly in conky re-enable gif output
|
|
1276 |
#if int(bearingcode) > 0 and int(bearingcode) <= 4:
|
|
1277 |
# fileext = "gif" # use animated gif for VAR output
|
|
1278 |
#else:
|
|
1279 |
# fileext = "png"
|
|
1280 |
||
1281 |
fileext = "png" #force to always be png until animated gifs are supported |
|
1282 |
imagesrc = "%s/images/bearingicons/%s.%s"%(app_path, str(bearingcode).rjust(2,"0"),fileext) |
|
1283 |
return imagesrc |
|
1284 |
||
1285 |
def main(): |
|
1286 |
||
1287 |
parser = CommandLineParser() |
|
1288 |
(options, args) = parser.parse_args() |
|
1289 |
||
1290 |
if options.version == True: |
|
1291 |
||
1292 |
print >> sys.stdout,"conkyForecast v.2.21" |
|
1293 |
||
1294 |
else: |
|
1295 |
||
1296 |
if options.verbose == True: |
|
1297 |
print >> sys.stdout, "*** INITIAL OPTIONS:" |
|
1298 |
print >> sys.stdout, " config:", options.config |
|
1299 |
print >> sys.stdout, " location:", options.location |
|
1300 |
print >> sys.stdout, " datatype:", options.datatype |
|
1301 |
print >> sys.stdout, " start day:", options.startday |
|
1302 |
print >> sys.stdout, " end day:", options.endday |
|
1303 |
print >> sys.stdout, " spaces:", options.spaces |
|
1304 |
print >> sys.stdout, " template:", options.template |
|
1305 |
print >> sys.stdout, " locale:", options.locale |
|
1306 |
print >> sys.stdout, " imperial:", options.imperial |
|
1307 |
print >> sys.stdout, " beaufort:", options.beaufort |
|
1308 |
print >> sys.stdout, " metrespersecond:", options.metrespersecond |
|
1309 |
print >> sys.stdout, " night:", options.night |
|
1310 |
print >> sys.stdout, " shortweekday:", options.shortweekday |
|
1311 |
print >> sys.stdout, " hideunits:", options.hideunits |
|
1312 |
print >> sys.stdout, " hidedegreesymbol:", options.hidedegreesymbol |
|
1313 |
print >> sys.stdout, " minuteshide:", options.minuteshide |
|
1314 |
print >> sys.stdout, " centeredwidth:", options.centeredwidth |
|
1315 |
print >> sys.stdout, " refetch:", options.refetch |
|
1316 |
print >> sys.stdout, " verbose:", options.verbose |
|
1317 |
print >> sys.stdout, " errorlogfile:",options.errorlogfile |
|
1318 |
print >> sys.stdout, " infologfile:",options.infologfile |
|
1319 |
||
1320 |
forecastinfo = ForecastInfo(options) |
|
1321 |
forecastinfo.writeOutput() |
|
1322 |
||
1323 |
if __name__ == '__main__': |
|
1324 |
main() |
|
1325 |
sys.exit() |
|
1326 |