No issues found
1 /*
2 * Copyright (C) 2011 Red Hat, Inc.
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, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
17 * 02111-1307, USA.
18 *
19 * Author: David Zeuthen <davidz@redhat.com>
20 *
21 * Based on code from gnome-panel's clock-applet, file calendar-client.c, with Authors:
22 *
23 * Mark McLoughlin <mark@skynet.ie>
24 * William Jon McCann <mccann@jhu.edu>
25 * Martin Grimme <martin@pycage.de>
26 * Christian Kellner <gicmo@xatom.net>
27 *
28 */
29
30 #include "config.h"
31
32 #include <string.h>
33 #include <sys/types.h>
34 #include <unistd.h>
35
36 #include <gio/gio.h>
37
38 #define HANDLE_LIBICAL_MEMORY
39 #include <libecal/libecal.h>
40
41 #include "calendar-sources.h"
42
43 /* Set the environment variable CALENDAR_SERVER_DEBUG to show debug */
44 static void print_debug (const gchar *str, ...);
45
46 #define BUS_NAME "org.gnome.Shell.CalendarServer"
47
48 static const gchar introspection_xml[] =
49 "<node>"
50 " <interface name='org.gnome.Shell.CalendarServer'>"
51 " <method name='GetEvents'>"
52 " <arg type='x' name='since' direction='in'/>"
53 " <arg type='x' name='until' direction='in'/>"
54 " <arg type='b' name='force_reload' direction='in'/>"
55 " <arg type='a(sssbxxa{sv})' name='events' direction='out'/>"
56 " </method>"
57 " <signal name='Changed'/>"
58 " <property name='Since' type='x' access='read'/>"
59 " <property name='Until' type='x' access='read'/>"
60 " </interface>"
61 "</node>";
62 static GDBusNodeInfo *introspection_data = NULL;
63
64 struct _App;
65 typedef struct _App App;
66
67 static gboolean opt_replace = FALSE;
68 static GOptionEntry opt_entries[] = {
69 {"replace", 0, 0, G_OPTION_ARG_NONE, &opt_replace, "Replace existing daemon", NULL},
70 {NULL }
71 };
72 static App *_global_app = NULL;
73
74 /* ---------------------------------------------------------------------------------------------------- */
75
76 typedef struct
77 {
78 time_t start_time;
79 time_t end_time;
80 } CalendarOccurrence;
81
82 typedef struct
83 {
84 char *uid;
85 char *rid;
86 char *backend_name;
87 char *summary;
88 char *description;
89 char *color_string;
90 time_t start_time;
91 time_t end_time;
92 guint is_all_day : 1;
93
94 /* Only used internally */
95 GSList *occurrences;
96 } CalendarAppointment;
97
98 static time_t
99 get_time_from_property (icalcomponent *ical,
100 icalproperty_kind prop_kind,
101 struct icaltimetype (* get_prop_func) (const icalproperty *prop),
102 icaltimezone *default_zone)
103 {
104 icalproperty *prop;
105 struct icaltimetype ical_time;
106 icalparameter *param;
107 icaltimezone *timezone = NULL;
108
109 prop = icalcomponent_get_first_property (ical, prop_kind);
110 if (!prop)
111 return 0;
112
113 ical_time = get_prop_func (prop);
114
115 param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
116 if (param)
117 timezone = icaltimezone_get_builtin_timezone_from_tzid (icalparameter_get_tzid (param));
118 else if (icaltime_is_utc (ical_time))
119 timezone = icaltimezone_get_utc_timezone ();
120 else
121 timezone = default_zone;
122
123 return icaltime_as_timet_with_zone (ical_time, timezone);
124 }
125
126 static char *
127 get_ical_uid (icalcomponent *ical)
128 {
129 return g_strdup (icalcomponent_get_uid (ical));
130 }
131
132 static char *
133 get_ical_rid (icalcomponent *ical)
134 {
135 icalproperty *prop;
136 struct icaltimetype ical_time;
137
138 prop = icalcomponent_get_first_property (ical, ICAL_RECURRENCEID_PROPERTY);
139 if (!prop)
140 return NULL;
141
142 ical_time = icalproperty_get_recurrenceid (prop);
143
144 return icaltime_is_valid_time (ical_time) && !icaltime_is_null_time (ical_time) ?
145 g_strdup (icaltime_as_ical_string (ical_time)) : NULL;
146 }
147
148 static char *
149 get_ical_summary (icalcomponent *ical)
150 {
151 icalproperty *prop;
152
153 prop = icalcomponent_get_first_property (ical, ICAL_SUMMARY_PROPERTY);
154 if (!prop)
155 return NULL;
156
157 return g_strdup (icalproperty_get_summary (prop));
158 }
159
160 static char *
161 get_ical_description (icalcomponent *ical)
162 {
163 icalproperty *prop;
164
165 prop = icalcomponent_get_first_property (ical, ICAL_DESCRIPTION_PROPERTY);
166 if (!prop)
167 return NULL;
168
169 return g_strdup (icalproperty_get_description (prop));
170 }
171
172 static inline time_t
173 get_ical_start_time (icalcomponent *ical,
174 icaltimezone *default_zone)
175 {
176 return get_time_from_property (ical,
177 ICAL_DTSTART_PROPERTY,
178 icalproperty_get_dtstart,
179 default_zone);
180 }
181
182 static inline time_t
183 get_ical_end_time (icalcomponent *ical,
184 icaltimezone *default_zone)
185 {
186 return get_time_from_property (ical,
187 ICAL_DTEND_PROPERTY,
188 icalproperty_get_dtend,
189 default_zone);
190 }
191
192 static gboolean
193 get_ical_is_all_day (icalcomponent *ical,
194 time_t start_time,
195 icaltimezone *default_zone)
196 {
197 icalproperty *prop;
198 struct tm *start_tm;
199 time_t end_time;
200 struct icaldurationtype duration;
201 struct icaltimetype start_icaltime;
202
203 start_icaltime = icalcomponent_get_dtstart (ical);
204 if (start_icaltime.is_date)
205 return TRUE;
206
207 start_tm = gmtime (&start_time);
208 if (start_tm->tm_sec != 0 ||
209 start_tm->tm_min != 0 ||
210 start_tm->tm_hour != 0)
211 return FALSE;
212
213 if ((end_time = get_ical_end_time (ical, default_zone)))
214 return (end_time - start_time) % 86400 == 0;
215
216 prop = icalcomponent_get_first_property (ical, ICAL_DURATION_PROPERTY);
217 if (!prop)
218 return FALSE;
219
220 duration = icalproperty_get_duration (prop);
221
222 return icaldurationtype_as_int (duration) % 86400 == 0;
223 }
224
225 static inline time_t
226 get_ical_due_time (icalcomponent *ical,
227 icaltimezone *default_zone)
228 {
229 return get_time_from_property (ical,
230 ICAL_DUE_PROPERTY,
231 icalproperty_get_due,
232 default_zone);
233 }
234
235 static inline time_t
236 get_ical_completed_time (icalcomponent *ical,
237 icaltimezone *default_zone)
238 {
239 return get_time_from_property (ical,
240 ICAL_COMPLETED_PROPERTY,
241 icalproperty_get_completed,
242 default_zone);
243 }
244
245 static char *
246 get_source_color (ECalClient *esource)
247 {
248 ESource *source;
249 ECalClientSourceType source_type;
250 ESourceSelectable *extension;
251 const gchar *extension_name;
252
253 g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL);
254
255 source = e_client_get_source (E_CLIENT (esource));
256 source_type = e_cal_client_get_source_type (esource);
257
258 switch (source_type)
259 {
260 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
261 extension_name = E_SOURCE_EXTENSION_CALENDAR;
262 break;
263 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
264 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
265 break;
266 default:
267 g_return_val_if_reached (NULL);
268 }
269
270 extension = e_source_get_extension (source, extension_name);
271
272 return e_source_selectable_dup_color (extension);
273 }
274
275 static gchar *
276 get_source_backend_name (ECalClient *esource)
277 {
278 ESource *source;
279 ECalClientSourceType source_type;
280 ESourceBackend *extension;
281 const gchar *extension_name;
282
283 g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL);
284
285 source = e_client_get_source (E_CLIENT (esource));
286 source_type = e_cal_client_get_source_type (esource);
287
288 switch (source_type)
289 {
290 case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
291 extension_name = E_SOURCE_EXTENSION_CALENDAR;
292 break;
293 case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
294 extension_name = E_SOURCE_EXTENSION_TASK_LIST;
295 break;
296 default:
297 g_return_val_if_reached (NULL);
298 }
299
300 extension = e_source_get_extension (source, extension_name);
301
302 return e_source_backend_dup_backend_name (extension);
303 }
304
305 static inline int
306 null_safe_strcmp (const char *a,
307 const char *b)
308 {
309 return (!a && !b) ? 0 : (a && !b) || (!a && b) ? 1 : strcmp (a, b);
310 }
311
312 static inline gboolean
313 calendar_appointment_equal (CalendarAppointment *a,
314 CalendarAppointment *b)
315 {
316 GSList *la, *lb;
317
318 if (g_slist_length (a->occurrences) != g_slist_length (b->occurrences))
319 return FALSE;
320
321 for (la = a->occurrences, lb = b->occurrences; la && lb; la = la->next, lb = lb->next)
322 {
323 CalendarOccurrence *oa = la->data;
324 CalendarOccurrence *ob = lb->data;
325
326 if (oa->start_time != ob->start_time ||
327 oa->end_time != ob->end_time)
328 return FALSE;
329 }
330
331 return
332 null_safe_strcmp (a->uid, b->uid) == 0 &&
333 null_safe_strcmp (a->backend_name, b->backend_name) == 0 &&
334 null_safe_strcmp (a->summary, b->summary) == 0 &&
335 null_safe_strcmp (a->description, b->description) == 0 &&
336 null_safe_strcmp (a->color_string, b->color_string) == 0 &&
337 a->start_time == b->start_time &&
338 a->end_time == b->end_time &&
339 a->is_all_day == b->is_all_day;
340 }
341
342 static void
343 calendar_appointment_free (CalendarAppointment *appointment)
344 {
345 GSList *l;
346
347 for (l = appointment->occurrences; l; l = l->next)
348 g_free (l->data);
349 g_slist_free (appointment->occurrences);
350 appointment->occurrences = NULL;
351
352 g_free (appointment->uid);
353 appointment->uid = NULL;
354
355 g_free (appointment->rid);
356 appointment->rid = NULL;
357
358 g_free (appointment->backend_name);
359 appointment->backend_name = NULL;
360
361 g_free (appointment->summary);
362 appointment->summary = NULL;
363
364 g_free (appointment->description);
365 appointment->description = NULL;
366
367 g_free (appointment->color_string);
368 appointment->color_string = NULL;
369
370 appointment->start_time = 0;
371 appointment->is_all_day = FALSE;
372 }
373
374 static void
375 calendar_appointment_init (CalendarAppointment *appointment,
376 icalcomponent *ical,
377 ECalClient *cal,
378 icaltimezone *default_zone)
379 {
380 appointment->uid = get_ical_uid (ical);
381 appointment->rid = get_ical_rid (ical);
382 appointment->backend_name = get_source_backend_name (cal);
383 appointment->summary = get_ical_summary (ical);
384 appointment->description = get_ical_description (ical);
385 appointment->color_string = get_source_color (cal);
386 appointment->start_time = get_ical_start_time (ical, default_zone);
387 appointment->end_time = get_ical_end_time (ical, default_zone);
388 appointment->is_all_day = get_ical_is_all_day (ical,
389 appointment->start_time,
390 default_zone);
391 }
392
393 static icaltimezone *
394 resolve_timezone_id (const char *tzid,
395 ECalClient *source)
396 {
397 icaltimezone *retval;
398
399 retval = icaltimezone_get_builtin_timezone_from_tzid (tzid);
400 if (!retval)
401 {
402 e_cal_client_get_timezone_sync (source, tzid, &retval, NULL, NULL);
403 }
404
405 return retval;
406 }
407
408 static gboolean
409 calendar_appointment_collect_occurrence (ECalComponent *component,
410 time_t occurrence_start,
411 time_t occurrence_end,
412 gpointer data)
413 {
414 CalendarOccurrence *occurrence;
415 GSList **collect_loc = data;
416
417 occurrence = g_new0 (CalendarOccurrence, 1);
418 occurrence->start_time = occurrence_start;
419 occurrence->end_time = occurrence_end;
420
421 *collect_loc = g_slist_prepend (*collect_loc, occurrence);
422
423 return TRUE;
424 }
425
426 static void
427 calendar_appointment_generate_occurrences (CalendarAppointment *appointment,
428 icalcomponent *ical,
429 ECalClient *cal,
430 time_t start,
431 time_t end,
432 icaltimezone *default_zone)
433 {
434 ECalComponent *ecal;
435
436 g_assert (appointment->occurrences == NULL);
437
438 ecal = e_cal_component_new ();
439 e_cal_component_set_icalcomponent (ecal,
440 icalcomponent_new_clone (ical));
441
442 e_cal_recur_generate_instances (ecal,
443 start,
444 end,
445 calendar_appointment_collect_occurrence,
446 &appointment->occurrences,
447 (ECalRecurResolveTimezoneFn) resolve_timezone_id,
448 cal,
449 default_zone);
450
451 g_object_unref (ecal);
452
453 appointment->occurrences = g_slist_reverse (appointment->occurrences);
454 }
455
456 static CalendarAppointment *
457 calendar_appointment_new (icalcomponent *ical,
458 ECalClient *cal,
459 icaltimezone *default_zone)
460 {
461 CalendarAppointment *appointment;
462
463 appointment = g_new0 (CalendarAppointment, 1);
464
465 calendar_appointment_init (appointment,
466 ical,
467 cal,
468 default_zone);
469 return appointment;
470 }
471
472 /* ---------------------------------------------------------------------------------------------------- */
473
474 struct _App
475 {
476 GDBusConnection *connection;
477
478 time_t since;
479 time_t until;
480
481 icaltimezone *zone;
482
483 CalendarSources *sources;
484 gulong sources_signal_id;
485
486 /* hash from uid to CalendarAppointment objects */
487 GHashTable *appointments;
488
489 gchar *timezone_location;
490
491 guint changed_timeout_id;
492
493 gboolean cache_invalid;
494
495 GList *live_views;
496 };
497
498 static void
499 app_update_timezone (App *app)
500 {
501 gchar *location;
502
503 location = e_cal_system_timezone_get_location ();
504 if (g_strcmp0 (location, app->timezone_location) != 0)
505 {
506 if (location == NULL)
507 app->zone = icaltimezone_get_utc_timezone ();
508 else
509 app->zone = icaltimezone_get_builtin_timezone (location);
510 g_free (app->timezone_location);
511 app->timezone_location = location;
512 print_debug ("Using timezone %s", app->timezone_location);
513 }
514 }
515
516 static gboolean
517 on_app_schedule_changed_cb (gpointer user_data)
518 {
519 App *app = user_data;
520 print_debug ("Emitting changed");
521 g_dbus_connection_emit_signal (app->connection,
522 NULL, /* destination_bus_name */
523 "/org/gnome/Shell/CalendarServer",
524 "org.gnome.Shell.CalendarServer",
525 "Changed",
526 NULL, /* no params */
527 NULL);
528 app->changed_timeout_id = 0;
529 return FALSE;
530 }
531
532 static void
533 app_schedule_changed (App *app)
534 {
535 print_debug ("Scheduling changed");
536 if (app->changed_timeout_id == 0)
537 {
538 app->changed_timeout_id = g_timeout_add (2000,
539 on_app_schedule_changed_cb,
540 app);
541 }
542 }
543
544 static void
545 invalidate_cache (App *app)
546 {
547 app->cache_invalid = TRUE;
548 }
549
550 static void
551 on_objects_added (ECalClientView *view,
552 GSList *objects,
553 gpointer user_data)
554 {
555 App *app = user_data;
556 GSList *l;
557
558 print_debug ("%s for calendar", G_STRFUNC);
559
560 for (l = objects; l != NULL; l = l->next)
561 {
562 icalcomponent *ical = l->data;
563 const char *uid;
564
565 uid = icalcomponent_get_uid (ical);
566
567 if (g_hash_table_lookup (app->appointments, uid) == NULL)
568 {
569 /* new appointment we don't know about => changed signal */
570 invalidate_cache (app);
571 app_schedule_changed (app);
572 }
573 }
574 }
575
576 static void
577 on_objects_modified (ECalClientView *view,
578 GSList *objects,
579 gpointer user_data)
580 {
581 App *app = user_data;
582 print_debug ("%s for calendar", G_STRFUNC);
583 invalidate_cache (app);
584 app_schedule_changed (app);
585 }
586
587 static void
588 on_objects_removed (ECalClientView *view,
589 GSList *uids,
590 gpointer user_data)
591 {
592 App *app = user_data;
593 print_debug ("%s for calendar", G_STRFUNC);
594 invalidate_cache (app);
595 app_schedule_changed (app);
596 }
597
598 static void
599 app_load_events (App *app)
600 {
601 GList *clients;
602 GList *l;
603 GList *ll;
604 gchar *since_iso8601;
605 gchar *until_iso8601;
606
607 /* out with the old */
608 g_hash_table_remove_all (app->appointments);
609 /* nuke existing views */
610 for (ll = app->live_views; ll != NULL; ll = ll->next)
611 {
612 ECalClientView *view = E_CAL_CLIENT_VIEW (ll->data);
613 g_signal_handlers_disconnect_by_func (view, on_objects_added, app);
614 g_signal_handlers_disconnect_by_func (view, on_objects_modified, app);
615 g_signal_handlers_disconnect_by_func (view, on_objects_removed, app);
616 e_cal_client_view_stop (view, NULL);
617 g_object_unref (view);
618 }
619 g_list_free (app->live_views);
620 app->live_views = NULL;
621
622 /* timezone could have changed */
623 app_update_timezone (app);
624
625 since_iso8601 = isodate_from_time_t (app->since);
626 until_iso8601 = isodate_from_time_t (app->until);
627
628 print_debug ("Loading events since %s until %s",
629 since_iso8601,
630 until_iso8601);
631
632 clients = calendar_sources_get_appointment_clients (app->sources);
633 for (l = clients; l != NULL; l = l->next)
634 {
635 ECalClient *cal = E_CAL_CLIENT (l->data);
636 GError *error;
637 gchar *query;
638 GSList *objects, *j;
639 ECalClientView *view;
640
641 e_cal_client_set_default_timezone (cal, app->zone);
642
643 error = NULL;
644 if (!e_client_open_sync (E_CLIENT (cal), TRUE, NULL, &error))
645 {
646 ESource *source = e_client_get_source (E_CLIENT (cal));
647 g_warning ("Error opening calendar %s: %s\n",
648 e_source_get_uid (source), error->message);
649 g_error_free (error);
650 continue;
651 }
652
653 query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") "
654 "(make-time \"%s\")",
655 since_iso8601,
656 until_iso8601);
657 error = NULL;
658 objects = NULL;
659 if (!e_cal_client_get_object_list_sync (cal,
660 query,
661 &objects,
662 NULL, /* cancellable */
663 &error))
664 {
665 ESource *source = e_client_get_source (E_CLIENT (cal));
666 g_warning ("Error querying calendar %s: %s\n",
667 e_source_get_uid (source), error->message);
668 g_error_free (error);
669 g_free (query);
670 continue;
671 }
672
673 for (j = objects; j != NULL; j = j->next)
674 {
675 icalcomponent *ical = j->data;
676 CalendarAppointment *appointment;
677
678 appointment = calendar_appointment_new (ical, cal, app->zone);
679 if (appointment == NULL)
680 continue;
681
682 calendar_appointment_generate_occurrences (appointment,
683 ical,
684 cal,
685 app->since,
686 app->until,
687 app->zone);
688 g_hash_table_insert (app->appointments, g_strdup (appointment->uid), appointment);
689 }
690
691 e_cal_client_free_icalcomp_slist (objects);
692
693 error = NULL;
694 if (!e_cal_client_get_view_sync (cal,
695 query,
696 &view,
697 NULL, /* cancellable */
698 &error))
699 {
700 g_warning ("Error setting up live-query on calendar: %s\n", error->message);
701 g_error_free (error);
702 }
703 else
704 {
705 g_signal_connect (view,
706 "objects-added",
707 G_CALLBACK (on_objects_added),
708 app);
709 g_signal_connect (view,
710 "objects-modified",
711 G_CALLBACK (on_objects_modified),
712 app);
713 g_signal_connect (view,
714 "objects-removed",
715 G_CALLBACK (on_objects_removed),
716 app);
717 e_cal_client_view_start (view, NULL);
718 app->live_views = g_list_prepend (app->live_views, view);
719 }
720
721 g_free (query);
722 }
723 g_list_free (clients);
724 g_free (since_iso8601);
725 g_free (until_iso8601);
726 app->cache_invalid = FALSE;
727 }
728
729 static void
730 on_appointment_sources_changed (CalendarSources *sources,
731 gpointer user_data)
732 {
733 App *app = user_data;
734
735 print_debug ("Sources changed\n");
736 app_load_events (app);
737 }
738
739 static App *
740 app_new (GDBusConnection *connection)
741 {
742 App *app;
743
744 app = g_new0 (App, 1);
745 app->connection = g_object_ref (connection);
746 app->sources = calendar_sources_get ();
747 app->sources_signal_id = g_signal_connect (app->sources,
748 "appointment-sources-changed",
749 G_CALLBACK (on_appointment_sources_changed),
750 app);
751
752 app->appointments = g_hash_table_new_full (g_str_hash,
753 g_str_equal,
754 g_free,
755 (GDestroyNotify) calendar_appointment_free);
756
757 app_update_timezone (app);
758
759 return app;
760 }
761
762 static void
763 app_free (App *app)
764 {
765 GList *ll;
766 for (ll = app->live_views; ll != NULL; ll = ll->next)
767 {
768 ECalClientView *view = E_CAL_CLIENT_VIEW (ll->data);
769 g_signal_handlers_disconnect_by_func (view, on_objects_added, app);
770 g_signal_handlers_disconnect_by_func (view, on_objects_modified, app);
771 g_signal_handlers_disconnect_by_func (view, on_objects_removed, app);
772 e_cal_client_view_stop (view, NULL);
773 g_object_unref (view);
774 }
775 g_list_free (app->live_views);
776
777 g_free (app->timezone_location);
778
779 g_hash_table_unref (app->appointments);
780
781 g_object_unref (app->connection);
782 g_signal_handler_disconnect (app->sources,
783 app->sources_signal_id);
784 g_object_unref (app->sources);
785
786 if (app->changed_timeout_id != 0)
787 g_source_remove (app->changed_timeout_id);
788
789 g_free (app);
790 }
791
792 /* ---------------------------------------------------------------------------------------------------- */
793
794 static void
795 handle_method_call (GDBusConnection *connection,
796 const gchar *sender,
797 const gchar *object_path,
798 const gchar *interface_name,
799 const gchar *method_name,
800 GVariant *parameters,
801 GDBusMethodInvocation *invocation,
802 gpointer user_data)
803 {
804 App *app = user_data;
805
806 if (g_strcmp0 (method_name, "GetEvents") == 0)
807 {
808 GVariantBuilder builder;
809 GHashTableIter hash_iter;
810 CalendarAppointment *a;
811 gint64 since;
812 gint64 until;
813 gboolean force_reload;
814 gboolean window_changed;
815
816 g_variant_get (parameters,
817 "(xxb)",
818 &since,
819 &until,
820 &force_reload);
821
822 if (until < since)
823 {
824 g_dbus_method_invocation_return_dbus_error (invocation,
825 "org.gnome.Shell.CalendarServer.Error.Failed",
826 "until cannot be before since");
827 goto out;
828 }
829
830 print_debug ("Handling GetEvents (since=%" G_GINT64_FORMAT ", until=%" G_GINT64_FORMAT ", force_reload=%s)",
831 since,
832 until,
833 force_reload ? "true" : "false");
834
835 window_changed = FALSE;
836 if (!(app->until == until && app->since == since))
837 {
838 GVariantBuilder *builder;
839 GVariantBuilder *invalidated_builder;
840
841 app->until = until;
842 app->since = since;
843 window_changed = TRUE;
844
845 builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
846 invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
847 g_variant_builder_add (builder, "{sv}",
848 "Until", g_variant_new_int64 (app->until));
849 g_variant_builder_add (builder, "{sv}",
850 "Since", g_variant_new_int64 (app->since));
851 g_dbus_connection_emit_signal (app->connection,
852 NULL, /* destination_bus_name */
853 "/org/gnome/Shell/CalendarServer",
854 "org.freedesktop.DBus.Properties",
855 "PropertiesChanged",
856 g_variant_new ("(sa{sv}as)",
857 "org.gnome.Shell.CalendarServer",
858 builder,
859 invalidated_builder),
860 NULL); /* GError** */
861 }
862
863 /* reload events if necessary */
864 if (window_changed || force_reload || app->cache_invalid)
865 {
866 app_load_events (app);
867 }
868
869 /* The a{sv} is used as an escape hatch in case we want to provide more
870 * information in the future without breaking ABI
871 */
872 g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sssbxxa{sv})"));
873 g_hash_table_iter_init (&hash_iter, app->appointments);
874 while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &a))
875 {
876 GVariantBuilder extras_builder;
877 GSList *l;
878
879 for (l = a->occurrences; l; l = l->next)
880 {
881 CalendarOccurrence *o = l->data;
882 time_t start_time = o->start_time;
883 time_t end_time = o->end_time;
884
885 if ((start_time >= app->since &&
886 start_time < app->until) ||
887 (start_time <= app->since &&
888 (end_time - 1) > app->since))
889 {
890 g_variant_builder_init (&extras_builder, G_VARIANT_TYPE ("a{sv}"));
891 g_variant_builder_add (&builder,
892 "(sssbxxa{sv})",
893 a->uid,
894 a->summary != NULL ? a->summary : "",
895 a->description != NULL ? a->description : "",
896 (gboolean) a->is_all_day,
897 (gint64) start_time,
898 (gint64) end_time,
899 extras_builder);
900 }
901 }
902 }
903 g_dbus_method_invocation_return_value (invocation,
904 g_variant_new ("(a(sssbxxa{sv}))", &builder));
905 }
906 else
907 {
908 g_assert_not_reached ();
909 }
910
911 out:
912 ;
913 }
914
915 static GVariant *
916 handle_get_property (GDBusConnection *connection,
917 const gchar *sender,
918 const gchar *object_path,
919 const gchar *interface_name,
920 const gchar *property_name,
921 GError **error,
922 gpointer user_data)
923 {
924 App *app = user_data;
925 GVariant *ret;
926
927 ret = NULL;
928 if (g_strcmp0 (property_name, "Since") == 0)
929 {
930 ret = g_variant_new_int64 (app->since);
931 }
932 else if (g_strcmp0 (property_name, "Until") == 0)
933 {
934 ret = g_variant_new_int64 (app->until);
935 }
936 else
937 {
938 g_assert_not_reached ();
939 }
940 return ret;
941 }
942
943 static const GDBusInterfaceVTable interface_vtable =
944 {
945 handle_method_call,
946 handle_get_property,
947 NULL /* handle_set_property */
948 };
949
950 static void
951 on_bus_acquired (GDBusConnection *connection,
952 const gchar *name,
953 gpointer user_data)
954 {
955 GError *error;
956 guint registration_id;
957
958 _global_app = app_new (connection);
959
960 error = NULL;
961 registration_id = g_dbus_connection_register_object (connection,
962 "/org/gnome/Shell/CalendarServer",
963 introspection_data->interfaces[0],
964 &interface_vtable,
965 _global_app,
966 NULL, /* user_data_free_func */
967 &error);
968 if (registration_id == 0)
969 {
970 g_printerr ("Error exporting object: %s (%s %d)",
971 error->message,
972 g_quark_to_string (error->domain),
973 error->code);
974 g_error_free (error);
975 _exit (1);
976 }
977
978 print_debug ("Connected to the session bus");
979
980 }
981
982 static void
983 on_name_lost (GDBusConnection *connection,
984 const gchar *name,
985 gpointer user_data)
986 {
987 GMainLoop *main_loop = user_data;
988
989 g_print ("gnome-shell-calendar-server[%d]: Lost (or failed to acquire) the name " BUS_NAME " - exiting\n",
990 (gint) getpid ());
991 g_main_loop_quit (main_loop);
992 }
993
994 static void
995 on_name_acquired (GDBusConnection *connection,
996 const gchar *name,
997 gpointer user_data)
998 {
999 print_debug ("Acquired the name " BUS_NAME);
1000 }
1001
1002 static gboolean
1003 stdin_channel_io_func (GIOChannel *source,
1004 GIOCondition condition,
1005 gpointer data)
1006 {
1007 GMainLoop *main_loop = data;
1008
1009 if (condition & G_IO_HUP)
1010 {
1011 g_debug ("gnome-shell-calendar-server[%d]: Got HUP on stdin - exiting\n",
1012 (gint) getpid ());
1013 g_main_loop_quit (main_loop);
1014 }
1015 else
1016 {
1017 g_warning ("Unhandled condition %d on GIOChannel for stdin", condition);
1018 }
1019 return FALSE; /* remove source */
1020 }
1021
1022 int
1023 main (int argc,
1024 char **argv)
1025 {
1026 GError *error;
1027 GOptionContext *opt_context;
1028 GMainLoop *main_loop;
1029 gint ret;
1030 guint name_owner_id;
1031 GIOChannel *stdin_channel;
1032
1033 ret = 1;
1034 opt_context = NULL;
1035 name_owner_id = 0;
1036 stdin_channel = NULL;
1037
1038 g_type_init ();
1039
1040 introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
1041 g_assert (introspection_data != NULL);
1042
1043 opt_context = g_option_context_new ("gnome-shell calendar server");
1044 g_option_context_add_main_entries (opt_context, opt_entries, NULL);
1045 error = NULL;
1046 if (!g_option_context_parse (opt_context, &argc, &argv, &error))
1047 {
1048 g_printerr ("Error parsing options: %s", error->message);
1049 g_error_free (error);
1050 goto out;
1051 }
1052
1053 main_loop = g_main_loop_new (NULL, FALSE);
1054
1055 stdin_channel = g_io_channel_unix_new (STDIN_FILENO);
1056 g_io_add_watch_full (stdin_channel,
1057 G_PRIORITY_DEFAULT,
1058 G_IO_HUP,
1059 stdin_channel_io_func,
1060 g_main_loop_ref (main_loop),
1061 (GDestroyNotify) g_main_loop_unref);
1062
1063 name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
1064 BUS_NAME,
1065 G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
1066 (opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
1067 on_bus_acquired,
1068 on_name_acquired,
1069 on_name_lost,
1070 g_main_loop_ref (main_loop),
1071 (GDestroyNotify) g_main_loop_unref);
1072
1073 g_main_loop_run (main_loop);
1074
1075 g_main_loop_unref (main_loop);
1076
1077 ret = 0;
1078
1079 out:
1080 if (stdin_channel != NULL)
1081 g_io_channel_unref (stdin_channel);
1082 if (_global_app != NULL)
1083 app_free (_global_app);
1084 if (name_owner_id != 0)
1085 g_bus_unown_name (name_owner_id);
1086 if (opt_context != NULL)
1087 g_option_context_free (opt_context);
1088 return ret;
1089 }
1090
1091 /* ---------------------------------------------------------------------------------------------------- */
1092
1093 static void
1094 print_debug (const gchar *format, ...)
1095 {
1096 gchar *s;
1097 va_list ap;
1098 gchar timebuf[64];
1099 GTimeVal now;
1100 time_t now_t;
1101 struct tm broken_down;
1102 static volatile gsize once_init_value = 0;
1103 static gboolean show_debug = FALSE;
1104 static guint pid = 0;
1105
1106 if (g_once_init_enter (&once_init_value))
1107 {
1108 show_debug = (g_getenv ("CALENDAR_SERVER_DEBUG") != NULL);
1109 pid = getpid ();
1110 g_once_init_leave (&once_init_value, 1);
1111 }
1112
1113 if (!show_debug)
1114 goto out;
1115
1116 g_get_current_time (&now);
1117 now_t = now.tv_sec;
1118 localtime_r (&now_t, &broken_down);
1119 strftime (timebuf, sizeof timebuf, "%H:%M:%S", &broken_down);
1120
1121 va_start (ap, format);
1122 s = g_strdup_vprintf (format, ap);
1123 va_end (ap);
1124
1125 g_print ("gnome-shell-calendar-server[%d]: %s.%03d: %s\n", pid, timebuf, (gint) (now.tv_usec / 1000), s);
1126 g_free (s);
1127 out:
1128 ;
1129 }