[libgweather/wip/forecasts: 1/6] Add Yahoo! Weather forecast provider.



commit 349f453c9ac8c8b464b631b6c53e1de30932bc77
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Sun Mar 18 19:04:08 2012 +0100

    Add Yahoo! Weather forecast provider.
    
    Adds a forecast provider that uses Yahoo! Weather RSS channels to
    retrieve informations. For the time being, this requires a new tag
    in the database, <yahoo-woeid>, containing a Where on Earth ID (the
    one for Milan is provided for testing purposes).
    The matchings from Yahoo condition codes to GWeatherConditions and
    GWeatherSky need to be checked.

 data/Locations.xml.in           |    1 +
 data/locations.dtd              |    5 +-
 libgweather/Makefile.am         |    2 +-
 libgweather/gweather-location.c |   14 ++-
 libgweather/weather-iwin.c      |   35 +++--
 libgweather/weather-priv.h      |    8 +-
 libgweather/weather-yahoo.c     |  299 +++++++++++++++++++++++++++++++++++++++
 libgweather/weather.c           |   18 ++-
 8 files changed, 357 insertions(+), 25 deletions(-)
---
diff --git a/data/Locations.xml.in b/data/Locations.xml.in
index 3182161..8cc7926 100644
--- a/data/Locations.xml.in
+++ b/data/Locations.xml.in
@@ -16058,6 +16058,7 @@
         <location>
           <name>Linate Airport</name>
           <code>LIML</code>
+	  <yahoo-woeid>718345</yahoo-woeid>
           <coordinates>45.433333 9.283333</coordinates>
         </location>
         <location>
diff --git a/data/locations.dtd b/data/locations.dtd
index 33afd95..f30376e 100644
--- a/data/locations.dtd
+++ b/data/locations.dtd
@@ -7,8 +7,8 @@
 <!ELEMENT region (%name;, country+) >
 <!ELEMENT country (%name;, iso-code, fips-code+, pref-lang?, timezones, tz-hint?, (location|state|city)*) >
 <!ELEMENT state (%name;, fips-code+, tz-hint?, (location|city)*) >
-<!ELEMENT city (%name;, coordinates?, zone?, radar?, location+) >
-<!ELEMENT location (%name;, code, tz-hint?, zone?, radar?, coordinates?) >
+<!ELEMENT city (%name;, coordinates?, location+) >
+<!ELEMENT location (%name;, code, tz-hint?, zone?, yahoo-woeid?, radar?, coordinates?) >
 
 <!ELEMENT timezones (timezone+) >
 <!ELEMENT timezone (_name?, name*, obsoletes*) >
@@ -31,3 +31,4 @@
 <!ELEMENT zone (#PCDATA) >
 <!ELEMENT radar (#PCDATA) >
 <!ELEMENT coordinates (#PCDATA) >
+<!ELEMENT yahoo-woeid (#PCDATA) >
diff --git a/libgweather/Makefile.am b/libgweather/Makefile.am
index a5e514b..f78891b 100644
--- a/libgweather/Makefile.am
+++ b/libgweather/Makefile.am
@@ -29,7 +29,7 @@ noinst_HEADERS = weather-priv.h gweather-win32.h
 libgweather_internal_3_la_SOURCES = \
 	weather.c weather-priv.h \
 	weather-metar.c weather-iwin.c weather-met.c \
-	weather-bom.c weather-wx.c \
+	weather-bom.c weather-yahoo.c weather-wx.c \
 	weather-sun.c weather-moon.c \
 	gweather-enum-types.c \
 	gweather-xml.c gweather-xml.h \
diff --git a/libgweather/gweather-location.c b/libgweather/gweather-location.c
index b1d12e1..8469c70 100644
--- a/libgweather/gweather-location.c
+++ b/libgweather/gweather-location.c
@@ -49,7 +49,7 @@ struct _GWeatherLocation {
     GWeatherLocation *parent, **children;
     GWeatherLocationLevel level;
     char *country_code, *tz_hint;
-    char *station_code, *forecast_zone, *radar;
+    char *station_code, *forecast_zone, *yahoo_id, *radar;
     double latitude, longitude;
     gboolean latlon_valid;
     GWeatherTimezone **zones;
@@ -197,6 +197,12 @@ location_new_from_xml (GWeatherParser *parser, GWeatherLocationLevel level,
 		goto error_out;
 	    loc->forecast_zone = g_strdup (value);
 	    xmlFree (value);
+	} else if (!strcmp (tagname, "yahoo-woeid") && !loc->yahoo_id) {
+	    value = gweather_parser_get_value (parser);
+	    if (!value)
+		goto error_out;
+	    loc->yahoo_id = g_strdup (value);
+	    xmlFree (value);
 	} else if (!strcmp (tagname, "radar") && !loc->radar) {
 	    value = gweather_parser_get_value (parser);
 	    if (!value)
@@ -730,7 +736,7 @@ gweather_location_get_city_name (GWeatherLocation *loc)
 WeatherLocation *
 _weather_location_from_gweather_location (GWeatherLocation *gloc, const gchar *name)
 {
-    const char *code = NULL, *zone = NULL, *radar = NULL, *tz_hint = NULL;
+    const char *code = NULL, *zone = NULL, *yahoo_id = NULL, *radar = NULL, *tz_hint = NULL;
     gboolean latlon_valid = FALSE;
     gdouble lat = DBL_MAX, lon = DBL_MAX;
     GWeatherLocation *l;
@@ -748,6 +754,8 @@ _weather_location_from_gweather_location (GWeatherLocation *gloc, const gchar *n
 	    code = l->station_code;
 	if (!zone && l->forecast_zone)
 	    zone = l->forecast_zone;
+	if (!yahoo_id && l->yahoo_id)
+	    yahoo_id = l->yahoo_id;
 	if (!radar && l->radar)
 	    radar = l->radar;
 	if (!tz_hint && l->tz_hint)
@@ -761,7 +769,7 @@ _weather_location_from_gweather_location (GWeatherLocation *gloc, const gchar *n
     }
 
     wloc = _weather_location_new (name ? name : gweather_location_get_name (gloc),
-				  code, zone, radar,
+				  code, zone, yahoo_id, radar,
 				  latlon_valid, lat, lon,
 				  gweather_location_get_country (gloc),
 				  tz_hint);
diff --git a/libgweather/weather-iwin.c b/libgweather/weather-iwin.c
index f2baefa..0a446e0 100644
--- a/libgweather/weather-iwin.c
+++ b/libgweather/weather-iwin.c
@@ -382,7 +382,7 @@ iwin_finish (SoupSession *session, SoupMessage *msg, gpointer data)
 }
 
 /* Get forecast into newly alloc'ed string */
-void
+gboolean
 iwin_start_open (GWeatherInfo *info)
 {
     GWeatherInfoPrivate *priv;
@@ -390,21 +390,28 @@ iwin_start_open (GWeatherInfo *info)
     WeatherLocation *loc;
     SoupMessage *msg;
 
-    g_return_if_fail (info != NULL);
+    g_return_val_if_fail (info != NULL, FALSE);
 
     priv = info->priv;
     loc = priv->location;
-    g_return_if_fail (loc != NULL);
+    g_return_val_if_fail (loc != NULL, FALSE);
 
-    if ((!loc->zone || loc->zone[0] == '-') && (priv->forecast_type != GWEATHER_FORECAST_LIST || !loc->latlon_valid))
-        return;
+    /* No zone (or -) means no weather information from national offices */
+    if ((!loc->zone || loc->zone[0] == '-'))
+        return FALSE;
 
-    if (priv->forecast) {
-        g_free (priv->forecast);
-        priv->forecast = NULL;
-    }
+    /* Zones starting with : are for the UK Met Office, @ is for Austrialian
+       Bureau of Metereology. GWEATHER_FORECAST_LIST only works for US, so bail
+       out early if the location is outside.
+    */
+    if (priv->forecast_type == GWEATHER_FORECAST_LIST &&
+	(loc->zone[0] == ':' || loc->zone[0] == '@'))
+	return FALSE;
 
-    free_forecast_list (info);    
+    /* We also need a pair of coordinates for GWEATHER_FORECAST_LIST */
+    if (priv->forecast_type == GWEATHER_FORECAST_LIST &&
+	!loc->latlon_valid)
+	return FALSE;
 
     if (priv->forecast_type == GWEATHER_FORECAST_LIST) {
         /* see the description here: http://www.weather.gov/forecasts/xml/ */
@@ -424,17 +431,17 @@ iwin_start_open (GWeatherInfo *info)
 	soup_session_queue_message (priv->session, msg, iwin_finish, info);
 
         priv->requests_pending++;
-        return;
+        return TRUE;
     }
 
     if (loc->zone[0] == ':') {
         /* Met Office Region Names */
         metoffice_start_open (info);
-        return;
+        return TRUE;
     } else if (loc->zone[0] == '@') {
         /* Australian BOM forecasts */
         bom_start_open (info);
-        return;
+        return TRUE;
     }
 
     /* The zone for Pittsburgh (for example) is given as PAZ021 in the locations
@@ -454,4 +461,6 @@ iwin_start_open (GWeatherInfo *info)
     soup_session_queue_message (priv->session, msg, iwin_finish, info);
 
     priv->requests_pending++;
+
+    return TRUE;
 }
diff --git a/libgweather/weather-priv.h b/libgweather/weather-priv.h
index 01782b1..c2320ab 100644
--- a/libgweather/weather-priv.h
+++ b/libgweather/weather-priv.h
@@ -47,13 +47,11 @@ const char *gweather_dpgettext (const char *context, const char *str) G_GNUC_FOR
 
 #define WEATHER_LOCATION_CODE_LEN 4
 
-/* Why we have this in code?
-   (at least, it's not exposed in API anymore)
-*/
 typedef struct {
     gchar *name;
     gchar *code;
     gchar *zone;
+    gchar *yahoo_id;
     gchar *radar;
     gboolean latlon_valid;
     gdouble  latitude;
@@ -67,6 +65,7 @@ WeatherLocation *       _weather_location_from_gweather_location (GWeatherLocati
 WeatherLocation *	_weather_location_new 	(const gchar *trans_name,
 						 const gchar *code,
 						 const gchar *zone,
+						 const gchar *yahoo_id,
 						 const gchar *radar,
 						 gboolean     latlon_valid,
 						 double       latitude,
@@ -175,10 +174,11 @@ struct _GWeatherInfoPrivate {
 #define PERIGEE_LONGITUDE(d)       (282.93768193 + (d)/36525.*0.32327364)
 
 void		metar_start_open	(GWeatherInfo *info);
-void		iwin_start_open		(GWeatherInfo *info);
+gboolean	iwin_start_open		(GWeatherInfo *info);
 void		metoffice_start_open	(GWeatherInfo *info);
 void		bom_start_open		(GWeatherInfo *info);
 void		wx_start_open		(GWeatherInfo *info);
+void            yahoo_start_open        (GWeatherInfo *info);
 
 gboolean	metar_parse		(gchar *metar,
 					 GWeatherInfo *info);
diff --git a/libgweather/weather-yahoo.c b/libgweather/weather-yahoo.c
new file mode 100644
index 0000000..b0500f7
--- /dev/null
+++ b/libgweather/weather-yahoo.c
@@ -0,0 +1,299 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* weather-yahoo.c - Yahoo! Weather service.
+ *
+ * Copyright 2012 Giovanni Campagna <scampa giovanni gmail com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE /* for strptime */
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
+#include "weather-priv.h"
+
+#define XC(t) ((const xmlChar *)(t))
+
+static GWeatherConditions condition_codes[] = {
+    { TRUE, GWEATHER_PHENOMENON_TORNADO, GWEATHER_QUALIFIER_NONE }, /* tornado */
+    { TRUE, GWEATHER_PHENOMENON_INVALID, GWEATHER_QUALIFIER_NONE }, /* FIXME: tropical storm */
+    { TRUE, GWEATHER_PHENOMENON_INVALID, GWEATHER_QUALIFIER_NONE }, /* FIXME: hurricane */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_THUNDERSTORM }, /* FIXME: severe thunderstorms */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_THUNDERSTORM }, /* thunderstorms */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_NONE }, /* FIXME: mixed rain and snow */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_NONE }, /* FIXME: mixed rain and sleet */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_NONE }, /* FIXME: mixed snow and sleet */
+    { TRUE, GWEATHER_PHENOMENON_DRIZZLE, GWEATHER_QUALIFIER_FREEZING }, /* freezing drizzle */
+    { TRUE, GWEATHER_PHENOMENON_DRIZZLE, GWEATHER_QUALIFIER_NONE }, /* drizzle */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_FREEZING }, /* freezing rain */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_SHOWERS }, /* showers */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_SHOWERS }, /* showers */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_BLOWING }, /* FIXME: snow flurries */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_LIGHT }, /* FIXME: light snow showers */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_BLOWING }, /* blowing snow */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_NONE }, /* snow */
+    { TRUE, GWEATHER_PHENOMENON_HAIL, GWEATHER_QUALIFIER_NONE }, /* hail */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_NONE }, /* FIXME: sleet */
+    { TRUE, GWEATHER_PHENOMENON_DUST, GWEATHER_QUALIFIER_NONE }, /* dust */
+    { TRUE, GWEATHER_PHENOMENON_FOG, GWEATHER_QUALIFIER_NONE }, /* foggy */
+    { TRUE, GWEATHER_PHENOMENON_HAZE, GWEATHER_QUALIFIER_NONE }, /* haze */
+    { TRUE, GWEATHER_PHENOMENON_SMOKE, GWEATHER_QUALIFIER_NONE }, /* smoky */
+    { TRUE, GWEATHER_PHENOMENON_INVALID, GWEATHER_QUALIFIER_NONE }, /* FIXME: blustery */
+    { TRUE, GWEATHER_PHENOMENON_INVALID, GWEATHER_QUALIFIER_NONE }, /* FIXME: windy */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* cold */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* cloudy */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* mostly cloudy (night) */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* mostly cloudy (day) */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* partly cloudy (night) */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* partly cloudy (day) */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* clear (night) */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* sunny */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* fair (night) */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* fair (day) */
+    { TRUE, GWEATHER_PHENOMENON_HAIL, GWEATHER_QUALIFIER_NONE }, /* FIXME: mixed_rain_and_hail */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* hot */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_THUNDERSTORM }, /* FIXME: isolated thunderstorms */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_THUNDERSTORM }, /* FIXME: scattered thunderstorms */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_THUNDERSTORM }, /* FIXME: scattered thunderstorms */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_SHOWERS }, /* FIXME: scattered showers */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_HEAVY }, /* heavy snow */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_SHOWERS }, /* FIXME: scattered snow showers */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_HEAVY }, /* heavy snow */
+    { FALSE, GWEATHER_PHENOMENON_NONE, GWEATHER_QUALIFIER_NONE }, /* partly cloudy */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_THUNDERSTORM }, /* FIXME: thundershowers */
+    { TRUE, GWEATHER_PHENOMENON_SNOW, GWEATHER_QUALIFIER_SHOWERS }, /* snow showers */
+    { TRUE, GWEATHER_PHENOMENON_RAIN, GWEATHER_QUALIFIER_THUNDERSTORM }, /* FIXME: isolated thundershowers */
+};
+
+/* FIXME: check sky values for codes that have a phenomenon too
+   (scattered is what weather-iwin.c does for rain and snow)
+*/
+static GWeatherSky sky_codes[] = {
+    GWEATHER_SKY_INVALID, /* tornado */
+    GWEATHER_SKY_SCATTERED, /* tropical storm */
+    GWEATHER_SKY_SCATTERED, /* hurricane */
+    GWEATHER_SKY_SCATTERED, /* severe thunderstorms */
+    GWEATHER_SKY_SCATTERED, /* thunderstorms */
+    GWEATHER_SKY_SCATTERED, /* mixed rain and snow */
+    GWEATHER_SKY_SCATTERED, /* mixed rain and sleet */
+    GWEATHER_SKY_SCATTERED, /* mixed snow and sleet */
+    GWEATHER_SKY_SCATTERED, /* freezing drizzle */
+    GWEATHER_SKY_SCATTERED, /* drizzle */
+    GWEATHER_SKY_SCATTERED, /* freezing rain */
+    GWEATHER_SKY_SCATTERED, /* showers */
+    GWEATHER_SKY_SCATTERED, /* showers */
+    GWEATHER_SKY_SCATTERED, /* snow flurries */
+    GWEATHER_SKY_SCATTERED, /* light snow showers */
+    GWEATHER_SKY_SCATTERED, /* blowing snow */
+    GWEATHER_SKY_SCATTERED, /* snow */
+    GWEATHER_SKY_INVALID, /* hail */
+    GWEATHER_SKY_INVALID, /* sleet */
+    GWEATHER_SKY_INVALID, /* dust */
+    GWEATHER_SKY_INVALID, /* foggy */
+    GWEATHER_SKY_INVALID, /* haze */
+    GWEATHER_SKY_INVALID, /* smoky */
+    GWEATHER_SKY_INVALID, /* blustery */
+    GWEATHER_SKY_INVALID, /* windy */
+    GWEATHER_SKY_CLEAR, /* cold */
+    GWEATHER_SKY_OVERCAST, /* cloudy */
+    GWEATHER_SKY_FEW, /* mostly cloudy (night) */
+    GWEATHER_SKY_FEW, /* mostly cloudy (day) */
+    GWEATHER_SKY_BROKEN, /* partly cloudy (night) */
+    GWEATHER_SKY_BROKEN, /* partly cloudy (day) */
+    GWEATHER_SKY_CLEAR, /* clear (night) */
+    GWEATHER_SKY_CLEAR, /* sunny */
+    GWEATHER_SKY_CLEAR, /* fair (night) */
+    GWEATHER_SKY_CLEAR, /* fair (day) */
+    GWEATHER_SKY_SCATTERED, /* mixed rain and hail */
+    GWEATHER_SKY_CLEAR, /* hot */
+    GWEATHER_SKY_SCATTERED, /* isolated thunderstorms */
+    GWEATHER_SKY_SCATTERED, /* scattered thunderstorms */
+    GWEATHER_SKY_SCATTERED, /* scattered thunderstorms */
+    GWEATHER_SKY_SCATTERED, /* scattered showers */
+    GWEATHER_SKY_SCATTERED, /* heavy snow */
+    GWEATHER_SKY_SCATTERED, /* scattered snow showers */
+    GWEATHER_SKY_SCATTERED, /* heavy snow */
+    GWEATHER_SKY_BROKEN, /* partly cloudy */
+    GWEATHER_SKY_SCATTERED, /* thundershowers */
+    GWEATHER_SKY_SCATTERED, /* snow showers */
+    GWEATHER_SKY_SCATTERED, /* isolated thundershowers */
+};
+
+G_STATIC_ASSERT (G_N_ELEMENTS(condition_codes) == G_N_ELEMENTS(sky_codes));
+
+static time_t
+date_to_time_t (const xmlChar *str)
+{
+    struct tm time = { 0 };
+
+    if (!strptime ((const char*) str, "%d %b %Y", &time)) {
+	g_warning ("Cannot parse date string \"%s\"", str);
+	return 0;
+    }
+
+    return mktime(&time);
+}
+
+static GWeatherInfo *
+make_info_from_node (GWeatherInfo *master_info,
+		     xmlNodePtr    node)
+{
+    GWeatherInfo *info;
+    GWeatherInfoPrivate *priv;
+    xmlChar *val;
+    int code;
+
+    g_return_val_if_fail (node->type == XML_ELEMENT_NODE, NULL);
+
+    info = _gweather_info_new_clone (master_info);
+    priv = info->priv;
+
+    val = xmlGetProp (node, XC("date"));
+    priv->update = date_to_time_t (val);
+    xmlFree (val);
+
+    val = xmlGetProp (node, XC("high"));
+    priv->temp_max = g_ascii_strtod ((const char*) val, NULL);
+    xmlFree (val);
+
+    val = xmlGetProp (node, XC("low"));
+    priv->temp_min = g_ascii_strtod ((const char*) val, NULL);
+    xmlFree (val);
+
+    priv->tempMinMaxValid = priv->tempMinMaxValid || (priv->temp_max > -999.0 && priv->temp_min > -999.0);
+    priv->valid = priv->tempMinMaxValid;
+
+    val = xmlGetProp (node, XC("text"));
+    priv->forecast = g_strdup ((const char*) val);
+    xmlFree (val);
+
+    val = xmlGetProp (node, XC("code"));
+    code = strtol((const char*) val, NULL, 0);
+    if (code >= 0 && code < G_N_ELEMENTS (condition_codes)) {
+	priv->cond = condition_codes[code];
+	priv->sky = sky_codes[code];
+    } else
+	priv->valid = FALSE;
+    xmlFree (val);
+
+    return info;
+}
+
+static void
+parse_forecast_xml (GWeatherInfo    *master_info,
+		    SoupMessageBody *body)
+{
+    GWeatherInfoPrivate *priv;
+    xmlDocPtr doc;
+    xmlXPathContextPtr xpath_ctx;
+    xmlXPathObjectPtr xpath_result;
+    int i;
+
+    priv = master_info->priv;
+
+    doc = xmlParseMemory (body->data, body->length);
+    if (!doc)
+	return;
+
+    xpath_ctx = xmlXPathNewContext (doc);
+    xmlXPathRegisterNs (xpath_ctx, XC("yweather"), XC("http://xml.weather.yahoo.com/ns/rss/1.0";));
+    xpath_result = xmlXPathEval (XC("/rss/channel/item/yweather:forecast"), xpath_ctx);
+
+    if (!xpath_result || xpath_result->type != XPATH_NODESET)
+	goto out;
+
+    for (i = 0; i < xpath_result->nodesetval->nodeNr; i++) {
+	xmlNodePtr node;
+	GWeatherInfo *info;
+
+	node = xpath_result->nodesetval->nodeTab[i];
+	info = make_info_from_node (master_info, node);
+
+	priv->forecast_list = g_slist_append (priv->forecast_list, info);
+    }
+
+    xmlXPathFreeObject (xpath_result);
+
+ out:
+    xmlXPathFreeContext (xpath_ctx);
+    xmlFreeDoc (doc);
+}
+
+static void
+yahoo_finish (SoupSession *session,
+	      SoupMessage *msg,
+	      gpointer     user_data)
+{
+    GWeatherInfo *info = GWEATHER_INFO (user_data);
+
+    if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+	/* forecast data is not really interesting anyway ;) */
+	g_warning ("Failed to get Yahoo! Weather forecast data: %d %s\n",
+		   msg->status_code, msg->reason_phrase);
+	request_done (info, FALSE);
+	return;
+    }
+
+    parse_forecast_xml (info, msg->response_body);
+
+    request_done (info, TRUE);
+}
+
+void
+yahoo_start_open (GWeatherInfo *info)
+{
+    GWeatherInfoPrivate *priv;
+    WeatherLocation *loc;
+    gchar *url;
+    SoupMessage *message;
+
+    priv = info->priv;
+    loc = priv->location;
+
+    if (!loc || !loc->yahoo_id)
+	return;
+
+    /* Yahoo! Weather only supports forecast list
+       (and really, the other types only make sense with national
+       weather offices that cannot return structured data)
+    */
+    if (!priv->forecast_type != GWEATHER_FORECAST_LIST)
+
+    /* u=f means that the values are in imperial system (which is what
+       weather.c expects). They're converted to user preferences before
+       displaying.
+    */
+    url = g_strdup_printf("http://weather.yahooapis.com/forecastrss?w=%s&u=f";, loc->yahoo_id);
+
+    message = soup_message_new ("GET", url);
+    soup_session_queue_message (priv->session, message, yahoo_finish, info);
+
+    priv->requests_pending++;
+
+    g_free (url);
+}
diff --git a/libgweather/weather.c b/libgweather/weather.c
index 7841280..e49ad1d 100644
--- a/libgweather/weather.c
+++ b/libgweather/weather.c
@@ -109,7 +109,8 @@ gweather_dpgettext (const char *context,
 
 WeatherLocation *
 _weather_location_new (const gchar *name, const gchar *code,
-		       const gchar *zone, const gchar *radar,
+		       const gchar *zone, const gchar *yahoo_id,
+		       const gchar *radar,
 		       gboolean     latlon_valid,
 		       double       latitude,
 		       double       longitude,
@@ -132,6 +133,9 @@ _weather_location_new (const gchar *name, const gchar *code,
     if (radar)
         location->radar = g_strdup (radar);
 
+    if (yahoo_id)
+	location->yahoo_id = g_strdup (yahoo_id);
+
     location->latlon_valid = latlon_valid;
     location->latitude = latitude;
     location->longitude = longitude;
@@ -160,6 +164,9 @@ _weather_location_clone (const WeatherLocation *location)
     if (location->radar)
 	clone->radar = g_strdup (location->radar);
 
+    if (location->yahoo_id)
+	clone->yahoo_id = g_strdup (location->yahoo_id);
+
     clone->latlon_valid = location->latlon_valid;
     clone->latitude = location->latitude;
     clone->longitude = location->longitude;
@@ -174,6 +181,7 @@ _weather_location_free (WeatherLocation *location)
         g_free (location->name);
         g_free (location->code);
         g_free (location->zone);
+	g_free (location->yahoo_id);
         g_free (location->radar);
         g_free (location->country_code);
         g_free (location->tz_hint);
@@ -514,11 +522,17 @@ gweather_info_update (GWeatherInfo *info)
     }
 
     metar_start_open (info);
-    iwin_start_open (info);
 
     if (priv->radar) {
         wx_start_open (info);
     }
+
+    /* Try national forecast services first */
+    if (iwin_start_open (info))
+	return;
+
+    /* Try Yahoo! Weather next */
+    yahoo_start_open (info);
 }
 
 void



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]