493
493
--hidedegreesymbol : Hide the degree symbol used with temperature output, this is only valid if used in conjunction with --hideunits.")
494
494
--minuteshide : 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.")
497
497
def __init__(self, options):
498
498
self.options = options
499
499
self.logger = logging.getLogger(app_name+"."+module_name)
500
500
self.loadConfigData()
501
501
self.loadTranslation()
503
503
# setup timeout for connections
504
504
# TODO: seems like this doesn't work in all cases..
505
505
socket.setdefaulttimeout(self.config.CONNECTION_TIMEOUT)
509
509
def loadTranslation(self):
512
512
if self.config.LOCALE == None:
513
513
self.config.LOCALE = locale.getdefaultlocale()[0][0:2]
515
515
self.logger.info("Locale set to " + self.config.LOCALE)
517
# if not the default "en" locale, configure the i18n language translation
517
# if not the default "en" locale, configure the i18n language translation
518
518
if self.config.LOCALE != "en":
520
520
self.logger.info("Looking for translation file for '%s' under %s" % (self.config.LOCALE, localedirectory))
522
522
if gettext.find(domain, localedirectory, languages=[self.config.LOCALE]) != None:
523
523
self.logger.info("Translation file found for '%s'" % self.config.LOCALE)
526
526
trans = gettext.translation(domain, localedirectory, languages=[self.config.LOCALE])
527
527
trans.install(unicode=True)
528
528
self.logger.info("Translation installed for '%s'" % self.config.LOCALE)
530
530
except Exception, e:
531
531
self.logger.error("Unable to load translation for '%s' %s" % (self.config.LOCALE, e.__str__()))
533
533
self.logger.info("Translation file not found for '%s', defaulting to 'en'" % self.config.LOCALE)
534
534
self.config.LOCALE = "en"
536
536
except Exception, e:
537
537
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
539
539
def loadConfigData(self):
542
542
self.config = ForecastConfig()
544
544
if self.options.config != None:
545
545
# load the config based on options passed in from the main app
546
546
configfilepath = self.options.config
548
548
# load plugin config from home directory of the user
549
549
configfilepath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/"+module_name+".config")
551
551
if os.path.exists(configfilepath):
553
553
self.logger.info("Loading config settings from \"%s\""%configfilepath)
555
555
for line in fileinput.input(os.path.expanduser(configfilepath)):
556
556
line = line.strip()
557
557
if len(line) > 0 and line[0:1] != "#": # ignore commented lines or empty ones
559
559
name = line.split("=")[0].strip().upper() # config setting name on the left of =
560
560
value = line.split("=")[1].split("#")[0].strip() # config value on the right of = (minus any trailing comments)
562
562
if len(value) > 0:
563
563
if name == "HEADERTEMPLATE":
564
self.config.HEADERTEMPLATE = self.getTypedValue(value, "string")
564
self.config.HEADERTEMPLATE = getTypedValue(value, "string")
565
565
elif name == "TEMPLATE":
566
self.config.TEMPLATE = self.getTypedValue(value, "string")
566
self.config.TEMPLATE = getTypedValue(value, "string")
567
567
elif name == "LOCATION":
568
self.config.LOCATION = self.getTypedValue(value, "string")
568
self.config.LOCATION = getTypedValue(value, "string")
569
569
elif name == "CACHE_FOLDERPATH":
570
self.config.CACHE_FOLDERPATH = self.getTypedValue(value, "string")
570
self.config.CACHE_FOLDERPATH = getTypedValue(value, "string")
571
571
elif name == "CONNECTION_TIMEOUT":
572
self.config.CONNECTION_TIMEOUT = self.getTypedValue(value, "integer")
572
self.config.CONNECTION_TIMEOUT = getTypedValue(value, "integer")
573
573
elif name == "EXPIRY_MINUTES":
574
self.config.EXPIRY_MINUTES = self.getTypedValue(value, "integer")
574
self.config.EXPIRY_MINUTES = getTypedValue(value, "integer")
575
575
elif name == "TIMEFORMAT":
576
self.config.TIMEFORMAT = self.getTypedValue(value, "string")
576
self.config.TIMEFORMAT = getTypedValue(value, "string")
577
577
elif name == "DATEFORMAT":
578
self.config.DATEFORMAT = self.getTypedValue(value, "string")
578
self.config.DATEFORMAT = getTypedValue(value, "string")
579
579
elif name == "LOCALE":
580
self.config.LOCALE = self.getTypedValue(value, "string")
580
self.config.LOCALE = getTypedValue(value, "string")
581
581
elif name == "XOAP_PARTNER_ID":
582
self.config.XOAP_PARTNER_ID = self.getTypedValue(value, "integer")
582
self.config.XOAP_PARTNER_ID = getTypedValue(value, "integer")
583
583
elif name == "XOAP_LICENCE_KEY":
584
self.config.XOAP_LICENCE_KEY = self.getTypedValue(value, "string")
584
self.config.XOAP_LICENCE_KEY = getTypedValue(value, "string")
585
585
elif name == "MAXIMUM_DAYS_FORECAST":
586
self.config.MAXIMUM_DAYS_FORECAST = self.getTypedValue(value, "integer")
586
self.config.MAXIMUM_DAYS_FORECAST = getTypedValue(value, "integer")
587
587
elif name == "IMAGESIZE":
588
self.config.IMAGESIZE = self.getTypedValue(value, "integer")
588
self.config.IMAGESIZE = getTypedValue(value, "integer")
589
589
elif name == "REFETCH":
590
self.config.REFETCH = self.getTypedValue(value, "boolean")
590
self.config.REFETCH = getTypedValue(value, "boolean")
592
592
self.logger.error("Unknown option in config file: " + name)
594
594
self.logger.info("Config data file %s not found, using defaults and setting up config file for next time" % configfilepath)
596
596
userconfigpath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/")
597
597
configsource = os.path.join(app_path, "config/"+module_name+".config")
599
599
if os.path.exists(userconfigpath) == False:
600
600
os.makedirs(userconfigpath)
602
602
shutil.copy(configsource, configfilepath)
604
604
except Exception, e:
605
605
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
607
def getTypedValue(self, value, expectedtype):
610
if len(value.strip(" ")) == 0:
613
elif value.lower() == "true":
614
if expectedtype == "boolean":
617
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
619
elif value.lower() == "false":
620
if expectedtype == "boolean":
623
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
625
elif self.isNumeric(value) == True:
626
if expectedtype == "integer":
629
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
634
except (TypeError, ValueError):
635
self.logger.error("Cannot convert '%s' to expected type of '%s'"%(value,expectedtype))
638
607
def checkAndLoad(self, location):
640
609
# define CACHE_FILEPATH based on cache folder and location code
641
610
cachefilepath = os.path.join(self.config.CACHE_FOLDERPATH, self.CACHE_FILENAME_TEMPLATE.replace("LOCATION", location))
643
612
# if the location was not loaded before (or attempted to load)
644
613
if not location in self.loaded_locations:
645
614
# add it to the list so it doesn't get loaded again (or attempted to load)
730
699
current_code = self.getText(current_condition_n, 'icon')
731
700
current_temp = self.getText(current_condition_n, 'tmp')
732
701
current_temp_feels = self.getText(current_condition_n, 'flik')
734
703
bar_n = self.getChild(current_condition_n, 'bar')
735
704
bar_read = self.getText(bar_n, 'r')
736
705
bar_desc = self.getText(bar_n, 'd')
738
707
wind_n = self.getChild(current_condition_n, 'wind')
739
708
wind_speed = self.getText(wind_n, 's')
740
709
wind_gusts = self.getText(wind_n, 'gust')
741
710
wind_direction_numeric = self.getText(wind_n, 'd')
742
711
wind_direction = self.getText(wind_n, 't')
744
713
humidity = self.getText(current_condition_n, 'hmid')
745
714
visibility = self.getText(current_condition_n, 'vis')
746
#if self.isNumeric(visibility):
715
#if isNumeric(visibility):
747
716
# visibility = visibility + visibility_unit
749
718
uv_n = self.getChild(current_condition_n, 'uv')
750
719
uv_index = self.getText(uv_n, 'i')
751
720
uv_text = self.getText(uv_n, 't')
753
722
dew_point = self.getText(current_condition_n, 'dewp')
755
724
moon_n = self.getChild(current_condition_n, 'moon')
756
725
moon_icon = self.getText(moon_n, 'icon')
757
726
moon_phase = self.getText(moon_n, 't')
759
728
weathermap = self.getImageSrcForWeatherMap(location)
761
730
current_forecast_data = ForecastDataset(last_update, day_of_week, current_temp_feels, current_temp, current_code, current_desc, precip, humidity, wind_direction, wind_direction_numeric, wind_speed, wind_gusts, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country, weathermap)
763
732
# collect forecast data
764
733
observatory = _(u"N/A")
765
734
bar_read = _(u"N/A")
797
766
wind_gusts = self.getText(wind_n, 'gust')
798
767
wind_direction_numeric = self.getText(wind_n, 'd')
799
768
wind_direction = self.getText(wind_n, 't')
801
770
precip = self.getText(daytime_n, 'ppcp')
802
771
humidity = self.getText(daytime_n, 'hmid')
804
773
day_forecast_data = ForecastDataset(last_update, day_of_week, low_temp, high_temp, condition_code, condition, precip, humidity, wind_direction, wind_direction_numeric, wind_speed, wind_gusts, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country, weathermap)
805
774
day_forecast_data_list.append(day_forecast_data)
807
776
# night forecast specific data
808
777
daytime_n = self.getChild(day, 'part', 1)
809
778
condition_code = self.getText(daytime_n, 'icon')
814
783
wind_gusts = self.getText(wind_n, 'gust')
815
784
wind_direction_numeric = self.getText(wind_n, 'd')
816
785
wind_direction = self.getText(wind_n, 't')
818
787
precip = self.getText(daytime_n, 'ppcp')
819
788
humidity = self.getText(daytime_n, 'hmid')
821
790
night_forecast_data = ForecastDataset(last_update, day_of_week, low_temp, high_temp, condition_code, condition, precip, humidity, wind_direction, wind_direction_numeric, wind_speed, wind_gusts, sunrise, sunset, moon_phase, moon_icon, bar_read, bar_desc, uv_index, uv_text, dew_point, observatory, visibility, city, country, weathermap)
822
791
night_forecast_data_list.append(night_forecast_data)
824
793
self.forecast_data[location] = ForecastLocation(current_forecast_data, day_forecast_data_list, night_forecast_data_list, datetime.today())
828
797
except Exception, e:
829
798
self.logger.error("Error reading weather data: " + e.__str__()+"\n"+traceback.format_exc())
831
def getTimestampOutput(self, timestamp, minuteshide):
800
def getTimestampOutput(self, timestamp, minuteshide):
833
802
# None = disabled
834
803
# -1 = hide days and use config.EXPIRY_MINUTES
835
804
# 0 = hide days and always show hours
839
808
today = datetime.today()
840
809
days = today.day - timestamp.day
841
810
if days or minuteshide == None:
842
811
output += timestamp.strftime(self.config.DATEFORMAT)
844
813
if minuteshide == -1:
845
814
minuteshide = self.config.EXPIRY_MINUTES
847
816
delta = today - timestamp
848
817
if days or minuteshide == None or minuteshide == 0 or delta.seconds > minuteshide * 60:
849
818
if (len(output) > 0):
851
820
output += timestamp.strftime(self.config.TIMEFORMAT)
1418
def getHoursMinutesStringFromSeconds(self,seconds):
1420
hours, time = divmod(time, 60*60)
1421
minutes, seconds = divmod(time, 60)
1422
output = str(hours).rjust(2,"0") + ":" + str(minutes).rjust(2,"0")
1425
1374
def getImageSrcForConditionCode(self, conditioncode):
1426
if self.isNumeric(conditioncode) == False:
1375
if isNumeric(conditioncode) == False:
1427
1376
conditioncode = "25" # N/A image
1429
1378
imagesrc = "file://%s/images/weathericons/%s.png"%(app_path, str(conditioncode).rjust(2,"0"))
1430
1379
return imagesrc
1432
1381
def getImageSrcForMoonCode(self, mooncode):
1433
1382
imagesrc = "file://%s/images/moonicons/%s.png"%(app_path, str(mooncode).rjust(2,"0"))
1434
1383
return imagesrc
1436
1385
def getImageSrcForBearing(self, bearingcode):
1437
1386
if int(bearingcode) > 0 and int(bearingcode) <= 4:
1438
fileext = "gif" # use animated gif for VAR output
1387
fileext = "gif" # use animated gif for VAR output
1440
1389
fileext = "png"
1441
1390
imagesrc = "file://%s/images/bearingicons/%s.%s"%(app_path, str(bearingcode).rjust(2,"0"),fileext)
1516
1438
# to enable testing in isolation
1517
1439
if __name__ == "__main__":
1519
1441
parser = OptionParser()
1520
parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
1442
parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
1521
1443
parser.add_option("--headertemplate", dest="headertemplate", type="string", metavar="FILE", help=u"Override the header template for the plugin, default or config based template ignored.")
1522
1444
parser.add_option("--template", dest="template", type="string", metavar="FILE", help=u"Override the template for the plugin, default or config based template ignored.")
1523
1445
parser.add_option("--verbose", dest="verbose", default=False, action="store_true", help=u"Outputs verbose info to the terminal")
1524
1446
parser.add_option("--version", dest="version", default=False, action="store_true", help=u"Displays the version of the script.")
1525
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
1447
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
1527
1449
(options, args) = parser.parse_args()
1529
1451
output = Output(options)
1530
1452
html = output.getOutput()