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