2
* Copyright 2013 Canonical Ltd.
5
* Charles Kerr <charles.kerr@canonical.com>
7
* This program is free software: you can redistribute it and/or modify it
8
* under the terms of the GNU General Public License version 3, as published
9
* by the Free Software Foundation.
11
* This program is distributed in the hope that it will be useful, but
12
* WITHOUT ANY WARRANTY; without even the implied warranties of
13
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14
* PURPOSE. See the GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License along
17
* with this program. If not, see <http://www.gnu.org/licenses/>.
20
#include <libical/ical.h>
21
#include <libical/icaltime.h>
22
#include <libecal/libecal.h>
23
#include <libedataserver/libedataserver.h>
25
#include "planner-eds.h"
27
struct _IndicatorDatetimePlannerEdsPriv
30
GCancellable * cancellable;
31
ESourceRegistry * source_registry;
34
typedef IndicatorDatetimePlannerEdsPriv priv_t;
36
G_DEFINE_TYPE (IndicatorDatetimePlannerEds,
37
indicator_datetime_planner_eds,
38
INDICATOR_TYPE_DATETIME_PLANNER)
40
G_DEFINE_QUARK ("source-client", source_client)
44
**** my_get_appointments() helpers
48
/* whole-task data that all the subtasks can see */
49
struct appointment_task_data
51
/* a ref to the planner's cancellable */
52
GCancellable * cancellable;
54
/* how many subtasks are still running on */
57
/* the list of appointments to be returned */
58
GSList * appointments;
61
static struct appointment_task_data *
62
appointment_task_data_new (GCancellable * cancellable)
64
struct appointment_task_data * data;
66
data = g_slice_new0 (struct appointment_task_data);
67
data->cancellable = g_object_ref (cancellable);
72
appointment_task_data_free (gpointer gdata)
74
struct appointment_task_data * data = gdata;
76
g_object_unref (data->cancellable);
78
g_slice_free (struct appointment_task_data, data);
82
appointment_task_done (GTask * task)
84
struct appointment_task_data * data = g_task_get_task_data (task);
86
g_task_return_pointer (task, data->appointments, NULL);
87
g_object_unref (task);
91
appointment_task_decrement_subtasks (GTask * task)
93
struct appointment_task_data * data = g_task_get_task_data (task);
95
if (g_atomic_int_dec_and_test (&data->subtask_count))
96
appointment_task_done (task);
100
appointment_task_increment_subtasks (GTask * task)
102
struct appointment_task_data * data = g_task_get_task_data (task);
104
g_atomic_int_inc (&data->subtask_count);
108
*** get-the-appointment's-uri subtasks
111
struct appointment_uri_subtask_data
113
/* The parent task */
116
/* The appointment whose uri we're looking for.
117
This pointer is owned by the Task and isn't reffed/unreffed by the subtask */
118
struct IndicatorDatetimeAppt * appt;
122
appointment_uri_subtask_done (struct appointment_uri_subtask_data * subdata)
124
GTask * task = subdata->task;
126
/* free the subtask data */
127
g_slice_free (struct appointment_uri_subtask_data, subdata);
129
appointment_task_decrement_subtasks (task);
132
static struct appointment_uri_subtask_data *
133
appointment_uri_subtask_data_new (GTask * task, struct IndicatorDatetimeAppt * appt)
135
struct appointment_uri_subtask_data * subdata;
137
appointment_task_increment_subtasks (task);
139
subdata = g_slice_new0 (struct appointment_uri_subtask_data);
140
subdata->task = task;
141
subdata->appt = appt;
146
on_appointment_uris_ready (GObject * client,
152
struct appointment_uri_subtask_data * subdata = gsubdata;
156
e_cal_client_get_attachment_uris_finish (E_CAL_CLIENT(client), res, &uris, &error);
159
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
160
g_warning ("Error getting appointment uris: %s", error->message);
162
g_error_free (error);
164
else if (uris != NULL)
166
struct IndicatorDatetimeAppt * appt = subdata->appt;
167
appt->url = g_strdup (uris->data); /* copy the first URL */
168
g_debug ("found url '%s' for appointment '%s'", appt->url, appt->summary);
169
e_client_util_free_string_slist (uris);
172
appointment_uri_subtask_done (subdata);
176
*** enumerate-the-components subtasks
179
/* data struct for the enumerate-components subtask */
180
struct appointment_component_subtask_data
182
/* The parent task */
185
/* The client we're walking through. The subtask owns a ref to this */
188
/* The appointment's color coding. The subtask owns this string */
193
on_appointment_component_subtask_done (gpointer gsubdata)
195
struct appointment_component_subtask_data * subdata = gsubdata;
196
GTask * task = subdata->task;
198
/* free the subtask data */
199
g_free (subdata->color);
200
g_object_unref (subdata->client);
201
g_slice_free (struct appointment_component_subtask_data, subdata);
203
appointment_task_decrement_subtasks (task);
206
static struct appointment_component_subtask_data *
207
appointment_component_subtask_data_new (GTask * task, ECalClient * client, const gchar * color)
209
struct appointment_component_subtask_data * subdata;
211
appointment_task_increment_subtasks (task);
213
subdata = g_slice_new0 (struct appointment_component_subtask_data);
214
subdata->task = task;
215
subdata->client = g_object_ref (client);
216
subdata->color = g_strdup (color);
221
my_get_appointments_foreach (ECalComponent * component,
226
const ECalComponentVType vtype = e_cal_component_get_vtype (component);
227
struct appointment_component_subtask_data * subdata = gsubdata;
228
struct appointment_task_data * data = g_task_get_task_data (subdata->task);
230
if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO))
232
const gchar * uid = NULL;
233
icalproperty_status status = 0;
235
e_cal_component_get_uid (component, &uid);
236
e_cal_component_get_status (component, &status);
239
(status != ICAL_STATUS_COMPLETED) &&
240
(status != ICAL_STATUS_CANCELLED))
245
ECalComponentText text;
246
struct IndicatorDatetimeAppt * appt;
247
struct appointment_uri_subtask_data * uri_subdata;
249
appt = g_slice_new0 (struct IndicatorDatetimeAppt);
251
/* Determine whether this is a recurring event.
252
NB: icalrecurrencetype supports complex recurrence patterns;
253
however, since design only allows daily recurrence,
254
that's all we support here. */
255
e_cal_component_get_rrule_list (component, &recur_list);
256
for (l=recur_list; l!=NULL; l=l->next)
258
const struct icalrecurrencetype * recur = l->data;
259
appt->is_daily |= ((recur->freq == ICAL_DAILY_RECURRENCE)
260
&& (recur->interval == 1));
262
e_cal_component_free_recur_list (recur_list);
265
e_cal_component_get_summary (component, &text);
267
appt->begin = g_date_time_new_from_unix_local (begin);
268
appt->end = g_date_time_new_from_unix_local (end);
269
appt->color = g_strdup (subdata->color);
270
appt->is_event = vtype == E_CAL_COMPONENT_EVENT;
271
appt->summary = g_strdup (text.value);
272
appt->uid = g_strdup (uid);
274
alarm_uids = e_cal_component_get_alarm_uids (component);
275
appt->has_alarms = alarm_uids != NULL;
276
cal_obj_uid_list_free (alarm_uids);
278
data->appointments = g_slist_prepend (data->appointments, appt);
280
/* start a new subtask to get the associated URIs */
281
uri_subdata = appointment_uri_subtask_data_new (subdata->task, appt);
282
e_cal_client_get_attachment_uris (subdata->client,
286
on_appointment_uris_ready,
291
return G_SOURCE_CONTINUE;
295
**** IndicatorDatetimePlanner virtual funcs
299
my_get_appointments (IndicatorDatetimePlanner * planner,
300
GDateTime * begin_datetime,
301
GDateTime * end_datetime,
302
GAsyncReadyCallback callback,
308
icaltimezone * default_timezone;
309
const int64_t begin = g_date_time_to_unix (begin_datetime);
310
const int64_t end = g_date_time_to_unix (end_datetime);
312
gboolean subtasks_added;
314
p = INDICATOR_DATETIME_PLANNER_EDS (planner)->priv;
317
*** init the default timezone
320
default_timezone = NULL;
322
if ((str = indicator_datetime_planner_get_timezone (planner)))
324
default_timezone = icaltimezone_get_builtin_timezone (str);
326
if (default_timezone == NULL) /* maybe str is a tzid? */
327
default_timezone = icaltimezone_get_builtin_timezone_from_tzid (str);
331
*** walk through the sources to build the appointment list
334
task = g_task_new (planner, p->cancellable, callback, user_data);
335
g_task_set_task_data (task,
336
appointment_task_data_new (p->cancellable),
337
appointment_task_data_free);
339
subtasks_added = FALSE;
340
for (l=p->sources; l!=NULL; l=l->next)
345
struct appointment_component_subtask_data * subdata;
348
client = g_object_get_qdata (l->data, source_client_quark());
352
if (default_timezone != NULL)
353
e_cal_client_set_default_timezone (client, default_timezone);
355
/* start a new subtask to enumerate all the components in this client. */
356
color = e_source_selectable_get_color (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR));
357
subdata = appointment_component_subtask_data_new (task, client, color);
358
subtasks_added = TRUE;
359
e_cal_client_generate_instances (client,
363
my_get_appointments_foreach,
365
on_appointment_component_subtask_done);
369
appointment_task_done (task);
373
my_get_appointments_finish (IndicatorDatetimePlanner * self G_GNUC_UNUSED,
377
return g_task_propagate_pointer (G_TASK(res), error);
381
my_is_configured (IndicatorDatetimePlanner * planner)
383
IndicatorDatetimePlannerEds * self;
385
/* confirm that it's installed... */
386
gchar *evo = g_find_program_in_path ("evolution");
390
g_debug ("found calendar app: '%s'", evo);
393
/* see if there are any calendar sources */
394
self = INDICATOR_DATETIME_PLANNER_EDS (planner);
395
return self->priv->sources != NULL;
399
my_activate (IndicatorDatetimePlanner * self G_GNUC_UNUSED)
401
GError * error = NULL;
402
const char * const command = "evolution -c calendar";
404
if (!g_spawn_command_line_async (command, &error))
406
g_warning ("Unable to start %s: %s", command, error->message);
407
g_error_free (error);
412
my_activate_time (IndicatorDatetimePlanner * self G_GNUC_UNUSED,
413
GDateTime * activate_time)
419
isodate = g_date_time_format (activate_time, "%Y%m%d");
420
command = g_strdup_printf ("evolution \"calendar:///?startdate=%s\"", isodate);
422
if (!g_spawn_command_line_async (command, &err))
424
g_warning ("Unable to start %s: %s", command, err->message);
433
**** Source / Client Wrangling
437
on_client_connected (GObject * unused G_GNUC_UNUSED,
445
client = e_cal_client_connect_finish (res, &error);
448
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
449
g_warning ("indicator-datetime cannot connect to EDS source: %s", error->message);
451
g_error_free (error);
455
/* we've got a new connected ECalClient, so store it & notify clients */
457
g_object_set_qdata_full (G_OBJECT(e_client_get_source(client)),
458
source_client_quark(),
462
indicator_datetime_planner_emit_appointments_changed (gself);
467
on_source_enabled (ESourceRegistry * registry G_GNUC_UNUSED,
471
IndicatorDatetimePlannerEds * self = INDICATOR_DATETIME_PLANNER_EDS (gself);
472
priv_t * p = self->priv;
474
e_cal_client_connect (source,
475
E_CAL_CLIENT_SOURCE_TYPE_EVENTS,
482
on_source_added (ESourceRegistry * registry,
486
IndicatorDatetimePlannerEds * self = INDICATOR_DATETIME_PLANNER_EDS (gself);
487
priv_t * p = self->priv;
489
p->sources = g_slist_prepend (p->sources, g_object_ref(source));
491
if (e_source_get_enabled (source))
492
on_source_enabled (registry, source, gself);
496
on_source_disabled (ESourceRegistry * registry G_GNUC_UNUSED,
502
/* If this source has a connected ECalClient, remove it & notify clients */
503
if ((client = g_object_steal_qdata (G_OBJECT(source), source_client_quark())))
505
g_object_unref (client);
506
indicator_datetime_planner_emit_appointments_changed (gself);
511
on_source_removed (ESourceRegistry * registry,
515
IndicatorDatetimePlannerEds * self = INDICATOR_DATETIME_PLANNER_EDS (gself);
516
priv_t * p = self->priv;
518
on_source_disabled (registry, source, gself);
520
p->sources = g_slist_remove (p->sources, source);
521
g_object_unref (source);
525
on_source_changed (ESourceRegistry * registry G_GNUC_UNUSED,
526
ESource * source G_GNUC_UNUSED,
529
indicator_datetime_planner_emit_appointments_changed (gself);
533
on_source_registry_ready (GObject * source_object G_GNUC_UNUSED,
541
r = e_source_registry_new_finish (res, &error);
544
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
545
g_warning ("indicator-datetime cannot show EDS appointments: %s", error->message);
547
g_error_free (error);
551
IndicatorDatetimePlannerEds * self;
556
self = INDICATOR_DATETIME_PLANNER_EDS (gself);
559
g_signal_connect (r, "source-added", G_CALLBACK(on_source_added), self);
560
g_signal_connect (r, "source-removed", G_CALLBACK(on_source_removed), self);
561
g_signal_connect (r, "source-changed", G_CALLBACK(on_source_changed), self);
562
g_signal_connect (r, "source-disabled", G_CALLBACK(on_source_disabled), self);
563
g_signal_connect (r, "source-enabled", G_CALLBACK(on_source_enabled), self);
565
p->source_registry = r;
567
sources = e_source_registry_list_sources (r, E_SOURCE_EXTENSION_CALENDAR);
568
for (l=sources; l!=NULL; l=l->next)
569
on_source_added (r, l->data, self);
570
g_list_free_full (sources, g_object_unref);
575
**** GObject virtual funcs
579
my_dispose (GObject * o)
581
IndicatorDatetimePlannerEds * self = INDICATOR_DATETIME_PLANNER_EDS (o);
582
priv_t * p = self->priv;
584
if (p->cancellable != NULL)
586
g_cancellable_cancel (p->cancellable);
587
g_clear_object (&p->cancellable);
590
if (p->source_registry != NULL)
592
g_signal_handlers_disconnect_by_func (p->source_registry,
593
indicator_datetime_planner_emit_appointments_changed,
596
g_clear_object (&self->priv->source_registry);
599
G_OBJECT_CLASS (indicator_datetime_planner_eds_parent_class)->dispose (o);
607
indicator_datetime_planner_eds_class_init (IndicatorDatetimePlannerEdsClass * klass)
609
GObjectClass * object_class;
610
IndicatorDatetimePlannerClass * planner_class;
612
object_class = G_OBJECT_CLASS (klass);
613
object_class->dispose = my_dispose;
615
planner_class = INDICATOR_DATETIME_PLANNER_CLASS (klass);
616
planner_class->is_configured = my_is_configured;
617
planner_class->activate = my_activate;
618
planner_class->activate_time = my_activate_time;
619
planner_class->get_appointments = my_get_appointments;
620
planner_class->get_appointments_finish = my_get_appointments_finish;
622
g_type_class_add_private (klass, sizeof (IndicatorDatetimePlannerEdsPriv));
626
indicator_datetime_planner_eds_init (IndicatorDatetimePlannerEds * self)
630
p = G_TYPE_INSTANCE_GET_PRIVATE (self,
631
INDICATOR_TYPE_DATETIME_PLANNER_EDS,
632
IndicatorDatetimePlannerEdsPriv);
636
p->cancellable = g_cancellable_new ();
638
e_source_registry_new (p->cancellable,
639
on_source_registry_ready,
647
IndicatorDatetimePlanner *
648
indicator_datetime_planner_eds_new (void)
650
gpointer o = g_object_new (INDICATOR_TYPE_DATETIME_PLANNER_EDS, NULL);
652
return INDICATOR_DATETIME_PLANNER (o);