diff options
-rw-r--r-- | include/datetime/alarm-queue-simple.h | 18 | ||||
-rw-r--r-- | include/datetime/alarm-queue.h | 2 | ||||
-rw-r--r-- | include/datetime/appointment.h | 26 | ||||
-rw-r--r-- | include/datetime/clock-mock.h | 2 | ||||
-rw-r--r-- | include/datetime/clock.h | 2 | ||||
-rw-r--r-- | include/datetime/date-time.h | 3 | ||||
-rw-r--r-- | include/datetime/planner-snooze.h | 3 | ||||
-rw-r--r-- | include/datetime/snap.h | 3 | ||||
-rw-r--r-- | include/datetime/wakeup-timer-mainloop.h | 4 | ||||
-rw-r--r-- | include/datetime/wakeup-timer-powerd.h | 4 | ||||
-rw-r--r-- | include/datetime/wakeup-timer.h | 2 | ||||
-rw-r--r-- | src/actions-live.cpp | 19 | ||||
-rw-r--r-- | src/alarm-queue-simple.cpp | 226 | ||||
-rw-r--r-- | src/appointment.cpp | 13 | ||||
-rw-r--r-- | src/date-time.cpp | 10 | ||||
-rw-r--r-- | src/engine-eds.cpp | 163 | ||||
-rw-r--r-- | src/main.cpp | 12 | ||||
-rw-r--r-- | src/planner-snooze.cpp | 16 | ||||
-rw-r--r-- | src/snap.cpp | 16 | ||||
-rw-r--r-- | tests/manual-test-snap.cpp | 10 | ||||
-rw-r--r-- | tests/test-alarm-queue.cpp | 17 | ||||
-rw-r--r-- | tests/test-live-actions.cpp | 6 | ||||
-rw-r--r-- | tests/test-snap.cpp | 20 |
23 files changed, 387 insertions, 210 deletions
diff --git a/include/datetime/alarm-queue-simple.h b/include/datetime/alarm-queue-simple.h index d191aec..838d28a 100644 --- a/include/datetime/alarm-queue-simple.h +++ b/include/datetime/alarm-queue-simple.h @@ -20,6 +20,8 @@ #ifndef INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H #define INDICATOR_DATETIME_ALARM_QUEUE_SIMPLE_H +#include <memory> // std::shared_ptr + #include <datetime/alarm-queue.h> #include <datetime/clock.h> #include <datetime/planner.h> @@ -39,20 +41,12 @@ public: const std::shared_ptr<Planner>& upcoming_planner, const std::shared_ptr<WakeupTimer>& timer); ~SimpleAlarmQueue(); - core::Signal<const Appointment&>& alarm_reached(); + core::Signal<const Appointment&, const Alarm&>& alarm_reached() override; private: - void requeue(); - bool find_next_alarm(Appointment& setme) const; - std::vector<Appointment> find_current_alarms() const; - void check_alarms(); - - std::set<std::pair<std::string,DateTime>> m_triggered; - const std::shared_ptr<Clock> m_clock; - const std::shared_ptr<Planner> m_planner; - const std::shared_ptr<WakeupTimer> m_timer; - core::Signal<const Appointment&> m_alarm_reached; - DateTime m_datetime; + class Impl; + friend class Impl; + std::unique_ptr<Impl> impl; }; diff --git a/include/datetime/alarm-queue.h b/include/datetime/alarm-queue.h index ea51957..98abd7a 100644 --- a/include/datetime/alarm-queue.h +++ b/include/datetime/alarm-queue.h @@ -45,7 +45,7 @@ class AlarmQueue public: AlarmQueue() =default; virtual ~AlarmQueue() =default; - virtual core::Signal<const Appointment&>& alarm_reached() = 0; + virtual core::Signal<const Appointment&, const Alarm&>& alarm_reached() =0; }; /*** diff --git a/include/datetime/appointment.h b/include/datetime/appointment.h index ab89c2f..337d9b8 100644 --- a/include/datetime/appointment.h +++ b/include/datetime/appointment.h @@ -21,14 +21,31 @@ #define INDICATOR_DATETIME_APPOINTMENT_H #include <datetime/date-time.h> + +#include <chrono> #include <string> +#include <utility> +#include <vector> namespace unity { namespace indicator { namespace datetime { /** - * \brief Plain Old Data Structure that represents a calendar appointment. + * \brief Basic information required to raise a notification about some Appointment. + */ +struct Alarm +{ + std::string text; + std::string audio_url; + DateTime time; + std::chrono::seconds duration; + + bool operator== (const Alarm& that) const; +}; + +/** + * \brief An instance of an appointment; e.g. a calendar event or clock-app alarm * * @see Planner */ @@ -39,14 +56,15 @@ public: Type type = EVENT; bool is_ubuntu_alarm() const { return type == UBUNTU_ALARM; } + std::string uid; std::string color; std::string summary; - std::string url; - std::string uid; - std::string audio_url; + std::string activation_url; DateTime begin; DateTime end; + std::vector<Alarm> alarms; + bool operator== (const Appointment& that) const; }; diff --git a/include/datetime/clock-mock.h b/include/datetime/clock-mock.h index 84fd860..011d079 100644 --- a/include/datetime/clock-mock.h +++ b/include/datetime/clock-mock.h @@ -39,7 +39,7 @@ public: explicit MockClock(const DateTime& dt): m_localtime(dt) {} ~MockClock() =default; - DateTime localtime() const { return m_localtime; } + DateTime localtime() const override { return m_localtime; } void set_localtime(const DateTime& dt) { diff --git a/include/datetime/clock.h b/include/datetime/clock.h index 8745d24..4a0642f 100644 --- a/include/datetime/clock.h +++ b/include/datetime/clock.h @@ -76,7 +76,7 @@ class LiveClock: public Clock public: LiveClock (const std::shared_ptr<const Timezone>& zones); virtual ~LiveClock(); - virtual DateTime localtime() const; + virtual DateTime localtime() const override; private: class Impl; diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h index 7dfc207..7e19a9d 100644 --- a/include/datetime/date-time.h +++ b/include/datetime/date-time.h @@ -22,6 +22,7 @@ #include <glib.h> // GDateTime +#include <chrono> #include <ctime> // time_t #include <memory> // std::shared_ptr @@ -43,6 +44,8 @@ public: DateTime(GTimeZone* tz, GDateTime* dt); DateTime(GTimeZone* tz, int year, int month, int day, int hour, int minute, double seconds); DateTime& operator=(const DateTime& in); + DateTime& operator+=(const std::chrono::minutes&); + DateTime& operator+=(const std::chrono::seconds&); DateTime to_timezone(const std::string& zone) const; DateTime start_of_month() const; DateTime start_of_day() const; diff --git a/include/datetime/planner-snooze.h b/include/datetime/planner-snooze.h index e2619fa..fc08d27 100644 --- a/include/datetime/planner-snooze.h +++ b/include/datetime/planner-snooze.h @@ -39,9 +39,8 @@ public: SnoozePlanner(const std::shared_ptr<Settings>&, const std::shared_ptr<Clock>&); ~SnoozePlanner(); - void add(const Appointment&); - core::Property<std::vector<Appointment>>& appointments() override; + void add(const Appointment&, const Alarm&); protected: class Impl; diff --git a/include/datetime/snap.h b/include/datetime/snap.h index 572158d..cc091d3 100644 --- a/include/datetime/snap.h +++ b/include/datetime/snap.h @@ -42,8 +42,9 @@ public: const std::shared_ptr<const Settings>& settings); virtual ~Snap(); - typedef std::function<void(const Appointment&)> appointment_func; + typedef std::function<void(const Appointment&, const Alarm&)> appointment_func; void operator()(const Appointment& appointment, + const Alarm& alarm, appointment_func snooze, appointment_func ok); diff --git a/include/datetime/wakeup-timer-mainloop.h b/include/datetime/wakeup-timer-mainloop.h index 86da8cf..5acb150 100644 --- a/include/datetime/wakeup-timer-mainloop.h +++ b/include/datetime/wakeup-timer-mainloop.h @@ -41,8 +41,8 @@ class MainloopWakeupTimer: public WakeupTimer public: explicit MainloopWakeupTimer(const std::shared_ptr<Clock>&); ~MainloopWakeupTimer(); - void set_wakeup_time (const DateTime&); - core::Signal<>& timeout(); + void set_wakeup_time (const DateTime&) override; + core::Signal<>& timeout() override; private: MainloopWakeupTimer(const MainloopWakeupTimer&) =delete; diff --git a/include/datetime/wakeup-timer-powerd.h b/include/datetime/wakeup-timer-powerd.h index f11febe..5237fb9 100644 --- a/include/datetime/wakeup-timer-powerd.h +++ b/include/datetime/wakeup-timer-powerd.h @@ -41,8 +41,8 @@ class PowerdWakeupTimer: public WakeupTimer public: explicit PowerdWakeupTimer(const std::shared_ptr<Clock>&); ~PowerdWakeupTimer(); - void set_wakeup_time(const DateTime&); - core::Signal<>& timeout(); + void set_wakeup_time(const DateTime&) override; + core::Signal<>& timeout() override; private: PowerdWakeupTimer(const PowerdWakeupTimer&) =delete; diff --git a/include/datetime/wakeup-timer.h b/include/datetime/wakeup-timer.h index 0a9923c..3e5344c 100644 --- a/include/datetime/wakeup-timer.h +++ b/include/datetime/wakeup-timer.h @@ -41,7 +41,7 @@ public: WakeupTimer() =default; virtual ~WakeupTimer() =default; virtual void set_wakeup_time (const DateTime&) =0; - virtual core::Signal<>& timeout() = 0; + virtual core::Signal<>& timeout() =0; }; /*** diff --git a/src/actions-live.cpp b/src/actions-live.cpp index 4d1f770..3cbfb78 100644 --- a/src/actions-live.cpp +++ b/src/actions-live.cpp @@ -135,12 +135,19 @@ void LiveActions::phone_open_alarm_app() void LiveActions::phone_open_appointment(const Appointment& appt) { - if (!appt.url.empty()) - dispatch_url(appt.url); - else if (appt.is_ubuntu_alarm()) - phone_open_alarm_app(); - else - phone_open_calendar_app(DateTime::NowLocal()); + if (!appt.activation_url.empty()) + { + dispatch_url(appt.activation_url); + } + else switch (appt.type) + { + case Appointment::UBUNTU_ALARM: + phone_open_alarm_app(); + break; + + default: + phone_open_calendar_app(appt.begin); + } } void LiveActions::phone_open_calendar_app(const DateTime&) diff --git a/src/alarm-queue-simple.cpp b/src/alarm-queue-simple.cpp index f45e61a..db0fd21 100644 --- a/src/alarm-queue-simple.cpp +++ b/src/alarm-queue-simple.cpp @@ -20,134 +20,174 @@ #include <datetime/alarm-queue-simple.h> #include <cmath> +#include <set> namespace unity { namespace indicator { namespace datetime { /*** -**** Public API +**** ***/ -SimpleAlarmQueue::SimpleAlarmQueue(const std::shared_ptr<Clock>& clock, - const std::shared_ptr<Planner>& planner, - const std::shared_ptr<WakeupTimer>& timer): - m_clock(clock), - m_planner(planner), - m_timer(timer), - m_datetime(clock->localtime()) +class SimpleAlarmQueue::Impl { - m_planner->appointments().changed().connect([this](const std::vector<Appointment>&){ - g_debug("AlarmQueue %p calling requeue() due to appointments changed", this); - requeue(); - }); - - m_clock->minute_changed.connect([=]{ - const auto now = m_clock->localtime(); - constexpr auto skew_threshold_usec = int64_t{90} * G_USEC_PER_SEC; - const bool clock_jumped = std::abs(now - m_datetime) > skew_threshold_usec; - m_datetime = now; - if (clock_jumped) { - g_debug("AlarmQueue %p calling requeue() due to clock skew", this); +public: + + Impl(const std::shared_ptr<Clock>& clock, + const std::shared_ptr<Planner>& planner, + const std::shared_ptr<WakeupTimer>& timer): + m_clock{clock}, + m_planner{planner}, + m_timer{timer}, + m_datetime{clock->localtime()} + { + m_planner->appointments().changed().connect([this](const std::vector<Appointment>&){ + g_debug("AlarmQueue %p calling requeue() due to appointments changed", this); requeue(); - } - }); + }); + + m_clock->minute_changed.connect([=]{ + const auto now = m_clock->localtime(); + constexpr auto skew_threshold_usec = int64_t{90} * G_USEC_PER_SEC; + const bool clock_jumped = std::abs(now - m_datetime) > skew_threshold_usec; + m_datetime = now; + if (clock_jumped) { + g_debug("AlarmQueue %p calling requeue() due to clock skew", this); + requeue(); + } + }); + + m_timer->timeout().connect([this](){ + g_debug("AlarmQueue %p calling requeue() due to timeout", this); + requeue(); + }); - m_timer->timeout().connect([this](){ - g_debug("AlarmQueue %p calling requeue() due to timeout", this); requeue(); - }); - - requeue(); -} + } -SimpleAlarmQueue::~SimpleAlarmQueue() -{ -} + ~Impl() + { + } -core::Signal<const Appointment&>& SimpleAlarmQueue::alarm_reached() -{ - return m_alarm_reached; -} + core::Signal<const Appointment&, const Alarm&>& alarm_reached() + { + return m_alarm_reached; + } -/*** -**** -***/ +private: -void SimpleAlarmQueue::requeue() -{ - // kick any current alarms - for (auto current : find_current_alarms()) + void requeue() { - const std::pair<std::string,DateTime> trig {current.uid, current.begin}; - m_triggered.insert(trig); - m_alarm_reached(current); + // kick any current alarms + for (const auto& appointment : m_planner->appointments().get()) + { + Alarm alarm; + if (appointment_get_current_alarm(appointment, alarm)) + { + m_triggered.insert(std::make_pair(appointment.uid, alarm.time)); + m_alarm_reached(appointment, alarm); + } + } + + // idle until the next alarm + Alarm next; + if (find_next_alarm(next)) + { + g_debug ("setting timer to wake up for next appointment '%s' at %s", + next.text.c_str(), + next.time.format("%F %T").c_str()); + + m_timer->set_wakeup_time(next.time); + } } - // idle until the next alarm - Appointment next; - if (find_next_alarm(next)) + // find the next alarm that will kick now or in the future + bool find_next_alarm(Alarm& setme) const { - g_debug ("setting timer to wake up for next appointment '%s' at %s", - next.summary.c_str(), - next.begin.format("%F %T").c_str()); + bool found = false; + Alarm best; + const auto now = m_clock->localtime(); + const auto beginning_of_minute = now.start_of_minute(); - m_timer->set_wakeup_time(next.begin); - } -} + const auto appointments = m_planner->appointments().get(); + g_debug ("planner has %zu appointments in it", (size_t)appointments.size()); -// find the next alarm that will kick now or in the future -bool SimpleAlarmQueue::find_next_alarm(Appointment& setme) const -{ - bool found = false; - Appointment tmp; - const auto now = m_clock->localtime(); - const auto beginning_of_minute = now.start_of_minute(); + for(const auto& appointment : appointments) + { + for(const auto& alarm : appointment.alarms) + { + const std::pair<std::string,DateTime> trig{appointment.uid, alarm.time}; + if (m_triggered.count(trig)) + continue; - const auto appointments = m_planner->appointments().get(); - g_debug ("planner has %zu appointments in it", (size_t)appointments.size()); + if (alarm.time < beginning_of_minute) // has this one already passed? + continue; - for(const auto& walk : appointments) - { - const std::pair<std::string,DateTime> trig {walk.uid, walk.begin}; - if (m_triggered.count(trig)) - continue; + if (found && (best.time < alarm.time)) // do we already have a better match? + continue; - if (walk.begin < beginning_of_minute) // has this one already passed? - continue; + best = alarm; + found = true; + } + } - if (found && (tmp.begin < walk.begin)) // do we already have a better match? - continue; + if (found) + setme = best; - tmp = walk; - found = true; + return found; } - if (found) - setme = tmp; - return found; -} + bool appointment_get_current_alarm(const Appointment& appointment, Alarm& setme) const + { + const auto now = m_clock->localtime(); -// find the alarm(s) that should kick right now -std::vector<Appointment> SimpleAlarmQueue::find_current_alarms() const -{ - std::vector<Appointment> appointments; + for (const auto& alarm : appointment.alarms) + { + const auto trig = std::make_pair(appointment.uid, alarm.time); + if (m_triggered.count(trig)) // did we already use this one? + continue; + + if (DateTime::is_same_minute(now, alarm.time)) + { + setme = alarm; + return true; + } + } - const auto now = m_clock->localtime(); + return false; + } - for(const auto& walk : m_planner->appointments().get()) - { - const std::pair<std::string,DateTime> trig {walk.uid, walk.begin}; - if (m_triggered.count(trig)) // did we already use this one? - continue; - if (!DateTime::is_same_minute(now, walk.begin)) - continue; - appointments.push_back(walk); - } + std::set<std::pair<std::string,DateTime>> m_triggered; + const std::shared_ptr<Clock> m_clock; + const std::shared_ptr<Planner> m_planner; + const std::shared_ptr<WakeupTimer> m_timer; + core::Signal<const Appointment&, const Alarm&> m_alarm_reached; + DateTime m_datetime; +}; + +/*** +**** Public API +***/ + - return appointments; +SimpleAlarmQueue::SimpleAlarmQueue(const std::shared_ptr<Clock>& clock, + const std::shared_ptr<Planner>& planner, + const std::shared_ptr<WakeupTimer>& timer): + impl{new Impl{clock, planner, timer}} +{ +} + +SimpleAlarmQueue::~SimpleAlarmQueue() +{ +} + +core::Signal<const Appointment&, const Alarm&>& +SimpleAlarmQueue::alarm_reached() +{ + return impl->alarm_reached(); } /*** diff --git a/src/appointment.cpp b/src/appointment.cpp index ae71459..236c5f4 100644 --- a/src/appointment.cpp +++ b/src/appointment.cpp @@ -27,16 +27,23 @@ namespace datetime { ***** ****/ +bool Alarm::operator==(const Alarm& that) const +{ + return (text==that.text) + && (audio_url==that.audio_url) + && (this->time==that.time) + && (duration==that.duration); +} + bool Appointment::operator==(const Appointment& that) const { return (type==that.type) && (uid==that.uid) && (color==that.color) && (summary==that.summary) - && (url==that.url) - && (audio_url==that.audio_url) && (begin==that.begin) - && (end==that.end); + && (end==that.end) + && (alarms==that.alarms); } /**** diff --git a/src/date-time.cpp b/src/date-time.cpp index 689688c..ecfc971 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -55,6 +55,16 @@ DateTime& DateTime::operator=(const DateTime& that) return *this; } +DateTime& DateTime::operator+=(const std::chrono::minutes& minutes) +{ + return (*this = add_full(0, 0, 0, 0, minutes.count(), 0)); +} + +DateTime& DateTime::operator+=(const std::chrono::seconds& seconds) +{ + return (*this = add_full(0, 0, 0, 0, 0, seconds.count())); +} + DateTime::DateTime(time_t t) { auto gtz = g_time_zone_new_local(); diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index 47c7a9b..820d9d4 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -126,12 +126,18 @@ public: auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR); const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension)); g_debug("calling e_cal_client_generate_instances for %p", (void*)client); + auto subtask = new AppointmentSubtask(main_task, + client, + color, + default_timezone, + begin_timet, + end_timet); e_cal_client_generate_instances(client, begin_timet, end_timet, m_cancellable, my_get_appointments_foreach, - new AppointmentSubtask (main_task, client, color), + subtask, [](gpointer g){delete static_cast<AppointmentSubtask*>(g);}); } } @@ -409,14 +415,71 @@ private: std::shared_ptr<Task> task; ECalClient* client; std::string color; - AppointmentSubtask(const std::shared_ptr<Task>& task_in, ECalClient* client_in, const char* color_in): - task(task_in), client(client_in) + icaltimezone* default_timezone; + time_t begin; + time_t end; + + AppointmentSubtask(const std::shared_ptr<Task>& task_in, + ECalClient* client_in, + const char* color_in, + icaltimezone* default_tz, + time_t begin_, + time_t end_): + task(task_in), + client(client_in), + default_timezone(default_tz), + begin(begin_), + end(end_) { if (color_in) color = color_in; } }; + static std::string get_alarm_text(ECalComponentAlarm * alarm) + { + std::string ret; + + ECalComponentAlarmAction action; + e_cal_component_alarm_get_action(alarm, &action); + if (action == E_CAL_COMPONENT_ALARM_DISPLAY) + { + ECalComponentText text; + text.value = nullptr; + e_cal_component_alarm_get_description(alarm, &text); + if (text.value) + ret = text.value; + } + + return ret; + } + + static std::string get_alarm_sound_url(ECalComponentAlarm * alarm) + { + std::string ret; + + ECalComponentAlarmAction action; + e_cal_component_alarm_get_action(alarm, &action); + if (action == E_CAL_COMPONENT_ALARM_AUDIO) + { + icalattach* attach = nullptr; + e_cal_component_alarm_get_attach(alarm, &attach); + if (attach != nullptr) + { + if (icalattach_get_is_url (attach)) + { + const char* url = icalattach_get_url(attach); + if (url != nullptr) + ret = url; + } + + icalattach_unref(attach); + } + } + + return ret; + } + static gboolean my_get_appointments_foreach(ECalComponent* component, time_t begin, @@ -461,6 +524,7 @@ private: (status != ICAL_STATUS_COMPLETED) && (status != ICAL_STATUS_CANCELLED)) { + ECalComponentAlarmAction omit[] = { (ECalComponentAlarmAction)-1 }; // list of action types to omit, terminated with -1 Appointment appointment; ECalComponentText text; @@ -475,47 +539,76 @@ private: appointment.uid = uid; appointment.type = type; - // Look through all of this component's alarms - // for DISPLAY or AUDIO url attachments. - // If we find any, use them for appointment.url and audio_sound - auto alarm_uids = e_cal_component_get_alarm_uids(component); - for(auto walk=alarm_uids; appointment.url.empty() && walk!=nullptr; walk=walk->next) + icalcomponent * icc = e_cal_component_get_icalcomponent(component); + g_debug("%s", icalcomponent_as_ical_string(icc)); // libical owns this string; no leak + + auto e_alarms = e_cal_util_generate_alarms_for_comp(component, + subtask->begin, + subtask->end, + omit, + e_cal_client_resolve_tzid_cb, + subtask->client, + subtask->default_timezone); + + std::map<DateTime,Alarm> alarms; + + if (e_alarms != nullptr) { - auto alarm = e_cal_component_get_alarm(component, static_cast<const char*>(walk->data)); + for (auto l=e_alarms->alarms; l!=nullptr; l=l->next) + { + auto ai = static_cast<ECalComponentAlarmInstance*>(l->data); + auto a = e_cal_component_get_alarm(component, ai->auid); - ECalComponentAlarmAction action; - e_cal_component_alarm_get_action(alarm, &action); - if ((action == E_CAL_COMPONENT_ALARM_DISPLAY) || (action == E_CAL_COMPONENT_ALARM_AUDIO)) + if (a != nullptr) + { + const DateTime alarm_begin{ai->occur_start}; + auto& alarm = alarms[alarm_begin]; + + if (alarm.text.empty()) + alarm.text = get_alarm_text(a); + if (alarm.audio_url.empty()) + alarm.audio_url = get_alarm_sound_url(a); + if (!alarm.time.is_set()) + alarm.time = alarm_begin; + if (alarm.duration == std::chrono::seconds::zero()) + alarm.duration = std::chrono::seconds(ai->occur_end - ai->occur_start); + + e_cal_component_alarm_free(a); + } + } + + e_cal_component_alarms_free(e_alarms); + } + // hm, no trigger. if this came from ubuntu-clock-app, + // manually add a single trigger for the todo event's time + else if (appointment.is_ubuntu_alarm()) + { + Alarm tmp; + tmp.time = appointment.begin; + + auto auids = e_cal_component_get_alarm_uids(component); + for(auto l=auids; l!=nullptr; l=l->next) { - icalattach* attach = nullptr; - e_cal_component_alarm_get_attach(alarm, &attach); - if (attach != nullptr) + const auto auid = static_cast<const char*>(l->data); + auto a = e_cal_component_get_alarm(component, auid); + if (a != nullptr) { - if (icalattach_get_is_url (attach)) - { - const char* url = icalattach_get_url(attach); - if (url != nullptr) - { - if ((action == E_CAL_COMPONENT_ALARM_DISPLAY) && appointment.url.empty()) - { - appointment.url = url; - } - else if ((action == E_CAL_COMPONENT_ALARM_AUDIO) && appointment.audio_url.empty()) - { - appointment.audio_url = url; - } - } - } - - icalattach_unref(attach); + if (tmp.text.empty()) + tmp.text = get_alarm_text(a); + if (tmp.audio_url.empty()) + tmp.audio_url = get_alarm_sound_url(a); + e_cal_component_alarm_free(a); } } + cal_obj_uid_list_free(auids); - e_cal_component_alarm_free(alarm); + alarms[tmp.time] = tmp; } - cal_obj_uid_list_free(alarm_uids); - g_debug("adding appointment '%s' '%s'", appointment.summary.c_str(), appointment.url.c_str()); + appointment.alarms.reserve(alarms.size()); + for (const auto& it : alarms) + appointment.alarms.push_back(it.second); + subtask->task->appointments.push_back(appointment); } } diff --git a/src/main.cpp b/src/main.cpp index 9aa502c..907d49f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,11 +138,13 @@ main(int /*argc*/, char** /*argv*/) auto notification_engine = std::make_shared<uin::Engine>("indicator-datetime-service"); std::unique_ptr<Snap> snap (new Snap(notification_engine, state->settings)); auto alarm_queue = create_simple_alarm_queue(state->clock, snooze_planner, engine, timezone_); - auto on_snooze = [snooze_planner](const Appointment& a) {snooze_planner->add(a);}; - auto on_ok = [](const Appointment&){}; - auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& a) { - (*snap)(a, on_snooze, on_ok); - engine->disable_ubuntu_alarm(a); + auto on_snooze = [snooze_planner](const Appointment& appointment, const Alarm& alarm) { + snooze_planner->add(appointment, alarm); + }; + auto on_ok = [](const Appointment&, const Alarm&){}; + auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment, const Alarm& alarm) { + (*snap)(appointment, alarm, on_snooze, on_ok); + engine->disable_ubuntu_alarm(appointment); }; alarm_queue->alarm_reached().connect(on_alarm_reached); diff --git a/src/planner-snooze.cpp b/src/planner-snooze.cpp index 29d5f06..e4062d2 100644 --- a/src/planner-snooze.cpp +++ b/src/planner-snooze.cpp @@ -51,14 +51,18 @@ public: return m_appointments; } - void add(const Appointment& appt_in) + void add(const Appointment& appt_in, const Alarm& alarm) { + // make a copy of the appointment with only this alarm Appointment appt = appt_in; + appt.alarms.clear(); + appt.alarms.push_back(alarm); // reschedule the alarm to go off N minutes from now - const auto alarm_duration_secs = appt.end - appt.begin; - appt.begin = m_clock->localtime().add_full(0,0,0,0,m_settings->snooze_duration.get(),0); - appt.end = appt.begin.add_full(0,0,0,0,0,alarm_duration_secs); + const auto offset = std::chrono::minutes(m_settings->snooze_duration.get()); + appt.begin += offset; + appt.end += offset; + appt.alarms[0].time += offset; // give it a new ID gchar* uid = e_uid_new(); @@ -95,9 +99,9 @@ SnoozePlanner::~SnoozePlanner() } void -SnoozePlanner::add(const Appointment& appointment) +SnoozePlanner::add(const Appointment& appointment, const Alarm& alarm) { - impl->add(appointment); + impl->add(appointment, alarm); } core::Property<std::vector<Appointment>>& diff --git a/src/snap.cpp b/src/snap.cpp index e655d2d..ae0a62a 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -79,6 +79,7 @@ public: } void operator()(const Appointment& appointment, + const Alarm& alarm, appointment_func snooze, appointment_func ok) { @@ -96,7 +97,7 @@ public: if (appointment.is_ubuntu_alarm() || !silent_mode()) { // create the sound. const auto role = appointment.is_ubuntu_alarm() ? "alarm" : "alert"; - const auto uri = get_alarm_uri(appointment, m_settings); + const auto uri = get_alarm_uri(alarm, m_settings); const auto volume = m_settings->alarm_volume.get(); const bool loop = interactive; sound = std::make_shared<uin::Sound>(role, uri, volume, loop); @@ -140,12 +141,12 @@ public: // add 'sound', 'haptic', and 'awake' objects to the capture so // they stay alive until the closed callback is called; i.e., // for the lifespan of the notficiation - b.set_closed_callback([appointment, snooze, ok, sound, awake, haptic] + b.set_closed_callback([appointment, alarm, snooze, ok, sound, awake, haptic] (const std::string& action){ if (action == "snooze") - snooze(appointment); + snooze(appointment, alarm); else - ok(appointment); + ok(appointment, alarm); }); const auto key = m_engine->show(b); @@ -180,12 +181,12 @@ private: && (accounts_service_sound_get_silent_mode(m_accounts_service_sound_proxy)); } - std::string get_alarm_uri(const Appointment& appointment, + std::string get_alarm_uri(const Alarm& alarm, const std::shared_ptr<const Settings>& settings) const { const char* FALLBACK {"/usr/share/sounds/ubuntu/ringtones/Suru arpeggio.ogg"}; - const std::string candidates[] = { appointment.audio_url, + const std::string candidates[] = { alarm.audio_url, settings->alarm_sound.get(), FALLBACK }; @@ -236,10 +237,11 @@ Snap::~Snap() void Snap::operator()(const Appointment& appointment, + const Alarm& alarm, appointment_func show, appointment_func ok) { - (*impl)(appointment, show, ok); + (*impl)(appointment, alarm, show, ok); } /*** diff --git a/tests/manual-test-snap.cpp b/tests/manual-test-snap.cpp index 22ef137..cbe79cd 100644 --- a/tests/manual-test-snap.cpp +++ b/tests/manual-test-snap.cpp @@ -67,18 +67,18 @@ int main(int argc, const char* argv[]) Appointment a; a.color = "green"; a.summary = "Alarm"; - a.url = "alarm:///hello-world"; a.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; a.type = Appointment::UBUNTU_ALARM; a.begin = DateTime::Local(2014, 12, 25, 0, 0, 0); a.end = a.begin.end_of_day(); + a.alarms.push_back(Alarm{"Alarm Text", "", a.begin, std::chrono::seconds::zero()}); auto loop = g_main_loop_new(nullptr, false); - auto on_snooze = [loop](const Appointment& appt){ - g_message("You clicked 'Snooze' for appt url '%s'", appt.url.c_str()); + auto on_snooze = [loop](const Appointment& appt, const Alarm&){ + g_message("You clicked 'Snooze' for appt url '%s'", appt.summary.c_str()); g_idle_add(quit_idle, loop); }; - auto on_ok = [loop](const Appointment&){ + auto on_ok = [loop](const Appointment&, const Alarm&){ g_message("You clicked 'OK'"); g_idle_add(quit_idle, loop); }; @@ -93,7 +93,7 @@ int main(int argc, const char* argv[]) auto notification_engine = std::make_shared<uin::Engine>("indicator-datetime-service"); Snap snap (notification_engine, settings); - snap(a, on_snooze, on_ok); + snap(a, a.alarms.front(), on_snooze, on_ok); g_main_loop_run(loop); g_main_loop_unref(loop); diff --git a/tests/test-alarm-queue.cpp b/tests/test-alarm-queue.cpp index 3fdf787..fdab425 100644 --- a/tests/test-alarm-queue.cpp +++ b/tests/test-alarm-queue.cpp @@ -48,7 +48,7 @@ protected: m_range_planner.reset(new MockRangePlanner); m_upcoming.reset(new UpcomingPlanner(m_range_planner, m_state->clock->localtime())); m_watcher.reset(new SimpleAlarmQueue(m_state->clock, m_upcoming, m_wakeup_timer)); - m_watcher->alarm_reached().connect([this](const Appointment& appt){ + m_watcher->alarm_reached().connect([this](const Appointment& appt, const Alarm& /*alarm*/){ m_triggered.push_back(appt.uid); }); @@ -71,7 +71,7 @@ protected: const auto tomorrow_begin = now.add_days(1).start_of_day(); const auto tomorrow_end = tomorrow_begin.end_of_day(); - Appointment a1; // an alarm clock appointment + Appointment a1; // an ubuntu alarm a1.color = "red"; a1.summary = "Alarm"; a1.summary = "http://www.example.com/"; @@ -79,18 +79,20 @@ protected: a1.type = Appointment::UBUNTU_ALARM; a1.begin = tomorrow_begin; a1.end = tomorrow_end; + a1.alarms.push_back(Alarm{"Alarm Text", "", a1.begin, std::chrono::seconds::zero()}); const auto ubermorgen_begin = now.add_days(2).start_of_day(); const auto ubermorgen_end = ubermorgen_begin.end_of_day(); - Appointment a2; // a non-alarm appointment + Appointment a2; // something else a2.color = "green"; a2.summary = "Other Text"; a2.summary = "http://www.monkey.com/"; a2.uid = "monkey"; - a1.type = Appointment::EVENT; + a2.type = Appointment::EVENT; a2.begin = ubermorgen_begin; a2.end = ubermorgen_end; + a2.alarms.push_back(Alarm{"Alarm Text", "", a2.begin, std::chrono::seconds::zero()}); return std::vector<Appointment>({a1, a2}); } @@ -105,7 +107,7 @@ TEST_F(AlarmQueueFixture, AppointmentsChanged) // Add some appointments to the planner. // One of these matches our state's localtime, so that should get triggered. std::vector<Appointment> a = build_some_appointments(); - a[0].begin = m_state->clock->localtime(); + a[0].begin = a[0].alarms.front().time = m_state->clock->localtime(); m_range_planner->appointments().set(a); // Confirm that it got fired @@ -135,7 +137,8 @@ TEST_F(AlarmQueueFixture, MoreThanOne) { const auto now = m_state->clock->localtime(); std::vector<Appointment> a = build_some_appointments(); - a[0].begin = a[1].begin = now; + a[0].alarms.front().time = now; + a[1].alarms.front().time = now; m_range_planner->appointments().set(a); ASSERT_EQ(2, m_triggered.size()); @@ -151,7 +154,7 @@ TEST_F(AlarmQueueFixture, NoDuplicates) const std::vector<Appointment> appointments = build_some_appointments(); std::vector<Appointment> a; a.push_back(appointments[0]); - a[0].begin = now; + a[0].alarms.front().time = now; m_range_planner->appointments().set(a); ASSERT_EQ(1, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); diff --git a/tests/test-live-actions.cpp b/tests/test-live-actions.cpp index 1a34511..4f84f25 100644 --- a/tests/test-live-actions.cpp +++ b/tests/test-live-actions.cpp @@ -319,10 +319,6 @@ TEST_F(LiveActionsFixture, PhoneOpenAppointment) a.type = Appointment::UBUNTU_ALARM; m_actions->phone_open_appointment(a); EXPECT_EQ(clock_app_url, m_live_actions->last_url); - - a.url = "appid://blah"; - m_actions->phone_open_appointment(a); - EXPECT_EQ(a.url, m_live_actions->last_url); } TEST_F(LiveActionsFixture, PhoneOpenCalendarApp) @@ -392,7 +388,6 @@ TEST_F(LiveActionsFixture, CalendarState) Appointment a1; a1.color = "green"; a1.summary = "write unit tests"; - a1.url = "http://www.ubuntu.com/"; a1.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; a1.begin = tomorrow_begin; a1.end = tomorrow_end; @@ -403,7 +398,6 @@ TEST_F(LiveActionsFixture, CalendarState) Appointment a2; a2.color = "orange"; a2.summary = "code review"; - a2.url = "http://www.ubuntu.com/"; a2.uid = "2756ff7de3745bbffd65d2e4779c37c7ca60d843"; a2.begin = ubermorgen_begin; a2.end = ubermorgen_end; diff --git a/tests/test-snap.cpp b/tests/test-snap.cpp index 3dd4501..2e29132 100644 --- a/tests/test-snap.cpp +++ b/tests/test-snap.cpp @@ -106,12 +106,12 @@ protected: // init the Appointment appt.color = "green"; appt.summary = "Alarm"; - appt.url = "alarm:///hello-world"; appt.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; appt.type = Appointment::EVENT; const auto christmas = DateTime::Local(2015,12,25,0,0,0); appt.begin = christmas.start_of_day(); appt.end = christmas.end_of_day(); + appt.alarms.push_back(Alarm{"Alarm Text", "", appt.begin, std::chrono::seconds::zero()}); service = dbus_test_service_new(nullptr); @@ -343,8 +343,8 @@ TEST_F(SnapFixture, InteractiveDuration) make_interactive(); // call the Snap Decision - auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; - snap(appt, func, func); + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + snap(appt, appt.alarms.front(), func, func); // confirm that Notify got called once guint len = 0; @@ -393,8 +393,8 @@ TEST_F(SnapFixture, InhibitSleep) make_interactive(); // invoke the notification - auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; - (*snap)(appt, func, func); + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + (*snap)(appt, appt.alarms.front(), func, func); wait_msec(1000); @@ -448,8 +448,8 @@ TEST_F(SnapFixture, ForceScreen) make_interactive(); // invoke the notification - auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; - (*snap)(appt, func, func); + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + (*snap)(appt, appt.alarms.front(), func, func); wait_msec(1000); @@ -484,14 +484,14 @@ TEST_F(SnapFixture, HapticModes) { auto settings = std::make_shared<Settings>(); auto ne = std::make_shared<unity::indicator::notifications::Engine>(APP_NAME); - auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; GError * error = nullptr; // invoke a snap decision while haptic feedback is set to "pulse", // confirm that VibratePattern got called settings->alarm_haptic.set("pulse"); auto snap = new Snap (ne, settings); - (*snap)(appt, func, func); + (*snap)(appt, appt.alarms.front(), func, func); wait_msec(100); EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (haptic_mock, haptic_obj, @@ -506,7 +506,7 @@ TEST_F(SnapFixture, HapticModes) dbus_test_dbus_mock_object_clear_method_calls (haptic_mock, haptic_obj, &error); settings->alarm_haptic.set("none"); snap = new Snap (ne, settings); - (*snap)(appt, func, func); + (*snap)(appt, appt.alarms.front(), func, func); wait_msec(100); EXPECT_FALSE (dbus_test_dbus_mock_object_check_method_call (haptic_mock, haptic_obj, |