Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / calendar / backends / weather / e-weather-source-ccf.c
1 /* Evolution calendar - weather backend source class for parsing
2  *      CCF (coded cities forecast) formatted NWS reports
3  *
4  * Copyright (C) 2005 Novell, Inc (www.novell.com)
5  *
6  * Authors: David Trowbridge <trowbrds@cs.colorado.edu>
7  *
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.
11  *
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.
16  *
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.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <string.h>
27 #include <stdlib.h>
28
29 #include <libxml/parser.h>
30 #include <libxml/tree.h>
31
32 #include <glib/gi18n-lib.h>
33
34 #include <gconf/gconf-client.h>
35
36 #include "libedataserver/e-xml-utils.h"
37
38 #include "e-weather-source-ccf.h"
39
40 #define DATA_SIZE 5000
41
42 #ifdef G_OS_WIN32
43
44 #include "libedataserver/e-data-server-util.h"
45
46 /* The localtime_r() in <pthread.h> doesn't guard against localtime()
47  * returning NULL
48  */
49 #undef localtime_r
50
51 /* The localtime() in Microsoft's C library is MT-safe */
52 #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
53
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
56  * that's enough).
57  */
58 #define strtok_r(s,sep,lasts) (*(lasts)=strtok((s),(sep)))
59 #endif
60
61 static gchar *
62 parse_for_url (char *code, char *name, xmlNode *parent)
63 {
64         xmlNode *child;
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) {
70                                 xmlAttr *attr;
71                                 gchar *url = NULL;
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)
75                                                         return NULL;
76                                         }
77                                         if (strcmp ((char*)attr->name, "url") == 0)
78                                                 url = (char*)attr->children->content;
79                                 }
80                                 return g_strdup (url);
81                         }
82                         return NULL;
83                 } else {
84                         for (child = parent->children; child; child = child->next) {
85                                 gchar *url = parse_for_url (code, name, child);
86                                 if (url)
87                                         return url;
88                         }
89                 }
90         }
91         return NULL;
92 }
93
94 static void
95 find_station_url (gchar *station, EWeatherSourceCCF *source)
96 {
97         xmlDoc *doc;
98         xmlNode *root;
99         gchar **sstation;
100         gchar *url;
101         gchar *filename;
102
103         sstation = g_strsplit (station, "/", 2);
104
105 #ifndef G_OS_WIN32
106         filename = g_strdup (WEATHER_DATADIR "/Locations.xml");
107 #else
108         filename = e_util_replace_prefix (E_DATA_SERVER_PREFIX,
109                                           e_util_get_prefix (),
110                                           WEATHER_DATADIR "/Locations.xml");
111 #endif
112
113         doc = e_xml_parse_file (filename);
114
115         g_assert (doc != NULL);
116
117         root = xmlDocGetRootElement (doc);
118
119         url = parse_for_url (sstation[0], sstation[1], root);
120
121         source->url = g_strdup (url);
122         source->substation = g_strdup (sstation[0]);
123
124         g_strfreev (sstation);
125 }
126
127 EWeatherSource*
128 e_weather_source_ccf_new (const char *uri)
129 {
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.
134          */
135         EWeatherSourceCCF *source = E_WEATHER_SOURCE_CCF (g_object_new (e_weather_source_ccf_get_type (), NULL));
136
137         find_station_url (strchr (uri, '/') + 1, source);
138         return E_WEATHER_SOURCE (source);
139 }
140
141 static GSList*
142 tokenize (char *buffer)
143 {
144         char *token;
145         char *tokbuf;
146         GSList *ret;
147
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));
152         return ret;
153 }
154
155 static void
156 date2tm (char *date, struct tm *times)
157 {
158         char tmp[3];
159         time_t curtime = time(NULL);
160         tmp[2] = '\0';
161
162         localtime_r (&curtime, times);
163
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);
170 }
171
172 static WeatherConditions
173 decodeConditions (char code)
174 {
175         switch (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;
204         }
205 }
206
207 static int
208 decodePOP (char data)
209 {
210         int ret;
211
212         switch (data) {
213                 case '-':
214                         ret = 5;
215                         break;
216                 case '+':
217                         ret = 95;
218                         break;
219                 case '/':
220                         ret = -1;       /* missing data */
221                         break;
222                 default:
223                         ret = (data - '0') * 10;
224         }
225         return ret;
226 }
227
228 static void
229 decodeSnowfall (char *data, float *low, float *high)
230 {
231         char num[3];
232         num[2] = '\0';
233
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;
238 }
239
240 static float
241 ftoc (char *data)
242 {
243         int fahrenheit = atoi(data);
244         if (fahrenheit >= 900)
245                 fahrenheit = (fahrenheit - 900) * -1;
246         return ((float)(fahrenheit-32)) * 5.0f / 9.0f;
247 }
248
249 static void
250 e_weather_source_ccf_do_parse (EWeatherSourceCCF *source, char *buffer)
251 {
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.
257          *
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
261          */
262         WeatherForecast *forecasts = g_new0 (WeatherForecast, 7);
263         GSList *tokens = tokenize (buffer);
264         GSList *date;
265         GSList *current = tokens;
266         GList *fc = NULL;
267         struct tm tms;
268         int i;
269         time_t base;
270         gint n;
271
272         date = g_slist_nth (tokens, 3);
273         date2tm (date->data, &tms);
274
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]);
283
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);
291                 }
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]);
296         } else {
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);
302                 }
303                 current = g_slist_next (current);
304                 forecasts[0].pop = decodePOP (((char*)(current->data))[1]);
305                 forecasts[1].pop = decodePOP (((char*)(current->data))[3]);
306         }
307
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);
315                 } else {
316                         current = g_slist_next (current);
317                         decodeSnowfall (current->data, &forecasts[0].low, &forecasts[0].high);
318                 }
319                 current = g_slist_next (current);
320         }
321
322         /* set dates */
323         base = mktime (&tms);
324         if (tms.tm_hour >= 12)
325                 base += 43200;
326         for (i = 0; i < 7; i++)
327                 forecasts[i].date = base + 86400*i;
328
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);
336         }
337
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]);
344
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);
354                 }
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]);
364                 n = 7;
365         } else {
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);
371                 }
372                 n = 6;
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]);
381         }
382
383         for (i = 0; i < n; i++) {
384                 fc = g_list_append (fc, &forecasts[i]);
385         }
386         source->done (fc, source->finished_data);
387
388         g_free (forecasts);
389         g_list_free (fc);
390 }
391
392 static void
393 retrieval_done (SoupMessage *message, EWeatherSourceCCF *source)
394 {
395         char *str;
396         const char *newuri;
397
398         /* Handle redirection ourselves */
399         if (SOUP_STATUS_IS_REDIRECTION (message->status_code)) {
400                 newuri = soup_message_get_header (message->response_headers, "Location");
401
402                 if (newuri) {
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);
407                         return;
408                 } else {
409                         source->done (NULL, source->finished_data);
410                 }
411
412                 return;
413         }
414
415         /* check status code */
416         if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
417                 source->done (NULL, source->finished_data);
418                 return;
419         }
420
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);
424         g_free (str);
425 }
426
427 static void
428 e_weather_source_ccf_parse (EWeatherSource *source, EWeatherSourceFinished done, gpointer data)
429 {
430         EWeatherSourceCCF *ccfsource = (EWeatherSourceCCF*) source;
431         SoupMessage *soup_message;
432
433         ccfsource->finished_data = data;
434
435         ccfsource->done = done;
436
437         if (!ccfsource->soup_session) {
438                 GConfClient *conf_client;
439                 ccfsource->soup_session = soup_session_async_new ();
440
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;
445                         int port;
446
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);
449
450                         if (server && server[0]) {
451                                 SoupUri *suri;
452                                 if (gconf_client_get_bool (conf_client, "/system/http_proxy/use_authentication", NULL)) {
453                                         char *user, *password;
454
455                                         user = gconf_client_get_string (conf_client,
456                                                                         "/system/http_proxy/authentication_user",
457                                                                         NULL);
458                                         password = gconf_client_get_string (conf_client,
459                                                                             "/system/http_proxy/authentication_password",
460                                                                             NULL);
461
462                                         proxy_uri = g_strdup_printf("http://%s:%s@%s:%d", user, password, server, port);
463
464                                         g_free (user);
465                                         g_free (password);
466                                 } else
467                                         proxy_uri = g_strdup_printf ("http://%s:%d", server, port);
468
469                                 suri = soup_uri_new (proxy_uri);
470                                 g_object_set (G_OBJECT (ccfsource->soup_session), SOUP_SESSION_PROXY_URI, suri, NULL);
471
472                                 soup_uri_free (suri);
473                                 g_free (server);
474                                 g_free (proxy_uri);
475                         }
476                 }
477                 g_object_unref (conf_client);
478         }
479
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);
483 }
484
485 static void
486 e_weather_source_ccf_class_init (EWeatherSourceCCFClass *class)
487 {
488         EWeatherSourceClass *source_class;
489
490         source_class = (EWeatherSourceClass *) class;
491
492         source_class->parse = e_weather_source_ccf_parse;
493 }
494
495 static void
496 e_weather_source_ccf_init (EWeatherSourceCCF *source)
497 {
498         source->url = NULL;
499         source->substation = NULL;
500         source->soup_session = NULL;
501 }
502
503 GType
504 e_weather_source_ccf_get_type (void)
505 {
506         static GType e_weather_source_ccf_type = 0;
507
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,
514                         NULL, NULL,
515                         sizeof (EWeatherSourceCCF),
516                         0,
517                         (GInstanceInitFunc) e_weather_source_ccf_init
518                 };
519                 e_weather_source_ccf_type = g_type_register_static (E_TYPE_WEATHER_SOURCE, "EWeatherSourceCCF", &info, 0);
520         }
521
522         return e_weather_source_ccf_type;
523 }