weather objects: move init and print code to weather.c
[monky] / src / weather.c
1 /* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2  * vim: ts=4 sw=4 noet ai cindent syntax=c
3  *
4  * Conky, a system monitor, based on torsmo
5  *
6  * Please see COPYING for details
7  *
8  * Copyright (c) 2005-2009 Brenden Matthews, Philip Kovacs, et. al.
9  *      (see AUTHORS)
10  * All rights reserved.
11  *
12  * This program is free software: you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation, either version 3 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  *
24  */
25
26 #include "config.h"
27 #include "conky.h"
28 #include "logging.h"
29 #include "weather.h"
30 #include "temphelper.h"
31 #include "text_object.h"
32 #include "ccurl_thread.h"
33 #include <time.h>
34 #include <ctype.h>
35 #ifdef MATH
36 #include <math.h>
37 #endif /* MATH */
38 #ifdef XOAP
39 #include <libxml/parser.h>
40 #include <libxml/xpath.h>
41
42 /* WEATHER data */
43 typedef struct PWEATHER_ {
44         char lastupd[32];
45 #ifdef XOAP
46         char xoap_t[32];
47         char icon[3];
48 #endif /* XOAP */
49         int temp;
50         int dew;
51         int cc;
52         int bar;
53         int wind_s;
54         int wind_d;
55         int hmid;
56         int wc;
57 } PWEATHER;
58
59 #ifdef XOAP
60 #define FORECAST_DAYS 5
61 typedef struct PWEATHER_FORECAST_ {
62         int hi[FORECAST_DAYS];
63         int low[FORECAST_DAYS];
64         char icon[FORECAST_DAYS][3];
65         char xoap_t[FORECAST_DAYS][32];
66         char day[FORECAST_DAYS][9];
67         char date[FORECAST_DAYS][7];
68         int wind_s[FORECAST_DAYS];
69         int wind_d[FORECAST_DAYS];
70         int hmid[FORECAST_DAYS];
71         int ppcp[FORECAST_DAYS];
72 } PWEATHER_FORECAST;
73 #endif /* XOAP */
74
75 /* Xpath expressions for XOAP xml parsing */
76 #define NUM_XPATH_EXPRESSIONS_CC 8
77 const char *xpath_expression_cc[NUM_XPATH_EXPRESSIONS_CC] = {
78         "/weather/cc/lsup", "/weather/cc/tmp", "/weather/cc/t",
79         "/weather/cc/bar/r", "/weather/cc/wind/s", "/weather/cc/wind/d",
80         "/weather/cc/hmid", "/weather/cc/icon"
81 };
82
83 #define NUM_XPATH_EXPRESSIONS_DF 10
84 const char *xpath_expression_df[NUM_XPATH_EXPRESSIONS_DF] = {
85         "/weather/dayf/day[*]/hi", "/weather/dayf/day[*]/low",
86         "/weather/dayf/day[*]/part[1]/icon", "/weather/dayf/day[*]/part[1]/t",
87         "/weather/dayf/day[*]/part[1]/wind/s","/weather/dayf/day[*]/part[1]/wind/d",
88         "/weather/dayf/day[*]/part[1]/ppcp", "/weather/dayf/day[*]/part[1]/hmid",
89         "/weather/dayf/day[*]/@t", "/weather/dayf/day[*]/@dt"
90 };
91 #endif /* XOAP */
92
93 /* Possible sky conditions */
94 #define NUM_CC_CODES 6
95 const char *CC_CODES[NUM_CC_CODES] = {
96         "SKC", "CLR", "FEW", "SCT", "BKN", "OVC"
97 };
98
99 /* Possible weather modifiers */
100 #define NUM_WM_CODES 9
101 const char *WM_CODES[NUM_WM_CODES] = {
102         "VC", "MI", "BC", "PR", "TS", "BL",
103         "SH", "DR", "FZ"
104 };
105
106 /* Possible weather conditions */
107 #define NUM_WC_CODES 17
108 const char *WC_CODES[NUM_WC_CODES] = {
109         "DZ", "RA", "GR", "GS", "SN", "SG",
110         "FG", "HZ", "FU", "BR", "DU", "SA",
111         "FC", "PO", "SQ", "SS", "DS"
112 };
113
114 static ccurl_location_t *locations_head_cc = 0;
115 #ifdef XOAP
116 static ccurl_location_t *locations_head_df = 0;
117 #endif
118
119 void weather_free_info(void)
120 {
121         ccurl_free_locations(&locations_head_cc);
122 #ifdef XOAP
123         ccurl_free_locations(&locations_head_df);
124 #endif
125 }
126
127 int rel_humidity(int dew_point, int air) {
128         const float a = 17.27f;
129         const float b = 237.7f;
130
131         float diff = a*(dew_point/(b+dew_point)-air/(b+air));
132 #ifdef MATH
133         return (int)(100.f*expf(diff));
134 #else
135         return (int)(16.666667163372f*(6.f+diff*(6.f+diff*(3.f+diff))));
136 #endif /* MATH */
137 }
138
139 #ifdef XOAP
140 static void parse_df(PWEATHER_FORECAST *res, xmlXPathContextPtr xpathCtx)
141 {
142         int i, j, k;
143         char *content;
144         xmlXPathObjectPtr xpathObj;
145
146         xpathObj = xmlXPathEvalExpression((const xmlChar *)"/error/err", xpathCtx);
147         if (xpathObj && xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0 &&
148                         xpathObj->nodesetval->nodeTab[0]->type == XML_ELEMENT_NODE) {
149                 content = (char *)xmlNodeGetContent(xpathObj->nodesetval->nodeTab[0]);
150                 NORM_ERR("XOAP error: %s", content);
151                 xmlFree(content);
152                 xmlXPathFreeObject(xpathObj);
153                 return;
154         }
155         xmlXPathFreeObject(xpathObj);
156
157         for (i = 0; i < NUM_XPATH_EXPRESSIONS_DF; i++) {
158                 xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath_expression_df[i], xpathCtx);
159                 if (xpathObj != NULL) {
160                         xmlNodeSetPtr nodes = xpathObj->nodesetval;
161                         k = 0;
162                         for (j = 0; j < nodes->nodeNr; ++j) {
163                                 if (nodes->nodeTab[j]->type == XML_ELEMENT_NODE) {
164                                         content = (char *)xmlNodeGetContent(nodes->nodeTab[k]);
165                                         switch(i) {
166                                         case 0:
167                                                 res->hi[k] = atoi(content);
168                                                 break;
169                                         case 1:
170                                                 res->low[k] = atoi(content);
171                                                 break;
172                                         case 2:
173                                                 strncpy(res->icon[k], content, 2);
174                                         case 3:
175                                                 strncpy(res->xoap_t[k], content, 31);
176                                                 break;
177                                         case 4:
178                                                 res->wind_s[k] = atoi(content);
179                                                 break;
180                                         case 5:
181                                                 res->wind_d[k] = atoi(content);
182                                                 break;
183                                         case 6:
184                                                 res->ppcp[k] = atoi(content);
185                                                 break;
186                                         case 7:
187                                                 res->hmid[k] = atoi(content);
188                                         }
189                                 } else if (nodes->nodeTab[j]->type == XML_ATTRIBUTE_NODE) {
190                                         content = (char *)xmlNodeGetContent(nodes->nodeTab[k]);
191                                         switch(i) {
192                                         case 8:
193                                                 strncpy(res->day[k], content, 8);
194                                                 break;
195                                         case 9:
196                                                 strncpy(res->date[k], content, 6);
197                                         }
198                                 }
199                                 xmlFree(content);
200                                 if (++k == FORECAST_DAYS) break;
201                         }
202                 }
203                 xmlXPathFreeObject(xpathObj);
204         }
205         return;
206 }
207
208 static void parse_weather_forecast_xml(PWEATHER_FORECAST *res, const char *data)
209 {
210         xmlDocPtr doc;
211         xmlXPathContextPtr xpathCtx;
212
213         if (!(doc = xmlReadMemory(data, strlen(data), "", NULL, 0))) {
214                 NORM_ERR("weather_forecast: can't read xml data");
215                 return;
216         }
217
218         xpathCtx = xmlXPathNewContext(doc);
219         if(xpathCtx == NULL) {
220                 NORM_ERR("weather_forecast: unable to create new XPath context");
221                 xmlFreeDoc(doc);
222                 return;
223         }
224
225         parse_df(res, xpathCtx);
226         xmlXPathFreeContext(xpathCtx);
227         xmlFreeDoc(doc);
228         return;
229 }
230
231 static void parse_cc(PWEATHER *res, xmlXPathContextPtr xpathCtx)
232 {
233         int i;
234         char *content;
235         xmlXPathObjectPtr xpathObj;
236
237         xpathObj = xmlXPathEvalExpression((const xmlChar *)"/error/err", xpathCtx);
238         if (xpathObj && xpathObj->nodesetval && xpathObj->nodesetval->nodeNr > 0 &&
239                         xpathObj->nodesetval->nodeTab[0]->type == XML_ELEMENT_NODE) {
240                 content = (char *)xmlNodeGetContent(xpathObj->nodesetval->nodeTab[0]);
241                 NORM_ERR("XOAP error: %s", content);
242                 xmlFree(content);
243                 xmlXPathFreeObject(xpathObj);
244                 return;
245         }
246         xmlXPathFreeObject(xpathObj);
247
248         for (i = 0; i < NUM_XPATH_EXPRESSIONS_CC; i++) {
249                 xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath_expression_cc[i], xpathCtx);
250                 if (xpathObj && xpathObj->nodesetval && xpathObj->nodesetval->nodeNr >0 &&
251                                 xpathObj->nodesetval->nodeTab[0]->type ==
252                                 XML_ELEMENT_NODE) {
253                         content = (char *)xmlNodeGetContent(xpathObj->nodesetval->nodeTab[0]);
254                         switch(i) {
255                                 case 0:
256                                         strncpy(res->lastupd, content, 31);
257                                         break;
258                                 case 1:
259                                         res->temp = atoi(content);
260                                         break;
261                                 case 2:
262                                         strncpy(res->xoap_t, content, 31);
263                                         break;
264                                 case 3:
265                                         res->bar = atoi(content);
266                                         break;
267                                 case 4:
268                                         res->wind_s = atoi(content);
269                                         break;
270                                 case 5:
271                                         res->wind_d = atoi(content);
272                                         break;
273                                 case 6:
274                                         res->hmid = atoi(content);
275                                         break;
276                                 case 7:
277                                         strncpy(res->icon, content, 2);
278                         }
279                         xmlFree(content);
280                 }
281                 xmlXPathFreeObject(xpathObj);
282         }
283         return;
284 }
285
286 static void parse_weather_xml(PWEATHER *res, const char *data)
287 {
288         xmlDocPtr doc;
289         xmlXPathContextPtr xpathCtx;
290
291         if (!(doc = xmlReadMemory(data, strlen(data), "", NULL, 0))) {
292                 NORM_ERR("weather: can't read xml data");
293                 return;
294         }
295
296         xpathCtx = xmlXPathNewContext(doc);
297         if(xpathCtx == NULL) {
298                 NORM_ERR("weather: unable to create new XPath context");
299                 xmlFreeDoc(doc);
300                 return;
301         }
302
303         parse_cc(res, xpathCtx);
304         xmlXPathFreeContext(xpathCtx);
305         xmlFreeDoc(doc);
306         return;
307 }
308 #endif /* XOAP */
309
310 /*
311  * Horrible hack to avoid using regexes
312  *
313  */
314
315 static inline void parse_token(PWEATHER *res, char *token) {
316
317         int i;
318         char s_tmp[64];
319
320         switch (strlen(token)) {
321
322                 //Check all tokens 2 chars long
323                 case 2:
324
325                         //Check if token is a weather condition
326                         for (i=0; i<2; i++) {
327                                 if (!isalpha(token[i])) break;
328                         }
329                         if (i==2) {
330                                 for(i=0; i<NUM_WC_CODES; i++) {
331                                         if (!strncmp(token, WC_CODES[i], 2)) {
332                                                 res->wc=i+1;
333                                                 break;
334                                         }
335                                 }
336                                 return;
337                         }
338
339                         //Check for CB
340                         if (!strcmp(token, "CB")) {
341                                 res->cc = 8;
342                                 return;
343                         }
344
345                         break;
346
347                         //Check all tokens 3 chars long
348                 case 3:
349
350                         //Check if token is a modified weather condition
351                         if ((token[0] == '+') || (token[0] == '-')) {
352                                 for (i=1; i<3; i++) {
353                                         if (!isalpha(token[i])) break;
354                                 }
355                                 if (i==3) {
356                                         for(i=0; i<NUM_WC_CODES; i++) {
357                                                 if (!strncmp(&token[1], WC_CODES[i], 2)) {
358                                                         res->wc=i+1;
359                                                         break;
360                                                 }
361                                         }
362                                         return;
363                                 }
364                         }
365
366                         //Check for NCD or NSC
367                         if ((!strcmp(token, "NCD")) || (!strcmp(token, "NSC"))) {
368                                 res->cc = 1;
369                                 return;
370                         }
371
372                         //Check for TCU
373                         if (!strcmp(token, "TCU")) {
374                                 res->cc = 7;
375                                 return;
376                         }
377
378                         break;
379
380                         //Check all tokens 4 chars long
381                 case 4:
382
383                         //Check if token is a modified weather condition
384                         for(i=0; i<NUM_WM_CODES; i++) {
385                                 if (!strncmp(token, WM_CODES[i], 2)) {
386                                         for(i=0; i<NUM_WC_CODES; i++) {
387                                                 if (!strncmp(&token[2], WC_CODES[i], 2)) {
388                                                         res->wc=i+1;
389                                                         return;
390                                                 }
391                                         }
392                                         break;
393                                 }
394                         }
395
396                         break;
397
398                         //Check all tokens 5 chars long
399                 case 5:
400
401                         //Check for CAVOK
402                         if (!strcmp(token, "CAVOK")) {
403                                 res->cc = 1;
404                                 return;
405                         }
406
407                         //Check if token is the temperature
408                         for (i=0; i<2; i++) {
409                                 if (!isdigit(token[i])) break;
410                         }
411                         if ((i==2) && (token[2] == '/')) {
412                                 for (i=3; i<5; i++) {
413                                         if (!isdigit(token[i])) break;
414                                 }
415                                 if (i==5) {
416                                         //First 2 digits gives the air temperature
417                                         res->temp=atoi(token);
418
419                                         //4th and 5th digits gives the dew point temperature
420                                         res->dew=atoi(&token[3]);
421
422                                         //Compute humidity
423                                         res->hmid = rel_humidity(res->dew, res->temp);
424
425                                         return;
426                                 }
427                         }
428
429                         //Check if token is the pressure
430                         if ((token[0] == 'Q') || (token[0] == 'A')) {
431                                 for (i=1; i<5; i++) {
432                                         if (!isdigit(token[i])) break;
433                                 }
434                                 if (i==5) {
435                                         if (token[0] == 'A') {
436                                                 //Convert inches of mercury to mbar
437                                                 res->bar = (int)(atoi(&token[1])*0.338637526f);
438                                                 return;
439                                         }
440
441                                         //Last 4 digits is pressure im mbar
442                                         res->bar = atoi(&token[1]);
443                                         return;
444                                 }
445                         }
446
447                         //Check if token is a modified weather condition
448                         if ((token[0] == '+') || (token[0] == '-')) {
449                                 for(i=0; i<NUM_WM_CODES; i++) {
450                                         if (!strncmp(&token[1], WM_CODES[i], 2)) {
451                                                 for(i=0; i<NUM_WC_CODES; i++) {
452                                                         if (!strncmp(&token[3], WC_CODES[i], 2)) {
453                                                                 res->wc=i+1;
454                                                                 return;
455                                                         }
456                                                 }
457                                                 break;
458                                         }
459                                 }
460                         }
461                         break;
462
463                         //Check all tokens 6 chars long
464                 case 6:
465
466                         //Check if token is the cloud cover
467                         for (i=0; i<3; i++) {
468                                 if (!isalpha(token[i])) break;
469                         }
470                         if (i==3) {
471                                 for (i=3; i<6; i++) {
472                                         if (!isdigit(token[i])) break;
473                                 }
474                                 if (i==6) {
475                                         //Check if first 3 digits gives the cloud cover condition
476                                         for(i=0; i<NUM_CC_CODES; i++) {
477                                                 if (!strncmp(token, CC_CODES[i], 3)) {
478                                                         res->cc=i+1;
479                                                         break;
480                                                 }
481                                         }
482                                         return;
483                                 }
484                         }
485
486                         //Check if token is positive temp and negative dew
487                         for (i=0; i<2; i++) {
488                                 if (!isdigit(token[i])) break;
489                         }
490                         if ((i==2) && (token[2] == '/')  && (token[3] == 'M')) {
491                                 for (i=4; i<6; i++) {
492                                         if (!isdigit(token[i])) break;
493                                 }
494                                 if (i==6) {
495                                         //1st and 2nd digits gives the temperature
496                                         res->temp = atoi(token);
497
498                                         //5th and 6th digits gives the dew point temperature
499                                         res->dew = -atoi(&token[4]);
500
501                                         //Compute humidity
502                                         res->hmid = rel_humidity(res->dew, res->temp);
503
504                                         return;
505                                 }
506                         }
507
508                         break;
509
510                         //Check all tokens 7 chars long
511                 case 7:
512
513                         //Check if token is the observation time
514                         for (i=0; i<6; i++) {
515                                 if (!isdigit(token[i])) break;
516                         }
517                         if ((i==6) && (token[6] == 'Z')) return;
518
519                         //Check if token is the wind speed/direction in knots
520                         for (i=0; i<5; i++) {
521                                 if (!isdigit(token[i])) break;
522                         }
523                         if ((i==5) && (token[5] == 'K') &&  (token[6] == 'T')) {
524
525                                 //First 3 digits are wind direction
526                                 strncpy(s_tmp, token, 3);
527                                 s_tmp[3]='\0';
528                                 res->wind_d=atoi(s_tmp);
529
530                                 //4th and 5th digit are wind speed in knots (convert to km/hr)
531                                 res->wind_s = (int)(atoi(&token[3])*1.852);
532
533                                 return;
534                         }
535
536                         //Check if token is negative temperature
537                         if ((token[0] == 'M') && (token[4] == 'M')) {
538                                 for (i=1; i<3; i++) {
539                                         if (!isdigit(token[i])) break;
540                                 }
541                                 if ((i==3) && (token[3] == '/')) {
542                                         for (i=5; i<7; i++) {
543                                                 if (!isdigit(token[i])) break;
544                                         }
545                                         if (i==7) {
546                                                 //2nd and 3rd digits gives the temperature
547                                                 res->temp = -atoi(&token[1]);
548
549                                                 //6th and 7th digits gives the dew point temperature
550                                                 res->dew = -atoi(&token[5]);
551
552                                                 //Compute humidity
553                                                 res->hmid = rel_humidity(res->dew, res->temp);
554
555                                                 return;
556                                         }
557                                 }
558                         }
559
560                         //Check if token is wind variability
561                         for (i=0; i<3; i++) {
562                                 if (!isdigit(token[i])) break;
563                         }
564                         if ((i==3) && (token[3] == 'V')) {
565                                 for (i=4; i<7; i++) {
566                                         if (!isdigit(token[i])) break;
567                                 }
568                                 if (i==7) return;
569                         }
570
571                         break;
572
573                         //Check all tokens 8 chars long
574                 case 8:
575
576                         //Check if token is the wind speed/direction in m/s
577                         for (i=0; i<5; i++) {
578                                 if (!isdigit(token[i])) break;
579                         }
580                         if ((i==5)&&(token[5] == 'M')&&(token[6] == 'P')&&(token[7] == 'S')) {
581
582                                 //First 3 digits are wind direction
583                                 strncpy(s_tmp, token, 3);
584                                 s_tmp[3]='\0';
585                                 res->wind_d=atoi(s_tmp);
586
587                                 //4th and 5th digit are wind speed in m/s (convert to km/hr)
588                                 res->wind_s = (int)(atoi(&token[3])*3.6);
589
590                                 return;
591                         }
592
593                 default:
594
595                         //printf("token : %s\n", token);
596                         break;
597         }
598 }
599
600 #ifdef XOAP
601 void parse_weather_forecast(void *result, const char *data)
602 {
603         PWEATHER_FORECAST *res = (PWEATHER_FORECAST*)result;
604         /* Reset results */
605         memset(res, 0, sizeof(PWEATHER_FORECAST));
606
607         //Check if it is an xml file
608         if ( strncmp(data, "<?xml ", 6) == 0 ) {
609                 parse_weather_forecast_xml(res, data);
610         }
611 }
612 #endif /* XOAP */
613
614 void parse_weather(void *result, const char *data)
615 {
616         PWEATHER *res = (PWEATHER*)result;
617         /* Reset results */
618         memset(res, 0, sizeof(PWEATHER));
619
620 #ifdef XOAP
621         //Check if it is an xml file
622         if ( strncmp(data, "<?xml ", 6) == 0 ) {
623                 parse_weather_xml(res, data);
624         } else
625 #endif /* XOAP */
626         {
627                 //We assume its a text file
628                 char s_tmp[256];
629                 const char delim[] = " ";
630
631                 //Divide time stamp and metar data
632                 if (sscanf(data, "%[^'\n']\n%[^'\n']", res->lastupd, s_tmp) == 2) {
633
634                         //Process all tokens
635                         char *p_tok = NULL;
636                         char *p_save = NULL;
637
638                         if ((strtok_r(s_tmp, delim, &p_save)) != NULL) {
639
640                                 //Jump first token, must be icao
641                                 p_tok = strtok_r(NULL, delim, &p_save);
642
643                                 do {
644
645                                         parse_token(res, p_tok);
646                                         p_tok = strtok_r(NULL, delim, &p_save);
647
648                                 } while (p_tok != NULL);
649                         }
650                         return;
651                 }
652                 else {
653                         return;
654                 }
655         }
656 }
657
658 void wind_deg_to_dir(char *p, int p_max_size, int wind_deg) {
659         if ((wind_deg >= 349) || (wind_deg < 12)) {
660                 strncpy(p, "N", p_max_size);
661         } else if (wind_deg < 33) {
662                 strncpy(p, "NNE", p_max_size);
663         } else if (wind_deg < 57) {
664                 strncpy(p, "NE", p_max_size);
665         } else if (wind_deg < 79) {
666                 strncpy(p, "ENE", p_max_size);
667         } else if (wind_deg < 102) {
668                 strncpy(p, "E", p_max_size);
669         } else if (wind_deg < 124) {
670                 strncpy(p, "ESE", p_max_size);
671         } else if (wind_deg < 147) {
672                 strncpy(p, "SE", p_max_size);
673         } else if (wind_deg < 169) {
674                 strncpy(p, "SSE", p_max_size);
675         } else if (wind_deg < 192) {
676                 strncpy(p, "S", p_max_size);
677         } else if (wind_deg < 214) {
678                 strncpy(p, "SSW", p_max_size);
679         } else if (wind_deg < 237) {
680                 strncpy(p, "SW", p_max_size);
681         } else if (wind_deg < 259) {
682                 strncpy(p, "WSW", p_max_size);
683         } else if (wind_deg < 282) {
684                         strncpy(p, "W", p_max_size);
685         } else if (wind_deg < 304) {
686                 strncpy(p, "WNW", p_max_size);
687         } else if (wind_deg < 327) {
688                 strncpy(p, "NW", p_max_size);
689         } else if (wind_deg < 349) {
690                 strncpy(p, "NNW", p_max_size);
691         };
692 }
693
694 #ifdef XOAP
695 static void weather_forecast_process_info(char *p, int p_max_size, char *uri, unsigned int day, char *data_type, int interval)
696 {
697         PWEATHER_FORECAST *data;
698
699         ccurl_location_t *curloc = ccurl_find_location(&locations_head_df, uri);
700         if (!curloc->p_timed_thread) {
701                 curloc->result = malloc(sizeof(PWEATHER_FORECAST));
702                 memset(curloc->result, 0, sizeof(PWEATHER_FORECAST));
703                 curloc->process_function = &parse_weather_forecast;
704                 ccurl_init_thread(curloc, interval);
705                 if (!curloc->p_timed_thread) {
706                         NORM_ERR("error setting up weather_forecast thread");
707                 }
708         }
709
710         timed_thread_lock(curloc->p_timed_thread);
711         data = (PWEATHER_FORECAST*)curloc->result;
712         if (strcmp(data_type, "hi") == EQUAL) {
713                 temp_print(p, p_max_size, data->hi[day], TEMP_CELSIUS);
714         } else if (strcmp(data_type, "low") == EQUAL) {
715                 temp_print(p, p_max_size, data->low[day], TEMP_CELSIUS);
716         } else if (strcmp(data_type, "icon") == EQUAL) {
717                 strncpy(p, data->icon[day], p_max_size);
718         } else if (strcmp(data_type, "forecast") == EQUAL) {
719                 strncpy(p, data->xoap_t[day], p_max_size);
720         } else if (strcmp(data_type, "wind_speed") == EQUAL) {
721                 snprintf(p, p_max_size, "%d", data->wind_s[day]);
722         } else if (strcmp(data_type, "wind_dir") == EQUAL) {
723                 wind_deg_to_dir(p, p_max_size, data->wind_d[day]);
724         } else if (strcmp(data_type, "wind_dir_DEG") == EQUAL) {
725                 snprintf(p, p_max_size, "%d", data->wind_d[day]);
726         } else if (strcmp(data_type, "humidity") == EQUAL) {
727                 snprintf(p, p_max_size, "%d", data->hmid[day]);
728         } else if (strcmp(data_type, "precipitation") == EQUAL) {
729                 snprintf(p, p_max_size, "%d", data->ppcp[day]);
730         } else if (strcmp(data_type, "day") == EQUAL) {
731                 strncpy(p, data->day[day], p_max_size);
732         } else if (strcmp(data_type, "date") == EQUAL) {
733                 strncpy(p, data->date[day], p_max_size);
734         }
735
736         timed_thread_unlock(curloc->p_timed_thread);
737 }
738 #endif /* XOAP */
739
740 static void weather_process_info(char *p, int p_max_size, char *uri, char *data_type, int interval)
741 {
742         static const char *wc[] = {
743                 "", "drizzle", "rain", "hail", "soft hail",
744                 "snow", "snow grains", "fog", "haze", "smoke",
745                 "mist", "dust", "sand", "funnel cloud tornado",
746                 "dust/sand", "squall", "sand storm", "dust storm"
747         };
748         PWEATHER *data;
749
750         ccurl_location_t *curloc = ccurl_find_location(&locations_head_cc, uri);
751         if (!curloc->p_timed_thread) {
752                 curloc->result = malloc(sizeof(PWEATHER));
753                 memset(curloc->result, 0, sizeof(PWEATHER));
754                 curloc->process_function = &parse_weather;
755                 ccurl_init_thread(curloc, interval);
756                 if (!curloc->p_timed_thread) {
757                         NORM_ERR("error setting up weather thread");
758                 }
759         }
760
761         timed_thread_lock(curloc->p_timed_thread);
762         data = (PWEATHER*)curloc->result;
763         if (strcmp(data_type, "last_update") == EQUAL) {
764                 strncpy(p, data->lastupd, p_max_size);
765         } else if (strcmp(data_type, "temperature") == EQUAL) {
766                 temp_print(p, p_max_size, data->temp, TEMP_CELSIUS);
767         } else if (strcmp(data_type, "cloud_cover") == EQUAL) {
768 #ifdef XOAP
769                 if (data->xoap_t[0] != '\0') {
770                         char *s = p;
771                         strncpy(p, data->xoap_t, p_max_size);
772                         while (*s) {
773                                 *s = tolower(*s);
774                                 s++;
775                         }
776                 } else
777 #endif /* XOAP */
778                         if (data->cc == 0) {
779                                 strncpy(p, "", p_max_size);
780                         } else if (data->cc < 3) {
781                                 strncpy(p, "clear", p_max_size);
782                         } else if (data->cc < 5) {
783                                 strncpy(p, "partly cloudy", p_max_size);
784                         } else if (data->cc == 5) {
785                                 strncpy(p, "cloudy", p_max_size);
786                         } else if (data->cc == 6) {
787                                 strncpy(p, "overcast", p_max_size);
788                         } else if (data->cc == 7) {
789                                 strncpy(p, "towering cumulus", p_max_size);
790                         } else  {
791                                 strncpy(p, "cumulonimbus", p_max_size);
792                         }
793 #ifdef XOAP
794         } else if (strcmp(data_type, "icon") == EQUAL) {
795                 strncpy(p, data->icon, p_max_size);
796 #endif /* XOAP */
797         } else if (strcmp(data_type, "pressure") == EQUAL) {
798                 snprintf(p, p_max_size, "%d", data->bar);
799         } else if (strcmp(data_type, "wind_speed") == EQUAL) {
800                 snprintf(p, p_max_size, "%d", data->wind_s);
801         } else if (strcmp(data_type, "wind_dir") == EQUAL) {
802                 wind_deg_to_dir(p, p_max_size, data->wind_d);
803         } else if (strcmp(data_type, "wind_dir_DEG") == EQUAL) {
804                 snprintf(p, p_max_size, "%d", data->wind_d);
805
806         } else if (strcmp(data_type, "humidity") == EQUAL) {
807                 snprintf(p, p_max_size, "%d", data->hmid);
808         } else if (strcmp(data_type, "weather") == EQUAL) {
809                 strncpy(p, wc[data->wc], p_max_size);
810         }
811
812         timed_thread_unlock(curloc->p_timed_thread);
813 }
814
815 /* xoap suffix for weather from weather.com */
816 static char *xoap_cc = NULL;
817 static char *xoap_df = NULL;
818
819 static int process_weather_uri(char *uri, char *locID, int dayf UNUSED_ATTR)
820 {
821         /* locID MUST BE upper-case */
822         char *tmp_p = locID;
823
824         while (*tmp_p) {
825                 *tmp_p = toupper(*tmp_p);
826                 tmp_p++;
827         }
828
829         /* Construct complete uri */
830 #ifdef XOAP
831         if (strstr(uri, "xoap.weather.com")) {
832                 if ((dayf == 0) && (xoap_cc != NULL)) {
833                         strcat(uri, locID);
834                         strcat(uri, xoap_cc);
835                 } else if ((dayf == 1) && (xoap_df != NULL)) {
836                         strcat(uri, locID);
837                         strcat(uri, xoap_df);
838                 } else {
839                         free(uri);
840                         uri = NULL;
841                 }
842         } else
843 #endif /* XOAP */
844         if (strstr(uri, "weather.noaa.gov")) {
845                 strcat(uri, locID);
846                 strcat(uri, ".TXT");
847         } else  if (!strstr(uri, "localhost") && !strstr(uri, "127.0.0.1")) {
848                 return -1;
849         }
850         return 0;
851 }
852
853 #ifdef XOAP
854
855 /*
856  * TODO: make the xoap keys file readable from the config file
857  *       make the keys directly readable from the config file
858  *       make the xoap keys file giveable as a command line option
859  */
860 void load_xoap_keys(void)
861 {
862         FILE *fp;
863         char *par  = (char *) malloc(11 * sizeof(char));
864         char *key  = (char *) malloc(17 * sizeof(char));
865         char *xoap = (char *) malloc(64 * sizeof(char));
866
867         to_real_path(xoap, XOAP_FILE);
868         fp = fopen(xoap, "r");
869         if (fp != NULL) {
870                 if (fscanf(fp, "%10s %16s", par, key) == 2) {
871                         xoap_cc = (char *) malloc(128 * sizeof(char));
872                         xoap_df = (char *) malloc(128 * sizeof(char));
873
874                         strcpy(xoap_cc, "?cc=*&link=xoap&prod=xoap&par=");
875                         strcat(xoap_cc, par);
876                         strcat(xoap_cc, "&key=");
877                         strcat(xoap_cc, key);
878                         strcat(xoap_cc, "&unit=m");
879
880                         /* TODO: Use FORECAST_DAYS instead of 5 */
881                         strcpy(xoap_df, "?dayf=5&link=xoap&prod=xoap&par=");
882                         strcat(xoap_df, par);
883                         strcat(xoap_df, "&key=");
884                         strcat(xoap_df, key);
885                         strcat(xoap_df, "&unit=m");
886                 }
887                 fclose(fp);
888         }
889         free(par);
890         free(key);
891         free(xoap);
892 }
893
894 void scan_weather_forecast_arg(struct text_object *obj, const char *arg, void *free_at_crash)
895 {
896         int argc;
897         unsigned int day;
898         float interval = 0;
899         char *locID = (char *) malloc(9 * sizeof(char));
900         char *uri = (char *) malloc(128 * sizeof(char));
901         char *data_type = (char *) malloc(32 * sizeof(char));
902
903         argc = sscanf(arg, "%119s %8s %1u %31s %f", uri, locID, &day, data_type, &interval);
904
905         if (argc < 4) {
906                 free(data_type);
907                 free(uri);
908                 free(locID);
909                 CRIT_ERR(obj, free_at_crash, "wrong number of arguments for $weather_forecast");
910         }
911         if (process_weather_uri(uri, locID, 1)) {
912                 free(data_type);
913                 free(uri);
914                 free(locID);
915                 CRIT_ERR(obj, free_at_crash, \
916                                 "could not recognize the weather forecast uri");
917         }
918
919         obj->data.weather_forecast.uri = uri;
920         obj->data.weather_forecast.data_type = data_type;
921
922         /* Limit the day between 0 (today) and FORECAST_DAYS */
923         if (day >= FORECAST_DAYS) {
924                 day = FORECAST_DAYS-1;
925         }
926         obj->data.weather_forecast.day = day;
927
928         /* Limit the data retrieval interval to 3 hours and an half */
929         if (interval < 210) {
930                 interval = 210;
931         }
932
933         /* Convert to seconds */
934         obj->data.weather_forecast.interval = interval * 60;
935         free(locID);
936
937         DBGP("weather_forecast: fetching %s for day %d from %s every %d seconds", \
938                         data_type, day, uri, obj->data.weather_forecast.interval);
939 }
940
941 void print_weather_forecast(struct text_object *obj, char *p, int p_max_size)
942 {
943         if (!obj->data.weather_forecast.uri) {
944                 NORM_ERR("error processing weather forecast data, check that you have a valid XOAP key if using XOAP.");
945                 return;
946         }
947         weather_forecast_process_info(p, p_max_size, obj->data.weather_forecast.uri, obj->data.weather_forecast.day, obj->data.weather_forecast.data_type, obj->data.weather_forecast.interval);
948 }
949 #endif /* XOAP */
950
951 void scan_weather_arg(struct text_object *obj, const char *arg, void *free_at_crash)
952 {
953         int argc;
954         float interval = 0;
955         char *locID = (char *) malloc(9 * sizeof(char));
956         char *uri = (char *) malloc(128 * sizeof(char));
957         char *data_type = (char *) malloc(32 * sizeof(char));
958
959         argc = sscanf(arg, "%119s %8s %31s %f", uri, locID, data_type, &interval);
960
961         if (argc < 3) {
962                 free(data_type);
963                 free(uri);
964                 free(locID);
965                 CRIT_ERR(obj, free_at_crash, "wrong number of arguments for $weather");
966         }
967         if (process_weather_uri(uri, locID, 0)) {
968                 free(data_type);
969                 free(uri);
970                 free(locID);
971                 CRIT_ERR(obj, free_at_crash, \
972                                 "could not recognize the weather uri");
973         }
974
975         obj->data.weather.uri = uri;
976         obj->data.weather.data_type = data_type;
977
978         /* Limit the data retrieval interval to half hour min */
979         if (interval < 30) {
980                 interval = 30;
981         }
982
983         /* Convert to seconds */
984         obj->data.weather.interval = interval * 60;
985         free(locID);
986
987         DBGP("weather: fetching %s from %s every %d seconds", \
988                         data_type, uri, obj->data.weather.interval);
989 }
990
991 void print_weather(struct text_object *obj, char *p, int p_max_size)
992 {
993         if (!obj->data.weather.uri) {
994                 NORM_ERR("error processing weather data, check that you have a valid XOAP key if using XOAP.");
995                 return;
996         }
997         weather_process_info(p, p_max_size, obj->data.weather.uri, obj->data.weather.data_type, obj->data.weather.interval);
998 }