1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4
5 #include <dirent.h>
6 #include <errno.h>
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <string.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <time.h>
13 #include <unistd.h>
14 #include <math.h>
15
16 #include <glib.h>
17 #include <gio/gio.h>
18 #include <gtk/gtk.h>
19
20 #include "clock-location.h"
21 #include "clock-marshallers.h"
22 #include "set-timezone.h"
23 #include "system-timezone.h"
24
25 typedef struct {
26 gchar *name;
27 gchar *city;
28
29 SystemTimezone *systz;
30
31 gchar *timezone;
32
33 gchar *tzname;
34
35 gfloat latitude;
36 gfloat longitude;
37
38 gchar *weather_code;
39 WeatherInfo *weather_info;
40 guint weather_timeout;
41 guint weather_retry_time;
42
43 TempUnit temperature_unit;
44 SpeedUnit speed_unit;
45 } ClockLocationPrivate;
46
47 G_DEFINE_TYPE_WITH_PRIVATE (ClockLocation, clock_location, G_TYPE_OBJECT)
48
49 #define WEATHER_TIMEOUT_BASE 30
50 #define WEATHER_TIMEOUT_MAX 1800
51 #define WEATHER_EMPTY_CODE "-"
52
53 enum {
54 WEATHER_UPDATED,
55 SET_CURRENT,
56 LAST_SIGNAL
57 };
58
59 static guint location_signals[LAST_SIGNAL] = { 0 };
60
61 static void clock_location_finalize (GObject *);
62 static void clock_location_set_tz (ClockLocation *this);
63 static void clock_location_unset_tz (ClockLocation *this);
64 static gboolean update_weather_info (gpointer data);
65 static void setup_weather_updates (ClockLocation *loc);
66
67 static gchar *clock_location_get_valid_weather_code (const gchar *code);
68
69 ClockLocation *
clock_location_find_and_ref(GSList * locations,const gchar * name,const gchar * city,const gchar * timezone,gfloat latitude,gfloat longitude,const gchar * code)70 clock_location_find_and_ref (GSList *locations,
71 const gchar *name,
72 const gchar *city,
73 const gchar *timezone,
74 gfloat latitude,
75 gfloat longitude,
76 const gchar *code)
77 {
78 GSList *l;
79
80 for (l = locations; l != NULL; l = l->next) {
81
82 ClockLocationPrivate *priv =
83 clock_location_get_instance_private (l->data);
84
85 if (priv->latitude == latitude &&
86 priv->longitude == longitude &&
87 g_strcmp0 (priv->weather_code, code) == 0 &&
88 g_strcmp0 (priv->timezone, timezone) == 0 &&
89 g_strcmp0 (priv->city, city) == 0 &&
90 g_strcmp0 (priv->name, name) == 0)
91 break;
92 }
93
94 if (l != NULL)
95 return CLOCK_LOCATION (l->data);
96 else
97 return NULL;
98 }
99
100 ClockLocation *
clock_location_new(const gchar * name,const gchar * city,const gchar * timezone,gfloat latitude,gfloat longitude,const gchar * code,WeatherPrefs * prefs)101 clock_location_new (const gchar *name, const gchar *city,
102 const gchar *timezone,
103 gfloat latitude, gfloat longitude,
104 const gchar *code, WeatherPrefs *prefs)
105 {
106 ClockLocation *this;
107 ClockLocationPrivate *priv;
108
109 this = g_object_new (CLOCK_LOCATION_TYPE, NULL);
110 priv = clock_location_get_instance_private (this);
111
112 priv->name = g_strdup (name);
113 priv->city = g_strdup (city);
114 priv->timezone = g_strdup (timezone);
115
116 /* initialize priv->tzname */
117 clock_location_set_tz (this);
118 clock_location_unset_tz (this);
119
120 priv->latitude = latitude;
121 priv->longitude = longitude;
122
123 priv->weather_code = clock_location_get_valid_weather_code (code);
124
125 if (prefs) {
126 priv->temperature_unit = prefs->temperature_unit;
127 priv->speed_unit = prefs->speed_unit;
128 }
129
130 setup_weather_updates (this);
131
132 return this;
133 }
134
135 static ClockLocation *current_location = NULL;
136
137 static void
clock_location_class_init(ClockLocationClass * this_class)138 clock_location_class_init (ClockLocationClass *this_class)
139 {
140 GObjectClass *g_obj_class = G_OBJECT_CLASS (this_class);
141
142 g_obj_class->finalize = clock_location_finalize;
143
144 location_signals[WEATHER_UPDATED] =
145 g_signal_new ("weather-updated",
146 G_OBJECT_CLASS_TYPE (g_obj_class),
147 G_SIGNAL_RUN_FIRST,
148 G_STRUCT_OFFSET (ClockLocationClass, weather_updated),
149 NULL, NULL,
150 g_cclosure_marshal_VOID__POINTER,
151 G_TYPE_NONE, 1, G_TYPE_POINTER);
152
153 location_signals[SET_CURRENT] =
154 g_signal_new ("set-current",
155 G_OBJECT_CLASS_TYPE (g_obj_class),
156 G_SIGNAL_RUN_FIRST,
157 G_STRUCT_OFFSET (ClockLocationClass, set_current),
158 NULL, NULL,
159 g_cclosure_marshal_VOID__VOID,
160 G_TYPE_NONE, 0);
161 }
162
163 static void
network_changed(GNetworkMonitor * monitor,gboolean available,ClockLocation * loc)164 network_changed (GNetworkMonitor *monitor,
165 gboolean available,
166 ClockLocation *loc)
167 {
168 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
169
170 if (available) {
171 priv->weather_retry_time = WEATHER_TIMEOUT_BASE;
172 update_weather_info (loc);
173 }
174 }
175
176 static void
clock_location_init(ClockLocation * this)177 clock_location_init (ClockLocation *this)
178 {
179 ClockLocationPrivate *priv = clock_location_get_instance_private (this);
180 GNetworkMonitor *monitor;
181
182 priv->name = NULL;
183 priv->city = NULL;
184
185 priv->systz = system_timezone_new ();
186
187 priv->timezone = NULL;
188
189 priv->tzname = NULL;
190
191 priv->latitude = 0;
192 priv->longitude = 0;
193
194 monitor = g_network_monitor_get_default();
195 g_signal_connect (monitor, "network-changed",
196 G_CALLBACK (network_changed), this);
197
198 priv->temperature_unit = TEMP_UNIT_CENTIGRADE;
199 priv->speed_unit = SPEED_UNIT_MS;
200 }
201
202 static void
clock_location_finalize(GObject * g_obj)203 clock_location_finalize (GObject *g_obj)
204 {
205 ClockLocationPrivate *priv = clock_location_get_instance_private (CLOCK_LOCATION(g_obj));
206 GNetworkMonitor *monitor;
207
208 monitor = g_network_monitor_get_default ();
209 g_signal_handlers_disconnect_by_func (monitor,
210 G_CALLBACK (network_changed),
211 CLOCK_LOCATION (g_obj));
212
213 g_free (priv->name);
214 g_free (priv->city);
215
216 if (priv->systz) {
217 g_object_unref (priv->systz);
218 priv->systz = NULL;
219 }
220
221 g_free (priv->timezone);
222 g_free (priv->tzname);
223 g_free (priv->weather_code);
224
225 if (priv->weather_info) {
226 weather_info_free (priv->weather_info);
227 priv->weather_info = NULL;
228 }
229
230 if (priv->weather_timeout) {
231 g_source_remove (priv->weather_timeout);
232 priv->weather_timeout = 0;
233 }
234
235 G_OBJECT_CLASS (clock_location_parent_class)->finalize (g_obj);
236 }
237
238 const gchar *
clock_location_get_display_name(ClockLocation * loc)239 clock_location_get_display_name (ClockLocation *loc)
240 {
241 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
242
243 if (priv->name && priv->name[0])
244 return priv->name;
245 else
246 return priv->city;
247 }
248
249 const gchar *
clock_location_get_name(ClockLocation * loc)250 clock_location_get_name (ClockLocation *loc)
251 {
252 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
253
254 return priv->name;
255 }
256
257 void
clock_location_set_name(ClockLocation * loc,const gchar * name)258 clock_location_set_name (ClockLocation *loc, const gchar *name)
259 {
260 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
261
262 g_free (priv->name);
263 priv->name = g_strdup (name);
264 }
265
266 const gchar *
clock_location_get_city(ClockLocation * loc)267 clock_location_get_city (ClockLocation *loc)
268 {
269 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
270
271 return priv->city;
272 }
273
274 void
clock_location_set_city(ClockLocation * loc,const gchar * city)275 clock_location_set_city (ClockLocation *loc, const gchar *city)
276 {
277 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
278
279 g_free (priv->city);
280 priv->city = g_strdup (city);
281 }
282
283 gchar *
clock_location_get_timezone(ClockLocation * loc)284 clock_location_get_timezone (ClockLocation *loc)
285 {
286 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
287
288 return priv->timezone;
289 }
290
291 void
clock_location_set_timezone(ClockLocation * loc,const gchar * timezone)292 clock_location_set_timezone (ClockLocation *loc, const gchar *timezone)
293 {
294 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
295
296 g_free (priv->timezone);
297 priv->timezone = g_strdup (timezone);
298 }
299
300 gchar *
clock_location_get_tzname(ClockLocation * loc)301 clock_location_get_tzname (ClockLocation *loc)
302 {
303 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
304
305 return priv->tzname;
306 }
307
308 void
clock_location_get_coords(ClockLocation * loc,gfloat * latitude,gfloat * longitude)309 clock_location_get_coords (ClockLocation *loc, gfloat *latitude,
310 gfloat *longitude)
311 {
312 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
313
314 *latitude = priv->latitude;
315 *longitude = priv->longitude;
316 }
317
318 void
clock_location_set_coords(ClockLocation * loc,gfloat latitude,gfloat longitude)319 clock_location_set_coords (ClockLocation *loc, gfloat latitude,
320 gfloat longitude)
321 {
322 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
323
324 priv->latitude = latitude;
325 priv->longitude = longitude;
326 }
327
328 static void
clock_location_set_tzname(ClockLocation * this,const char * tzname)329 clock_location_set_tzname (ClockLocation *this, const char *tzname)
330 {
331 ClockLocationPrivate *priv = clock_location_get_instance_private (CLOCK_LOCATION(this));
332
333 if (priv->tzname && strcmp (priv->tzname, tzname) == 0)
334 return;
335
336 g_free (priv->tzname);
337 if (tzname && *tzname != '\0') {
338 priv->tzname = g_strdup (tzname);
339 } else {
340 priv->tzname = NULL;
341 }
342 }
343
344 static void
clock_location_set_tz(ClockLocation * this)345 clock_location_set_tz (ClockLocation *this)
346 {
347 ClockLocationPrivate *priv = clock_location_get_instance_private (this);
348
349 time_t now_t;
350 struct tm now;
351
352 if (priv->timezone == NULL) {
353 return;
354 }
355
356 setenv ("TZ", priv->timezone, 1);
357 tzset();
358
359 now_t = time (NULL);
360 localtime_r (&now_t, &now);
361
362 if (now.tm_isdst > 0) {
363 clock_location_set_tzname (this, tzname[1]);
364 } else {
365 clock_location_set_tzname (this, tzname[0]);
366 }
367 }
368
369 static void
clock_location_unset_tz(ClockLocation * this)370 clock_location_unset_tz (ClockLocation *this)
371 {
372 ClockLocationPrivate *priv = clock_location_get_instance_private (this);
373 const char *env_timezone;
374
375 if (priv->timezone == NULL) {
376 return;
377 }
378
379 env_timezone = system_timezone_get_env (priv->systz);
380
381 if (env_timezone) {
382 setenv ("TZ", env_timezone, 1);
383 } else {
384 unsetenv ("TZ");
385 }
386 tzset();
387 }
388
389 void
clock_location_localtime(ClockLocation * loc,struct tm * tm)390 clock_location_localtime (ClockLocation *loc, struct tm *tm)
391 {
392 time_t now;
393
394 clock_location_set_tz (loc);
395
396 time (&now);
397 localtime_r (&now, tm);
398
399 clock_location_unset_tz (loc);
400 }
401
402 gboolean
clock_location_is_current_timezone(ClockLocation * loc)403 clock_location_is_current_timezone (ClockLocation *loc)
404 {
405 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
406 const char *zone;
407
408 zone = system_timezone_get (priv->systz);
409
410 if (zone)
411 return strcmp (zone, priv->timezone) == 0;
412 else
413 return clock_location_get_offset (loc) == 0;
414 }
415
416 gboolean
clock_location_is_current(ClockLocation * loc)417 clock_location_is_current (ClockLocation *loc)
418 {
419 if (current_location == loc)
420 return TRUE;
421 else if (current_location != NULL)
422 return FALSE;
423
424 if (clock_location_is_current_timezone (loc)) {
425 /* Note that some code in clock.c depends on the fact that
426 * calling this function can set the current location if
427 * there's none */
428 current_location = loc;
429 g_object_add_weak_pointer (G_OBJECT (current_location),
430 (gpointer *)¤t_location);
431 g_signal_emit (current_location, location_signals[SET_CURRENT],
432 0, NULL);
433
434 return TRUE;
435 }
436
437 return FALSE;
438 }
439
440
441 glong
clock_location_get_offset(ClockLocation * loc)442 clock_location_get_offset (ClockLocation *loc)
443 {
444 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
445 glong sys_timezone, local_timezone;
446 glong offset;
447 time_t t;
448 struct tm *tm;
449
450 t = time (NULL);
451
452 unsetenv ("TZ");
453 tm = localtime (&t);
454 sys_timezone = timezone;
455
456 if (tm->tm_isdst > 0) {
457 sys_timezone -= 3600;
458 }
459
460 setenv ("TZ", priv->timezone, 1);
461 tm = localtime (&t);
462 local_timezone = timezone;
463
464 if (tm->tm_isdst > 0) {
465 local_timezone -= 3600;
466 }
467
468 offset = local_timezone - sys_timezone;
469
470 clock_location_unset_tz (loc);
471
472 return offset;
473 }
474
475 typedef struct {
476 ClockLocation *location;
477 GFunc callback;
478 gpointer data;
479 GDestroyNotify destroy;
480 } MakeCurrentData;
481
482 static void
make_current_cb(gpointer data,GError * error)483 make_current_cb (gpointer data, GError *error)
484 {
485 MakeCurrentData *mcdata = data;
486
487 if (error == NULL) {
488 if (current_location)
489 g_object_remove_weak_pointer (G_OBJECT (current_location),
490 (gpointer *)¤t_location);
491 current_location = mcdata->location;
492 g_object_add_weak_pointer (G_OBJECT (current_location),
493 (gpointer *)¤t_location);
494 g_signal_emit (current_location, location_signals[SET_CURRENT],
495 0, NULL);
496 }
497
498 if (mcdata->callback)
499 mcdata->callback (mcdata->data, error);
500 }
501
502 static void
free_make_current_data(gpointer data)503 free_make_current_data (gpointer data)
504 {
505 MakeCurrentData *mcdata = data;
506
507 if (mcdata->destroy)
508 mcdata->destroy (mcdata->data);
509
510 g_object_unref (mcdata->location);
511 g_free (mcdata);
512 }
513
514 void
clock_location_make_current(ClockLocation * loc,GFunc callback,gpointer data,GDestroyNotify destroy)515 clock_location_make_current (ClockLocation *loc,
516 GFunc callback,
517 gpointer data,
518 GDestroyNotify destroy)
519 {
520 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
521 gchar *filename;
522 MakeCurrentData *mcdata;
523
524 if (loc == current_location) {
525 if (destroy)
526 destroy (data);
527 return;
528 }
529
530 if (clock_location_is_current_timezone (loc)) {
531 if (current_location)
532 g_object_remove_weak_pointer (G_OBJECT (current_location),
533 (gpointer *)¤t_location);
534 current_location = loc;
535 g_object_add_weak_pointer (G_OBJECT (current_location),
536 (gpointer *)¤t_location);
537 g_signal_emit (current_location, location_signals[SET_CURRENT],
538 0, NULL);
539 if (callback)
540 callback (data, NULL);
541 if (destroy)
542 destroy (data);
543 return;
544 }
545
546 mcdata = g_new (MakeCurrentData, 1);
547
548 mcdata->location = g_object_ref (loc);
549 mcdata->callback = callback;
550 mcdata->data = data;
551 mcdata->destroy = destroy;
552
553 filename = g_build_filename (SYSTEM_ZONEINFODIR, priv->timezone, NULL);
554 set_system_timezone_async (filename,
555 (GFunc)make_current_cb,
556 mcdata,
557 free_make_current_data);
558 g_free (filename);
559 }
560
561 static gchar *
clock_location_get_valid_weather_code(const gchar * code)562 clock_location_get_valid_weather_code (const gchar *code)
563 {
564 if (!code || *code == '\0')
565 return g_strdup (WEATHER_EMPTY_CODE);
566 else
567 return g_strdup (code);
568 }
569
570 const gchar *
clock_location_get_weather_code(ClockLocation * loc)571 clock_location_get_weather_code (ClockLocation *loc)
572 {
573 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
574
575 return priv->weather_code;
576 }
577
578 void
clock_location_set_weather_code(ClockLocation * loc,const gchar * code)579 clock_location_set_weather_code (ClockLocation *loc, const gchar *code)
580 {
581 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
582
583 g_free (priv->weather_code);
584 priv->weather_code = clock_location_get_valid_weather_code (code);
585
586 setup_weather_updates (loc);
587 }
588
589 WeatherInfo *
clock_location_get_weather_info(ClockLocation * loc)590 clock_location_get_weather_info (ClockLocation *loc)
591 {
592 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
593
594 return priv->weather_info;
595 }
596
597 static void
set_weather_update_timeout(ClockLocation * loc)598 set_weather_update_timeout (ClockLocation *loc)
599 {
600 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
601 guint timeout;
602
603 if (!weather_info_network_error (priv->weather_info)) {
604 /* The last update succeeded; set the next update to
605 * happen in half an hour, and reset the retry timer.
606 */
607 timeout = WEATHER_TIMEOUT_MAX;
608 priv->weather_retry_time = WEATHER_TIMEOUT_BASE;
609 } else {
610 /* The last update failed; set the next update
611 * according to the retry timer, and exponentially
612 * back off the retry timer.
613 */
614 timeout = priv->weather_retry_time;
615 priv->weather_retry_time *= 2;
616 if (priv->weather_retry_time > WEATHER_TIMEOUT_MAX)
617 priv->weather_retry_time = WEATHER_TIMEOUT_MAX;
618 }
619
620 if (priv->weather_timeout)
621 g_source_remove (priv->weather_timeout);
622 priv->weather_timeout =
623 g_timeout_add_seconds (timeout, update_weather_info, loc);
624 }
625
626 static void
weather_info_updated(WeatherInfo * info,gpointer data)627 weather_info_updated (WeatherInfo *info, gpointer data)
628 {
629 ClockLocation *loc = data;
630 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
631
632 set_weather_update_timeout (loc);
633 g_signal_emit (loc, location_signals[WEATHER_UPDATED],
634 0, priv->weather_info);
635 }
636
637 static gboolean
update_weather_info(gpointer data)638 update_weather_info (gpointer data)
639 {
640 ClockLocation *loc = (ClockLocation *) data;
641 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
642 WeatherPrefs prefs = {
643 FORECAST_STATE,
644 FALSE,
645 NULL,
646 TEMP_UNIT_CENTIGRADE,
647 SPEED_UNIT_MS,
648 PRESSURE_UNIT_MB,
649 DISTANCE_UNIT_KM
650 };
651
652 /* set temperature and speed units only if different from
653 * invalid/default
654 */
655 if (priv->temperature_unit > TEMP_UNIT_DEFAULT)
656 prefs.temperature_unit = priv->temperature_unit;
657 if (priv->speed_unit > SPEED_UNIT_DEFAULT)
658 prefs.speed_unit = priv->speed_unit;
659
660 weather_info_abort (priv->weather_info);
661 weather_info_update (priv->weather_info,
662 &prefs, weather_info_updated, loc);
663
664 return TRUE;
665 }
666
667 static gchar *
rad2dms(gfloat lat,gfloat lon)668 rad2dms (gfloat lat, gfloat lon)
669 {
670 gchar h, h2;
671 gfloat d, deg, min, d2, deg2, min2;
672
673 h = lat > 0 ? 'N' : 'S';
674 d = fabs (lat);
675 deg = floor (d);
676 min = floor (60 * (d - deg));
677 h2 = lon > 0 ? 'E' : 'W';
678 d2 = fabs (lon);
679 deg2 = floor (d2);
680 min2 = floor (60 * (d2 - deg2));
681 return g_strdup_printf ("%02d-%02d%c %02d-%02d%c",
682 (int)deg, (int)min, h,
683 (int)deg2, (int)min2, h2);
684 }
685
686 static void
setup_weather_updates(ClockLocation * loc)687 setup_weather_updates (ClockLocation *loc)
688 {
689 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
690 WeatherLocation *wl;
691 WeatherPrefs prefs = {
692 FORECAST_STATE,
693 FALSE,
694 NULL,
695 TEMP_UNIT_CENTIGRADE,
696 SPEED_UNIT_MS,
697 PRESSURE_UNIT_MB,
698 DISTANCE_UNIT_KM
699 };
700
701 gchar *dms;
702
703 prefs.temperature_unit = priv->temperature_unit;
704 prefs.speed_unit = priv->speed_unit;
705
706 if (priv->weather_info) {
707 weather_info_free (priv->weather_info);
708 priv->weather_info = NULL;
709 }
710
711 if (priv->weather_timeout) {
712 g_source_remove (priv->weather_timeout);
713 priv->weather_timeout = 0;
714 }
715
716 if (!priv->weather_code ||
717 strcmp (priv->weather_code, WEATHER_EMPTY_CODE) == 0)
718 return;
719
720 dms = rad2dms (priv->latitude, priv->longitude);
721 wl = weather_location_new (priv->city, priv->weather_code,
722 NULL, NULL, dms, NULL, NULL);
723
724 priv->weather_info =
725 weather_info_new (wl, &prefs, weather_info_updated, loc);
726
727 set_weather_update_timeout (loc);
728
729 weather_location_free (wl);
730 g_free (dms);
731 }
732
733 void
clock_location_set_weather_prefs(ClockLocation * loc,WeatherPrefs * prefs)734 clock_location_set_weather_prefs (ClockLocation *loc,
735 WeatherPrefs *prefs)
736 {
737 ClockLocationPrivate *priv = clock_location_get_instance_private (loc);
738
739 priv->temperature_unit = prefs->temperature_unit;
740 priv->speed_unit = prefs->speed_unit;
741
742 update_weather_info (loc);
743 }
744
745