aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Kerr <charles.kerr@canonical.com>2015-03-31 18:54:04 -0500
committerCharles Kerr <charles.kerr@canonical.com>2015-03-31 18:54:04 -0500
commitfff19d70649589b81a896e4deb032a7bd4bdca1e (patch)
tree3b1db77981d5561aaec31c308c69c6dbbf9ded68
parent3c5eda144d9d83be1aef24afaa307999975ec617 (diff)
downloadayatana-indicator-datetime-fff19d70649589b81a896e4deb032a7bd4bdca1e.tar.gz
ayatana-indicator-datetime-fff19d70649589b81a896e4deb032a7bd4bdca1e.tar.bz2
ayatana-indicator-datetime-fff19d70649589b81a896e4deb032a7bd4bdca1e.zip
add an Alarm class to represent ical valarm components; change the Appointment class to hold an arbitrary number of Alarms.
-rw-r--r--include/datetime/appointment.h26
-rw-r--r--include/datetime/snap.h3
-rw-r--r--src/actions-live.cpp19
-rw-r--r--src/appointment.cpp13
-rw-r--r--src/engine-eds.cpp163
-rw-r--r--src/main.cpp12
-rw-r--r--src/snap.cpp16
-rw-r--r--tests/manual-test-snap.cpp10
-rw-r--r--tests/test-live-actions.cpp6
-rw-r--r--tests/test-snap.cpp20
10 files changed, 206 insertions, 82 deletions
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/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/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/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/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..38e5caa 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&) {snooze_planner->add(appointment);};
+ auto on_ok = [](const Appointment&, const Alarm&){};
+ auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment) {
+#warning placeholder; next step is to have AlarmQueue pass an appointment and alarm
+ 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/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-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,