aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/main.c49
-rw-r--r--src/planner-eds.c219
-rw-r--r--src/planner-eds.h2
-rw-r--r--src/planner-mock.c178
-rw-r--r--src/planner-mock.h58
-rw-r--r--src/planner.c20
-rw-r--r--src/planner.h6
-rw-r--r--src/service.c382
-rw-r--r--src/service.h7
10 files changed, 832 insertions, 91 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 093a258..640650a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -20,6 +20,8 @@ libindicator_datetime_service_a_CFLAGS = \
libindicator_datetime_service_a_SOURCES = \
planner.c \
planner.h \
+ planner-mock.c \
+ planner-mock.h \
planner-eds.c \
planner-eds.h \
service.c \
diff --git a/src/main.c b/src/main.c
index c75b2d7..f791683 100644
--- a/src/main.c
+++ b/src/main.c
@@ -24,40 +24,81 @@
#include <glib/gi18n.h>
#include <gio/gio.h>
+#include <libnotify/notify.h>
+#include "planner-eds.h"
+#include "planner-mock.h"
#include "service.h"
/***
****
***/
+/* When enabled, new alarms will show up every minute to test snap decisions */
+static gboolean test_alarms = FALSE;
+
+static GOptionEntry entries[] = {
+ { "test-alarms", '\0', 0, G_OPTION_ARG_NONE, &test_alarms, "Test Alarms", NULL },
+ { NULL }
+};
+
static void
on_name_lost (gpointer instance G_GNUC_UNUSED, gpointer loop)
{
g_message ("exiting: service couldn't acquire or lost ownership of busname");
- g_main_loop_quit ((GMainLoop*)loop);
+
+ if (!test_alarms)
+ g_main_loop_quit ((GMainLoop*)loop);
}
int
main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED)
{
- GMainLoop * loop;
+ GOptionContext * context;
+ GError * error;
+ IndicatorDatetimePlanner * planner;
IndicatorDatetimeService * service;
+ GMainLoop * loop;
/* boilerplate i18n */
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
textdomain (GETTEXT_PACKAGE);
+ /* init libnotify */
+ if (!notify_init ("indicator-datetime-service"))
+ g_critical ("libnotify initialization failed");
+
+ /* parse command-line options */
+ context = g_option_context_new (NULL);
+ g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
+ if (!g_option_context_parse (context, &argc, &argv, &error))
+ {
+ g_print("option parsing failed: %s\n", error->message);
+ return EXIT_FAILURE;
+ }
+
+ /* set up the planner */
+ if (test_alarms)
+ {
+ g_message ("Using fake appointment book for testing alarms.");
+ planner = indicator_datetime_planner_mock_new ();
+ }
+ else
+ {
+ planner = indicator_datetime_planner_eds_new ();
+ }
+
/* run */
- service = indicator_datetime_service_new ();
+ service = indicator_datetime_service_new (planner);
loop = g_main_loop_new (NULL, FALSE);
g_signal_connect (service, INDICATOR_DATETIME_SERVICE_SIGNAL_NAME_LOST,
G_CALLBACK(on_name_lost), loop);
g_main_loop_run (loop);
/* cleanup */
- g_clear_object (&service);
g_main_loop_unref (loop);
+ g_object_unref (service);
+ g_object_unref (planner);
return 0;
}
diff --git a/src/planner-eds.c b/src/planner-eds.c
index f121a32..876fdfc 100644
--- a/src/planner-eds.c
+++ b/src/planner-eds.c
@@ -43,78 +43,180 @@ G_DEFINE_QUARK ("source-client", source_client)
/***
****
+**** my_get_appointments() helpers
+****
***/
-void
-indicator_datetime_appt_free (struct IndicatorDatetimeAppt * appt)
+/* whole-task data that all the subtasks can see */
+struct appointment_task_data
{
- if (appt != NULL)
- {
- g_date_time_unref (appt->end);
- g_date_time_unref (appt->begin);
- g_free (appt->color);
- g_free (appt->summary);
- g_slice_free (struct IndicatorDatetimeAppt, appt);
- }
-}
-
-/***
-**** my_get_appointments() helpers
-***/
+ /* a ref to the planner's cancellable */
+ GCancellable * cancellable;
-struct get_appointments_task_data
-{
/* how many subtasks are still running on */
int subtask_count;
/* the list of appointments to be returned */
GSList * appointments;
-
- /* ensure that recurring events don't get multiple IndicatorDatetimeAppts */
- GHashTable * added;
};
+static struct appointment_task_data *
+appointment_task_data_new (GCancellable * cancellable)
+{
+ struct appointment_task_data * data;
+
+ data = g_slice_new0 (struct appointment_task_data);
+ data->cancellable = g_object_ref (cancellable);
+ return data;
+}
+
static void
-get_appointments_task_data_free (gpointer gdata)
+appointment_task_data_free (gpointer gdata)
{
- struct get_appointments_task_data * data = gdata;
- g_hash_table_unref (data->added);
- g_slice_free (struct get_appointments_task_data, data);
+ struct appointment_task_data * data = gdata;
+
+ g_object_unref (data->cancellable);
+
+ g_slice_free (struct appointment_task_data, data);
}
static void
-on_all_subtasks_done (GTask * task)
+appointment_task_done (GTask * task)
{
- struct get_appointments_task_data * data = g_task_get_task_data (task);
+ struct appointment_task_data * data = g_task_get_task_data (task);
+
g_task_return_pointer (task, data->appointments, NULL);
g_object_unref (task);
}
-struct get_appointments_subtask_data
+static void
+appointment_task_decrement_subtasks (GTask * task)
{
+ struct appointment_task_data * data = g_task_get_task_data (task);
+
+ if (g_atomic_int_dec_and_test (&data->subtask_count))
+ appointment_task_done (task);
+}
+
+static void
+appointment_task_increment_subtasks (GTask * task)
+{
+ struct appointment_task_data * data = g_task_get_task_data (task);
+
+ g_atomic_int_inc (&data->subtask_count);
+}
+
+/**
+*** get-the-appointment's-uri subtasks
+**/
+
+struct appointment_uri_subtask_data
+{
+ /* The parent task */
GTask * task;
- gchar * color;
+ /* The appointment whose uri we're looking for.
+ This pointer is owned by the Task and isn't reffed/unreffed by the subtask */
+ struct IndicatorDatetimeAppt * appt;
};
static void
-on_subtask_done (gpointer gsubdata)
+appointment_uri_subtask_done (struct appointment_uri_subtask_data * subdata)
+{
+ GTask * task = subdata->task;
+
+ /* free the subtask data */
+ g_slice_free (struct appointment_uri_subtask_data, subdata);
+
+ appointment_task_decrement_subtasks (task);
+}
+
+static struct appointment_uri_subtask_data *
+appointment_uri_subtask_data_new (GTask * task, struct IndicatorDatetimeAppt * appt)
+{
+ struct appointment_uri_subtask_data * subdata;
+
+ appointment_task_increment_subtasks (task);
+
+ subdata = g_slice_new0 (struct appointment_uri_subtask_data);
+ subdata->task = task;
+ subdata->appt = appt;
+ return subdata;
+}
+
+static void
+on_appointment_uris_ready (GObject * client,
+ GAsyncResult * res,
+ gpointer gsubdata)
{
- struct get_appointments_subtask_data * subdata;
+ GSList * uris;
+ GError * error;
+ struct appointment_uri_subtask_data * subdata = gsubdata;
+
+ uris = NULL;
+ error = NULL;
+ e_cal_client_get_attachment_uris_finish (E_CAL_CLIENT(client), res, &uris, &error);
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Error getting appointment uris: %s", error->message);
+
+ g_error_free (error);
+ }
+ else if (uris != NULL)
+ {
+ struct IndicatorDatetimeAppt * appt = subdata->appt;
+ appt->url = g_strdup (uris->data); /* copy the first URL */
+ g_debug ("found url '%s' for appointment '%s'", appt->url, appt->summary);
+ e_client_util_free_string_slist (uris);
+ }
+
+ appointment_uri_subtask_done (subdata);
+}
+
+/**
+*** enumerate-the-components subtasks
+**/
+
+/* data struct for the enumerate-components subtask */
+struct appointment_component_subtask_data
+{
+ /* The parent task */
GTask * task;
- struct get_appointments_task_data * data;
- subdata = gsubdata;
- task = subdata->task;
+ /* The client we're walking through. The subtask owns a ref to this */
+ ECalClient * client;
+
+ /* The appointment's color coding. The subtask owns this string */
+ gchar * color;
+};
+
+static void
+on_appointment_component_subtask_done (gpointer gsubdata)
+{
+ struct appointment_component_subtask_data * subdata = gsubdata;
+ GTask * task = subdata->task;
/* free the subtask data */
g_free (subdata->color);
- g_slice_free (struct get_appointments_subtask_data, subdata);
+ g_object_unref (subdata->client);
+ g_slice_free (struct appointment_component_subtask_data, subdata);
- /* poke the task */
- data = g_task_get_task_data (task);
- if (g_atomic_int_dec_and_test (&data->subtask_count))
- on_all_subtasks_done (task);
+ appointment_task_decrement_subtasks (task);
+}
+
+static struct appointment_component_subtask_data *
+appointment_component_subtask_data_new (GTask * task, ECalClient * client, const gchar * color)
+{
+ struct appointment_component_subtask_data * subdata;
+
+ appointment_task_increment_subtasks (task);
+
+ subdata = g_slice_new0 (struct appointment_component_subtask_data);
+ subdata->task = task;
+ subdata->client = g_object_ref (client);
+ subdata->color = g_strdup (color);
+ return subdata;
}
static gboolean
@@ -124,8 +226,8 @@ my_get_appointments_foreach (ECalComponent * component,
gpointer gsubdata)
{
const ECalComponentVType vtype = e_cal_component_get_vtype (component);
- struct get_appointments_subtask_data * subdata = gsubdata;
- struct get_appointments_task_data * data = g_task_get_task_data (subdata->task);
+ struct appointment_component_subtask_data * subdata = gsubdata;
+ struct appointment_task_data * data = g_task_get_task_data (subdata->task);
if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO))
{
@@ -136,7 +238,6 @@ my_get_appointments_foreach (ECalComponent * component,
e_cal_component_get_status (component, &status);
if ((uid != NULL) &&
- (!g_hash_table_contains (data->added, uid)) &&
(status != ICAL_STATUS_COMPLETED) &&
(status != ICAL_STATUS_CANCELLED))
{
@@ -145,6 +246,7 @@ my_get_appointments_foreach (ECalComponent * component,
GSList * recur_list;
ECalComponentText text;
struct IndicatorDatetimeAppt * appt;
+ struct appointment_uri_subtask_data * uri_subdata;
appt = g_slice_new0 (struct IndicatorDatetimeAppt);
@@ -169,13 +271,22 @@ my_get_appointments_foreach (ECalComponent * component,
appt->color = g_strdup (subdata->color);
appt->is_event = vtype == E_CAL_COMPONENT_EVENT;
appt->summary = g_strdup (text.value);
+ appt->uid = g_strdup (uid);
alarm_uids = e_cal_component_get_alarm_uids (component);
appt->has_alarms = alarm_uids != NULL;
cal_obj_uid_list_free (alarm_uids);
data->appointments = g_slist_prepend (data->appointments, appt);
- g_hash_table_add (data->added, g_strdup(uid));
+
+ /* start a new subtask to get the associated URIs */
+ uri_subdata = appointment_uri_subtask_data_new (subdata->task, appt);
+ e_cal_client_get_attachment_uris (subdata->client,
+ uid,
+ NULL,
+ data->cancellable,
+ on_appointment_uris_ready,
+ uri_subdata);
}
}
@@ -197,7 +308,6 @@ my_get_appointments (IndicatorDatetimePlanner * planner,
priv_t * p;
const char * str;
icaltimezone * default_timezone;
- struct get_appointments_task_data * data;
const int64_t begin = g_date_time_to_unix (begin_datetime);
const int64_t end = g_date_time_to_unix (end_datetime);
GTask * task;
@@ -223,17 +333,18 @@ my_get_appointments (IndicatorDatetimePlanner * planner,
*** walk through the sources to build the appointment list
**/
- data = g_slice_new0 (struct get_appointments_task_data);
- data->added = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
task = g_task_new (planner, p->cancellable, callback, user_data);
- g_task_set_task_data (task, data, get_appointments_task_data_free);
+ g_task_set_task_data (task,
+ appointment_task_data_new (p->cancellable),
+ appointment_task_data_free);
subtasks_added = FALSE;
for (l=p->sources; l!=NULL; l=l->next)
{
ESource * source;
ECalClient * client;
- struct get_appointments_subtask_data * subdata;
+ const char * color;
+ struct appointment_component_subtask_data * subdata;
source = l->data;
client = g_object_get_qdata (l->data, source_client_quark());
@@ -243,11 +354,9 @@ my_get_appointments (IndicatorDatetimePlanner * planner,
if (default_timezone != NULL)
e_cal_client_set_default_timezone (client, default_timezone);
- subdata = g_slice_new (struct get_appointments_subtask_data);
- subdata->task = task;
- subdata->color = e_source_selectable_dup_color (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR));
-
- g_atomic_int_inc (&data->subtask_count);
+ /* start a new subtask to enumerate all the components in this client. */
+ color = e_source_selectable_get_color (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR));
+ subdata = appointment_component_subtask_data_new (task, client, color);
subtasks_added = TRUE;
e_cal_client_generate_instances (client,
begin,
@@ -255,11 +364,11 @@ my_get_appointments (IndicatorDatetimePlanner * planner,
p->cancellable,
my_get_appointments_foreach,
subdata,
- on_subtask_done);
+ on_appointment_component_subtask_done);
}
if (!subtasks_added)
- on_all_subtasks_done (task);
+ appointment_task_done (task);
}
static GSList *
@@ -270,7 +379,7 @@ my_get_appointments_finish (IndicatorDatetimePlanner * self G_GNUC_UNUSED,
return g_task_propagate_pointer (G_TASK(res), error);
}
-gboolean
+static gboolean
my_is_configured (IndicatorDatetimePlanner * planner)
{
IndicatorDatetimePlannerEds * self;
diff --git a/src/planner-eds.h b/src/planner-eds.h
index a2c803a..dea9371 100644
--- a/src/planner-eds.h
+++ b/src/planner-eds.h
@@ -51,8 +51,6 @@ struct _IndicatorDatetimePlannerEdsClass
IndicatorDatetimePlannerClass parent_class;
};
-gboolean indicator_datetime_planner_eds_is_usable (void);
-
IndicatorDatetimePlanner * indicator_datetime_planner_eds_new (void);
G_END_DECLS
diff --git a/src/planner-mock.c b/src/planner-mock.c
new file mode 100644
index 0000000..e67ad7e
--- /dev/null
+++ b/src/planner-mock.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "planner-mock.h"
+
+struct _IndicatorDatetimePlannerMockPriv
+{
+ gboolean is_configured;
+};
+
+typedef IndicatorDatetimePlannerMockPriv priv_t;
+
+G_DEFINE_TYPE (IndicatorDatetimePlannerMock,
+ indicator_datetime_planner_mock,
+ INDICATOR_TYPE_DATETIME_PLANNER)
+
+/***
+**** IndicatorDatetimePlanner virtual funcs
+***/
+
+static void
+my_get_appointments (IndicatorDatetimePlanner * planner,
+ GDateTime * begin_datetime,
+ GDateTime * end_datetime G_GNUC_UNUSED,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask * task;
+ GSList * appointments;
+ struct IndicatorDatetimeAppt * appt;
+ struct IndicatorDatetimeAppt * prev;
+
+ task = g_task_new (planner, NULL, callback, user_data);
+
+ /**
+ *** Build the appointments list
+ **/
+
+ appointments = NULL;
+
+ /* add a daily appointment that occurs at the beginning of the next minute */
+ appt = g_slice_new0 (struct IndicatorDatetimeAppt);
+ appt->is_daily = TRUE;
+ appt->begin = g_date_time_add_seconds (begin_datetime, 60-g_date_time_get_seconds(begin_datetime));
+ appt->end = g_date_time_add_minutes (appt->begin, 1);
+ appt->color = g_strdup ("#00FF00");
+ appt->is_event = TRUE;
+ appt->summary = g_strdup ("Daily alarm");
+ appt->uid = g_strdup ("this uid isn't very random.");
+ appt->has_alarms = TRUE;
+ appt->url = g_strdup ("alarm:///some-alarm-info-goes-here");
+ appointments = g_slist_prepend (appointments, appt);
+ prev = appt;
+
+ /* and add one for a minute later that has an alarm uri */
+ appt = g_slice_new0 (struct IndicatorDatetimeAppt);
+ appt->is_daily = TRUE;
+ appt->begin = g_date_time_add_minutes (prev->end, 1);
+ appt->end = g_date_time_add_minutes (appt->begin, 1);
+ appt->color = g_strdup ("#0000FF");
+ appt->is_event = TRUE;
+ appt->summary = g_strdup ("Second Daily alarm");
+ appt->uid = g_strdup ("this uid isn't very random either.");
+ appt->has_alarms = FALSE;
+ appointments = g_slist_prepend (appointments, appt);
+
+ /* done */
+ g_task_return_pointer (task, appointments, NULL);
+ g_object_unref (task);
+}
+
+static GSList *
+my_get_appointments_finish (IndicatorDatetimePlanner * self G_GNUC_UNUSED,
+ GAsyncResult * res,
+ GError ** error)
+{
+ return g_task_propagate_pointer (G_TASK(res), error);
+}
+
+static gboolean
+my_is_configured (IndicatorDatetimePlanner * planner)
+{
+ IndicatorDatetimePlannerMock * self;
+ self = INDICATOR_DATETIME_PLANNER_MOCK (planner);
+ return self->priv->is_configured;
+}
+
+static void
+my_activate (IndicatorDatetimePlanner * self G_GNUC_UNUSED)
+{
+ g_message ("%s %s", G_STRLOC, G_STRFUNC);
+}
+
+static void
+my_activate_time (IndicatorDatetimePlanner * self G_GNUC_UNUSED,
+ GDateTime * activate_time)
+{
+ gchar * str = g_date_time_format (activate_time, "%F %T");
+ g_message ("%s %s: %s", G_STRLOC, G_STRFUNC, str);
+ g_free (str);
+}
+
+/***
+**** GObject virtual funcs
+***/
+
+static void
+my_dispose (GObject * o)
+{
+ G_OBJECT_CLASS (indicator_datetime_planner_mock_parent_class)->dispose (o);
+}
+
+/***
+**** Instantiation
+***/
+
+static void
+indicator_datetime_planner_mock_class_init (IndicatorDatetimePlannerMockClass * klass)
+{
+ GObjectClass * object_class;
+ IndicatorDatetimePlannerClass * planner_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = my_dispose;
+
+ planner_class = INDICATOR_DATETIME_PLANNER_CLASS (klass);
+ planner_class->is_configured = my_is_configured;
+ planner_class->activate = my_activate;
+ planner_class->activate_time = my_activate_time;
+ planner_class->get_appointments = my_get_appointments;
+ planner_class->get_appointments_finish = my_get_appointments_finish;
+
+ g_type_class_add_private (klass, sizeof (IndicatorDatetimePlannerMockPriv));
+}
+
+static void
+indicator_datetime_planner_mock_init (IndicatorDatetimePlannerMock * self)
+{
+ priv_t * p;
+
+ p = G_TYPE_INSTANCE_GET_PRIVATE (self,
+ INDICATOR_TYPE_DATETIME_PLANNER_MOCK,
+ IndicatorDatetimePlannerMockPriv);
+
+ p->is_configured = TRUE;
+
+ self->priv = p;
+}
+
+/***
+**** Public
+***/
+
+IndicatorDatetimePlanner *
+indicator_datetime_planner_mock_new (void)
+{
+ gpointer o = g_object_new (INDICATOR_TYPE_DATETIME_PLANNER_MOCK, NULL);
+
+ return INDICATOR_DATETIME_PLANNER (o);
+}
diff --git a/src/planner-mock.h b/src/planner-mock.h
new file mode 100644
index 0000000..8d7d7c2
--- /dev/null
+++ b/src/planner-mock.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __INDICATOR_DATETIME_PLANNER_MOCK__H__
+#define __INDICATOR_DATETIME_PLANNER_MOCK__H__
+
+#include "planner.h" /* parent class */
+
+G_BEGIN_DECLS
+
+#define INDICATOR_TYPE_DATETIME_PLANNER_MOCK (indicator_datetime_planner_mock_get_type())
+#define INDICATOR_DATETIME_PLANNER_MOCK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_DATETIME_PLANNER_MOCK, IndicatorDatetimePlannerMock))
+#define INDICATOR_DATETIME_PLANNER_MOCK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), INDICATOR_TYPE_DATETIME_PLANNER_MOCK, IndicatorDatetimePlannerMockClass))
+#define INDICATOR_IS_DATETIME_PLANNER_MOCK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_DATETIME_PLANNER_MOCK))
+
+typedef struct _IndicatorDatetimePlannerMock IndicatorDatetimePlannerMock;
+typedef struct _IndicatorDatetimePlannerMockPriv IndicatorDatetimePlannerMockPriv;
+typedef struct _IndicatorDatetimePlannerMockClass IndicatorDatetimePlannerMockClass;
+
+GType indicator_datetime_planner_mock_get_type (void);
+
+/**
+ * An IndicatorDatetimePlanner which uses Evolution Data Server
+ * to get its list of appointments.
+ */
+struct _IndicatorDatetimePlannerMock
+{
+ /*< private >*/
+ IndicatorDatetimePlanner parent;
+ IndicatorDatetimePlannerMockPriv * priv;
+};
+
+struct _IndicatorDatetimePlannerMockClass
+{
+ IndicatorDatetimePlannerClass parent_class;
+};
+
+IndicatorDatetimePlanner * indicator_datetime_planner_mock_new (void);
+
+G_END_DECLS
+
+#endif /* __INDICATOR_DATETIME_PLANNER_MOCK__H__ */
diff --git a/src/planner.c b/src/planner.c
index e826c2c..9b9a77f 100644
--- a/src/planner.c
+++ b/src/planner.c
@@ -259,3 +259,23 @@ indicator_datetime_planner_get_timezone (IndicatorDatetimePlanner * self)
return self->priv->timezone;
}
+
+/***
+****
+***/
+
+void
+indicator_datetime_appt_free (struct IndicatorDatetimeAppt * appt)
+{
+ if (appt != NULL)
+ {
+ g_date_time_unref (appt->end);
+ g_date_time_unref (appt->begin);
+ g_free (appt->color);
+ g_free (appt->summary);
+ g_free (appt->url);
+ g_free (appt->uid);
+ g_slice_free (struct IndicatorDatetimeAppt, appt);
+ }
+}
+
diff --git a/src/planner.h b/src/planner.h
index 206bfe5..ffe8937 100644
--- a/src/planner.h
+++ b/src/planner.h
@@ -40,8 +40,10 @@ GType indicator_datetime_planner_get_type (void);
struct IndicatorDatetimeAppt
{
- char * color;
- char * summary;
+ gchar * color;
+ gchar * summary;
+ gchar * url;
+ gchar * uid;
GDateTime * begin;
GDateTime * end;
gboolean is_event;
diff --git a/src/service.c b/src/service.c
index c283cf6..e83410e 100644
--- a/src/service.c
+++ b/src/service.c
@@ -24,11 +24,11 @@
#include <glib/gi18n.h>
#include <gio/gio.h>
+#include <libnotify/notify.h>
#include <json-glib/json-glib.h>
#include <url-dispatcher.h>
#include "dbus-shared.h"
-#include "planner-eds.h"
#include "timezone-file.h"
#include "timezone-geoclue.h"
#include "service.h"
@@ -53,6 +53,15 @@ static guint signals[LAST_SIGNAL] = { 0 };
enum
{
+ PROP_0,
+ PROP_PLANNER,
+ PROP_LAST
+};
+
+static GParamSpec * properties[PROP_LAST] = { 0 };
+
+enum
+{
SECTION_HEADER = (1<<0),
SECTION_CALENDAR = (1<<1),
SECTION_APPOINTMENTS = (1<<2),
@@ -119,6 +128,7 @@ struct _IndicatorDatetimeServicePrivate
guint header_timer;
guint timezone_timer;
+ guint alarm_timer;
/* Which year/month to show in the calendar,
and which day should get the cursor.
@@ -388,6 +398,193 @@ start_header_timer (IndicatorDatetimeService * self)
g_date_time_unref (now);
}
+/***
+**** ALARMS
+***/
+
+static void set_alarm_timer (IndicatorDatetimeService * self);
+
+static gboolean
+appointment_has_alarm_url (const struct IndicatorDatetimeAppt * appt)
+{
+ return (appt->has_alarms) &&
+ (appt->url != NULL) &&
+ (g_str_has_prefix (appt->url, "alarm:///"));
+}
+
+static gboolean
+datetimes_have_the_same_minute (GDateTime * a G_GNUC_UNUSED, GDateTime * b G_GNUC_UNUSED)
+{
+ int ay, am, ad;
+ int by, bm, bd;
+
+ g_date_time_get_ymd (a, &ay, &am, &ad);
+ g_date_time_get_ymd (b, &by, &bm, &bd);
+
+ return (ay == by) &&
+ (am == bm) &&
+ (ad == bd) &&
+ (g_date_time_get_hour (a) == g_date_time_get_hour (b)) &&
+ (g_date_time_get_minute (a) == g_date_time_get_minute (b));
+}
+
+static void
+dispatch_alarm_url (const struct IndicatorDatetimeAppt * appt)
+{
+ gchar * str;
+
+ g_return_if_fail (appt != NULL);
+ g_return_if_fail (appointment_has_alarm_url (appt));
+
+ str = g_date_time_format (appt->begin, "%F %T");
+ g_debug ("dispatching url \"%s\" for appointment \"%s\", which begins at %s",
+ appt->url, appt->summary, str);
+ g_free (str);
+
+ url_dispatch_send (appt->url, NULL, NULL);
+}
+
+static void
+on_snap_decided (NotifyNotification * notification G_GNUC_UNUSED,
+ char * action,
+ gpointer gurl)
+{
+ g_debug ("%s: %s", G_STRFUNC, action);
+
+ if (!g_strcmp0 (action, "ok"))
+ {
+ const gchar * url = gurl;
+ g_debug ("dispatching url '%s'", url);
+ url_dispatch_send (url, NULL, NULL);
+ }
+}
+
+static void
+show_snap_decision_for_alarm (const struct IndicatorDatetimeAppt * appt)
+{
+ gchar * title;
+ const gchar * body;
+ const gchar * icon_name;
+ NotifyNotification * nn;
+ GError * error;
+
+ title = g_date_time_format (appt->begin,
+ get_terse_time_format_string (appt->begin));
+ body = appt->summary;
+ icon_name = ALARM_CLOCK_ICON_NAME;
+ g_debug ("creating a snap decision with title '%s', body '%s', icon '%s'",
+ title, body, icon_name);
+
+ nn = notify_notification_new (title, body, icon_name);
+ notify_notification_set_hint (nn, "x-canonical-snap-decisions",
+ g_variant_new_boolean(TRUE));
+ notify_notification_add_action (nn, "ok", _("OK"),
+ on_snap_decided, g_strdup(appt->url), g_free);
+ notify_notification_add_action (nn, "cancel", _("Cancel"),
+ on_snap_decided, NULL, NULL);
+
+ error = NULL;
+ notify_notification_show (nn, &error);
+ if (error != NULL)
+ {
+ g_warning ("Unable to show alarm '%s' popup: %s", body, error->message);
+ g_error_free (error);
+ dispatch_alarm_url (appt);
+ }
+
+ g_free (title);
+}
+
+static void update_appointment_lists (IndicatorDatetimeService * self);
+
+static gboolean
+on_alarm_timer (gpointer gself)
+{
+ IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (gself);
+ GDateTime * now;
+ GSList * l;
+
+ /* If there are any alarms at the current time, show a snap decision */
+ now = indicator_datetime_service_get_localtime (self);
+ for (l=self->priv->upcoming_appointments; l!=NULL; l=l->next)
+ {
+ const struct IndicatorDatetimeAppt * appt = l->data;
+
+ if (appointment_has_alarm_url (appt))
+ if (datetimes_have_the_same_minute (now, appt->begin))
+ show_snap_decision_for_alarm (appt);
+ }
+ g_date_time_unref (now);
+
+ /* rebuild the alarm list asynchronously.
+ set_upcoming_appointments() will update the alarm timer when this
+ async call is done, so no need to restart the timer here... */
+ update_appointment_lists (self);
+
+ return G_SOURCE_REMOVE;
+}
+
+/* if there are upcoming alarms, set the alarm timer to the nearest one.
+ otherwise, unset the alarm timer. */
+static void
+set_alarm_timer (IndicatorDatetimeService * self)
+{
+ priv_t * p;
+ GDateTime * now;
+ GDateTime * alarm_time;
+ GSList * l;
+
+ p = self->priv;
+ indicator_clear_timer (&p->alarm_timer);
+
+ now = indicator_datetime_service_get_localtime (self);
+
+ /* find the time of the next alarm on our calendar */
+ alarm_time = NULL;
+ for (l=p->upcoming_appointments; l!=NULL; l=l->next)
+ {
+ const struct IndicatorDatetimeAppt * appt = l->data;
+
+ if (appointment_has_alarm_url (appt))
+ if (g_date_time_compare (appt->begin, now) > 0)
+ if (!alarm_time || g_date_time_compare (alarm_time, appt->begin) > 0)
+ alarm_time = appt->begin;
+ }
+
+ /* if there's an upcoming alarm, set a timer to wake up at that time */
+ if (alarm_time != NULL)
+ {
+ GTimeSpan interval_msec;
+ gchar * str;
+ GDateTime * then;
+
+ interval_msec = g_date_time_difference (alarm_time, now);
+ interval_msec += G_USEC_PER_SEC; /* fire a moment after alarm_time */
+ interval_msec /= 1000; /* convert from usec to msec */
+
+ str = g_date_time_format (alarm_time, "%F %T");
+ g_debug ("%s is the next alarm time", str);
+ g_free (str);
+ then = g_date_time_add_seconds (now, interval_msec/1000);
+ str = g_date_time_format (then, "%F %T");
+ g_debug ("%s is when we'll wake up for it", str);
+ g_free (str);
+ g_date_time_unref (then);
+
+ p->alarm_timer = g_timeout_add_full (G_PRIORITY_HIGH,
+ (guint) interval_msec,
+ on_alarm_timer,
+ self,
+ NULL);
+ }
+
+ g_date_time_unref (now);
+}
+
+/***
+****
+***/
+
static void
update_internal_timezone (IndicatorDatetimeService * self)
{
@@ -746,23 +943,36 @@ get_appointment_time_format (struct IndicatorDatetimeAppt * appt,
}
static void
-add_appointments (IndicatorDatetimeService * self, GMenu * menu, gboolean terse)
+add_appointments (IndicatorDatetimeService * self, GMenu * menu, gboolean phone)
{
const int MAX_APPTS = 5;
- GDateTime * now = indicator_datetime_service_get_localtime (self);
+ GDateTime * now;
+ GHashTable * added;
GSList * appts;
GSList * l;
int i;
+ now = indicator_datetime_service_get_localtime (self);
+
+ added = g_hash_table_new (g_str_hash, g_str_equal);
+
/* build appointment menuitems */
appts = self->priv->upcoming_appointments;
for (l=appts, i=0; l!=NULL && i<MAX_APPTS; l=l->next, i++)
{
struct IndicatorDatetimeAppt * appt = l->data;
- char * fmt = get_appointment_time_format (appt, now, self->priv->settings, terse);
- const gint64 unix_time = g_date_time_to_unix (appt->begin);
+ char * fmt;
+ gint64 unix_time;
GMenuItem * menu_item;
+ if (g_hash_table_contains (added, appt->uid))
+ continue;
+
+ g_hash_table_add (added, appt->uid);
+
+ fmt = get_appointment_time_format (appt, now, self->priv->settings, phone);
+ unix_time = g_date_time_to_unix (appt->begin);
+
menu_item = g_menu_item_new (appt->summary, NULL);
if (appt->has_alarms)
@@ -779,15 +989,22 @@ add_appointments (IndicatorDatetimeService * self, GMenu * menu, gboolean terse)
g_menu_item_set_attribute (menu_item, "x-canonical-type",
"s", appt->has_alarms ? "com.canonical.indicator.alarm"
: "com.canonical.indicator.appointment");
- g_menu_item_set_action_and_target_value (menu_item,
- "indicator.activate-planner",
- g_variant_new_int64 (unix_time));
+
+ if (phone)
+ g_menu_item_set_action_and_target_value (menu_item,
+ "indicator.activate-appointment",
+ g_variant_new_string (appt->uid));
+ else
+ g_menu_item_set_action_and_target_value (menu_item,
+ "indicator.activate-planner",
+ g_variant_new_int64 (unix_time));
g_menu_append_item (menu, menu_item);
g_object_unref (menu_item);
g_free (fmt);
}
/* cleanup */
+ g_hash_table_unref (added);
g_date_time_unref (now);
}
@@ -1366,6 +1583,34 @@ on_phone_settings_activated (GSimpleAction * a G_GNUC_UNUSED,
}
static void
+on_activate_appointment (GSimpleAction * a G_GNUC_UNUSED,
+ GVariant * param,
+ gpointer gself)
+{
+ priv_t * p = INDICATOR_DATETIME_SERVICE(gself)->priv;
+ const gchar * uid = g_variant_get_string (param, NULL);
+
+ if (uid != NULL)
+ {
+ const struct IndicatorDatetimeAppt * appt;
+ GSList * l;
+
+ /* find the appointment that matches that uid */
+ for (l=p->upcoming_appointments, appt=NULL; l && !appt; l=l->next)
+ {
+ const struct IndicatorDatetimeAppt * tmp = l->data;
+ if (!g_strcmp0 (uid, tmp->uid))
+ appt = tmp;
+ }
+
+ /* if that appointment's an alarm, dispatch its url */
+ g_debug ("%s: uri '%s'; matching appt is %p", G_STRFUNC, uid, appt);
+ if (appt && appointment_has_alarm_url (appt))
+ dispatch_alarm_url (appt);
+ }
+}
+
+static void
on_phone_clock_activated (GSimpleAction * a G_GNUC_UNUSED,
GVariant * param G_GNUC_UNUSED,
gpointer gself G_GNUC_UNUSED)
@@ -1429,6 +1674,7 @@ init_gactions (IndicatorDatetimeService * self)
{ "activate-phone-settings", on_phone_settings_activated },
{ "activate-phone-clock-app", on_phone_clock_activated },
{ "activate-planner", on_activate_planner, "x", NULL },
+ { "activate-appointment", on_activate_appointment, "s", NULL },
{ "set-location", on_set_location, "s" }
};
@@ -1659,6 +1905,10 @@ set_upcoming_appointments (IndicatorDatetimeService * self,
/* sync the menus/actions */
rebuild_appointments_section_soon (self);
+
+ /* alarm timer is keyed off of the next alarm time,
+ so it needs to be rebuilt when the appointment list changes */
+ set_alarm_timer (self);
}
static void
@@ -1828,6 +2078,45 @@ on_name_lost (GDBusConnection * connection G_GNUC_UNUSED,
***/
static void
+my_get_property (GObject * o,
+ guint property_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o);
+
+ switch (property_id)
+ {
+ case PROP_PLANNER:
+ g_value_set_object (value, self->priv->planner);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ }
+}
+
+static void
+my_set_property (GObject * o,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ IndicatorDatetimeService * self = INDICATOR_DATETIME_SERVICE (o);
+
+ switch (property_id)
+ {
+ case PROP_PLANNER:
+ indicator_datetime_service_set_planner (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec);
+ }
+}
+
+
+static void
my_dispose (GObject * o)
{
int i;
@@ -1850,13 +2139,7 @@ my_dispose (GObject * o)
set_detect_location_enabled (self, FALSE);
- if (p->planner != NULL)
- {
- g_signal_handlers_disconnect_by_data (p->planner, self);
- g_clear_object (&p->planner);
- }
- g_clear_pointer (&p->upcoming_appointments, indicator_datetime_planner_free_appointments);
- g_clear_pointer (&p->calendar_appointments, indicator_datetime_planner_free_appointments);
+ indicator_datetime_service_set_planner (self, NULL);
if (p->login1_manager != NULL)
{
@@ -1868,6 +2151,7 @@ my_dispose (GObject * o)
indicator_clear_timer (&p->rebuild_id);
indicator_clear_timer (&p->timezone_timer);
indicator_clear_timer (&p->header_timer);
+ indicator_clear_timer (&p->alarm_timer);
if (p->settings != NULL)
{
@@ -1958,16 +2242,6 @@ indicator_datetime_service_init (IndicatorDatetimeService * self)
p->cancellable = g_cancellable_new ();
/***
- **** Create the planner and listen for changes
- ***/
-
- p->planner = indicator_datetime_planner_eds_new ();
-
- g_signal_connect_swapped (p->planner, "appointments-changed",
- G_CALLBACK(update_appointment_lists), self);
-
-
- /***
**** Create the settings object and listen for changes
***/
@@ -2036,6 +2310,8 @@ indicator_datetime_service_init (IndicatorDatetimeService * self)
on_local_time_jumped (self);
+ set_alarm_timer (self);
+
for (i=0; i<N_PROFILES; ++i)
create_menu (self, i);
@@ -2046,9 +2322,12 @@ static void
indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass)
{
GObjectClass * object_class = G_OBJECT_CLASS (klass);
+ const GParamFlags flags = G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS;
object_class->dispose = my_dispose;
object_class->finalize = my_finalize;
+ object_class->get_property = my_get_property;
+ object_class->set_property = my_set_property;
g_type_class_add_private (klass, sizeof (IndicatorDatetimeServicePrivate));
@@ -2060,6 +2339,18 @@ indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass)
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
+
+ /* install properties */
+
+ properties[PROP_0] = NULL;
+
+ properties[PROP_PLANNER] = g_param_spec_object ("planner",
+ "Planner",
+ "The appointment provider",
+ INDICATOR_TYPE_DATETIME_PLANNER,
+ flags);
+
+ g_object_class_install_properties (object_class, PROP_LAST, properties);
}
/***
@@ -2067,9 +2358,11 @@ indicator_datetime_service_class_init (IndicatorDatetimeServiceClass * klass)
***/
IndicatorDatetimeService *
-indicator_datetime_service_new (void)
+indicator_datetime_service_new (IndicatorDatetimePlanner * planner)
{
- GObject * o = g_object_new (INDICATOR_TYPE_DATETIME_SERVICE, NULL);
+ GObject * o = g_object_new (INDICATOR_TYPE_DATETIME_SERVICE,
+ "planner", planner,
+ NULL);
return INDICATOR_DATETIME_SERVICE (o);
}
@@ -2105,3 +2398,38 @@ indicator_datetime_service_set_calendar_date (IndicatorDatetimeService * self,
if (dirty)
update_appointment_lists (self);
}
+
+void
+indicator_datetime_service_set_planner (IndicatorDatetimeService * self,
+ IndicatorDatetimePlanner * planner)
+{
+ priv_t * p;
+
+ g_return_if_fail (INDICATOR_IS_DATETIME_SERVICE (self));
+ g_return_if_fail (INDICATOR_IS_DATETIME_PLANNER (planner));
+
+ p = self->priv;
+
+ /* clear the old planner & appointments */
+
+ if (p->planner != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (p->planner, self);
+ g_clear_object (&p->planner);
+ }
+
+ g_clear_pointer (&p->upcoming_appointments, indicator_datetime_planner_free_appointments);
+ g_clear_pointer (&p->calendar_appointments, indicator_datetime_planner_free_appointments);
+
+ /* set the new planner & begin fetching appointments from it */
+
+ if (planner != NULL)
+ {
+ p->planner = g_object_ref (planner);
+
+ g_signal_connect_swapped (p->planner, "appointments-changed",
+ G_CALLBACK(update_appointment_lists), self);
+
+ update_appointment_lists (self);
+ }
+}
diff --git a/src/service.h b/src/service.h
index b142882..25bb59a 100644
--- a/src/service.h
+++ b/src/service.h
@@ -22,6 +22,7 @@
#include <glib.h>
#include <glib-object.h>
+#include "planner.h"
G_BEGIN_DECLS
@@ -62,13 +63,17 @@ struct _IndicatorDatetimeServiceClass
GType indicator_datetime_service_get_type (void);
-IndicatorDatetimeService * indicator_datetime_service_new (void);
+IndicatorDatetimeService * indicator_datetime_service_new (IndicatorDatetimePlanner * planner);
GDateTime * indicator_datetime_service_get_localtime (IndicatorDatetimeService * service);
void indicator_datetime_service_set_calendar_date (IndicatorDatetimeService * self,
GDateTime * date);
+void indicator_datetime_service_set_planner (IndicatorDatetimeService * self,
+ IndicatorDatetimePlanner * planner);
+
+
G_END_DECLS