Merging upstream version 0.7.0 (Closes: #493243, #510888, #567617, #699414, #709777...
[debian/lxpanel.git] / plugins / weather / yahooutil.c
1 /**
2 * Copyright (c) 2012-2014 Piotr Sipika; see the AUTHORS file for more.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 *
18 * See the COPYRIGHT file for more information.
19 */
20
21 /* Provides utilities to use Yahoo's weather services */
22
23 #include "httputil.h"
24 #include "location.h"
25 #include "forecast.h"
26 #include "logutil.h"
27
28 #include <libxml/parser.h>
29 #include <libxml/tree.h>
30 #include <libxml/xpath.h>
31 #include <libxml/xmlstring.h>
32 #include <libxml/uri.h>
33
34 #include <gtk/gtk.h>
35 #include <gio/gio.h>
36
37 #include <string.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <locale.h>
41
42 #define XMLCHAR_P(x) (xmlChar *)(x)
43 #define CONSTXMLCHAR_P(x) (const xmlChar *)(x)
44 #define CONSTCHAR_P(x) (const char *)(x)
45 #define CHAR_P(x) (char *)(x)
46
47 static gint g_iInitialized = 0;
48
49 static const gchar * WOEID_QUERY = "SELECT%20*%20FROM%20geo.placefinder%20WHERE%20text=";
50 static const gchar * FORECAST_QUERY_P1 = "SELECT%20*%20FROM%20weather.forecast%20WHERE%20woeid=";
51 static const gchar * FORECAST_QUERY_P2 = "%20and%20u=";
52 static const gchar * FORECAST_URL = "http://query.yahooapis.com/v1/public/yql?format=xml&q=";
53
54 /**
55 * Returns the length for the appropriate WOEID query
56 *
57 * @param pczLocation Location string to be used inside query
58 *
59 * @return length of resulting query on success or 0 on failure
60 */
61 static gsize
62 getWOEIDQueryLength(const gchar * pczLocation)
63 {
64 // len of all strings plus two quotes ('%22') and \0
65 return strlen(FORECAST_URL) +
66 strlen(WOEID_QUERY) +
67 strlen(pczLocation) + 7;
68 }
69
70 /**
71 * Returns the length for the appropriate Forecast query
72 *
73 * @param pczWOEID WOEID string to be used inside query
74 *
75 * @return length of resulting query on success or 0 on failure
76 */
77 static gsize
78 getForecastQueryLength(const gchar * pczWOEID)
79 {
80 // len of all strings plus four quotes ('%27'), units char and \0
81 return strlen(FORECAST_URL) +
82 strlen(FORECAST_QUERY_P1) +
83 strlen(pczWOEID) + 14 +
84 strlen(FORECAST_QUERY_P2);
85 }
86
87 /**
88 * Generates the WOEID query string
89 *
90 * @param cQuery Buffer to contain the query
91 * @param pczLocation Location string
92 *
93 * @return 0 on success, -1 on failure
94 */
95 static gint
96 getWOEIDQuery(gchar * pcQuery, const gchar * pczLocation)
97 {
98 gsize totalLength = getWOEIDQueryLength(pczLocation);
99
100 snprintf(pcQuery, totalLength, "%s%s%s%s%s",
101 FORECAST_URL, WOEID_QUERY, "%22", pczLocation, "%22");
102
103 pcQuery[totalLength] = '\0';
104
105 return 0;
106 }
107
108 /**
109 * Generates the forecast query string
110 *
111 * @param cQuery Buffer to contain the query
112 * @param pczWOEID WOEID string
113 * @param czUnits Units character (length of 1)
114 *
115 * @return 0 on success, -1 on failure
116 */
117 static gint
118 getForecastQuery(gchar * pcQuery, const gchar * pczWOEID, const gchar czUnits)
119 {
120 gsize totalLength = getForecastQueryLength(pczWOEID);
121
122 snprintf(pcQuery, totalLength, "%s%s%s%s%s%s%s%c%s",
123 FORECAST_URL,
124 FORECAST_QUERY_P1,
125 "%22",
126 pczWOEID,
127 "%22",
128 FORECAST_QUERY_P2,
129 "%22",
130 czUnits,
131 "%22");
132
133 pcQuery[totalLength] = '\0';
134
135 return 0;
136 }
137
138 /**
139 * Converts the passed-in string from UTF-8 to ASCII for http transmisison.
140 *
141 * @param pczInString String to convert
142 *
143 * @return The converted string which MUST BE FREED BY THE CALLER.
144 */
145 static gchar *
146 convertToASCII(const gchar *pczInString)
147 {
148 // for UTF-8 to ASCII conversions
149 setlocale(LC_CTYPE, "en_US");
150
151 GError * pError = NULL;
152
153 gsize szBytesRead = 0;
154 gsize szBytesWritten = 0;
155
156 gchar * pcConvertedString = g_convert(pczInString,
157 strlen(pczInString),
158 "ASCII//TRANSLIT",
159 "UTF-8",
160 &szBytesRead,
161 &szBytesWritten,
162 &pError);
163
164 if (pError)
165 {
166 LXW_LOG(LXW_ERROR, "yahooutil::convertToASCII(%s): Error: %s",
167 pczInString, pError->message);
168
169 g_error_free(pError);
170
171 pcConvertedString = g_strndup(pczInString, strlen(pczInString));
172 }
173
174 // now escape space, if any
175 xmlChar * pxEscapedString = xmlURIEscapeStr((const xmlChar *)pcConvertedString, NULL);
176
177 if (pxEscapedString)
178 {
179 // release ConvertedString, reset it, then release EscapedString.
180 // I know it's confusing, but keeps everything as a gchar and g_free
181 g_free(pcConvertedString);
182
183 pcConvertedString = g_strndup((const gchar *)pxEscapedString,
184 strlen((const gchar *)pxEscapedString));
185
186 xmlFree(pxEscapedString);
187 }
188
189 // restore locale to default
190 setlocale(LC_CTYPE, "");
191
192 return pcConvertedString;
193 }
194
195 /**
196 * Compares two strings and then sets the storage variable to the second
197 * value if the two do not match. The storage variable is cleared first.
198 *
199 * @param pcStorage Pointer to the storage location with the first value.
200 * @param pczString2 The second string.
201 * @param szString2 The length of the second string.
202 *
203 * @return 0 on succes, -1 on failure.
204 */
205 static gint
206 setStringIfDifferent(gchar ** pcStorage,
207 const gchar * pczString2,
208 const gsize szString2)
209 {
210 // if diffrent, clear and set
211 if (g_strcmp0(*pcStorage, pczString2))
212 {
213 g_free(*pcStorage);
214
215 *pcStorage = g_strndup(pczString2, szString2);
216 }
217
218 return 0;
219 }
220
221 /**
222 * Compares the URL of an image to the 'new' value. If the two
223 * are different, the image at the 'new' URL is retrieved and replaces
224 * the old one. The old one is freed.
225 *
226 * @param pcStorage Pointer to the storage location with the first value.
227 * @param pImage Pointer to the image storage.
228 * @param pczNewURL The new url.
229 * @param szURLLength The length of the new URL.
230 *
231 * @return 0 on succes, -1 on failure.
232 */
233 static gint
234 setImageIfDifferent(gchar ** pcStorage,
235 GdkPixbuf ** pImage,
236 const gchar * pczNewURL,
237 const gsize szURLLength)
238 {
239 int err = 0;
240
241 // if diffrent, clear and set
242 if (g_strcmp0(*pcStorage, pczNewURL))
243 {
244 g_free(*pcStorage);
245
246 *pcStorage = g_strndup(pczNewURL, szURLLength);
247
248 if (*pImage)
249 {
250 g_object_unref(*pImage);
251
252 *pImage = NULL;
253 }
254
255 // retrieve the URL and create the new image
256 gint iRetCode = 0;
257 gint iDataSize = 0;
258
259 gpointer pResponse = getURL(pczNewURL, &iRetCode, &iDataSize);
260
261 if (!pResponse || iRetCode != HTTP_STATUS_OK)
262 {
263 LXW_LOG(LXW_ERROR, "yahooutil::setImageIfDifferent(): Failed to get URL (%d, %d)",
264 iRetCode, iDataSize);
265
266 return -1;
267 }
268
269 GInputStream * pInputStream = g_memory_input_stream_new_from_data(pResponse,
270 iDataSize,
271 g_free);
272
273 GError * pError = NULL;
274
275 *pImage = gdk_pixbuf_new_from_stream(pInputStream,
276 NULL,
277 &pError);
278
279 if (!*pImage)
280 {
281 LXW_LOG(LXW_ERROR, "yahooutil::setImageIfDifferent(): PixBuff allocation failed: %s",
282 pError->message);
283
284 g_error_free(pError);
285
286 err = -1;
287 }
288
289 if (!g_input_stream_close(pInputStream, NULL, &pError))
290 {
291 LXW_LOG(LXW_ERROR, "yahooutil::setImageIfDifferent(): InputStream closure failed: %s",
292 pError->message);
293
294 g_error_free(pError);
295
296 err = -1;
297 }
298
299 }
300
301 return err;
302 }
303
304 /**
305 * Compares an integer to a converted string and then sets the storage variable
306 * to the second value if the two do not match.
307 *
308 * @param piStorage Pointer to the storage location with the first value.
309 * @param pczString2 The second string.
310 *
311 * @return 0 on succes, -1 on failure.
312 */
313 static gint
314 setIntIfDifferent(gint * piStorage, const gchar * pczString2)
315 {
316 gint iValue = (gint)g_ascii_strtoll((pczString2)?pczString2:"0", NULL, 10);
317
318 // if diffrent, set
319 if (*piStorage != iValue)
320 {
321 *piStorage = iValue;
322 }
323
324 return 0;
325 }
326
327 /**
328 * Processes the passed-in node to generate a LocationInfo entry
329 *
330 * @param pNode Pointer to the XML Result Node.
331 *
332 * @return A newly created LocationInfo entry on success, or NULL on failure.
333 */
334 static gpointer
335 processResultNode(xmlNodePtr pNode)
336 {
337 if (!pNode)
338 {
339 return NULL;
340 }
341
342 LocationInfo * pEntry = (LocationInfo *)g_try_new0(LocationInfo, 1);
343
344 if (!pEntry)
345 {
346 return NULL;
347 }
348
349 xmlNodePtr pCurr = pNode->xmlChildrenNode;
350
351 for (; pCurr != NULL; pCurr = pCurr->next)
352 {
353 if (pCurr->type == XML_ELEMENT_NODE)
354 {
355 const char * pczContent = CONSTCHAR_P(xmlNodeListGetString(pCurr->doc,
356 pCurr->xmlChildrenNode,
357 1));
358
359 gsize contentLength = ((pczContent)?strlen(pczContent):0); // 1 is null char
360
361 if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("city")))
362 {
363 pEntry->pcCity_ = g_strndup(pczContent, contentLength);
364 }
365 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("state")))
366 {
367 pEntry->pcState_ = g_strndup(pczContent, contentLength);
368 }
369 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("country")))
370 {
371 pEntry->pcCountry_ = g_strndup(pczContent, contentLength);
372 }
373 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("woeid")))
374 {
375 pEntry->pcWOEID_ = g_strndup(pczContent, contentLength);
376 }
377 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("line2")))
378 {
379 pEntry->pcAlias_ = g_strndup(pczContent, contentLength);
380 }
381
382 xmlFree(XMLCHAR_P(pczContent));
383 }
384
385 }
386
387 return pEntry;
388 }
389
390 /**
391 * Processes the passed-in node to generate a LocationInfo entry
392 *
393 * @param pEntry Pointer to the pointer to the ForecastInfo entry being filled.
394 * @param pNode Pointer to the XML Item Node.
395 *
396 * @return 0 on success, -1 on failure
397 */
398 static gint
399 processItemNode(gpointer * pEntry, xmlNodePtr pNode)
400 {
401 if (!pNode || !pEntry)
402 {
403 return -1;
404 }
405
406 ForecastInfo * pInfo = *((ForecastInfo **)pEntry);
407
408 xmlNodePtr pCurr = pNode->xmlChildrenNode;
409
410 int iForecastCount = 0;
411
412 for (; pCurr != NULL; pCurr = pCurr->next)
413 {
414 if (pCurr->type == XML_ELEMENT_NODE)
415 {
416 if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("condition")))
417 {
418 const char * pczDate = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("date")));
419 const char * pczTemp = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("temp")));
420 const char * pczText = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("text")));
421
422 gsize dateLength = ((pczDate)?strlen(pczDate):0);
423 gsize textLength = ((pczText)?strlen(pczText):0);
424
425 setStringIfDifferent(&pInfo->pcTime_, pczDate, dateLength);
426
427 setIntIfDifferent(&pInfo->iTemperature_, pczTemp);
428
429 setStringIfDifferent(&pInfo->pcConditions_, pczText, textLength);
430
431 xmlFree(XMLCHAR_P(pczDate));
432 xmlFree(XMLCHAR_P(pczTemp));
433 xmlFree(XMLCHAR_P(pczText));
434 }
435 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("description")))
436 {
437 char * pcContent = CHAR_P(xmlNodeListGetString(pCurr->doc,
438 pCurr->xmlChildrenNode,
439 1));
440
441 char * pcSavePtr = NULL;
442
443 // initial call to find the first '"'
444 strtok_r(pcContent, "\"", &pcSavePtr);
445
446 // second call to find the second '"'
447 char * pcImageURL = strtok_r(NULL, "\"", &pcSavePtr);
448
449 // found the image
450 if (pcImageURL && strstr(pcImageURL, "yimg.com"))
451 {
452 LXW_LOG(LXW_DEBUG, "yahooutil::processItemNode(): IMG URL: %s",
453 pcImageURL);
454
455 setImageIfDifferent(&pInfo->pcImageURL_,
456 &pInfo->pImage_,
457 pcImageURL,
458 strlen(pcImageURL));
459 }
460
461 xmlFree(XMLCHAR_P(pcContent));
462 }
463 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("forecast")))
464 {
465 ++iForecastCount;
466
467 const char * pczDay = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("day")));
468 const char * pczHigh = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("high")));
469 const char * pczLow = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("low")));
470 const char * pczText = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("text")));
471
472 gsize dayLength = ((pczDay)?strlen(pczDay):0);
473 gsize textLength = ((pczText)?strlen(pczText):0);
474
475 if (iForecastCount == 1)
476 {
477 setStringIfDifferent(&pInfo->today_.pcDay_, pczDay, dayLength);
478
479 setIntIfDifferent(&pInfo->today_.iHigh_, pczHigh);
480
481 setIntIfDifferent(&pInfo->today_.iLow_, pczLow);
482
483 setStringIfDifferent(&pInfo->today_.pcConditions_, pczText, textLength);
484 }
485 else
486 {
487 setStringIfDifferent(&pInfo->tomorrow_.pcDay_, pczDay, dayLength);
488
489 setIntIfDifferent(&pInfo->tomorrow_.iHigh_, pczHigh);
490
491 setIntIfDifferent(&pInfo->tomorrow_.iLow_, pczLow);
492
493 setStringIfDifferent(&pInfo->tomorrow_.pcConditions_, pczText, textLength);
494 }
495
496 xmlFree(XMLCHAR_P(pczDay));
497 xmlFree(XMLCHAR_P(pczHigh));
498 xmlFree(XMLCHAR_P(pczLow));
499 xmlFree(XMLCHAR_P(pczText));
500 }
501
502 }
503
504 }
505
506 return 0;
507 }
508
509
510 /**
511 * Processes the passed-in node to generate a ForecastInfo entry
512 *
513 * @param pNode Pointer to the XML Channel Node.
514 * @param pEntry Pointer to the ForecastInfo entry to be filled in.
515 *
516 * @return A newly created ForecastInfo entry on success, or NULL on failure.
517 */
518 static gpointer
519 processChannelNode(xmlNodePtr pNode, ForecastInfo * pEntry)
520 {
521 if (!pNode)
522 {
523 return NULL;
524 }
525
526 xmlNodePtr pCurr = pNode->xmlChildrenNode;
527
528 for (; pCurr != NULL; pCurr = pCurr->next)
529 {
530 if (pCurr->type == XML_ELEMENT_NODE)
531 {
532 if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("title")))
533 {
534 /* Evaluate title to see if there was an error */
535 char * pcContent = CHAR_P(xmlNodeListGetString(pCurr->doc,
536 pCurr->xmlChildrenNode,
537 1));
538
539 if (strstr(pcContent, "Error"))
540 {
541 xmlFree(XMLCHAR_P(pcContent));
542
543 do
544 {
545 pCurr = pCurr->next;
546 } while (pCurr && !xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("item")));
547
548 xmlNodePtr pChild = (pCurr)?pCurr->xmlChildrenNode:NULL;
549
550 for (; pChild != NULL; pChild = pChild->next)
551 {
552 if (pChild->type == XML_ELEMENT_NODE &&
553 xmlStrEqual(pChild->name, CONSTXMLCHAR_P("title")))
554 {
555 pcContent = CHAR_P(xmlNodeListGetString(pChild->doc,
556 pChild->xmlChildrenNode,
557 1));
558
559 LXW_LOG(LXW_ERROR, "yahooutil::processChannelNode(): Forecast retrieval error: %s",
560 pcContent);
561
562
563 xmlFree(XMLCHAR_P(pcContent));
564 }
565 }
566
567 return NULL;
568 }
569 else
570 {
571 xmlFree(XMLCHAR_P(pcContent));
572 /* ...and continue... */
573 }
574
575 }
576 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("item")))
577 {
578 /* item child element gets 'special' treatment */
579 processItemNode((gpointer *)&pEntry, pCurr);
580 }
581 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("units")))
582 {
583 // distance
584 const char * pczDistance = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("distance")));
585
586 gsize distanceLength = ((pczDistance)?strlen(pczDistance):0);
587
588 setStringIfDifferent(&pEntry->units_.pcDistance_, pczDistance, distanceLength);
589
590 xmlFree(XMLCHAR_P(pczDistance));
591
592 // pressure
593 const char * pczPressure = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("pressure")));
594
595 gsize pressureLength = ((pczPressure)?strlen(pczPressure):0);
596
597 setStringIfDifferent(&pEntry->units_.pcPressure_, pczPressure, pressureLength);
598
599 xmlFree(XMLCHAR_P(pczPressure));
600
601 // speed
602 const char * pczSpeed = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("speed")));
603
604 gsize speedLength = ((pczSpeed)?strlen(pczSpeed):0);
605
606 setStringIfDifferent(&pEntry->units_.pcSpeed_, pczSpeed, speedLength);
607
608 xmlFree(XMLCHAR_P(pczSpeed));
609
610 // temperature
611 const char * pczTemperature = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("temperature")));
612
613 gsize temperatureLength = ((pczTemperature)?strlen(pczTemperature):0);
614
615 setStringIfDifferent(&pEntry->units_.pcTemperature_, pczTemperature, temperatureLength);
616
617 xmlFree(XMLCHAR_P(pczTemperature));
618 }
619 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("wind")))
620 {
621 // chill
622 const char * pczChill = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("chill")));
623
624 setIntIfDifferent(&pEntry->iWindChill_, pczChill);
625
626 xmlFree(XMLCHAR_P(pczChill));
627
628 // direction
629 const char * pczDirection = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("direction")));
630
631 gint iValue = (gint)g_ascii_strtoll((pczDirection)?pczDirection:"999", NULL, 10);
632
633 const gchar * pczDir = WIND_DIRECTION(iValue);
634
635 setStringIfDifferent(&pEntry->pcWindDirection_, pczDir, strlen(pczDir));
636
637 xmlFree(XMLCHAR_P(pczDirection));
638
639 // speed
640 const char * pczSpeed = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("speed")));
641
642 setIntIfDifferent(&pEntry->iWindSpeed_, pczSpeed);
643
644 xmlFree(XMLCHAR_P(pczSpeed));
645 }
646 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("atmosphere")))
647 {
648 // humidity
649 const char * pczHumidity = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("humidity")));
650
651 setIntIfDifferent(&pEntry->iHumidity_, pczHumidity);
652
653 xmlFree(XMLCHAR_P(pczHumidity));
654
655 // pressure
656 const char * pczPressure = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("pressure")));
657
658 pEntry->dPressure_ = g_ascii_strtod((pczPressure)?pczPressure:"0", NULL);
659
660 xmlFree(XMLCHAR_P(pczPressure));
661
662 // visibility
663 const char * pczVisibility = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("visibility")));
664
665 pEntry->dVisibility_ = g_ascii_strtod((pczVisibility)?pczVisibility:"0", NULL);
666
667 // need to divide by 100
668 //pEntry->dVisibility_ = pEntry->dVisibility_/100;
669
670 xmlFree(XMLCHAR_P(pczVisibility));
671
672 // state
673 const char * pczState = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("rising")));
674
675 pEntry->pressureState_ = (PressureState)g_ascii_strtoll((pczState)?pczState:"0", NULL, 10);
676
677 xmlFree(XMLCHAR_P(pczState));
678 }
679 else if (xmlStrEqual(pCurr->name, CONSTXMLCHAR_P("astronomy")))
680 {
681 // sunrise
682 const char * pczSunrise = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("sunrise")));
683
684 gsize sunriseLength = ((pczSunrise)?strlen(pczSunrise):0);
685
686 setStringIfDifferent(&pEntry->pcSunrise_, pczSunrise, sunriseLength);
687
688 xmlFree(XMLCHAR_P(pczSunrise));
689
690 // sunset
691 const char * pczSunset = CONSTCHAR_P(xmlGetProp(pCurr, XMLCHAR_P("sunset")));
692
693 gsize sunsetLength = ((pczSunset)?strlen(pczSunset):0);
694
695 setStringIfDifferent(&pEntry->pcSunset_, pczSunset, sunsetLength);
696
697 xmlFree(XMLCHAR_P(pczSunset));
698 }
699
700 }
701
702 }
703
704 return pEntry;
705 }
706
707 /**
708 * Evaluates an XPath expression on the passed-in context.
709 *
710 * @param pContext Pointer to the context.
711 * @param pczExpression The XPath expression to evaluate
712 *
713 * @return xmlNodeSetPtr pointing to the resulting node set, must be
714 * freed by the caller.
715 */
716 static xmlNodeSetPtr
717 evaluateXPathExpression(xmlXPathContextPtr pContext, const char * pczExpression)
718 {
719 xmlXPathObjectPtr pObject = xmlXPathEval(CONSTXMLCHAR_P(pczExpression),
720 pContext);
721
722 if (!pObject || !pObject->nodesetval)
723 {
724 return NULL;
725 }
726
727 xmlNodeSetPtr pNodeSet = pObject->nodesetval;
728
729 xmlXPathFreeNodeSetList(pObject);
730
731 return pNodeSet;
732 }
733
734 /**
735 * Parses the response and fills in the supplied list with entries (if any)
736 *
737 * @param pResponse Pointer to the response received.
738 * @param pList Pointer to the pointer to the list to populate.
739 * @param pForecast Pointer to the pointer to the forecast to retrieve.
740 *
741 * @return 0 on success, -1 on failure
742 *
743 * @note If the pList pointer is NULL or the pForecast pointer is NULL,
744 * nothing is done and failure is returned. Otherwise, the appropriate
745 * pointer is set based on the name of the XML element:
746 * 'Result' for GList (pList)
747 * 'channel' for Forecast (pForecast)
748 */
749 static gint
750 parseResponse(gpointer pResponse, GList ** pList, gpointer * pForecast)
751 {
752 int iLocation = (pList)?1:0;
753
754 xmlDocPtr pDoc = xmlReadMemory(CONSTCHAR_P(pResponse),
755 strlen(pResponse),
756 "",
757 NULL,
758 0);
759
760 if (!pDoc)
761 {
762 // failed
763 LXW_LOG(LXW_ERROR, "yahooutil::parseResponse(): Failed to parse response %s",
764 CONSTCHAR_P(pResponse));
765
766 return -1;
767 }
768
769 xmlNodePtr pRoot = xmlDocGetRootElement(pDoc);
770
771 // the second part of the if can be broken out
772 if (!pRoot || !xmlStrEqual(pRoot->name, CONSTXMLCHAR_P("query")))
773 {
774 // failed
775 LXW_LOG(LXW_ERROR, "yahooutil::parseResponse(): Failed to retrieve root %s",
776 CONSTCHAR_P(pResponse));
777
778 xmlFreeDoc(pDoc);
779
780 return -1;
781 }
782
783 // use xpath to find /query/results/Result
784 xmlXPathInit();
785
786 xmlXPathContextPtr pXCtxt = xmlXPathNewContext(pDoc);
787
788 const char * pczExpression = "/query/results/channel";
789
790 if (iLocation)
791 {
792 pczExpression = "/query/results/Result";
793 }
794
795 // have some results...
796 xmlNodeSetPtr pNodeSet = evaluateXPathExpression(pXCtxt, pczExpression);
797
798 if (!pNodeSet)
799 {
800 // error, or no results found -- failed
801 xmlXPathFreeContext(pXCtxt);
802
803 xmlFreeDoc(pDoc);
804
805 return -1;
806 }
807
808 int iCount = 0;
809 int iSize = pNodeSet->nodeNr;
810
811 gint iRetVal = 0;
812
813 for (; iCount < iSize; ++iCount)
814 {
815 if (pNodeSet->nodeTab)
816 {
817 xmlNodePtr pNode = pNodeSet->nodeTab[iCount];
818
819 if (pNode && pNode->type == XML_ELEMENT_NODE)
820 {
821 if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("Result")))
822 {
823 gpointer pEntry = processResultNode(pNode);
824
825 if (pEntry && pList)
826 {
827 *pList = g_list_prepend(*pList, pEntry);
828 }
829 }
830 else if (xmlStrEqual(pNode->name, CONSTXMLCHAR_P("channel")))
831 {
832 ForecastInfo * pEntry = NULL;
833
834 gboolean bNewed = FALSE;
835
836 /* Check if forecast is allocated, if not,
837 * allocate and populate
838 */
839 if (pForecast)
840 {
841 if (*pForecast)
842 {
843 pEntry = (ForecastInfo *)*pForecast;
844 }
845 else
846 {
847 pEntry = (ForecastInfo *)g_try_new0(ForecastInfo, 1);
848
849 bNewed = TRUE;
850 }
851
852 if (!pEntry)
853 {
854 iRetVal = -1;
855 }
856 else
857 {
858 *pForecast = processChannelNode(pNode, pEntry);
859
860 if (!*pForecast)
861 {
862 /* Failed, forecast is freed by caller */
863
864 /* Unless it was just newed... */
865 if (bNewed)
866 {
867 g_free(pEntry);
868 }
869
870 iRetVal = -1;
871 }
872 }
873
874 }// end else if pForecast
875
876 }// end else if 'channel'
877
878 }// end if element
879
880 }// end if nodeTab
881
882 }// end for noteTab size
883
884 xmlXPathFreeNodeSet(pNodeSet);
885
886 xmlXPathFreeContext(pXCtxt);
887
888 xmlFreeDoc(pDoc);
889
890 return iRetVal;
891 }
892
893 /**
894 * Initializes the internals: XML
895 *
896 */
897 void
898 initializeYahooUtil(void)
899 {
900 if (!g_iInitialized)
901 {
902 xmlInitParser();
903
904 g_iInitialized = 1;
905 }
906 }
907
908 /**
909 * Cleans up the internals: XML
910 *
911 */
912 void
913 cleanupYahooUtil(void)
914 {
915 if (g_iInitialized)
916 {
917 xmlCleanupParser();
918
919 g_iInitialized = 0;
920 }
921 }
922
923 /**
924 * Retrieves the details for the specified location
925 *
926 * @param pczLocation The string containing the name/code of the location
927 *
928 * @return A pointer to a list of LocationInfo entries, possibly empty,
929 * if no details were found. Caller is responsible for freeing the list.
930 */
931 GList *
932 getLocationInfo(const gchar * pczLocation)
933 {
934 gint iRetCode = 0;
935 gint iDataSize = 0;
936
937 GList * pList = NULL;
938
939 gchar * pcEscapedLocation = convertToASCII(pczLocation);
940
941 gsize len = getWOEIDQueryLength(pcEscapedLocation);
942
943 gchar cQueryBuffer[len];
944 bzero(cQueryBuffer, len);
945
946 gint iRet = getWOEIDQuery(cQueryBuffer, pcEscapedLocation);
947
948 g_free(pcEscapedLocation);
949
950 LXW_LOG(LXW_DEBUG, "yahooutil::getLocationInfo(%s): query[%d]: %s",
951 pczLocation, iRet, cQueryBuffer);
952
953 gpointer pResponse = getURL(cQueryBuffer, &iRetCode, &iDataSize);
954
955 if (!pResponse || iRetCode != HTTP_STATUS_OK)
956 {
957 LXW_LOG(LXW_ERROR, "yahooutil::getLocationInfo(%s): Failed with error code %d",
958 pczLocation, iRetCode);
959 }
960 else
961 {
962 LXW_LOG(LXW_DEBUG, "yahooutil::getLocationInfo(%s): Response code: %d, size: %d",
963 pczLocation, iRetCode, iDataSize);
964
965 LXW_LOG(LXW_VERBOSE, "yahooutil::getLocation(%s): Contents: %s",
966 pczLocation, (const char *)pResponse);
967
968 iRet = parseResponse(pResponse, &pList, NULL);
969
970 LXW_LOG(LXW_DEBUG, "yahooutil::getLocation(%s): Response parsing returned %d",
971 pczLocation, iRet);
972
973 if (iRet)
974 {
975 // failure
976 g_list_free_full(pList, freeLocation);
977 }
978
979 }
980
981 g_free(pResponse);
982
983 return pList;
984 }
985
986 /**
987 * Retrieves the forecast for the specified location WOEID
988 *
989 * @param pczWOEID The string containing the WOEID of the location
990 * @param czUnits The character containing the units for the forecast (c|f)
991 * @param pForecast The pointer to the forecast to be filled. If set to NULL,
992 * a new one will be allocated.
993 *
994 */
995 void
996 getForecastInfo(const gchar * pczWOEID, const gchar czUnits, gpointer * pForecast)
997 {
998 gint iRetCode = 0;
999 gint iDataSize = 0;
1000
1001 gsize len = getForecastQueryLength(pczWOEID);
1002
1003 gchar cQueryBuffer[len];
1004 bzero(cQueryBuffer, len);
1005
1006 gint iRet = getForecastQuery(cQueryBuffer, pczWOEID, czUnits);
1007
1008 LXW_LOG(LXW_DEBUG, "yahooutil::getForecastInfo(%s): query[%d]: %s",
1009 pczWOEID, iRet, cQueryBuffer);
1010
1011 gpointer pResponse = getURL(cQueryBuffer, &iRetCode, &iDataSize);
1012
1013 if (!pResponse || iRetCode != HTTP_STATUS_OK)
1014 {
1015 LXW_LOG(LXW_ERROR, "yahooutil::getForecastInfo(%s): Failed with error code %d",
1016 pczWOEID, iRetCode);
1017 }
1018 else
1019 {
1020 LXW_LOG(LXW_DEBUG, "yahooutil::getForecastInfo(%s): Response code: %d, size: %d",
1021 pczWOEID, iRetCode, iDataSize);
1022
1023 LXW_LOG(LXW_VERBOSE, "yahooutil::getForecastInfo(%s): Contents: %s",
1024 pczWOEID, (const char *)pResponse);
1025
1026 iRet = parseResponse(pResponse, NULL, pForecast);
1027
1028 LXW_LOG(LXW_DEBUG, "yahooutil::getForecastInfo(%s): Response parsing returned %d",
1029 pczWOEID, iRet);
1030
1031 if (iRet)
1032 {
1033 freeForecast(*pForecast);
1034
1035 *pForecast = NULL;
1036 }
1037
1038 }
1039
1040 g_free(pResponse);
1041 }