~dbarth/indicator-datetime/adjust-title-notification-icons

« back to all changes in this revision

Viewing changes to src/engine-eds.cpp

Fix bugs relating to timezones and triggers from clock-app alarms. Fixes: #1456281, #1465806
Approved by: Ted Gould, PS Jenkins bot

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
namespace indicator {
35
35
namespace datetime {
36
36
 
37
 
static constexpr char const * TAG_ALARM {"x-canonical-alarm"};
 
37
static constexpr char const * TAG_ALARM    {"x-canonical-alarm"};
38
38
static constexpr char const * TAG_DISABLED {"x-canonical-disabled"};
39
39
 
 
40
static constexpr char const * X_PROP_ACTIVATION_URL {"X-CANONICAL-ACTIVATION-URL"};
 
41
 
40
42
/****
41
43
*****
42
44
****/
77
79
                          const Timezone& timezone,
78
80
                          std::function<void(const std::vector<Appointment>&)> func)
79
81
    {
80
 
        const auto begin_timet = begin.to_unix();
81
 
        const auto end_timet = end.to_unix();
82
 
 
83
82
        const auto b_str = begin.format("%F %T");
84
83
        const auto e_str = end.format("%F %T");
85
84
        g_debug("getting all appointments from [%s ... %s]", b_str.c_str(), e_str.c_str());
104
103
        ***  walk through the sources to build the appointment list
105
104
        **/
106
105
 
107
 
        auto task_deleter = [](Task* task){
108
 
            // give the caller the (sorted) finished product
109
 
            auto& a = task->appointments;
110
 
            std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;});
111
 
            task->func(a);
112
 
            // we're done; delete the task
113
 
            g_debug("time to delete task %p", (void*)task);
114
 
            delete task;
115
 
        };
116
 
 
117
 
        std::shared_ptr<Task> main_task(new Task(this, func), task_deleter);
 
106
        auto gtz = default_timezone != nullptr
 
107
                 ? g_time_zone_new(icaltimezone_get_location(default_timezone))
 
108
                 : g_time_zone_new_local(); 
 
109
        auto main_task = std::make_shared<Task>(this, func, default_timezone, gtz, begin, end);
118
110
 
119
111
        for (auto& kv : m_clients)
120
112
        {
121
113
            auto& client = kv.second;
122
114
            if (default_timezone != nullptr)
123
115
                e_cal_client_set_default_timezone(client, default_timezone);
 
116
            g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
124
117
 
125
 
            // start a new subtask to enumerate all the components in this client.
126
118
            auto& source = kv.first;
127
119
            auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
128
120
            const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));
129
 
            g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
130
 
            auto subtask = new AppointmentSubtask(main_task,
131
 
                                                  client,
132
 
                                                  color,
133
 
                                                  default_timezone,
134
 
                                                  begin_timet,
135
 
                                                  end_timet);
136
 
            e_cal_client_generate_instances(client,
137
 
                                            begin_timet,
138
 
                                            end_timet,
139
 
                                            m_cancellable,
140
 
                                            my_get_appointments_foreach,
141
 
                                            subtask,
142
 
                                            [](gpointer g){delete static_cast<AppointmentSubtask*>(g);});
 
121
 
 
122
            auto begin_str = isodate_from_time_t(begin.to_unix());
 
123
            auto end_str = isodate_from_time_t(end.to_unix());
 
124
            auto sexp_fmt = g_strdup_printf("(%%s? (make-time \"%s\") (make-time \"%s\"))", begin_str, end_str);
 
125
            g_clear_pointer(&begin_str, g_free);
 
126
            g_clear_pointer(&end_str, g_free);
 
127
 
 
128
            // ask EDS about alarms that occur in this window...
 
129
            auto sexp = g_strdup_printf(sexp_fmt, "has-alarms-in-range");
 
130
            g_debug("%s alarm sexp is %s", G_STRLOC, sexp);
 
131
            e_cal_client_get_object_list_as_comps(
 
132
                client,
 
133
                sexp,
 
134
                m_cancellable,
 
135
                on_alarm_component_list_ready,
 
136
                new ClientSubtask(main_task, client, color));
 
137
            g_clear_pointer(&sexp, g_free);
 
138
 
 
139
            // ask EDS about events that occur in this window...
 
140
            sexp = g_strdup_printf(sexp_fmt, "occur-in-time-range");
 
141
            g_debug("%s event sexp is %s", G_STRLOC, sexp);
 
142
            e_cal_client_get_object_list_as_comps(
 
143
                client,
 
144
                sexp,
 
145
                m_cancellable,
 
146
                on_event_component_list_ready,
 
147
                new ClientSubtask(main_task, client, color));
 
148
            g_clear_pointer(&sexp, g_free);
 
149
 
 
150
            g_clear_pointer(&sexp_fmt, g_free);
143
151
        }
144
152
    }
145
153
 
289
297
            // add the client to our collection
290
298
            auto self = static_cast<Impl*>(gself);
291
299
            g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
292
 
            self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client);
 
300
            auto source = e_client_get_source(client);
 
301
            auto ecc = E_CAL_CLIENT(client);
 
302
            self->m_clients[source] = ecc;
 
303
 
 
304
            self->ensure_client_alarms_have_triggers(ecc);
293
305
 
294
306
            // now create a view for it so that we can listen for changes
295
 
            e_cal_client_get_view (E_CAL_CLIENT(client),
 
307
            e_cal_client_get_view (ecc,
296
308
                                   "#t", // match all
297
309
                                   self->m_cancellable,
298
310
                                   on_client_view_ready,
401
413
        static_cast<Impl*>(gself)->set_dirty_soon();
402
414
    }
403
415
 
 
416
    /***
 
417
    ****
 
418
    ***/
 
419
 
 
420
    // old ubuntu-clock-app alarms created VTODO VALARMS without the
 
421
    // required 'TRIGGER' property... http://pad.lv/1465806
 
422
 
 
423
    void ensure_client_alarms_have_triggers(ECalClient* client)
 
424
    {
 
425
        // ask the EDS server for all the ubuntu-clock-app alarms...
 
426
 
 
427
        auto sexp = g_strdup_printf("has-categories? '%s'", TAG_ALARM);
 
428
 
 
429
        e_cal_client_get_object_list_as_comps(
 
430
            client,
 
431
            sexp,
 
432
            m_cancellable,
 
433
            ensure_client_alarms_have_triggers_async_cb,
 
434
            this);
 
435
 
 
436
        g_clear_pointer(&sexp, g_free);
 
437
    }
 
438
 
 
439
    static void ensure_client_alarms_have_triggers_async_cb(
 
440
        GObject      * oclient,
 
441
        GAsyncResult * res,
 
442
        gpointer       gself)
 
443
    {
 
444
        ECalClient * client = E_CAL_CLIENT(oclient);
 
445
        GError * error = nullptr;
 
446
        GSList * components = nullptr;
 
447
 
 
448
        if (e_cal_client_get_object_list_as_comps_finish(client,
 
449
                                                         res,
 
450
                                                         &components,
 
451
                                                         &error))
 
452
        {
 
453
            auto self = static_cast<Impl*>(gself);
 
454
            self->ensure_canonical_alarms_have_triggers(client, components);
 
455
            e_cal_client_free_ecalcomp_slist(components);
 
456
        }
 
457
        else if (error != nullptr)
 
458
        {
 
459
            if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 
460
                g_warning("can't get clock-app alarm list: %s", error->message);
 
461
 
 
462
            g_error_free(error);
 
463
        }
 
464
    }
 
465
 
 
466
    void ensure_canonical_alarms_have_triggers(ECalClient * client,
 
467
                                               GSList     * components)
 
468
    {
 
469
        GSList * modify_slist = nullptr;
 
470
 
 
471
        // for each component..
 
472
        for (auto l=components; l!=nullptr; l=l->next)
 
473
        {
 
474
            bool changed = false;
 
475
 
 
476
            // for each alarm...
 
477
            auto component = E_CAL_COMPONENT(l->data);
 
478
            auto auids = e_cal_component_get_alarm_uids(component);
 
479
            for(auto l=auids; l!=nullptr; l=l->next)
 
480
            {
 
481
                auto auid = static_cast<const char*>(l->data);
 
482
                auto alarm = e_cal_component_get_alarm(component, auid);
 
483
                if (alarm == nullptr)
 
484
                    continue;
 
485
 
 
486
                // if the alarm has no trigger, add one.
 
487
                ECalComponentAlarmTrigger trigger;
 
488
                e_cal_component_alarm_get_trigger(alarm, &trigger);
 
489
                if (trigger.type == E_CAL_COMPONENT_ALARM_TRIGGER_NONE)
 
490
                {
 
491
                    trigger.type = E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START;
 
492
                    trigger.u.rel_duration = icaldurationtype_from_int(0);
 
493
                    e_cal_component_alarm_set_trigger (alarm, trigger);
 
494
                    changed = true;
 
495
                }
 
496
 
 
497
                g_clear_pointer(&alarm, e_cal_component_alarm_free);
 
498
            }
 
499
            g_clear_pointer(&auids, cal_obj_uid_list_free);
 
500
 
 
501
            if (changed)
 
502
            {
 
503
                auto icc = e_cal_component_get_icalcomponent(component); // icc owned by ecc
 
504
                modify_slist = g_slist_prepend(modify_slist, icc);
 
505
            }
 
506
        }
 
507
 
 
508
        if (modify_slist != nullptr)
 
509
        {
 
510
            e_cal_client_modify_objects(client,
 
511
                                        modify_slist,
 
512
                                        E_CAL_OBJ_MOD_ALL,
 
513
                                        m_cancellable,
 
514
                                        ensure_canonical_alarms_have_triggers_async_cb,
 
515
                                        this);
 
516
 
 
517
            g_clear_pointer(&modify_slist, g_slist_free);
 
518
        }
 
519
    }
 
520
 
 
521
    // log a warning if e_cal_client_modify_objects() failed
 
522
    static void ensure_canonical_alarms_have_triggers_async_cb(
 
523
        GObject      * oclient,
 
524
        GAsyncResult * res,
 
525
        gpointer       /*gself*/)
 
526
    {
 
527
        GError * error = nullptr;
 
528
 
 
529
        e_cal_client_modify_objects_finish (E_CAL_CLIENT(oclient), res, &error);
 
530
 
 
531
        if (error != nullptr)
 
532
        {
 
533
            if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 
534
                g_warning("couldn't add alarm triggers: %s", error->message);
 
535
 
 
536
            g_error_free(error);
 
537
        }
 
538
    }
 
539
 
 
540
    /***
 
541
    ****
 
542
    ***/
 
543
 
 
544
 
404
545
    typedef std::function<void(const std::vector<Appointment>&)> appointment_func;
405
546
 
406
547
    struct Task
407
548
    {
408
549
        Impl* p;
409
550
        appointment_func func;
 
551
        icaltimezone* default_timezone; // pointer owned by libical
 
552
        GTimeZone* gtz;
410
553
        std::vector<Appointment> appointments;
411
 
        Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {}
 
554
        const DateTime begin;
 
555
        const DateTime end;
 
556
 
 
557
        Task(Impl* p_in,
 
558
             appointment_func func_in,
 
559
             icaltimezone* tz_in,
 
560
             GTimeZone* gtz_in,
 
561
             const DateTime& begin_in,
 
562
             const DateTime& end_in):
 
563
                 p{p_in},
 
564
                 func{func_in},
 
565
                 default_timezone{tz_in},
 
566
                 gtz{gtz_in},
 
567
                 begin{begin_in},
 
568
                 end{end_in} {}
 
569
 
 
570
        ~Task() {
 
571
            g_clear_pointer(&gtz, g_time_zone_unref);
 
572
            // give the caller the sorted finished product
 
573
            auto& a = appointments;
 
574
            std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;});
 
575
            func(a);
 
576
        };
412
577
    };
413
578
 
414
 
    struct AppointmentSubtask
 
579
    struct ClientSubtask
415
580
    {
416
581
        std::shared_ptr<Task> task;
417
582
        ECalClient* client;
418
583
        std::string color;
419
 
        icaltimezone* default_timezone;
420
 
        time_t begin;
421
 
        time_t end;
422
584
 
423
 
        AppointmentSubtask(const std::shared_ptr<Task>& task_in,
424
 
                           ECalClient* client_in,
425
 
                           const char* color_in,
426
 
                           icaltimezone* default_tz,
427
 
                           time_t begin_,
428
 
                           time_t end_):
 
585
        ClientSubtask(const std::shared_ptr<Task>& task_in,
 
586
                      ECalClient* client_in,
 
587
                      const char* color_in):
429
588
            task(task_in),
430
 
            client(client_in),
431
 
            default_timezone(default_tz),
432
 
            begin(begin_),
433
 
            end(end_)
 
589
            client(client_in)
434
590
        {
435
591
            if (color_in)
436
592
                color = color_in;
480
636
        return ret;
481
637
    }
482
638
 
483
 
    static gboolean
484
 
    my_get_appointments_foreach(ECalComponent* component,
485
 
                                time_t         begin,
486
 
                                time_t         end,
487
 
                                gpointer       gsubtask)
488
 
    {
 
639
    static void
 
640
    on_alarm_component_list_ready(GObject      * oclient,
 
641
                                  GAsyncResult * res,
 
642
                                  gpointer       gsubtask)
 
643
    {
 
644
        GError * error = NULL;
 
645
        GSList * comps_slist = NULL;
 
646
        auto subtask = static_cast<ClientSubtask*>(gsubtask);
 
647
 
 
648
        if (e_cal_client_get_object_list_as_comps_finish(E_CAL_CLIENT(oclient),
 
649
                                                         res,
 
650
                                                         &comps_slist,
 
651
                                                         &error))
 
652
        {
 
653
            // _generate_alarms takes a GList, so make a shallow one
 
654
            GList * comps_list = nullptr;
 
655
            for (auto l=comps_slist; l!=nullptr; l=l->next)
 
656
                comps_list = g_list_prepend(comps_list, l->data);
 
657
 
 
658
            constexpr std::array<ECalComponentAlarmAction,1> omit = {
 
659
                (ECalComponentAlarmAction)-1
 
660
            }; // list of action types to omit, terminated with -1
 
661
            GSList * comp_alarms = nullptr;
 
662
            e_cal_util_generate_alarms_for_list(
 
663
                comps_list,
 
664
                subtask->task->begin.to_unix(),
 
665
                subtask->task->end.to_unix(),
 
666
                const_cast<ECalComponentAlarmAction*>(omit.data()),
 
667
                &comp_alarms,
 
668
                e_cal_client_resolve_tzid_cb,
 
669
                oclient,
 
670
                subtask->task->default_timezone);
 
671
 
 
672
            // walk the alarms & add them
 
673
            for (auto l=comp_alarms; l!=nullptr; l=l->next)
 
674
                add_alarms_to_subtask(static_cast<ECalComponentAlarms*>(l->data), subtask, subtask->task->gtz);
 
675
 
 
676
            // cleanup
 
677
            e_cal_free_alarms(comp_alarms);
 
678
            g_list_free(comps_list);
 
679
            e_cal_client_free_ecalcomp_slist(comps_slist);
 
680
        }
 
681
        else if (error != nullptr)
 
682
        {
 
683
            if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 
684
                g_warning("can't get ecalcomponent list: %s", error->message);
 
685
 
 
686
            g_error_free(error);
 
687
        }
 
688
 
 
689
        delete subtask;
 
690
    }
 
691
 
 
692
    static void
 
693
    on_event_component_list_ready(GObject      * oclient,
 
694
                                  GAsyncResult * res,
 
695
                                  gpointer       gsubtask)
 
696
    {
 
697
        GError * error = NULL;
 
698
        GSList * comps_slist = NULL;
 
699
        auto subtask = static_cast<ClientSubtask*>(gsubtask);
 
700
 
 
701
        if (e_cal_client_get_object_list_as_comps_finish(E_CAL_CLIENT(oclient),
 
702
                                                         res,
 
703
                                                         &comps_slist,
 
704
                                                         &error))
 
705
        {
 
706
            for (auto l=comps_slist; l!=nullptr; l=l->next)
 
707
                add_event_to_subtask(static_cast<ECalComponent*>(l->data), subtask, subtask->task->gtz);
 
708
 
 
709
            e_cal_client_free_ecalcomp_slist(comps_slist);
 
710
        }
 
711
        else if (error != nullptr)
 
712
        {
 
713
            if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
 
714
                g_warning("can't get ecalcomponent list: %s", error->message);
 
715
 
 
716
            g_error_free(error);
 
717
        }
 
718
 
 
719
        delete subtask;
 
720
    }
 
721
 
 
722
    static DateTime
 
723
    datetime_from_component_date_time(const ECalComponentDateTime & in,
 
724
                                      GTimeZone                   * default_timezone)
 
725
    {
 
726
        DateTime out;
 
727
 
 
728
        g_return_val_if_fail(in.value != nullptr, out);
 
729
 
 
730
        auto gtz = in.tzid == nullptr ? g_time_zone_ref(default_timezone)
 
731
                                      : g_time_zone_new(in.tzid);
 
732
        out = DateTime(gtz,
 
733
                       in.value->year,
 
734
                       in.value->month,
 
735
                       in.value->day,
 
736
                       in.value->hour,
 
737
                       in.value->minute,
 
738
                       in.value->second);
 
739
        g_time_zone_unref(gtz);
 
740
        return out;
 
741
    }
 
742
 
 
743
    static bool
 
744
    is_component_interesting(ECalComponent * component)
 
745
    {
 
746
        // we only want calendar events and vtodos
489
747
        const auto vtype = e_cal_component_get_vtype(component);
490
 
        auto subtask = static_cast<AppointmentSubtask*>(gsubtask);
491
 
 
492
 
        if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO))
493
 
        {
494
 
            const gchar* uid = nullptr;
495
 
            e_cal_component_get_uid(component, &uid);
496
 
 
497
 
            auto status = ICAL_STATUS_NONE;
498
 
            e_cal_component_get_status(component, &status);
499
 
 
500
 
            // get the timezone we want to use for generated Appointments/Alarms
501
 
            const char * location = icaltimezone_get_location(subtask->default_timezone);
502
 
            auto gtz = g_time_zone_new(location);
503
 
            g_debug("timezone abbreviation is %s", g_time_zone_get_abbreviation (gtz, 0));
504
 
 
505
 
            const DateTime begin_dt { gtz, begin };
506
 
            const DateTime end_dt { gtz, end };
507
 
            g_debug ("got appointment from %s to %s, uid %s status %d",
508
 
                     begin_dt.format("%F %T %z").c_str(),
509
 
                     end_dt.format("%F %T %z").c_str(),
510
 
                     uid,
511
 
                     (int)status);
512
 
 
513
 
            // look for the in-house tags
514
 
            bool disabled = false;
515
 
            Appointment::Type type = Appointment::EVENT;
516
 
            GSList * categ_list = nullptr;
517
 
            e_cal_component_get_categories_list (component, &categ_list);
518
 
            for (GSList * l=categ_list; l!=nullptr; l=l->next) {
519
 
                auto tag = static_cast<const char*>(l->data);
520
 
                if (!g_strcmp0(tag, TAG_ALARM))
521
 
                    type = Appointment::UBUNTU_ALARM;
522
 
                if (!g_strcmp0(tag, TAG_DISABLED))
523
 
                    disabled = true;
524
 
            }
525
 
            e_cal_component_free_categories_list(categ_list);
526
 
 
527
 
            if ((uid != nullptr) &&
528
 
                (!disabled) &&
529
 
                (status != ICAL_STATUS_COMPLETED) &&
530
 
                (status != ICAL_STATUS_CANCELLED))
531
 
            {
532
 
                constexpr std::array<ECalComponentAlarmAction,1> omit = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1
533
 
                Appointment appointment;
534
 
 
535
 
                ECalComponentText text {};
536
 
                e_cal_component_get_summary(component, &text);
537
 
                if (text.value)
538
 
                    appointment.summary = text.value;
539
 
 
540
 
                appointment.begin = begin_dt;
541
 
                appointment.end = end_dt;
542
 
                appointment.color = subtask->color;
543
 
                appointment.uid = uid;
544
 
                appointment.type = type;
545
 
 
546
 
                icalcomponent * icc = e_cal_component_get_icalcomponent(component);
547
 
                g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak
548
 
 
549
 
                auto e_alarms = e_cal_util_generate_alarms_for_comp(component,
550
 
                                                                    subtask->begin,
551
 
                                                                    subtask->end,
552
 
                                                                    const_cast<ECalComponentAlarmAction*>(omit.data()),
553
 
                                                                    e_cal_client_resolve_tzid_cb,
554
 
                                                                    subtask->client,
555
 
                                                                    subtask->default_timezone);
556
 
 
557
 
                std::map<DateTime,Alarm> alarms;
558
 
 
559
 
                if (e_alarms != nullptr)
560
 
                {
561
 
                    for (auto l=e_alarms->alarms; l!=nullptr; l=l->next)
562
 
                    {
563
 
                        auto ai = static_cast<ECalComponentAlarmInstance*>(l->data);
564
 
                        auto a = e_cal_component_get_alarm(component, ai->auid);
565
 
 
566
 
                        if (a != nullptr)
567
 
                        {
568
 
                            const DateTime alarm_begin{gtz, ai->trigger};
569
 
                            auto& alarm = alarms[alarm_begin];
570
 
 
571
 
                            if (alarm.text.empty())
572
 
                                alarm.text = get_alarm_text(a);
573
 
                            if (alarm.audio_url.empty())
574
 
                                alarm.audio_url = get_alarm_sound_url(a);
575
 
                            if (!alarm.time.is_set())
576
 
                                alarm.time = alarm_begin;
577
 
 
578
 
                            e_cal_component_alarm_free(a);
579
 
                        }
580
 
                    }
581
 
 
582
 
                    e_cal_component_alarms_free(e_alarms);
583
 
                }
584
 
                // Hm, no alarm triggers? 
585
 
                // That's a bug in alarms created by some versions of ubuntu-ui-toolkit.
586
 
                // If that's what's happening here, let's handle those alarms anyway
587
 
                // by effectively injecting a TRIGGER;VALUE=DURATION;RELATED=START:PT0S
588
 
                else if (appointment.is_ubuntu_alarm())
589
 
                {
590
 
                    Alarm tmp;
591
 
                    tmp.time = appointment.begin;
592
 
 
593
 
                    auto auids = e_cal_component_get_alarm_uids(component);
594
 
                    for(auto l=auids; l!=nullptr; l=l->next)
595
 
                    {
596
 
                        const auto auid = static_cast<const char*>(l->data);
597
 
                        auto a = e_cal_component_get_alarm(component, auid);
598
 
                        if (a != nullptr)
599
 
                        {
600
 
                            if (tmp.text.empty())
601
 
                                tmp.text = get_alarm_text(a);
602
 
                            if (tmp.audio_url.empty())
603
 
                                tmp.audio_url = get_alarm_sound_url(a);
604
 
                            e_cal_component_alarm_free(a);
605
 
                        }
606
 
                    }
607
 
                    cal_obj_uid_list_free(auids);
608
 
 
609
 
                    alarms[tmp.time] = tmp;
610
 
                }
611
 
 
612
 
                appointment.alarms.reserve(alarms.size());
613
 
                for (const auto& it : alarms)
614
 
                    appointment.alarms.push_back(it.second);
615
 
 
616
 
                subtask->task->appointments.push_back(appointment);
617
 
            }
618
 
 
619
 
            g_time_zone_unref(gtz);
620
 
        }
621
 
 
622
 
        return G_SOURCE_CONTINUE;
 
748
        if ((vtype != E_CAL_COMPONENT_EVENT) &&
 
749
                (vtype != E_CAL_COMPONENT_TODO))
 
750
            return false;
 
751
 
 
752
        // we're not interested in completed or cancelled components
 
753
        auto status = ICAL_STATUS_NONE;
 
754
        e_cal_component_get_status(component, &status);
 
755
        if ((status == ICAL_STATUS_COMPLETED) ||
 
756
                (status == ICAL_STATUS_CANCELLED))
 
757
            return false;
 
758
 
 
759
        // we don't want disabled alarms
 
760
        bool disabled = false;
 
761
        GSList * categ_list = nullptr;
 
762
        e_cal_component_get_categories_list (component, &categ_list);
 
763
        for (GSList * l=categ_list; l!=nullptr; l=l->next) {
 
764
            auto tag = static_cast<const char*>(l->data);
 
765
            if (!g_strcmp0(tag, TAG_DISABLED))
 
766
                disabled = true;
 
767
        }
 
768
        e_cal_component_free_categories_list(categ_list);
 
769
        if (disabled)
 
770
            return false;
 
771
 
 
772
        return true;
 
773
    }
 
774
 
 
775
    static Appointment
 
776
    get_appointment(ECalComponent * component, GTimeZone * gtz)
 
777
    {
 
778
        Appointment baseline;
 
779
 
 
780
        // get appointment.uid
 
781
        const gchar* uid = nullptr;
 
782
        e_cal_component_get_uid(component, &uid);
 
783
        if (uid != nullptr)
 
784
            baseline.uid = uid;
 
785
 
 
786
        // get appointment.summary
 
787
        ECalComponentText text {};
 
788
        e_cal_component_get_summary(component, &text);
 
789
        if (text.value)
 
790
            baseline.summary = text.value;
 
791
 
 
792
        // get appointment.begin
 
793
        ECalComponentDateTime eccdt_tmp {};
 
794
        e_cal_component_get_dtstart(component, &eccdt_tmp);
 
795
        baseline.begin = datetime_from_component_date_time(eccdt_tmp, gtz);
 
796
        e_cal_component_free_datetime(&eccdt_tmp);
 
797
 
 
798
        // get appointment.end
 
799
        e_cal_component_get_dtend(component, &eccdt_tmp);
 
800
        baseline.end = eccdt_tmp.value != nullptr
 
801
                                  ? datetime_from_component_date_time(eccdt_tmp, gtz)
 
802
                                  : baseline.begin;
 
803
        e_cal_component_free_datetime(&eccdt_tmp);
 
804
 
 
805
        // get appointment.activation_url from x-props
 
806
        auto icc = e_cal_component_get_icalcomponent(component); // icc owned by component
 
807
        auto icalprop = icalcomponent_get_first_property(icc, ICAL_X_PROPERTY);
 
808
        while (icalprop != nullptr) {
 
809
            const char * x_name = icalproperty_get_x_name(icalprop);
 
810
            if ((x_name != nullptr) && !g_ascii_strcasecmp(x_name, X_PROP_ACTIVATION_URL)) {
 
811
                const char * url = icalproperty_get_value_as_string(icalprop);
 
812
                if ((url != nullptr) && baseline.activation_url.empty())
 
813
                    baseline.activation_url = url;
 
814
            }
 
815
            icalprop = icalcomponent_get_next_property(icc, ICAL_X_PROPERTY);
 
816
        }
 
817
 
 
818
        // get appointment.type
 
819
        baseline.type = Appointment::EVENT;
 
820
        GSList * categ_list = nullptr;
 
821
        e_cal_component_get_categories_list (component, &categ_list);
 
822
        for (GSList * l=categ_list; l!=nullptr; l=l->next) {
 
823
            auto tag = static_cast<const char*>(l->data);
 
824
            if (!g_strcmp0(tag, TAG_ALARM))
 
825
                baseline.type = Appointment::UBUNTU_ALARM;
 
826
        }
 
827
        e_cal_component_free_categories_list(categ_list);
 
828
 
 
829
        g_debug("%s got appointment from %s to %s: %s", G_STRLOC,
 
830
                baseline.begin.format("%F %T %z").c_str(),
 
831
                baseline.end.format("%F %T %z").c_str(),
 
832
                icalcomponent_as_ical_string(icc) /* string owned by ical */);
 
833
 
 
834
        return baseline;
 
835
    }
 
836
 
 
837
    static void
 
838
    add_event_to_subtask(ECalComponent * component,
 
839
                         ClientSubtask * subtask,
 
840
                         GTimeZone     * gtz)
 
841
    {
 
842
        // events with alarms are covered by add_alarm_to_subtask(),
 
843
        // so skip them here
 
844
        auto auids = e_cal_component_get_alarm_uids(component);
 
845
        const bool has_alarms = auids != nullptr;
 
846
        cal_obj_uid_list_free(auids);
 
847
        if (has_alarms)
 
848
            return;
 
849
 
 
850
        // add it. simple, eh?
 
851
        if (is_component_interesting(component))
 
852
        {
 
853
            Appointment appointment = get_appointment(component, gtz);
 
854
            appointment.color = subtask->color;
 
855
            subtask->task->appointments.push_back(appointment);
 
856
        }
 
857
    }
 
858
 
 
859
    static void
 
860
    add_alarms_to_subtask(ECalComponentAlarms * comp_alarms,
 
861
                          ClientSubtask       * subtask,
 
862
                          GTimeZone           * gtz)
 
863
    {
 
864
        auto& component = comp_alarms->comp;
 
865
 
 
866
        if (!is_component_interesting(component))
 
867
            return;
 
868
 
 
869
        Appointment baseline = get_appointment(component, gtz);
 
870
        baseline.color = subtask->color;
 
871
 
 
872
        /**
 
873
        ***  Now loop through comp_alarms to get information that we need
 
874
        ***  to build the instance appointments and their alarms.
 
875
        ***
 
876
        ***  Outer map key is the instance component's start + end time.
 
877
        ***  We build Appointment.begin and .end from that.
 
878
        ***
 
879
        ***  inner map key is the alarm trigger, we build Alarm.time from that.
 
880
        ***
 
881
        ***  inner map value is the Alarm.
 
882
        ***
 
883
        ***  We map the alarms based on their trigger time so that we
 
884
        ***  can fold together multiple valarms that trigger for the
 
885
        ***  same componeng at the same time. This is commonplace;
 
886
        ***  e.g. one valarm will have a display action and another
 
887
        ***  will specify a sound to be played.
 
888
         */
 
889
        std::map<std::pair<DateTime,DateTime>,std::map<DateTime,Alarm>> alarms;
 
890
        for (auto l=comp_alarms->alarms; l!=nullptr; l=l->next)
 
891
        {
 
892
            auto ai = static_cast<ECalComponentAlarmInstance*>(l->data);
 
893
            auto a = e_cal_component_get_alarm(component, ai->auid);
 
894
            if (a == nullptr)
 
895
                continue;
 
896
 
 
897
            auto instance_time = std::make_pair(DateTime{gtz, ai->occur_start},
 
898
                                                DateTime{gtz, ai->occur_end});
 
899
            auto trigger_time = DateTime{gtz, ai->trigger};
 
900
 
 
901
            auto& alarm = alarms[instance_time][trigger_time];
 
902
 
 
903
            if (alarm.text.empty())
 
904
                alarm.text = get_alarm_text(a);
 
905
            if (alarm.audio_url.empty())
 
906
                alarm.audio_url = get_alarm_sound_url(a);
 
907
            if (!alarm.time.is_set())
 
908
                alarm.time = trigger_time;
 
909
 
 
910
            e_cal_component_alarm_free(a);
 
911
        }
 
912
 
 
913
        for (auto& i : alarms)
 
914
        {
 
915
            Appointment appointment = baseline;
 
916
            appointment.begin = i.first.first;
 
917
            appointment.end = i.first.second;
 
918
            appointment.alarms.reserve(i.second.size());
 
919
            for (auto& j : i.second)
 
920
                appointment.alarms.push_back(j.second);
 
921
            subtask->task->appointments.push_back(appointment);
 
922
        }
623
923
    }
624
924
 
625
925
    /***