~indicator-applet-developers/indicator-datetime/trunk.14.04

« back to all changes in this revision

Viewing changes to src/engine-eds.cpp

  • Committer: CI bot
  • Author(s): Charles Kerr
  • Date: 2014-03-14 17:37:00 UTC
  • mfrom: (312.5.9 upcoming-calendar-events)
  • Revision ID: ps-jenkins@lists.canonical.com-20140314173700-4bicnok0bp2efaa0
When the user clicks on a date in the calendar, update the "Upcoming Events" section to show events starting at that date. Fixes: 1290169, 1290171, 1291468

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2014 Canonical Ltd.
 
3
 *
 
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.
 
7
 *
 
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.
 
12
 *
 
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/>.
 
15
 *
 
16
 * Authors:
 
17
 *   Charles Kerr <charles.kerr@canonical.com>
 
18
 */
 
19
 
 
20
#include <datetime/engine-eds.h>
 
21
 
 
22
#include <libical/ical.h>
 
23
#include <libical/icaltime.h>
 
24
#include <libecal/libecal.h>
 
25
#include <libedataserver/libedataserver.h>
 
26
 
 
27
#include <algorithm> // std::sort()
 
28
#include <map>
 
29
#include <set>
 
30
 
 
31
namespace unity {
 
32
namespace indicator {
 
33
namespace datetime {
 
34
 
 
35
/****
 
36
*****
 
37
****/
 
38
 
 
39
class EdsEngine::Impl
 
40
{
 
41
public:
 
42
 
 
43
    Impl(EdsEngine& owner):
 
44
        m_owner(owner),
 
45
        m_cancellable(g_cancellable_new())
 
46
    {
 
47
        e_source_registry_new(m_cancellable, on_source_registry_ready, this);
 
48
    }
 
49
 
 
50
    ~Impl()
 
51
    {
 
52
        g_cancellable_cancel(m_cancellable);
 
53
        g_clear_object(&m_cancellable);
 
54
 
 
55
        while(!m_sources.empty())
 
56
            remove_source(*m_sources.begin());
 
57
 
 
58
        if (m_rebuild_tag)
 
59
            g_source_remove(m_rebuild_tag);
 
60
 
 
61
        if (m_source_registry)
 
62
            g_signal_handlers_disconnect_by_data(m_source_registry, this);
 
63
        g_clear_object(&m_source_registry);
 
64
    }
 
65
 
 
66
    core::Signal<>& changed()
 
67
    {
 
68
        return m_changed;
 
69
    }
 
70
 
 
71
    void get_appointments(const DateTime& begin,
 
72
                          const DateTime& end,
 
73
                          const Timezone& timezone,
 
74
                          std::function<void(const std::vector<Appointment>&)> func)
 
75
    {
 
76
        const auto begin_timet = begin.to_unix();
 
77
        const auto end_timet = end.to_unix();
 
78
 
 
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());
 
82
 
 
83
        /**
 
84
        ***  init the default timezone
 
85
        **/
 
86
 
 
87
        icaltimezone * default_timezone = nullptr;
 
88
        const auto tz = timezone.timezone.get().c_str();
 
89
        if (tz && *tz)
 
90
        {
 
91
            default_timezone = icaltimezone_get_builtin_timezone(tz);
 
92
 
 
93
            if (default_timezone == nullptr) // maybe str is a tzid?
 
94
                default_timezone = icaltimezone_get_builtin_timezone_from_tzid(tz);
 
95
 
 
96
            g_debug("default_timezone is %p", (void*)default_timezone);
 
97
        }
 
98
 
 
99
        /**
 
100
        ***  walk through the sources to build the appointment list
 
101
        **/
 
102
 
 
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;});
 
107
            task->func(a);
 
108
            // we're done; delete the task
 
109
            g_debug("time to delete task %p", (void*)task);
 
110
            delete task;
 
111
        };
 
112
 
 
113
        std::shared_ptr<Task> main_task(new Task(this, func), task_deleter);
 
114
 
 
115
        for (auto& kv : m_clients)
 
116
        {
 
117
            auto& client = kv.second;
 
118
            if (default_timezone != nullptr)
 
119
                e_cal_client_set_default_timezone(client, default_timezone);
 
120
 
 
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,
 
127
                                            begin_timet,
 
128
                                            end_timet,
 
129
                                            m_cancellable,
 
130
                                            my_get_appointments_foreach,
 
131
                                            new AppointmentSubtask (main_task, client, color),
 
132
                                            [](gpointer g){delete static_cast<AppointmentSubtask*>(g);});
 
133
        }
 
134
    }
 
135
 
 
136
private:
 
137
 
 
138
    void set_dirty_now()
 
139
    {
 
140
        m_changed();
 
141
    }
 
142
 
 
143
    static gboolean set_dirty_now_static (gpointer gself)
 
144
    {
 
145
        auto self = static_cast<Impl*>(gself);
 
146
        self->m_rebuild_tag = 0;
 
147
        self->set_dirty_now();
 
148
        return G_SOURCE_REMOVE;
 
149
    }
 
150
 
 
151
    void set_dirty_soon()
 
152
    {
 
153
        static const int ARBITRARY_BATCH_MSEC = 200;
 
154
 
 
155
        if (m_rebuild_tag == 0)
 
156
            m_rebuild_tag = g_timeout_add(ARBITRARY_BATCH_MSEC, set_dirty_now_static, this);
 
157
    }
 
158
 
 
159
    static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself)
 
160
    {
 
161
        GError * error = nullptr;
 
162
        auto r = e_source_registry_new_finish(res, &error);
 
163
        if (error != nullptr)
 
164
        {
 
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);
 
167
 
 
168
            g_error_free(error);
 
169
        }
 
170
        else
 
171
        {
 
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);
 
177
 
 
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);
 
182
        }
 
183
    }
 
184
 
 
185
    void add_sources_by_extension(const char* extension)
 
186
    {
 
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);
 
192
    }
 
193
 
 
194
    static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself)
 
195
    {
 
196
        auto self = static_cast<Impl*>(gself);
 
197
 
 
198
        self->m_sources.insert(E_SOURCE(g_object_ref(source)));
 
199
 
 
200
        if (e_source_get_enabled(source))
 
201
            on_source_enabled(registry, source, gself);
 
202
    }
 
203
 
 
204
    static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
 
205
    {
 
206
        auto self = static_cast<Impl*>(gself);
 
207
        ECalClientSourceType source_type;
 
208
        bool client_wanted = false;
 
209
 
 
210
        if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR))
 
211
        {
 
212
            source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
 
213
            client_wanted = true;
 
214
        }
 
215
        else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST))
 
216
        {
 
217
            source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
 
218
            client_wanted = true;
 
219
        }
 
220
 
 
221
        const auto source_uid = e_source_get_uid(source);
 
222
        if (client_wanted)
 
223
        {
 
224
            g_debug("%s connecting a client to source %s", G_STRFUNC, source_uid);
 
225
            e_cal_client_connect(source,
 
226
                                 source_type,
 
227
                                 self->m_cancellable,
 
228
                                 on_client_connected,
 
229
                                 gself);
 
230
        }
 
231
        else
 
232
        {
 
233
            g_debug("%s not using source %s -- no tasks/calendar", G_STRFUNC, source_uid);
 
234
        }
 
235
    }
 
236
 
 
237
    static void on_client_connected(GObject* /*source*/, GAsyncResult * res, gpointer gself)
 
238
    {
 
239
        GError * error = nullptr;
 
240
        EClient * client = e_cal_client_connect_finish(res, &error);
 
241
        if (error)
 
242
        {
 
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);
 
245
 
 
246
            g_error_free(error);
 
247
        }
 
248
        else
 
249
        {
 
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);
 
254
 
 
255
            // now create a view for it so that we can listen for changes
 
256
            e_cal_client_get_view (E_CAL_CLIENT(client),
 
257
                                   "#t", // match all
 
258
                                   self->m_cancellable,
 
259
                                   on_client_view_ready,
 
260
                                   self);
 
261
 
 
262
            g_debug("client connected; calling set_dirty_soon()");
 
263
            self->set_dirty_soon();
 
264
        }
 
265
    }
 
266
 
 
267
    static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself)
 
268
    {
 
269
        GError* error = nullptr;
 
270
        ECalClientView* view = nullptr;
 
271
 
 
272
        if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error))
 
273
        {
 
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;
 
279
 
 
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();
 
285
        }
 
286
        else if(error != nullptr)
 
287
        {
 
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);
 
290
 
 
291
            g_error_free(error);
 
292
        }
 
293
    }
 
294
 
 
295
    static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
 
296
    {
 
297
        g_debug("%s", G_STRFUNC);
 
298
        static_cast<Impl*>(gself)->set_dirty_soon();
 
299
    }
 
300
    static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
 
301
    {
 
302
        g_debug("%s", G_STRFUNC);
 
303
        static_cast<Impl*>(gself)->set_dirty_soon();
 
304
    }
 
305
    static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
 
306
    {
 
307
        g_debug("%s", G_STRFUNC);
 
308
        static_cast<Impl*>(gself)->set_dirty_soon();
 
309
    }
 
310
 
 
311
    static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
 
312
    {
 
313
        static_cast<Impl*>(gself)->disable_source(source);
 
314
    }
 
315
    void disable_source(ESource* source)
 
316
    {
 
317
        // if an ECalClientView is associated with this source, remove it
 
318
        auto vit = m_views.find(source);
 
319
        if (vit != m_views.end())
 
320
        {
 
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);
 
326
            m_views.erase(vit);
 
327
            set_dirty_soon();
 
328
        }
 
329
 
 
330
        // if an ECalClient is associated with this source, remove it
 
331
        auto cit = m_clients.find(source);
 
332
        if (cit != m_clients.end())
 
333
        {
 
334
            auto& client = cit->second;
 
335
            g_object_unref(client);
 
336
            m_clients.erase(cit);
 
337
            set_dirty_soon();
 
338
        }
 
339
    }
 
340
 
 
341
    static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
 
342
    {
 
343
        static_cast<Impl*>(gself)->remove_source(source);
 
344
    }
 
345
    void remove_source(ESource* source)
 
346
    {
 
347
        disable_source(source);
 
348
 
 
349
        auto sit = m_sources.find(source);
 
350
        if (sit != m_sources.end())
 
351
        {
 
352
            g_object_unref(*sit);
 
353
            m_sources.erase(sit);
 
354
            set_dirty_soon();
 
355
        }
 
356
    }
 
357
 
 
358
    static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself)
 
359
    {
 
360
        g_debug("source changed; calling set_dirty_soon()");
 
361
        static_cast<Impl*>(gself)->set_dirty_soon();
 
362
    }
 
363
 
 
364
private:
 
365
 
 
366
    typedef std::function<void(const std::vector<Appointment>&)> appointment_func;
 
367
 
 
368
    struct Task
 
369
    {
 
370
        Impl* p;
 
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) {}
 
374
    };
 
375
 
 
376
    struct AppointmentSubtask
 
377
    {
 
378
        std::shared_ptr<Task> task;
 
379
        ECalClient* client;
 
380
        std::string color;
 
381
        AppointmentSubtask(const std::shared_ptr<Task>& task_in, ECalClient* client_in, const char* color_in):
 
382
            task(task_in), client(client_in)
 
383
        {
 
384
            if (color_in)
 
385
                color = color_in;
 
386
        }
 
387
    };
 
388
 
 
389
    struct UrlSubtask
 
390
    {
 
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) {}
 
395
    };
 
396
 
 
397
    static gboolean
 
398
    my_get_appointments_foreach(ECalComponent* component,
 
399
                                time_t         begin,
 
400
                                time_t         end,
 
401
                                gpointer       gsubtask)
 
402
    {
 
403
        const auto vtype = e_cal_component_get_vtype(component);
 
404
        auto subtask = static_cast<AppointmentSubtask*>(gsubtask);
 
405
 
 
406
        if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO))
 
407
        {
 
408
          const gchar* uid = nullptr;
 
409
          e_cal_component_get_uid(component, &uid);
 
410
 
 
411
          auto status = ICAL_STATUS_NONE;
 
412
          e_cal_component_get_status(component, &status);
 
413
 
 
414
          if ((uid != nullptr) &&
 
415
              (status != ICAL_STATUS_COMPLETED) &&
 
416
              (status != ICAL_STATUS_CANCELLED))
 
417
          {
 
418
              Appointment appointment;
 
419
 
 
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. */
 
424
              GSList * recur_list;
 
425
              e_cal_component_get_rrule_list(component, &recur_list);
 
426
              for (auto l=recur_list; l!=nullptr; l=l->next)
 
427
              {
 
428
                  const auto recur = static_cast<struct icalrecurrencetype*>(l->data);
 
429
                  appointment.is_daily |= ((recur->freq == ICAL_DAILY_RECURRENCE)
 
430
                                             && (recur->interval == 1));
 
431
              }
 
432
              e_cal_component_free_recur_list(recur_list);
 
433
 
 
434
              ECalComponentText text;
 
435
              text.value = nullptr;
 
436
              e_cal_component_get_summary(component, &text);
 
437
              if (text.value)
 
438
                  appointment.summary = text.value;
 
439
 
 
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;
 
445
 
 
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);
 
449
 
 
450
              e_cal_client_get_attachment_uris(subtask->client,
 
451
                                               uid,
 
452
                                               nullptr,
 
453
                                               subtask->task->p->m_cancellable,
 
454
                                               on_appointment_uris_ready,
 
455
                                               new UrlSubtask(subtask->task, appointment));
 
456
            }
 
457
        }
 
458
 
 
459
        return G_SOURCE_CONTINUE;
 
460
    }
 
461
 
 
462
    static void on_appointment_uris_ready(GObject* client, GAsyncResult* res, gpointer gsubtask)
 
463
    {
 
464
        auto subtask = static_cast<UrlSubtask*>(gsubtask);
 
465
 
 
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)
 
470
        {
 
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))
 
473
            {
 
474
                g_warning("Error getting appointment uris: %s", error->message);
 
475
            }
 
476
 
 
477
            g_error_free(error);
 
478
        }
 
479
        else if (uris != nullptr)
 
480
        {
 
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);
 
484
        }
 
485
 
 
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);
 
488
        delete subtask;
 
489
    }
 
490
 
 
491
    EdsEngine& m_owner;
 
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;
 
499
};
 
500
 
 
501
/***
 
502
****
 
503
***/
 
504
 
 
505
EdsEngine::EdsEngine():
 
506
    p(new Impl(*this))
 
507
{
 
508
}
 
509
 
 
510
EdsEngine::~EdsEngine() =default;
 
511
 
 
512
core::Signal<>& EdsEngine::changed()
 
513
{
 
514
    return p->changed();
 
515
}
 
516
 
 
517
void EdsEngine::get_appointments(const DateTime& begin,
 
518
                                 const DateTime& end,
 
519
                                 const Timezone& tz,
 
520
                                 std::function<void(const std::vector<Appointment>&)> func)
 
521
{
 
522
    p->get_appointments(begin, end, tz, func);
 
523
}
 
524
 
 
525
/***
 
526
****
 
527
***/
 
528
 
 
529
} // namespace datetime
 
530
} // namespace indicator
 
531
} // namespace unity