File: | weather-iwin.c |
Warning: | line 385, column 18 Value stored to 'session' during its initialization is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */ |
2 | /* weather-iwin.c - US National Weather Service IWIN forecast source |
3 | * |
4 | * This program is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU General Public License as |
6 | * published by the Free Software Foundation; either version 2 of the |
7 | * License, or (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, but |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * 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, see |
16 | * <http://www.gnu.org/licenses/>. |
17 | */ |
18 | |
19 | #ifdef HAVE_CONFIG_H1 |
20 | #include <config.h> |
21 | #endif |
22 | |
23 | #include <ctype.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | |
27 | #include <libxml/parser.h> |
28 | |
29 | #define CAFEWEATHER_I_KNOW_THIS_IS_UNSTABLE |
30 | #include "weather.h" |
31 | #include "weather-priv.h" |
32 | |
33 | /** |
34 | * Humans don't deal well with .MONDAY...SUNNY AND BLAH BLAH.TUESDAY...THEN THIS AND THAT.WEDNESDAY...RAINY BLAH BLAH. |
35 | * This function makes it easier to read. |
36 | */ |
37 | static gchar * |
38 | formatWeatherMsg (gchar *forecast) |
39 | { |
40 | gchar *ptr = forecast; |
41 | gchar *startLine = NULL((void*)0); |
42 | |
43 | while (0 != *ptr) { |
44 | if (ptr[0] == '\n' && ptr[1] == '.') { |
45 | /* This removes the preamble by shifting the relevant data |
46 | * down to the start of the buffer. */ |
47 | if (NULL((void*)0) == startLine) { |
48 | memmove (forecast, ptr, strlen (ptr) + 1); |
49 | ptr = forecast; |
50 | ptr[0] = ' '; |
51 | } |
52 | ptr[1] = '\n'; |
53 | ptr += 2; |
54 | startLine = ptr; |
55 | } else if (ptr[0] == '.' && ptr[1] == '.' && ptr[2] == '.' && NULL((void*)0) != startLine) { |
56 | memmove (startLine + 2, startLine, (ptr - startLine) * sizeof (gchar)); |
57 | startLine[0] = ' '; |
58 | startLine[1] = '\n'; |
59 | ptr[2] = '\n'; |
60 | |
61 | ptr += 3; |
62 | |
63 | } else if (ptr[0] == '$' && ptr[1] == '$') { |
64 | ptr[0] = ptr[1] = ' '; |
65 | |
66 | } else { |
67 | ptr++; |
68 | } |
69 | } |
70 | |
71 | return forecast; |
72 | } |
73 | |
74 | static gboolean |
75 | hasAttr (xmlNode *node, const char *attr_name, const char *attr_value) |
76 | { |
77 | xmlChar *attr; |
78 | gboolean res = FALSE(0); |
79 | |
80 | if (!node) |
81 | return res; |
82 | |
83 | attr = xmlGetProp (node, (const xmlChar *) attr_name); |
84 | |
85 | if (!attr) |
86 | return res; |
87 | |
88 | res = g_str_equal ((const char *)attr, attr_value)(strcmp ((const char *) ((const char *)attr), (const char *) ( attr_value)) == 0); |
89 | |
90 | xmlFree (attr); |
91 | |
92 | return res; |
93 | } |
94 | |
95 | static GSList * |
96 | parseForecastXml (const char *buff, WeatherInfo *master_info) |
97 | { |
98 | GSList *res = NULL((void*)0); |
99 | xmlDocPtr doc; |
100 | xmlNode *root, *node; |
101 | |
102 | g_return_val_if_fail (master_info != NULL, NULL)do { if ((master_info != ((void*)0))) { } else { g_return_if_fail_warning ("CafeWeather", ((const char*) (__func__)), "master_info != NULL" ); return (((void*)0)); } } while (0); |
103 | |
104 | if (!buff || !*buff) |
105 | return NULL((void*)0); |
106 | |
107 | #define XC (const xmlChar *) |
108 | #define isElem(_node,_name) g_str_equal ((const char *)_node->name, _name)(strcmp ((const char *) ((const char *)_node->name), (const char *) (_name)) == 0) |
109 | |
110 | doc = xmlParseMemory (buff, strlen (buff)); |
111 | if (!doc) |
112 | return NULL((void*)0); |
113 | |
114 | /* Description at http://www.weather.gov/mdl/XML/Design/MDL_XML_Design.pdf */ |
115 | root = xmlDocGetRootElement (doc); |
116 | for (node = root->xmlChildrenNodechildren; node; node = node->next) { |
117 | if (node->name == NULL((void*)0) || node->type != XML_ELEMENT_NODE) |
118 | continue; |
119 | |
120 | if (isElem (node, "data")) { |
121 | xmlNode *n; |
122 | char *time_layout = NULL((void*)0); |
123 | time_t update_times[7] = {0}; |
124 | |
125 | for (n = node->children; n; n = n->next) { |
126 | if (!n->name) |
127 | continue; |
128 | |
129 | if (isElem (n, "time-layout")) { |
130 | if (!time_layout && hasAttr (n, "summarization", "24hourly")) { |
131 | xmlNode *c; |
132 | int count = 0; |
133 | |
134 | for (c = n->children; c && (count < 7 || !time_layout); c = c->next) { |
135 | if (c->name && !time_layout && isElem (c, "layout-key")) { |
136 | xmlChar *val = xmlNodeGetContent (c); |
137 | |
138 | if (val) { |
139 | time_layout = g_strdup ((const char *)val)g_strdup_inline ((const char *)val); |
140 | xmlFree (val); |
141 | } |
142 | } else if (c->name && isElem (c, "start-valid-time")) { |
143 | xmlChar *val = xmlNodeGetContent (c); |
144 | |
145 | if (val) { |
146 | GTimeVal tv; |
147 | |
148 | if (g_time_val_from_iso8601 ((const char *)val, &tv)) { |
149 | update_times[count] = tv.tv_sec; |
150 | } else { |
151 | update_times[count] = 0; |
152 | } |
153 | |
154 | count++; |
155 | |
156 | xmlFree (val); |
157 | } |
158 | } |
159 | } |
160 | |
161 | if (count != 7) { |
162 | /* There can be more than one time-layout element, the other |
163 | with only few children, which is not the one to use. */ |
164 | g_free (time_layout); |
165 | time_layout = NULL((void*)0); |
166 | } |
167 | } |
168 | } else if (isElem (n, "parameters")) { |
169 | xmlNode *p; |
170 | |
171 | /* time-layout should be always before parameters */ |
172 | if (!time_layout) |
173 | break; |
174 | |
175 | if (!res) { |
176 | int i; |
177 | |
178 | for (i = 0; i < 7; i++) { |
179 | WeatherInfo *nfo = weather_info_clone (master_info); |
180 | |
181 | if (nfo) { |
182 | nfo->valid = FALSE(0); |
183 | nfo->forecast_type = FORECAST_ZONE; |
184 | nfo->update = update_times [i]; |
185 | nfo->sky = -1; |
186 | nfo->temperature_unit = TEMP_UNIT_FAHRENHEIT; |
187 | nfo->temp = -1000.0; |
188 | nfo->temp_min = -1000.0; |
189 | nfo->temp_max = -1000.0; |
190 | nfo->tempMinMaxValid = FALSE(0); |
191 | nfo->cond.significant = FALSE(0); |
192 | nfo->cond.phenomenon = PHENOMENON_NONE; |
193 | nfo->cond.qualifier = QUALIFIER_NONE; |
194 | nfo->dew = -1000.0; |
195 | nfo->wind = -1; |
196 | nfo->windspeed = -1; |
197 | nfo->pressure = -1.0; |
198 | nfo->visibility = -1.0; |
199 | nfo->sunriseValid = FALSE(0); |
200 | nfo->sunsetValid = FALSE(0); |
201 | nfo->sunrise = 0; |
202 | nfo->sunset = 0; |
203 | g_free (nfo->forecast); |
204 | nfo->forecast = NULL((void*)0); |
205 | nfo->session = NULL((void*)0); |
206 | nfo->requests_pending = 0; |
207 | nfo->finish_cb = NULL((void*)0); |
208 | nfo->cb_data = NULL((void*)0); |
209 | res = g_slist_append (res, nfo); |
210 | } |
211 | } |
212 | } |
213 | |
214 | for (p = n->children; p; p = p->next) { |
215 | if (p->name && isElem (p, "temperature") && hasAttr (p, "time-layout", time_layout)) { |
216 | xmlNode *c; |
217 | GSList *at = res; |
218 | gboolean is_max = hasAttr (p, "type", "maximum"); |
219 | |
220 | if (!is_max && !hasAttr (p, "type", "minimum")) |
221 | break; |
222 | |
223 | for (c = p->children; c && at; c = c->next) { |
224 | if (isElem (c, "value")) { |
225 | WeatherInfo *nfo = (WeatherInfo *)at->data; |
226 | xmlChar *val = xmlNodeGetContent (c); |
227 | |
228 | /* can pass some values as <value xsi:nil="true"/> */ |
229 | if (!val || !*val) { |
230 | if (is_max) |
231 | nfo->temp_max = nfo->temp_min; |
232 | else |
233 | nfo->temp_min = nfo->temp_max; |
234 | } else { |
235 | if (is_max) |
236 | nfo->temp_max = atof ((const char *)val); |
237 | else |
238 | nfo->temp_min = atof ((const char *)val); |
239 | } |
240 | |
241 | if (val) |
242 | xmlFree (val); |
243 | |
244 | nfo->tempMinMaxValid = nfo->tempMinMaxValid || (nfo->temp_max > -999.0 && nfo->temp_min > -999.0); |
245 | nfo->valid = nfo->tempMinMaxValid; |
246 | |
247 | at = at->next; |
248 | } |
249 | } |
250 | } else if (p->name && isElem (p, "weather") && hasAttr (p, "time-layout", time_layout)) { |
251 | xmlNode *c; |
252 | GSList *at = res; |
253 | |
254 | for (c = p->children; c && at; c = c->next) { |
255 | if (c->name && isElem (c, "weather-conditions")) { |
256 | WeatherInfo *nfo = at->data; |
257 | xmlChar *val = xmlGetProp (c, XC "weather-summary"); |
258 | |
259 | if (val && nfo) { |
260 | /* Checking from top to bottom, if 'value' contains 'name', then that win, |
261 | thus put longer (more precise) values to the top. */ |
262 | int i; |
263 | struct _ph_list { |
264 | const char *name; |
265 | WeatherConditionPhenomenon ph; |
266 | } ph_list[] = { |
267 | { "Ice Crystals", PHENOMENON_ICE_CRYSTALS } , |
268 | { "Volcanic Ash", PHENOMENON_VOLCANIC_ASH } , |
269 | { "Blowing Sand", PHENOMENON_SANDSTORM } , |
270 | { "Blowing Dust", PHENOMENON_DUSTSTORM } , |
271 | { "Blowing Snow", PHENOMENON_FUNNEL_CLOUD } , |
272 | { "Drizzle", PHENOMENON_DRIZZLE } , |
273 | { "Rain", PHENOMENON_RAIN } , |
274 | { "Snow", PHENOMENON_SNOW } , |
275 | { "Fog", PHENOMENON_FOG } , |
276 | { "Smoke", PHENOMENON_SMOKE } , |
277 | { "Sand", PHENOMENON_SAND } , |
278 | { "Haze", PHENOMENON_HAZE } , |
279 | { "Dust", PHENOMENON_DUST } /*, |
280 | { "", PHENOMENON_SNOW_GRAINS } , |
281 | { "", PHENOMENON_ICE_PELLETS } , |
282 | { "", PHENOMENON_HAIL } , |
283 | { "", PHENOMENON_SMALL_HAIL } , |
284 | { "", PHENOMENON_UNKNOWN_PRECIPITATION } , |
285 | { "", PHENOMENON_MIST } , |
286 | { "", PHENOMENON_SPRAY } , |
287 | { "", PHENOMENON_SQUALL } , |
288 | { "", PHENOMENON_TORNADO } , |
289 | { "", PHENOMENON_DUST_WHIRLS } */ |
290 | }; |
291 | struct _sky_list { |
292 | const char *name; |
293 | WeatherSky sky; |
294 | } sky_list[] = { |
295 | { "Mostly Sunny", SKY_BROKEN } , |
296 | { "Mostly Clear", SKY_BROKEN } , |
297 | { "Partly Cloudy", SKY_SCATTERED } , |
298 | { "Mostly Cloudy", SKY_FEW } , |
299 | { "Sunny", SKY_CLEAR } , |
300 | { "Clear", SKY_CLEAR } , |
301 | { "Cloudy", SKY_OVERCAST } , |
302 | { "Clouds", SKY_SCATTERED } , |
303 | { "Rain", SKY_SCATTERED } , |
304 | { "Snow", SKY_SCATTERED } |
305 | }; |
306 | |
307 | nfo->valid = TRUE(!(0)); |
308 | g_free (nfo->forecast); |
309 | nfo->forecast = g_strdup ((const char *)val)g_strdup_inline ((const char *)val); |
310 | |
311 | for (i = 0; i < G_N_ELEMENTS (ph_list)(sizeof (ph_list) / sizeof ((ph_list)[0])); i++) { |
312 | if (strstr ((const char *)val, ph_list [i].name)) { |
313 | nfo->cond.phenomenon = ph_list [i].ph; |
314 | break; |
315 | } |
316 | } |
317 | |
318 | for (i = 0; i < G_N_ELEMENTS (sky_list)(sizeof (sky_list) / sizeof ((sky_list)[0])); i++) { |
319 | if (strstr ((const char *)val, sky_list [i].name)) { |
320 | nfo->sky = sky_list [i].sky; |
321 | break; |
322 | } |
323 | } |
324 | } |
325 | |
326 | if (val) |
327 | xmlFree (val); |
328 | |
329 | at = at->next; |
330 | } |
331 | } |
332 | } |
333 | } |
334 | |
335 | if (res) { |
336 | gboolean have_any = FALSE(0); |
337 | GSList *r; |
338 | |
339 | /* Remove invalid forecast data from the list. |
340 | They should be all valid or all invalid. */ |
341 | for (r = res; r; r = r->next) { |
342 | WeatherInfo *nfo = r->data; |
343 | |
344 | if (!nfo || !nfo->valid) { |
345 | if (r->data) |
346 | weather_info_free (r->data); |
347 | |
348 | r->data = NULL((void*)0); |
349 | } else { |
350 | have_any = TRUE(!(0)); |
351 | |
352 | if (nfo->tempMinMaxValid) |
353 | nfo->temp = (nfo->temp_min + nfo->temp_max) / 2.0; |
354 | } |
355 | } |
356 | |
357 | if (!have_any) { |
358 | /* data members are freed already */ |
359 | g_slist_free (res); |
360 | res = NULL((void*)0); |
361 | } |
362 | } |
363 | |
364 | break; |
365 | } |
366 | } |
367 | |
368 | g_free (time_layout); |
369 | |
370 | /* stop seeking XML */ |
371 | break; |
372 | } |
373 | } |
374 | xmlFreeDoc (doc); |
375 | |
376 | #undef XC |
377 | #undef isElem |
378 | |
379 | return res; |
380 | } |
381 | |
382 | static void |
383 | iwin_finish (GObject *object, GAsyncResult *result, gpointer data) |
384 | { |
385 | SoupSession *session = SOUP_SESSION (object); |
Value stored to 'session' during its initialization is never read | |
386 | SoupMessage *msg = soup_session_get_async_result_message (SOUP_SESSION (object), result); |
387 | WeatherInfo *info = (WeatherInfo *)data; |
388 | GBytes *response_body = NULL((void*)0); |
389 | const gchar *msgdata = NULL((void*)0); |
390 | |
391 | g_return_if_fail (info != NULL)do { if ((info != ((void*)0))) { } else { g_return_if_fail_warning ("CafeWeather", ((const char*) (__func__)), "info != NULL"); return; } } while (0); |
392 | |
393 | response_body = soup_session_send_and_read_finish(SOUP_SESSION(object), |
394 | result, NULL((void*)0)/*&error*/); |
395 | |
396 | if (!SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (msg))((soup_message_get_status (msg)) >= 200 && (soup_message_get_status (msg)) < 300)) { |
397 | /* forecast data is not really interesting anyway ;) */ |
398 | g_warning ("Failed to get IWIN forecast data: %d %s\n", |
399 | soup_message_get_status (msg), soup_message_get_reason_phrase (msg)); |
400 | request_done (info, FALSE(0)); |
401 | return; |
402 | } |
403 | |
404 | msgdata = g_bytes_get_data(response_body, NULL((void*)0)/*&length*/); |
405 | |
406 | if (info->forecast_type == FORECAST_LIST) |
407 | info->forecast_list = parseForecastXml (msgdata, info); |
408 | else |
409 | info->forecast = formatWeatherMsg (g_strdup (msgdata)g_strdup_inline (msgdata)); |
410 | |
411 | request_done (info, TRUE(!(0))); |
412 | } |
413 | |
414 | /* Get forecast into newly alloc'ed string */ |
415 | void |
416 | iwin_start_open (WeatherInfo *info) |
417 | { |
418 | gchar *url, *state, *zone; |
419 | WeatherLocation *loc; |
420 | SoupMessage *msg; |
421 | |
422 | g_return_if_fail (info != NULL)do { if ((info != ((void*)0))) { } else { g_return_if_fail_warning ("CafeWeather", ((const char*) (__func__)), "info != NULL"); return; } } while (0); |
423 | loc = info->location; |
424 | g_return_if_fail (loc != NULL)do { if ((loc != ((void*)0))) { } else { g_return_if_fail_warning ("CafeWeather", ((const char*) (__func__)), "loc != NULL"); return ; } } while (0); |
425 | |
426 | if (loc->zone[0] == '-' && (info->forecast_type != FORECAST_LIST || !loc->latlon_valid)) |
427 | return; |
428 | |
429 | if (info->forecast) { |
430 | g_free (info->forecast); |
431 | info->forecast = NULL((void*)0); |
432 | } |
433 | |
434 | free_forecast_list (info); |
435 | |
436 | if (info->forecast_type == FORECAST_LIST) { |
437 | /* see the description here: http://www.weather.gov/forecasts/xml/ */ |
438 | if (loc->latlon_valid) { |
439 | struct tm tm; |
440 | time_t now = time (NULL((void*)0)); |
441 | |
442 | localtime_r (&now, &tm); |
443 | |
444 | url = g_strdup_printf ("http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php?&lat=%.02f&lon=%.02f&format=24+hourly&startDate=%04d-%02d-%02d&numDays=7", |
445 | RADIANS_TO_DEGREES (loc->latitude)((loc->latitude) * 180. / 3.14159265358979323846), RADIANS_TO_DEGREES (loc->longitude)((loc->longitude) * 180. / 3.14159265358979323846), 1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday); |
446 | |
447 | msg = soup_message_new ("GET", url); |
448 | g_free (url); |
449 | soup_session_send_and_read_async (info->session, msg, G_PRIORITY_DEFAULT0, NULL((void*)0), iwin_finish, info); |
450 | |
451 | info->requests_pending++; |
452 | } |
453 | |
454 | return; |
455 | } |
456 | |
457 | if (loc->zone[0] == ':') { |
458 | /* Met Office Region Names */ |
459 | metoffice_start_open (info); |
460 | return; |
461 | } else if (loc->zone[0] == '@') { |
462 | /* Australian BOM forecasts */ |
463 | bom_start_open (info); |
464 | return; |
465 | } |
466 | |
467 | /* The zone for Pittsburgh (for example) is given as PAZ021 in the locations |
468 | ** file (the PA stands for the state pennsylvania). The url used wants the state |
469 | ** as pa, and the zone as lower case paz021. |
470 | */ |
471 | zone = g_ascii_strdown (loc->zone, -1); |
472 | state = g_strndup (zone, 2); |
473 | |
474 | url = g_strdup_printf ("http://tgftp.nws.noaa.gov/data/forecasts/zone/%s/%s.txt", state, zone); |
475 | |
476 | g_free (zone); |
477 | g_free (state); |
478 | |
479 | msg = soup_message_new ("GET", url); |
480 | g_free (url); |
481 | soup_session_send_and_read_async (info->session, msg, G_PRIORITY_DEFAULT0, NULL((void*)0), iwin_finish, info); |
482 | |
483 | info->requests_pending++; |
484 | } |