1 /* Evolution calendar - weather backend source class for parsing
2 * CCF (coded cities forecast) formatted NWS reports
4 * Copyright (C) 2005 Novell, Inc (www.novell.com)
6 * Authors: David Trowbridge <trowbrds@cs.colorado.edu>
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU Lesser General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 #include <libxml/parser.h>
30 #include <libxml/tree.h>
32 #include <glib/gi18n-lib.h>
34 #include <gconf/gconf-client.h>
36 #include "libedataserver/e-xml-utils.h"
38 #include "e-weather-source-ccf.h"
40 #define DATA_SIZE 5000
44 #include "libedataserver/e-data-server-util.h"
46 /* The localtime_r() in <pthread.h> doesn't guard against localtime()
51 /* The localtime() in Microsoft's C library is MT-safe */
52 #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
54 /* strtok() is also MT-safe (but not stateless, still uses only one
55 * buffer pointer per thread, but for the use of strtok_r() here
58 #define strtok_r(s,sep,lasts) (*(lasts)=strtok((s),(sep)))
62 parse_for_url (char *code, char *name, xmlNode *parent)
65 if (parent->type == XML_ELEMENT_NODE) {
66 if (strcmp ((char*)parent->name, "location") == 0) {
67 child = parent->children;
68 g_assert (child->type == XML_TEXT_NODE);
69 if (strcmp ((char*)child->content, name) == 0) {
72 for (attr = parent->properties; attr; attr = attr->next) {
73 if (strcmp ((char*)attr->name, "code") == 0) {
74 if (strcmp ((char*)attr->children->content, code) != 0)
77 if (strcmp ((char*)attr->name, "url") == 0)
78 url = (char*)attr->children->content;
80 return g_strdup (url);
84 for (child = parent->children; child; child = child->next) {
85 gchar *url = parse_for_url (code, name, child);
95 find_station_url (gchar *station, EWeatherSourceCCF *source)
103 sstation = g_strsplit (station, "/", 2);
106 filename = g_strdup (WEATHER_DATADIR "/Locations.xml");
108 filename = e_util_replace_prefix (E_DATA_SERVER_PREFIX,
109 e_util_get_prefix (),
110 WEATHER_DATADIR "/Locations.xml");
113 doc = e_xml_parse_file (filename);
115 g_assert (doc != NULL);
117 root = xmlDocGetRootElement (doc);
119 url = parse_for_url (sstation[0], sstation[1], root);
121 source->url = g_strdup (url);
122 source->substation = g_strdup (sstation[0]);
124 g_strfreev (sstation);
128 e_weather_source_ccf_new (const char *uri)
130 /* Our URI is formatted as weather://ccf/AAA[/BBB] - AAA is the 3-letter station
131 * code for identifying the providing station (subdirectory within the crh data
132 * repository). BBB is an optional additional station ID for the station within
133 * the CCF file. If not present, BBB is assumed to be the same station as AAA.
135 EWeatherSourceCCF *source = E_WEATHER_SOURCE_CCF (g_object_new (e_weather_source_ccf_get_type (), NULL));
137 find_station_url (strchr (uri, '/') + 1, source);
138 return E_WEATHER_SOURCE (source);
142 tokenize (char *buffer)
148 token = strtok_r (buffer, " \n", &tokbuf);
149 ret = g_slist_append (NULL, g_strdup (token));
150 while ((token = strtok_r (NULL, " \n/", &tokbuf)))
151 ret = g_slist_append (ret, g_strdup (token));
156 date2tm (char *date, struct tm *times)
159 time_t curtime = time(NULL);
162 localtime_r (&curtime, times);
164 tmp[0] = date[0]; tmp[1] = date[1];
165 times->tm_mday = atoi(tmp);
166 tmp[0] = date[2]; tmp[1] = date[3];
167 times->tm_hour = atoi(tmp);
168 tmp[0] = date[4]; tmp[1] = date[5];
169 times->tm_min = atoi(tmp);
172 static WeatherConditions
173 decodeConditions (char code)
176 case 'A': return WEATHER_FAIR;
177 case 'B': return WEATHER_PARTLY_CLOUDY;
178 case 'C': return WEATHER_CLOUDY;
179 case 'D': return WEATHER_DUST;
180 case 'E': return WEATHER_MOSTLY_CLOUDY;
181 case 'F': return WEATHER_FOGGY;
182 case 'G': return WEATHER_VERY_HOT_OR_HOT_HUMID;
183 case 'H': return WEATHER_HAZE;
184 case 'I': return WEATHER_VERY_COLD_WIND_CHILL;
185 case 'J': return WEATHER_SNOW_SHOWERS;
186 case 'K': return WEATHER_SMOKE;
187 case 'L': return WEATHER_DRIZZLE;
188 case 'M': return WEATHER_SNOW_SHOWERS;
189 case 'N': return WEATHER_WINDY;
190 case 'O': return WEATHER_RAIN_OR_SNOW_MIXED;
191 case 'P': return WEATHER_BLIZZARD;
192 case 'Q': return WEATHER_BLOWING_SNOW;
193 case 'R': return WEATHER_RAIN;
194 case 'S': return WEATHER_SNOW;
195 case 'T': return WEATHER_THUNDERSTORMS;
196 case 'U': return WEATHER_SUNNY;
197 case 'V': return WEATHER_CLEAR;
198 case 'W': return WEATHER_RAIN_SHOWERS;
199 case 'X': return WEATHER_SLEET;
200 case 'Y': return WEATHER_FREEZING_RAIN;
201 case 'Z': return WEATHER_FREEZING_DRIZZLE;
202 /* hmm, this should never happen. */
203 default: return WEATHER_SUNNY;
208 decodePOP (char data)
220 ret = -1; /* missing data */
223 ret = (data - '0') * 10;
229 decodeSnowfall (char *data, float *low, float *high)
234 num[0] = data[0]; num[1] = data[1];
235 *low = atof (num) * 2.54f;
236 num[0] = data[2]; num[1] = data[3];
237 *high = atof (num) * 2.54f;
243 int fahrenheit = atoi(data);
244 if (fahrenheit >= 900)
245 fahrenheit = (fahrenheit - 900) * -1;
246 return ((float)(fahrenheit-32)) * 5.0f / 9.0f;
250 e_weather_source_ccf_do_parse (EWeatherSourceCCF *source, char *buffer)
252 /* CCF gives us either 2 or 7 days of forecast data. IFPS WFO's
253 * will produce 7 day forecasts, whereas pre-IFPS WFO's are only
254 * mandated 2 (but may do 7). The morning forecast will give us either 2
255 * or 7 days worth of data. The evening forecast will give us the evening's
256 * low temperature plus 2 or 7 days forecast.
258 * The CCF format is described in NWS directive 10-503, but it's usually
259 * easier to look at a summary put up by one of the stations:
260 * http://www.crh.noaa.gov/lmk/product_guide/products/forecast/ccf.htm
262 WeatherForecast *forecasts = g_new0 (WeatherForecast, 7);
263 GSList *tokens = tokenize (buffer);
265 GSList *current = tokens;
272 date = g_slist_nth (tokens, 3);
273 date2tm (date->data, &tms);
275 /* fast-forward to the particular station we're interested in */
276 current = g_slist_nth (tokens, 5);
277 while (strcmp(current->data, source->substation))
278 current = g_slist_next (current);
279 current = g_slist_next (current);
280 /* pick up the first two conditions reports */
281 forecasts[0].conditions = decodeConditions (((char*)(current->data))[0]);
282 forecasts[1].conditions = decodeConditions (((char*)(current->data))[1]);
284 current = g_slist_next (current);
285 if (tms.tm_hour < 12) {
286 for (i = 0; i < 2; i++) {
287 forecasts[i].high = ftoc (current->data);
288 current = g_slist_next (current);
289 forecasts[i].low = ftoc (current->data);
290 current = g_slist_next (current);
292 forecasts[2].high = ftoc (current->data);
293 current = g_slist_next (current);
294 forecasts[0].pop = decodePOP (((char*)(current->data))[2]);
295 forecasts[1].pop = decodePOP (((char*)(current->data))[4]);
297 for (i = 0; i < 2; i++) {
298 current = g_slist_next (current);
299 forecasts[i].high = ftoc (current->data);
300 current = g_slist_next (current);
301 forecasts[i].low = ftoc (current->data);
303 current = g_slist_next (current);
304 forecasts[0].pop = decodePOP (((char*)(current->data))[1]);
305 forecasts[1].pop = decodePOP (((char*)(current->data))[3]);
308 current = g_slist_next (current);
309 if (strlen (current->data) == 4) {
310 /* we've got the optional snowfall field */
311 if (tms.tm_hour < 12) {
312 decodeSnowfall (current->data, &forecasts[0].low, &forecasts[0].high);
313 current = g_slist_next (g_slist_next (current));
314 decodeSnowfall (current->data, &forecasts[1].low, &forecasts[1].high);
316 current = g_slist_next (current);
317 decodeSnowfall (current->data, &forecasts[0].low, &forecasts[0].high);
319 current = g_slist_next (current);
323 base = mktime (&tms);
324 if (tms.tm_hour >= 12)
326 for (i = 0; i < 7; i++)
327 forecasts[i].date = base + 86400*i;
329 if (current == NULL || strlen (current->data) == 3) {
330 /* We've got a pre-IFPS station. Realloc and return */
331 WeatherForecast *f = g_new0(WeatherForecast, 2);
332 memcpy (f, forecasts, sizeof (WeatherForecast) * 2);
333 fc = g_list_append (fc, &f[0]);
334 fc = g_list_append (fc, &f[1]);
335 source->done (fc, source->finished_data);
338 /* Grab the conditions for the next 5 days */
339 forecasts[2].conditions = decodeConditions (((char*)(current->data))[0]);
340 forecasts[3].conditions = decodeConditions (((char*)(current->data))[1]);
341 forecasts[4].conditions = decodeConditions (((char*)(current->data))[2]);
342 forecasts[5].conditions = decodeConditions (((char*)(current->data))[3]);
343 forecasts[6].conditions = decodeConditions (((char*)(current->data))[4]);
345 /* Temperature forecasts */
346 current = g_slist_next (current);
347 if (tms.tm_hour < 12) {
348 forecasts[2].low = ftoc (current->data);
349 for (i = 3; i < 6; i++) {
350 current = g_slist_next (current);
351 forecasts[i].high = ftoc (current->data);
352 current = g_slist_next (current);
353 forecasts[i].low = ftoc (current->data);
355 current = g_slist_next (current);
356 forecasts[6].high = ftoc (current->data);
357 forecasts[6].low = forecasts[6].high;
358 current = g_slist_next (current);
359 forecasts[2].pop = decodePOP (((char*)(current->data))[1]);
360 forecasts[3].pop = decodePOP (((char*)(current->data))[3]);
361 forecasts[4].pop = decodePOP (((char*)(current->data))[5]);
362 forecasts[5].pop = decodePOP (((char*)(current->data))[7]);
363 forecasts[6].pop = decodePOP (((char*)(current->data))[9]);
366 for (i = 2; i < 6; i++) {
367 forecasts[i].high = ftoc (current->data);
368 current = g_slist_next (current);
369 forecasts[i].low = ftoc (current->data);
370 current = g_slist_next (current);
373 /* hack for people who put out bad data, like Pueblo, CO. Yes, PUB, that means you */
374 if (strlen (current->data) == 3)
375 current = g_slist_next (current);
376 forecasts[1].pop = decodePOP (((char*)(current->data))[0]);
377 forecasts[2].pop = decodePOP (((char*)(current->data))[2]);
378 forecasts[3].pop = decodePOP (((char*)(current->data))[4]);
379 forecasts[4].pop = decodePOP (((char*)(current->data))[6]);
380 forecasts[5].pop = decodePOP (((char*)(current->data))[8]);
383 for (i = 0; i < n; i++) {
384 fc = g_list_append (fc, &forecasts[i]);
386 source->done (fc, source->finished_data);
393 retrieval_done (SoupMessage *message, EWeatherSourceCCF *source)
398 /* Handle redirection ourselves */
399 if (SOUP_STATUS_IS_REDIRECTION (message->status_code)) {
400 newuri = soup_message_get_header (message->response_headers, "Location");
403 SoupMessage *soup_message;
404 soup_message = soup_message_new (SOUP_METHOD_GET, newuri);
405 soup_message_set_flags (soup_message, SOUP_MESSAGE_NO_REDIRECT);
406 soup_session_queue_message (source->soup_session, soup_message, (SoupMessageCallbackFn) retrieval_done, source);
409 source->done (NULL, source->finished_data);
415 /* check status code */
416 if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
417 source->done (NULL, source->finished_data);
421 str = g_malloc0 (message->response.length + 1);
422 strncpy (str, message->response.body, message->response.length);
423 e_weather_source_ccf_do_parse (source, str);
428 e_weather_source_ccf_parse (EWeatherSource *source, EWeatherSourceFinished done, gpointer data)
430 EWeatherSourceCCF *ccfsource = (EWeatherSourceCCF*) source;
431 SoupMessage *soup_message;
433 ccfsource->finished_data = data;
435 ccfsource->done = done;
437 if (!ccfsource->soup_session) {
438 GConfClient *conf_client;
439 ccfsource->soup_session = soup_session_async_new ();
441 /* set the HTTP proxy, if configuration is set to do so */
442 conf_client = gconf_client_get_default ();
443 if (gconf_client_get_bool (conf_client, "/system/http_proxy/use_http_proxy", NULL)) {
444 char *server, *proxy_uri;
447 server = gconf_client_get_string (conf_client, "/system/http_proxy/host", NULL);
448 port = gconf_client_get_int (conf_client, "/system/http_proxy/port", NULL);
450 if (server && server[0]) {
452 if (gconf_client_get_bool (conf_client, "/system/http_proxy/use_authentication", NULL)) {
453 char *user, *password;
455 user = gconf_client_get_string (conf_client,
456 "/system/http_proxy/authentication_user",
458 password = gconf_client_get_string (conf_client,
459 "/system/http_proxy/authentication_password",
462 proxy_uri = g_strdup_printf("http://%s:%s@%s:%d", user, password, server, port);
467 proxy_uri = g_strdup_printf ("http://%s:%d", server, port);
469 suri = soup_uri_new (proxy_uri);
470 g_object_set (G_OBJECT (ccfsource->soup_session), SOUP_SESSION_PROXY_URI, suri, NULL);
472 soup_uri_free (suri);
477 g_object_unref (conf_client);
480 soup_message = soup_message_new (SOUP_METHOD_GET, ccfsource->url);
481 soup_message_set_flags (soup_message, SOUP_MESSAGE_NO_REDIRECT);
482 soup_session_queue_message (ccfsource->soup_session, soup_message, (SoupMessageCallbackFn) retrieval_done, source);
486 e_weather_source_ccf_class_init (EWeatherSourceCCFClass *class)
488 EWeatherSourceClass *source_class;
490 source_class = (EWeatherSourceClass *) class;
492 source_class->parse = e_weather_source_ccf_parse;
496 e_weather_source_ccf_init (EWeatherSourceCCF *source)
499 source->substation = NULL;
500 source->soup_session = NULL;
504 e_weather_source_ccf_get_type (void)
506 static GType e_weather_source_ccf_type = 0;
508 if (!e_weather_source_ccf_type) {
509 static GTypeInfo info = {
510 sizeof (EWeatherSourceCCFClass),
511 (GBaseInitFunc) NULL,
512 (GBaseFinalizeFunc) NULL,
513 (GClassInitFunc) e_weather_source_ccf_class_init,
515 sizeof (EWeatherSourceCCF),
517 (GInstanceInitFunc) e_weather_source_ccf_init
519 e_weather_source_ccf_type = g_type_register_static (E_TYPE_WEATHER_SOURCE, "EWeatherSourceCCF", &info, 0);
522 return e_weather_source_ccf_type;