gnome-shell-3.6.3.1/src/calendar-server/gnome-shell-calendar-server.c

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 }