diff options
author | Perberos <[email protected]> | 2011-12-01 21:42:39 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-12-01 21:42:39 -0300 |
commit | fe8aea1c3b5348347633da18a02b0bffd3b266a1 (patch) | |
tree | 9881bf77df7572844707cc7c50bd8ca6b5a97076 /libmateweather/weather-iwin.c | |
download | libmateweather-fe8aea1c3b5348347633da18a02b0bffd3b266a1.tar.bz2 libmateweather-fe8aea1c3b5348347633da18a02b0bffd3b266a1.tar.xz |
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'libmateweather/weather-iwin.c')
-rw-r--r-- | libmateweather/weather-iwin.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/libmateweather/weather-iwin.c b/libmateweather/weather-iwin.c new file mode 100644 index 0000000..e098354 --- /dev/null +++ b/libmateweather/weather-iwin.c @@ -0,0 +1,475 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* weather-iwin.c - US National Weather Service IWIN forecast source + * + * 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 + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include <libxml/parser.h> + +#define MATEWEATHER_I_KNOW_THIS_IS_UNSTABLE +#include "weather.h" +#include "weather-priv.h" + +/** + * Humans don't deal well with .MONDAY...SUNNY AND BLAH BLAH.TUESDAY...THEN THIS AND THAT.WEDNESDAY...RAINY BLAH BLAH. + * This function makes it easier to read. + */ +static gchar * +formatWeatherMsg (gchar *forecast) +{ + gchar *ptr = forecast; + gchar *startLine = NULL; + + while (0 != *ptr) { + if (ptr[0] == '\n' && ptr[1] == '.') { + /* This removes the preamble by shifting the relevant data + * down to the start of the buffer. */ + if (NULL == startLine) { + memmove (forecast, ptr, strlen (ptr) + 1); + ptr = forecast; + ptr[0] = ' '; + } + ptr[1] = '\n'; + ptr += 2; + startLine = ptr; + } else if (ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '.' && NULL != startLine) { + memmove (startLine + 2, startLine, (ptr - startLine) * sizeof (gchar)); + startLine[0] = ' '; + startLine[1] = '\n'; + ptr[2] = '\n'; + + ptr += 3; + + } else if (ptr[0] == '$' && ptr[1] == '$') { + ptr[0] = ptr[1] = ' '; + + } else { + ptr++; + } + } + + return forecast; +} + +static gboolean +hasAttr (xmlNode *node, const char *attr_name, const char *attr_value) +{ + xmlChar *attr; + gboolean res = FALSE; + + if (!node) + return res; + + attr = xmlGetProp (node, (const xmlChar *) attr_name); + + if (!attr) + return res; + + res = g_str_equal ((const char *)attr, attr_value); + + xmlFree (attr); + + return res; +} + +static GSList * +parseForecastXml (const char *buff, WeatherInfo *master_info) +{ + GSList *res = NULL; + xmlDocPtr doc; + xmlNode *root, *node; + + g_return_val_if_fail (master_info != NULL, NULL); + + if (!buff || !*buff) + return NULL; + + #define XC (const xmlChar *) + #define isElem(_node,_name) g_str_equal ((const char *)_node->name, _name) + + doc = xmlParseMemory (buff, strlen (buff)); + if (!doc) + return NULL; + + /* Description at http://www.weather.gov/mdl/XML/Design/MDL_XML_Design.pdf */ + root = xmlDocGetRootElement (doc); + for (node = root->xmlChildrenNode; node; node = node->next) { + if (node->name == NULL || node->type != XML_ELEMENT_NODE) + continue; + + if (isElem (node, "data")) { + xmlNode *n; + char *time_layout = NULL; + time_t update_times[7] = {0}; + + for (n = node->children; n; n = n->next) { + if (!n->name) + continue; + + if (isElem (n, "time-layout")) { + if (!time_layout && hasAttr (n, "summarization", "24hourly")) { + xmlNode *c; + int count = 0; + + for (c = n->children; c && (count < 7 || !time_layout); c = c->next) { + if (c->name && !time_layout && isElem (c, "layout-key")) { + xmlChar *val = xmlNodeGetContent (c); + + if (val) { + time_layout = g_strdup ((const char *)val); + xmlFree (val); + } + } else if (c->name && isElem (c, "start-valid-time")) { + xmlChar *val = xmlNodeGetContent (c); + + if (val) { + GTimeVal tv; + + if (g_time_val_from_iso8601 ((const char *)val, &tv)) { + update_times[count] = tv.tv_sec; + } else { + update_times[count] = 0; + } + + count++; + + xmlFree (val); + } + } + } + + if (count != 7) { + /* There can be more than one time-layout element, the other + with only few children, which is not the one to use. */ + g_free (time_layout); + time_layout = NULL; + } + } + } else if (isElem (n, "parameters")) { + xmlNode *p; + + /* time-layout should be always before parameters */ + if (!time_layout) + break; + + if (!res) { + int i; + + for (i = 0; i < 7; i++) { + WeatherInfo *nfo = weather_info_clone (master_info); + + if (nfo) { + nfo->valid = FALSE; + nfo->forecast_type = FORECAST_ZONE; + nfo->update = update_times [i]; + nfo->sky = -1; + nfo->temperature_unit = TEMP_UNIT_FAHRENHEIT; + nfo->temp = -1000.0; + nfo->temp_min = -1000.0; + nfo->temp_max = -1000.0; + nfo->tempMinMaxValid = FALSE; + nfo->cond.significant = FALSE; + nfo->cond.phenomenon = PHENOMENON_NONE; + nfo->cond.qualifier = QUALIFIER_NONE; + nfo->dew = -1000.0; + nfo->wind = -1; + nfo->windspeed = -1; + nfo->pressure = -1.0; + nfo->visibility = -1.0; + nfo->sunriseValid = FALSE; + nfo->sunsetValid = FALSE; + nfo->sunrise = 0; + nfo->sunset = 0; + g_free (nfo->forecast); + nfo->forecast = NULL; + nfo->session = NULL; + nfo->requests_pending = 0; + nfo->finish_cb = NULL; + nfo->cb_data = NULL; + res = g_slist_append (res, nfo); + } + } + } + + for (p = n->children; p; p = p->next) { + if (p->name && isElem (p, "temperature") && hasAttr (p, "time-layout", time_layout)) { + xmlNode *c; + GSList *at = res; + gboolean is_max = hasAttr (p, "type", "maximum"); + + if (!is_max && !hasAttr (p, "type", "minimum")) + break; + + for (c = p->children; c && at; c = c->next) { + if (isElem (c, "value")) { + WeatherInfo *nfo = (WeatherInfo *)at->data; + xmlChar *val = xmlNodeGetContent (c); + + /* can pass some values as <value xsi:nil="true"/> */ + if (!val || !*val) { + if (is_max) + nfo->temp_max = nfo->temp_min; + else + nfo->temp_min = nfo->temp_max; + } else { + if (is_max) + nfo->temp_max = atof ((const char *)val); + else + nfo->temp_min = atof ((const char *)val); + } + + if (val) + xmlFree (val); + + nfo->tempMinMaxValid = nfo->tempMinMaxValid || (nfo->temp_max > -999.0 && nfo->temp_min > -999.0); + nfo->valid = nfo->tempMinMaxValid; + + at = at->next; + } + } + } else if (p->name && isElem (p, "weather") && hasAttr (p, "time-layout", time_layout)) { + xmlNode *c; + GSList *at = res; + + for (c = p->children; c && at; c = c->next) { + if (c->name && isElem (c, "weather-conditions")) { + WeatherInfo *nfo = at->data; + xmlChar *val = xmlGetProp (c, XC "weather-summary"); + + if (val && nfo) { + /* Checking from top to bottom, if 'value' contains 'name', then that win, + thus put longer (more precise) values to the top. */ + int i; + struct _ph_list { + const char *name; + WeatherConditionPhenomenon ph; + } ph_list[] = { + { "Ice Crystals", PHENOMENON_ICE_CRYSTALS } , + { "Volcanic Ash", PHENOMENON_VOLCANIC_ASH } , + { "Blowing Sand", PHENOMENON_SANDSTORM } , + { "Blowing Dust", PHENOMENON_DUSTSTORM } , + { "Blowing Snow", PHENOMENON_FUNNEL_CLOUD } , + { "Drizzle", PHENOMENON_DRIZZLE } , + { "Rain", PHENOMENON_RAIN } , + { "Snow", PHENOMENON_SNOW } , + { "Fog", PHENOMENON_FOG } , + { "Smoke", PHENOMENON_SMOKE } , + { "Sand", PHENOMENON_SAND } , + { "Haze", PHENOMENON_HAZE } , + { "Dust", PHENOMENON_DUST } /*, + { "", PHENOMENON_SNOW_GRAINS } , + { "", PHENOMENON_ICE_PELLETS } , + { "", PHENOMENON_HAIL } , + { "", PHENOMENON_SMALL_HAIL } , + { "", PHENOMENON_UNKNOWN_PRECIPITATION } , + { "", PHENOMENON_MIST } , + { "", PHENOMENON_SPRAY } , + { "", PHENOMENON_SQUALL } , + { "", PHENOMENON_TORNADO } , + { "", PHENOMENON_DUST_WHIRLS } */ + }; + struct _sky_list { + const char *name; + WeatherSky sky; + } sky_list[] = { + { "Mostly Sunny", SKY_BROKEN } , + { "Mostly Clear", SKY_BROKEN } , + { "Partly Cloudy", SKY_SCATTERED } , + { "Mostly Cloudy", SKY_FEW } , + { "Sunny", SKY_CLEAR } , + { "Clear", SKY_CLEAR } , + { "Cloudy", SKY_OVERCAST } , + { "Clouds", SKY_SCATTERED } , + { "Rain", SKY_SCATTERED } , + { "Snow", SKY_SCATTERED } + }; + + nfo->valid = TRUE; + g_free (nfo->forecast); + nfo->forecast = g_strdup ((const char *)val); + + for (i = 0; i < G_N_ELEMENTS (ph_list); i++) { + if (strstr ((const char *)val, ph_list [i].name)) { + nfo->cond.phenomenon = ph_list [i].ph; + break; + } + } + + for (i = 0; i < G_N_ELEMENTS (sky_list); i++) { + if (strstr ((const char *)val, sky_list [i].name)) { + nfo->sky = sky_list [i].sky; + break; + } + } + } + + if (val) + xmlFree (val); + + at = at->next; + } + } + } + } + + if (res) { + gboolean have_any = FALSE; + GSList *r; + + /* Remove invalid forecast data from the list. + They should be all valid or all invalid. */ + for (r = res; r; r = r->next) { + WeatherInfo *nfo = r->data; + + if (!nfo || !nfo->valid) { + if (r->data) + weather_info_free (r->data); + + r->data = NULL; + } else { + have_any = TRUE; + + if (nfo->tempMinMaxValid) + nfo->temp = (nfo->temp_min + nfo->temp_max) / 2.0; + } + } + + if (!have_any) { + /* data members are freed already */ + g_slist_free (res); + res = NULL; + } + } + + break; + } + } + + g_free (time_layout); + + /* stop seeking XML */ + break; + } + } + xmlFreeDoc (doc); + + #undef XC + #undef isElem + + return res; +} + +static void +iwin_finish (SoupSession *session, SoupMessage *msg, gpointer data) +{ + WeatherInfo *info = (WeatherInfo *)data; + + g_return_if_fail (info != NULL); + + if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { + /* forecast data is not really interesting anyway ;) */ + g_warning ("Failed to get IWIN forecast data: %d %s\n", + msg->status_code, msg->reason_phrase); + request_done (info, FALSE); + return; + } + + if (info->forecast_type == FORECAST_LIST) + info->forecast_list = parseForecastXml (msg->response_body->data, info); + else + info->forecast = formatWeatherMsg (g_strdup (msg->response_body->data)); + + request_done (info, TRUE); +} + +/* Get forecast into newly alloc'ed string */ +void +iwin_start_open (WeatherInfo *info) +{ + gchar *url, *state, *zone; + WeatherLocation *loc; + SoupMessage *msg; + + g_return_if_fail (info != NULL); + loc = info->location; + g_return_if_fail (loc != NULL); + + if (loc->zone[0] == '-' && (info->forecast_type != FORECAST_LIST || !loc->latlon_valid)) + return; + + if (info->forecast) { + g_free (info->forecast); + info->forecast = NULL; + } + + free_forecast_list (info); + + if (info->forecast_type == FORECAST_LIST) { + /* see the description here: http://www.weather.gov/forecasts/xml/ */ + if (loc->latlon_valid) { + struct tm tm; + time_t now = time (NULL); + + localtime_r (&now, &tm); + + url = g_strdup_printf ("http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php?&lat=%.02f&lon=%.02f&format=24+hourly&startDate=%04d-%02d-%02d&numDays=7", + RADIANS_TO_DEGREES (loc->latitude), RADIANS_TO_DEGREES (loc->longitude), 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday); + + msg = soup_message_new ("GET", url); + g_free (url); + soup_session_queue_message (info->session, msg, iwin_finish, info); + + info->requests_pending++; + } + + return; + } + + if (loc->zone[0] == ':') { + /* Met Office Region Names */ + metoffice_start_open (info); + return; + } else if (loc->zone[0] == '@') { + /* Australian BOM forecasts */ + bom_start_open (info); + return; + } + + /* The zone for Pittsburgh (for example) is given as PAZ021 in the locations + ** file (the PA stands for the state pennsylvania). The url used wants the state + ** as pa, and the zone as lower case paz021. + */ + zone = g_ascii_strdown (loc->zone, -1); + state = g_strndup (zone, 2); + + url = g_strdup_printf ("http://weather.noaa.gov/pub/data/forecasts/zone/%s/%s.txt", state, zone); + + g_free (zone); + g_free (state); + + msg = soup_message_new ("GET", url); + g_free (url); + soup_session_queue_message (info->session, msg, iwin_finish, info); + + info->requests_pending++; +} |