2
* Copyright 2014 Canonical Ltd.
4
* This program is free software: you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License version 3, as published
6
* by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranties of
10
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
* PURPOSE. See the GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License along
14
* with this program. If not, see <http://www.gnu.org/licenses/>.
17
* Charles Kerr <charles.kerr@canonical.com>
20
#include <datetime/engine-eds.h>
22
#include <libical/ical.h>
23
#include <libical/icaltime.h>
24
#include <libecal/libecal.h>
25
#include <libedataserver/libedataserver.h>
27
#include <algorithm> // std::sort()
43
Impl(EdsEngine& owner):
45
m_cancellable(g_cancellable_new())
47
e_source_registry_new(m_cancellable, on_source_registry_ready, this);
52
g_cancellable_cancel(m_cancellable);
53
g_clear_object(&m_cancellable);
55
while(!m_sources.empty())
56
remove_source(*m_sources.begin());
59
g_source_remove(m_rebuild_tag);
61
if (m_source_registry)
62
g_signal_handlers_disconnect_by_data(m_source_registry, this);
63
g_clear_object(&m_source_registry);
66
core::Signal<>& changed()
71
void get_appointments(const DateTime& begin,
73
const Timezone& timezone,
74
std::function<void(const std::vector<Appointment>&)> func)
76
const auto begin_timet = begin.to_unix();
77
const auto end_timet = end.to_unix();
79
const auto b_str = begin.format("%F %T");
80
const auto e_str = end.format("%F %T");
81
g_debug("getting all appointments from [%s ... %s]", b_str.c_str(), e_str.c_str());
84
*** init the default timezone
87
icaltimezone * default_timezone = nullptr;
88
const auto tz = timezone.timezone.get().c_str();
91
default_timezone = icaltimezone_get_builtin_timezone(tz);
93
if (default_timezone == nullptr) // maybe str is a tzid?
94
default_timezone = icaltimezone_get_builtin_timezone_from_tzid(tz);
96
g_debug("default_timezone is %p", (void*)default_timezone);
100
*** walk through the sources to build the appointment list
103
auto task_deleter = [](Task* task){
104
// give the caller the (sorted) finished product
105
auto& a = task->appointments;
106
std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;});
108
// we're done; delete the task
109
g_debug("time to delete task %p", (void*)task);
113
std::shared_ptr<Task> main_task(new Task(this, func), task_deleter);
115
for (auto& kv : m_clients)
117
auto& client = kv.second;
118
if (default_timezone != nullptr)
119
e_cal_client_set_default_timezone(client, default_timezone);
121
// start a new subtask to enumerate all the components in this client.
122
auto& source = kv.first;
123
auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
124
const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));
125
g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
126
e_cal_client_generate_instances(client,
130
my_get_appointments_foreach,
131
new AppointmentSubtask (main_task, client, color),
132
[](gpointer g){delete static_cast<AppointmentSubtask*>(g);});
143
static gboolean set_dirty_now_static (gpointer gself)
145
auto self = static_cast<Impl*>(gself);
146
self->m_rebuild_tag = 0;
147
self->set_dirty_now();
148
return G_SOURCE_REMOVE;
151
void set_dirty_soon()
153
static const int ARBITRARY_BATCH_MSEC = 200;
155
if (m_rebuild_tag == 0)
156
m_rebuild_tag = g_timeout_add(ARBITRARY_BATCH_MSEC, set_dirty_now_static, this);
159
static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself)
161
GError * error = nullptr;
162
auto r = e_source_registry_new_finish(res, &error);
163
if (error != nullptr)
165
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
166
g_warning("indicator-datetime cannot show EDS appointments: %s", error->message);
172
g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), gself);
173
g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), gself);
174
g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), gself);
175
g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself);
176
g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), gself);
178
auto self = static_cast<Impl*>(gself);
179
self->m_source_registry = r;
180
self->add_sources_by_extension(E_SOURCE_EXTENSION_CALENDAR);
181
self->add_sources_by_extension(E_SOURCE_EXTENSION_TASK_LIST);
185
void add_sources_by_extension(const char* extension)
187
auto& r = m_source_registry;
188
auto sources = e_source_registry_list_sources(r, extension);
189
for (auto l=sources; l!=nullptr; l=l->next)
190
on_source_added(r, E_SOURCE(l->data), this);
191
g_list_free_full(sources, g_object_unref);
194
static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself)
196
auto self = static_cast<Impl*>(gself);
198
self->m_sources.insert(E_SOURCE(g_object_ref(source)));
200
if (e_source_get_enabled(source))
201
on_source_enabled(registry, source, gself);
204
static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
206
auto self = static_cast<Impl*>(gself);
207
ECalClientSourceType source_type;
208
bool client_wanted = false;
210
if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR))
212
source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
213
client_wanted = true;
215
else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST))
217
source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
218
client_wanted = true;
221
const auto source_uid = e_source_get_uid(source);
224
g_debug("%s connecting a client to source %s", G_STRFUNC, source_uid);
225
e_cal_client_connect(source,
233
g_debug("%s not using source %s -- no tasks/calendar", G_STRFUNC, source_uid);
237
static void on_client_connected(GObject* /*source*/, GAsyncResult * res, gpointer gself)
239
GError * error = nullptr;
240
EClient * client = e_cal_client_connect_finish(res, &error);
243
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
244
g_warning("indicator-datetime cannot connect to EDS source: %s", error->message);
250
// add the client to our collection
251
auto self = static_cast<Impl*>(gself);
252
g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
253
self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client);
255
// now create a view for it so that we can listen for changes
256
e_cal_client_get_view (E_CAL_CLIENT(client),
259
on_client_view_ready,
262
g_debug("client connected; calling set_dirty_soon()");
263
self->set_dirty_soon();
267
static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself)
269
GError* error = nullptr;
270
ECalClientView* view = nullptr;
272
if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error))
274
// add the view to our collection
275
e_cal_client_view_start(view, &error);
276
g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
277
auto self = static_cast<Impl*>(gself);
278
self->m_views[e_client_get_source(E_CLIENT(client))] = view;
280
g_signal_connect(view, "objects-added", G_CALLBACK(on_view_objects_added), self);
281
g_signal_connect(view, "objects-modified", G_CALLBACK(on_view_objects_modified), self);
282
g_signal_connect(view, "objects-removed", G_CALLBACK(on_view_objects_removed), self);
283
g_debug("view connected; calling set_dirty_soon()");
284
self->set_dirty_soon();
286
else if(error != nullptr)
288
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
289
g_warning("indicator-datetime cannot get View to EDS client: %s", error->message);
295
static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
297
g_debug("%s", G_STRFUNC);
298
static_cast<Impl*>(gself)->set_dirty_soon();
300
static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
302
g_debug("%s", G_STRFUNC);
303
static_cast<Impl*>(gself)->set_dirty_soon();
305
static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
307
g_debug("%s", G_STRFUNC);
308
static_cast<Impl*>(gself)->set_dirty_soon();
311
static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
313
static_cast<Impl*>(gself)->disable_source(source);
315
void disable_source(ESource* source)
317
// if an ECalClientView is associated with this source, remove it
318
auto vit = m_views.find(source);
319
if (vit != m_views.end())
321
auto& view = vit->second;
322
e_cal_client_view_stop(view, nullptr);
323
const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this);
324
g_warn_if_fail(n_disconnected == 3);
325
g_object_unref(view);
330
// if an ECalClient is associated with this source, remove it
331
auto cit = m_clients.find(source);
332
if (cit != m_clients.end())
334
auto& client = cit->second;
335
g_object_unref(client);
336
m_clients.erase(cit);
341
static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
343
static_cast<Impl*>(gself)->remove_source(source);
345
void remove_source(ESource* source)
347
disable_source(source);
349
auto sit = m_sources.find(source);
350
if (sit != m_sources.end())
352
g_object_unref(*sit);
353
m_sources.erase(sit);
358
static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself)
360
g_debug("source changed; calling set_dirty_soon()");
361
static_cast<Impl*>(gself)->set_dirty_soon();
366
typedef std::function<void(const std::vector<Appointment>&)> appointment_func;
371
appointment_func func;
372
std::vector<Appointment> appointments;
373
Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {}
376
struct AppointmentSubtask
378
std::shared_ptr<Task> task;
381
AppointmentSubtask(const std::shared_ptr<Task>& task_in, ECalClient* client_in, const char* color_in):
382
task(task_in), client(client_in)
391
std::shared_ptr<Task> task;
392
Appointment appointment;
393
UrlSubtask(const std::shared_ptr<Task>& task_in, const Appointment& appointment_in):
394
task(task_in), appointment(appointment_in) {}
398
my_get_appointments_foreach(ECalComponent* component,
403
const auto vtype = e_cal_component_get_vtype(component);
404
auto subtask = static_cast<AppointmentSubtask*>(gsubtask);
406
if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO))
408
const gchar* uid = nullptr;
409
e_cal_component_get_uid(component, &uid);
411
auto status = ICAL_STATUS_NONE;
412
e_cal_component_get_status(component, &status);
414
if ((uid != nullptr) &&
415
(status != ICAL_STATUS_COMPLETED) &&
416
(status != ICAL_STATUS_CANCELLED))
418
Appointment appointment;
420
/* Determine whether this is a recurring event.
421
NB: icalrecurrencetype supports complex recurrence patterns;
422
however, since design only allows daily recurrence,
423
that's all we support here. */
425
e_cal_component_get_rrule_list(component, &recur_list);
426
for (auto l=recur_list; l!=nullptr; l=l->next)
428
const auto recur = static_cast<struct icalrecurrencetype*>(l->data);
429
appointment.is_daily |= ((recur->freq == ICAL_DAILY_RECURRENCE)
430
&& (recur->interval == 1));
432
e_cal_component_free_recur_list(recur_list);
434
ECalComponentText text;
435
text.value = nullptr;
436
e_cal_component_get_summary(component, &text);
438
appointment.summary = text.value;
440
appointment.begin = DateTime(begin);
441
appointment.end = DateTime(end);
442
appointment.color = subtask->color;
443
appointment.is_event = vtype == E_CAL_COMPONENT_EVENT;
444
appointment.uid = uid;
446
GList * alarm_uids = e_cal_component_get_alarm_uids(component);
447
appointment.has_alarms = alarm_uids != nullptr;
448
cal_obj_uid_list_free(alarm_uids);
450
e_cal_client_get_attachment_uris(subtask->client,
453
subtask->task->p->m_cancellable,
454
on_appointment_uris_ready,
455
new UrlSubtask(subtask->task, appointment));
459
return G_SOURCE_CONTINUE;
462
static void on_appointment_uris_ready(GObject* client, GAsyncResult* res, gpointer gsubtask)
464
auto subtask = static_cast<UrlSubtask*>(gsubtask);
466
GSList * uris = nullptr;
467
GError * error = nullptr;
468
e_cal_client_get_attachment_uris_finish(E_CAL_CLIENT(client), res, &uris, &error);
469
if (error != nullptr)
471
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
472
!g_error_matches(error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED))
474
g_warning("Error getting appointment uris: %s", error->message);
479
else if (uris != nullptr)
481
subtask->appointment.url = (const char*) uris->data; // copy the first URL
482
g_debug("found url '%s' for appointment '%s'", subtask->appointment.url.c_str(), subtask->appointment.summary.c_str());
483
e_client_util_free_string_slist(uris);
486
g_debug("adding appointment '%s' '%s'", subtask->appointment.summary.c_str(), subtask->appointment.url.c_str());
487
subtask->task->appointments.push_back(subtask->appointment);
492
core::Signal<> m_changed;
493
std::set<ESource*> m_sources;
494
std::map<ESource*,ECalClient*> m_clients;
495
std::map<ESource*,ECalClientView*> m_views;
496
GCancellable* m_cancellable = nullptr;
497
ESourceRegistry* m_source_registry = nullptr;
498
guint m_rebuild_tag = 0;
505
EdsEngine::EdsEngine():
510
EdsEngine::~EdsEngine() =default;
512
core::Signal<>& EdsEngine::changed()
517
void EdsEngine::get_appointments(const DateTime& begin,
520
std::function<void(const std::vector<Appointment>&)> func)
522
p->get_appointments(begin, end, tz, func);
529
} // namespace datetime
530
} // namespace indicator