From 3f4d409f21bbb1f79f149a5ee66dcddaa505ddb1 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 9 Mar 2014 21:08:47 -0500 Subject: decouple the planner's states; need three separate sets: upcoming-now (for alarms in the current time), upcoming-calendar (to show events coming from the selected calendar date), and calendar-month (all the appointments in the month displayed in the menu). --- include/datetime/clock-watcher.h | 9 +- include/datetime/date-time.h | 5 + include/datetime/engine-eds.h | 73 +++++ include/datetime/planner-eds.h | 23 +- include/datetime/planner-month.h | 56 ++++ include/datetime/planner-range.h | 62 ++++ include/datetime/planner-upcoming.h | 56 ++++ include/datetime/planner.h | 29 +- include/datetime/state.h | 14 +- src/CMakeLists.txt | 4 + src/actions-live.cpp | 2 +- src/actions.cpp | 13 +- src/clock-watcher.cpp | 26 +- src/date-time.cpp | 27 +- src/engine-eds.cpp | 531 ++++++++++++++++++++++++++++++++++ src/main.cpp | 11 +- src/menu.cpp | 14 +- src/planner-eds.cpp | 559 ++---------------------------------- src/planner-month.cpp | 66 +++++ src/planner-range.cpp | 72 +++++ src/planner-upcoming.cpp | 61 ++++ tests/planner-mock.h | 27 +- tests/state-mock.h | 12 +- tests/test-actions.cpp | 12 +- tests/test-clock-watcher.cpp | 20 +- tests/test-live-actions.cpp | 7 +- tests/test-menus.cpp | 10 +- tests/test-planner.cpp | 21 -- 28 files changed, 1181 insertions(+), 641 deletions(-) create mode 100644 include/datetime/engine-eds.h create mode 100644 include/datetime/planner-month.h create mode 100644 include/datetime/planner-range.h create mode 100644 include/datetime/planner-upcoming.h create mode 100644 src/engine-eds.cpp create mode 100644 src/planner-month.cpp create mode 100644 src/planner-range.cpp create mode 100644 src/planner-upcoming.cpp diff --git a/include/datetime/clock-watcher.h b/include/datetime/clock-watcher.h index e93b468..90bbb63 100644 --- a/include/datetime/clock-watcher.h +++ b/include/datetime/clock-watcher.h @@ -20,8 +20,9 @@ #ifndef INDICATOR_DATETIME_CLOCK_WATCHER_H #define INDICATOR_DATETIME_CLOCK_WATCHER_H -#include #include +#include +#include #include @@ -53,14 +54,16 @@ public: class ClockWatcherImpl: public ClockWatcher { public: - ClockWatcherImpl(const std::shared_ptr& state); + ClockWatcherImpl(const std::shared_ptr& clock, + const std::shared_ptr& upcoming_planner); ~ClockWatcherImpl() =default; core::Signal& alarm_reached(); private: void pulse(); std::set m_triggered; - std::shared_ptr m_state; + const std::shared_ptr m_clock; + const std::shared_ptr m_upcoming_planner; core::Signal m_alarm_reached; }; diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h index b054a1f..f861c2e 100644 --- a/include/datetime/date-time.h +++ b/include/datetime/date-time.h @@ -36,6 +36,8 @@ class DateTime { public: static DateTime NowLocal(); + static DateTime Local(int years, int months, int days, int hours, int minutes, int seconds); + explicit DateTime(time_t t); explicit DateTime(GDateTime* in=nullptr); DateTime& operator=(GDateTime* in); @@ -48,7 +50,10 @@ public: GDateTime* operator()() const {return get();} std::string format(const std::string& fmt) const; + void ymd(int& year, int& month, int& day) const; int day_of_month() const; + int hour() const; + int minute() const; double seconds() const; int64_t to_unix() const; diff --git a/include/datetime/engine-eds.h b/include/datetime/engine-eds.h new file mode 100644 index 0000000..e269167 --- /dev/null +++ b/include/datetime/engine-eds.h @@ -0,0 +1,73 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_ENGINE_EDS__H +#define INDICATOR_DATETIME_ENGINE_EDS__H + +#include +#include +#include + +#include +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/**** +***** +****/ + +/** + * Class wrapper around EDS so multiple #EdsPlanners can share resources + * + * @see EdsPlanner + */ +class EdsEngine +{ +public: + EdsEngine(); + ~EdsEngine(); + + void get_appointments(const DateTime& begin, + const DateTime& end, + const Timezone& default_timezone, + std::function&)> appointment_func); + + core::Signal<>& changed(); + +private: + class Impl; + std::unique_ptr p; + + // we've got a unique_ptr here, disable copying... + EdsEngine(const EdsEngine&) =delete; + EdsEngine& operator=(const EdsEngine&) =delete; +}; + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_ENGINE_EDS__H diff --git a/include/datetime/planner-eds.h b/include/datetime/planner-eds.h index 3d558df..95b5d79 100644 --- a/include/datetime/planner-eds.h +++ b/include/datetime/planner-eds.h @@ -20,8 +20,9 @@ #ifndef INDICATOR_DATETIME_PLANNER_EDS_H #define INDICATOR_DATETIME_PLANNER_EDS_H -#include -#include +#include + +#include #include #include // shared_ptr, unique_ptr @@ -31,18 +32,24 @@ namespace indicator { namespace datetime { /** - * \brief Planner which uses EDS as its backend + * \brief An EDS-based #RangePlanner */ -class PlannerEds: public Planner +class EdsPlanner: public RangePlanner { public: - PlannerEds(const std::shared_ptr& clock, + EdsPlanner(const std::shared_ptr& eds_engine, const std::shared_ptr& timezone); - virtual ~PlannerEds(); + virtual ~EdsPlanner(); + + core::Property>& appointments(); + +protected: + void rebuild_now(); private: - class Impl; - std::unique_ptr p; + std::shared_ptr m_engine; + std::shared_ptr m_timezone; + core::Property> m_appointments; }; } // namespace datetime diff --git a/include/datetime/planner-month.h b/include/datetime/planner-month.h new file mode 100644 index 0000000..492529f --- /dev/null +++ b/include/datetime/planner-month.h @@ -0,0 +1,56 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_PLANNER_MONTH_H +#define INDICATOR_DATETIME_PLANNER_MONTH_H + +#include + +#include +#include + +#include // std::shared_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A #Planner that contains appointments for a specified calendar month + */ +class MonthPlanner: public Planner +{ +public: + MonthPlanner(const std::shared_ptr& range_planner, + const DateTime& month_in); + ~MonthPlanner() =default; + + core::Property>& appointments(); + core::Property& month(); + +private: + std::shared_ptr m_range_planner; + core::Property m_month; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_PLANNER_MONTH_H diff --git a/include/datetime/planner-range.h b/include/datetime/planner-range.h new file mode 100644 index 0000000..5306cdc --- /dev/null +++ b/include/datetime/planner-range.h @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_PLANNER_RANGE_H +#define INDICATOR_DATETIME_PLANNER_RANGE_H + +#include + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A #Planner that contains appointments in a specified date range + * + * @see Planner + */ +class RangePlanner: public Planner +{ +public: + virtual ~RangePlanner(); + core::Property>& range(); + +protected: + RangePlanner(); + + void rebuild_soon(); + virtual void rebuild_now() =0; + +private: + static gboolean rebuild_now_static(gpointer); + guint m_rebuild_tag = 0; + core::Property> m_range; + + // we've got a GSignal tag here, so disable copying + RangePlanner(const RangePlanner&) =delete; + RangePlanner& operator=(const RangePlanner&) =delete; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_PLANNER_RANGE_H diff --git a/include/datetime/planner-upcoming.h b/include/datetime/planner-upcoming.h new file mode 100644 index 0000000..683543f --- /dev/null +++ b/include/datetime/planner-upcoming.h @@ -0,0 +1,56 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_PLANNER_UPCOMING_H +#define INDICATOR_DATETIME_PLANNER_UPCOMING_H + +#include + +#include +#include + +#include // std::shared_ptr + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief A collection of upcoming appointments starting from the specified date + */ +class UpcomingPlanner: public Planner +{ +public: + UpcomingPlanner(const std::shared_ptr& range_planner, + const DateTime& date); + ~UpcomingPlanner() =default; + + core::Property>& appointments(); + core::Property& date(); + +private: + std::shared_ptr m_range_planner; + core::Property m_date; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_PLANNER_UPCOMING_H diff --git a/include/datetime/planner.h b/include/datetime/planner.h index 376a31f..e6ef927 100644 --- a/include/datetime/planner.h +++ b/include/datetime/planner.h @@ -32,41 +32,16 @@ namespace indicator { namespace datetime { /** - * \brief Simple appointment book - * - * @see EdsPlanner - * @see State + * \brief Simple collection of appointments */ class Planner { public: virtual ~Planner() =default; - - /** - * \brief Timestamp used to determine the appointments in the `upcoming' and `this_month' properties. - * Setting this value will cause the planner to re-query its backend and - * update the `upcoming' and `this_month' properties. - */ - core::Property time; - - /** - * \brief The next few appointments that follow the time specified in the time property. - */ - core::Property> upcoming; - - /** - * \brief The appointments that occur in the same month as the time property - */ - core::Property> this_month; + virtual core::Property>& appointments() =0; protected: Planner() =default; - -private: - - // disable copying - Planner(const Planner&) =delete; - Planner& operator=(const Planner&) =delete; }; } // namespace datetime diff --git a/include/datetime/state.h b/include/datetime/state.h index 414be32..0e1043c 100644 --- a/include/datetime/state.h +++ b/include/datetime/state.h @@ -22,7 +22,8 @@ #include #include -#include +#include +#include #include #include @@ -60,9 +61,14 @@ struct State section of the #Menu */ std::shared_ptr locations; - /** \brief The appointments to be displayed in the Calendar and - Appointments sections of the #Menu */ - std::shared_ptr planner; + /** \brief Appointments in the month that's being displayed + in the calendar section of the #Menu */ + std::shared_ptr calendar_month; + + /** \brief The next appointments that follow highlighed date + highlighted in the calendar section of the #Menu + (default date = today) */ + std::shared_ptr calendar_upcoming; /** \brief Configuration options that modify the view */ std::shared_ptr settings; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b1d7df..5660e6a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ add_library (${SERVICE_LIB} STATIC clock-live.cpp clock-watcher.cpp date-time.cpp + engine-eds.cpp exporter.cpp formatter.cpp formatter-desktop.cpp @@ -22,6 +23,9 @@ add_library (${SERVICE_LIB} STATIC locations-settings.cpp menu.cpp planner-eds.cpp + planner-month.cpp + planner-range.cpp + planner-upcoming.cpp settings-live.cpp snap.cpp timezone-file.cpp diff --git a/src/actions-live.cpp b/src/actions-live.cpp index ccc7fcf..ca9ca9b 100644 --- a/src/actions-live.cpp +++ b/src/actions-live.cpp @@ -97,7 +97,7 @@ void LiveActions::open_planner_at(const DateTime& dt) void LiveActions::open_appointment(const std::string& uid) { - for(const auto& appt : state()->planner->upcoming.get()) + for(const auto& appt : state()->calendar_upcoming->appointments().get()) { if(appt.uid != uid) continue; diff --git a/src/actions.cpp b/src/actions.cpp index d6fa698..c9c6286 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -65,7 +65,7 @@ void on_activate_appointment(GSimpleAction * /*action*/, g_return_if_fail(uid && *uid); // find url of the upcoming appointment with this uid - for (const auto& appt : self->state()->planner->upcoming.get()) + for (const auto& appt : self->state()->calendar_upcoming->appointments().get()) { if (appt.uid == uid) { @@ -146,7 +146,7 @@ GVariant* create_default_header_state() GVariant* create_calendar_state(const std::shared_ptr& state) { gboolean days[32] = { 0 }; - for (const auto& appt : state->planner->this_month.get()) + for (const auto& appt : state->calendar_month->appointments().get()) days[appt.begin.day_of_month()] = true; GVariantBuilder day_builder; @@ -163,7 +163,7 @@ GVariant* create_calendar_state(const std::shared_ptr& state) g_variant_builder_add(&dict_builder, "{sv}", key, v); key = "calendar-day"; - v = g_variant_new_int64(state->planner->time.get().to_unix()); + v = g_variant_new_int64(state->calendar_month->month().get().to_unix()); g_variant_builder_add(&dict_builder, "{sv}", key, v); key = "show-week-numbers"; @@ -219,10 +219,10 @@ Actions::Actions(const std::shared_ptr& state): /// Keep our GActionGroup's action's states in sync with m_state /// - m_state->planner->time.changed().connect([this](const DateTime&){ + m_state->calendar_month->month().changed().connect([this](const DateTime&){ update_calendar_state(); }); - m_state->planner->this_month.changed().connect([this](const std::vector&){ + m_state->calendar_month->appointments().changed().connect([this](const std::vector&){ update_calendar_state(); }); m_state->settings->show_week_numbers.changed().connect([this](bool){ @@ -246,7 +246,8 @@ void Actions::update_calendar_state() void Actions::set_calendar_date(const DateTime& date) { - m_state->planner->time.set(date); + m_state->calendar_month->month().set(date); + m_state->calendar_upcoming->date().set(date); } GActionGroup* Actions::action_group() diff --git a/src/clock-watcher.cpp b/src/clock-watcher.cpp index a2e700d..5da66c8 100644 --- a/src/clock-watcher.cpp +++ b/src/clock-watcher.cpp @@ -27,17 +27,27 @@ namespace datetime { **** ***/ -ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr& state): - m_state(state) +ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr& clock, + const std::shared_ptr& upcoming_planner): + m_clock(clock), + m_upcoming_planner(upcoming_planner) { - m_state->planner->upcoming.changed().connect([this](const std::vector&){ - g_debug("ClockWatcher pulse because upcoming appointments changed"); + m_clock->date_changed.connect([this](){ + const auto now = m_clock->localtime(); + g_debug("ClockWatcher %p refretching appointments due to date change: %s", this, now.format("%F %T").c_str()); + m_upcoming_planner->date().set(now); + }); + + m_clock->minute_changed.connect([this](){ + g_debug("ClockWatcher %p calling pulse() due to clock minute_changed", this); pulse(); }); - m_state->clock->minute_changed.connect([this](){ - g_debug("ClockWatcher pulse because clock minute_changed"); + + m_upcoming_planner->appointments().changed().connect([this](const std::vector&){ + g_debug("ClockWatcher %p calling pulse() due to appointments changed", this); pulse(); }); + pulse(); } @@ -48,9 +58,9 @@ core::Signal& ClockWatcherImpl::alarm_reached() void ClockWatcherImpl::pulse() { - const auto now = m_state->clock->localtime(); + const auto now = m_clock->localtime(); - for(const auto& appointment : m_state->planner->upcoming.get()) + for(const auto& appointment : m_upcoming_planner->appointments().get()) { if (m_triggered.count(appointment.uid)) continue; diff --git a/src/date-time.cpp b/src/date-time.cpp index 432d877..a1c1d1b 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -46,14 +46,22 @@ DateTime& DateTime::operator=(const DateTime& that) DateTime::DateTime(time_t t) { - GDateTime * gdt = g_date_time_new_from_unix_local(t); + auto gdt = g_date_time_new_from_unix_local(t); reset(gdt); g_date_time_unref(gdt); } DateTime DateTime::NowLocal() { - GDateTime * gdt = g_date_time_new_now_local(); + auto gdt = g_date_time_new_now_local(); + DateTime dt(gdt); + g_date_time_unref(gdt); + return dt; +} + +DateTime DateTime::Local(int year, int month, int day, int hour, int minute, int seconds) +{ + auto gdt = g_date_time_new_local (year, month, day, hour, minute, seconds); DateTime dt(gdt); g_date_time_unref(gdt); return dt; @@ -97,11 +105,26 @@ std::string DateTime::format(const std::string& fmt) const return ret; } +void DateTime::ymd(int& year, int& month, int& day) const +{ + g_date_time_get_ymd(get(), &year, &month, &day); +} + int DateTime::day_of_month() const { return g_date_time_get_day_of_month(get()); } +int DateTime::hour() const +{ + return g_date_time_get_hour(get()); +} + +int DateTime::minute() const +{ + return g_date_time_get_minute(get()); +} + double DateTime::seconds() const { return g_date_time_get_seconds(get()); diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp new file mode 100644 index 0000000..c557857 --- /dev/null +++ b/src/engine-eds.cpp @@ -0,0 +1,531 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include +#include +#include + +#include // std::sort() +#include +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/**** +***** +****/ + +class EdsEngine::Impl +{ +public: + + Impl(EdsEngine& owner): + m_owner(owner), + m_cancellable(g_cancellable_new()) + { + e_source_registry_new(m_cancellable, on_source_registry_ready, this); + } + + ~Impl() + { + g_cancellable_cancel(m_cancellable); + g_clear_object(&m_cancellable); + + while(!m_sources.empty()) + remove_source(*m_sources.begin()); + + if (m_rebuild_tag) + g_source_remove(m_rebuild_tag); + + if (m_source_registry) + g_signal_handlers_disconnect_by_data(m_source_registry, this); + g_clear_object(&m_source_registry); + } + + core::Signal<>& changed() + { + return m_changed; + } + + void get_appointments(const DateTime& begin, + const DateTime& end, + const Timezone& timezone, + std::function&)> func) + { + const auto begin_timet = begin.to_unix(); + const auto end_timet = end.to_unix(); + + const auto b_str = begin.format("%F %T"); + const auto e_str = end.format("%F %T"); + g_debug("getting all appointments from [%s ... %s]", b_str.c_str(), e_str.c_str()); + + /** + *** init the default timezone + **/ + + icaltimezone * default_timezone = nullptr; + const auto tz = timezone.timezone.get().c_str(); + if (tz && *tz) + { + default_timezone = icaltimezone_get_builtin_timezone(tz); + + if (default_timezone == nullptr) // maybe str is a tzid? + default_timezone = icaltimezone_get_builtin_timezone_from_tzid(tz); + + g_debug("default_timezone is %p", (void*)default_timezone); + } + + /** + *** walk through the sources to build the appointment list + **/ + + auto task_deleter = [](Task* task){ + // give the caller the (sorted) finished product + auto& a = task->appointments; + std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;}); + task->func(a); + // we're done; delete the task + g_debug("time to delete task %p", (void*)task); + delete task; + }; + + std::shared_ptr main_task(new Task(this, func), task_deleter); + + for (auto& kv : m_clients) + { + auto& client = kv.second; + if (default_timezone != nullptr) + e_cal_client_set_default_timezone(client, default_timezone); + + // start a new subtask to enumerate all the components in this client. + auto& source = kv.first; + 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); + e_cal_client_generate_instances(client, + begin_timet, + end_timet, + m_cancellable, + my_get_appointments_foreach, + new AppointmentSubtask (main_task, client, color), + [](gpointer g){delete static_cast(g);}); + } + } + +private: + + void set_dirty_now() + { + m_changed(); + } + + static gboolean set_dirty_now_static (gpointer gself) + { + auto self = static_cast(gself); + self->m_rebuild_tag = 0; + self->set_dirty_now(); + return G_SOURCE_REMOVE; + } + + void set_dirty_soon() + { + static const int ARBITRARY_BATCH_MSEC = 200; + + if (m_rebuild_tag == 0) + m_rebuild_tag = g_timeout_add(ARBITRARY_BATCH_MSEC, set_dirty_now_static, this); + } + + static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself) + { + GError * error = nullptr; + auto r = e_source_registry_new_finish(res, &error); + if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("indicator-datetime cannot show EDS appointments: %s", error->message); + + g_error_free(error); + } + else + { + g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), gself); + g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), gself); + g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), gself); + g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself); + g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), gself); + + auto self = static_cast(gself); + self->m_source_registry = r; + self->add_sources_by_extension(E_SOURCE_EXTENSION_CALENDAR); + self->add_sources_by_extension(E_SOURCE_EXTENSION_TASK_LIST); + } + } + + void add_sources_by_extension(const char* extension) + { + auto& r = m_source_registry; + auto sources = e_source_registry_list_sources(r, extension); + for (auto l=sources; l!=nullptr; l=l->next) + on_source_added(r, E_SOURCE(l->data), this); + g_list_free_full(sources, g_object_unref); + } + + static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself) + { + auto self = static_cast(gself); + + self->m_sources.insert(E_SOURCE(g_object_ref(source))); + + if (e_source_get_enabled(source)) + on_source_enabled(registry, source, gself); + } + + static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + auto self = static_cast(gself); + ECalClientSourceType source_type; + bool client_wanted = false; + + if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR)) + { + source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; + client_wanted = true; + } + else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST)) + { + source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; + client_wanted = true; + } + + const auto source_uid = e_source_get_uid(source); + if (client_wanted) + { + g_debug("%s connecting a client to source %s", G_STRFUNC, source_uid); + e_cal_client_connect(source, + source_type, + self->m_cancellable, + on_client_connected, + gself); + } + else + { + g_debug("%s not using source %s -- no tasks/calendar", G_STRFUNC, source_uid); + } + } + + static void on_client_connected(GObject* /*source*/, GAsyncResult * res, gpointer gself) + { + GError * error = nullptr; + EClient * client = e_cal_client_connect_finish(res, &error); + if (error) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("indicator-datetime cannot connect to EDS source: %s", error->message); + + g_error_free(error); + } + else + { + // add the client to our collection + auto self = static_cast(gself); + g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client))); + self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client); + + // now create a view for it so that we can listen for changes + e_cal_client_get_view (E_CAL_CLIENT(client), + "#t", // match all + self->m_cancellable, + on_client_view_ready, + self); + + g_debug("client connected; calling set_dirty_soon()"); + self->set_dirty_soon(); + } + } + + static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself) + { + GError* error = nullptr; + ECalClientView* view = nullptr; + + if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error)) + { + // add the view to our collection + e_cal_client_view_start(view, &error); + g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client))); + auto self = static_cast(gself); + self->m_views[e_client_get_source(E_CLIENT(client))] = view; + + g_signal_connect(view, "objects-added", G_CALLBACK(on_view_objects_added), self); + g_signal_connect(view, "objects-modified", G_CALLBACK(on_view_objects_modified), self); + g_signal_connect(view, "objects-removed", G_CALLBACK(on_view_objects_removed), self); + g_debug("view connected; calling set_dirty_soon()"); + self->set_dirty_soon(); + } + else if(error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("indicator-datetime cannot get View to EDS client: %s", error->message); + + g_error_free(error); + } + } + + static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) + { + g_debug("%s", G_STRFUNC); + static_cast(gself)->set_dirty_soon(); + } + static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) + { + g_debug("%s", G_STRFUNC); + static_cast(gself)->set_dirty_soon(); + } + static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) + { + g_debug("%s", G_STRFUNC); + static_cast(gself)->set_dirty_soon(); + } + + static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + static_cast(gself)->disable_source(source); + } + void disable_source(ESource* source) + { + // if an ECalClientView is associated with this source, remove it + auto vit = m_views.find(source); + if (vit != m_views.end()) + { + auto& view = vit->second; + e_cal_client_view_stop(view, nullptr); + const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this); + g_warn_if_fail(n_disconnected == 3); + g_object_unref(view); + m_views.erase(vit); + set_dirty_soon(); + } + + // if an ECalClient is associated with this source, remove it + auto cit = m_clients.find(source); + if (cit != m_clients.end()) + { + auto& client = cit->second; + g_object_unref(client); + m_clients.erase(cit); + set_dirty_soon(); + } + } + + static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + static_cast(gself)->remove_source(source); + } + void remove_source(ESource* source) + { + disable_source(source); + + auto sit = m_sources.find(source); + if (sit != m_sources.end()) + { + g_object_unref(*sit); + m_sources.erase(sit); + set_dirty_soon(); + } + } + + static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself) + { + g_debug("source changed; calling set_dirty_soon()"); + static_cast(gself)->set_dirty_soon(); + } + +private: + + typedef std::function&)> appointment_func; + + struct Task + { + Impl* p; + appointment_func func; + std::vector appointments; + Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {} + }; + + struct AppointmentSubtask + { + std::shared_ptr task; + ECalClient* client; + std::string color; + AppointmentSubtask(const std::shared_ptr& task_in, ECalClient* client_in, const char* color_in): + task(task_in), client(client_in) + { + if (color_in) + color = color_in; + } + }; + + struct UrlSubtask + { + std::shared_ptr task; + Appointment appointment; + UrlSubtask(const std::shared_ptr& task_in, const Appointment& appointment_in): + task(task_in), appointment(appointment_in) {} + }; + + static gboolean + my_get_appointments_foreach(ECalComponent* component, + time_t begin, + time_t end, + gpointer gsubtask) + { + const auto vtype = e_cal_component_get_vtype(component); + auto subtask = static_cast(gsubtask); + + if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO)) + { + const gchar* uid = nullptr; + e_cal_component_get_uid(component, &uid); + + auto status = ICAL_STATUS_NONE; + e_cal_component_get_status(component, &status); + + if ((uid != nullptr) && + (status != ICAL_STATUS_COMPLETED) && + (status != ICAL_STATUS_CANCELLED)) + { + Appointment appointment; + + /* Determine whether this is a recurring event. + NB: icalrecurrencetype supports complex recurrence patterns; + however, since design only allows daily recurrence, + that's all we support here. */ + GSList * recur_list; + e_cal_component_get_rrule_list(component, &recur_list); + for (auto l=recur_list; l!=nullptr; l=l->next) + { + const auto recur = static_cast(l->data); + appointment.is_daily |= ((recur->freq == ICAL_DAILY_RECURRENCE) + && (recur->interval == 1)); + } + e_cal_component_free_recur_list(recur_list); + + ECalComponentText text; + text.value = nullptr; + e_cal_component_get_summary(component, &text); + if (text.value) + appointment.summary = text.value; + + appointment.begin = DateTime(begin); + appointment.end = DateTime(end); + appointment.color = subtask->color; + appointment.is_event = vtype == E_CAL_COMPONENT_EVENT; + appointment.uid = uid; + + GList * alarm_uids = e_cal_component_get_alarm_uids(component); + appointment.has_alarms = alarm_uids != nullptr; + cal_obj_uid_list_free(alarm_uids); + + e_cal_client_get_attachment_uris(subtask->client, + uid, + nullptr, + subtask->task->p->m_cancellable, + on_appointment_uris_ready, + new UrlSubtask(subtask->task, appointment)); + } + } + + return G_SOURCE_CONTINUE; + } + + static void on_appointment_uris_ready(GObject* client, GAsyncResult* res, gpointer gsubtask) + { + auto subtask = static_cast(gsubtask); + + GSList * uris = nullptr; + GError * error = nullptr; + e_cal_client_get_attachment_uris_finish(E_CAL_CLIENT(client), res, &uris, &error); + if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + !g_error_matches(error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED)) + { + g_warning("Error getting appointment uris: %s", error->message); + } + + g_error_free(error); + } + else if (uris != nullptr) + { + subtask->appointment.url = (const char*) uris->data; // copy the first URL + g_debug("found url '%s' for appointment '%s'", subtask->appointment.url.c_str(), subtask->appointment.summary.c_str()); + e_client_util_free_string_slist(uris); + } + + g_debug("adding appointment '%s' '%s'", subtask->appointment.summary.c_str(), subtask->appointment.url.c_str()); + subtask->task->appointments.push_back(subtask->appointment); + delete subtask; + } + + EdsEngine& m_owner; + core::Signal<> m_changed; + std::set m_sources; + std::map m_clients; + std::map m_views; + GCancellable* m_cancellable = nullptr; + ESourceRegistry* m_source_registry = nullptr; + guint m_rebuild_tag = 0; +}; + +/*** +**** +***/ + +EdsEngine::EdsEngine(): + p(new Impl(*this)) +{ +} + +EdsEngine::~EdsEngine() =default; + +core::Signal<>& EdsEngine::changed() +{ + return p->changed(); +} + +void EdsEngine::get_appointments(const DateTime& begin, + const DateTime& end, + const Timezone& tz, + std::function&)> func) +{ + p->get_appointments(begin, end, tz, func); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/main.cpp b/src/main.cpp index 24a9409..3e16555 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -58,16 +59,20 @@ main(int /*argc*/, char** /*argv*/) std::shared_ptr live_timezones(new LiveTimezones(live_settings, TIMEZONE_FILE)); std::shared_ptr live_clock(new LiveClock(live_timezones)); std::shared_ptr file_timezone(new FileTimezone(TIMEZONE_FILE)); + std::shared_ptr eds_engine (new EdsEngine); + const auto now = live_clock->localtime(); state->settings = live_settings; state->clock = live_clock; state->locations.reset(new SettingsLocations(live_settings, live_timezones)); - state->planner.reset(new PlannerEds(live_clock, file_timezone)); - state->planner->time = live_clock->localtime(); + auto calendar_month = new MonthPlanner(std::shared_ptr(new EdsPlanner(eds_engine, file_timezone)), now); + state->calendar_month.reset(calendar_month); + state->calendar_upcoming.reset(new UpcomingPlanner(std::shared_ptr(new EdsPlanner(eds_engine, file_timezone)), now)); std::shared_ptr actions(new LiveActions(state)); MenuFactory factory(actions, state); // snap decisions - ClockWatcherImpl clock_watcher(state); + std::shared_ptr upcoming_planner(new UpcomingPlanner(std::shared_ptr(new EdsPlanner (eds_engine, file_timezone)), now)); + ClockWatcherImpl clock_watcher(live_clock, upcoming_planner); Snap snap; clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){ auto snap_show = [](const Appointment& a){ diff --git a/src/menu.cpp b/src/menu.cpp index 797757f..d6abd27 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -104,7 +104,7 @@ protected: m_state->settings->show_events.changed().connect([this](bool){ update_section(Appointments); // showing events got toggled }); - m_state->planner->upcoming.changed().connect([this](const std::vector&){ + m_state->calendar_upcoming->appointments().changed().connect([this](const std::vector&){ update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time }); m_state->clock->date_changed.connect([this](){ @@ -138,12 +138,18 @@ protected: void update_upcoming() { + // show upcoming appointments that occur after "calendar_next_minute", + // where that is the wallclock time on the specified calendar day + const auto calendar_day = m_state->calendar_month->month().get(); const auto now = m_state->clock->localtime(); - const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds()); + int y, m, d; + calendar_day.ymd(y, m, d); + const auto calendar_now = DateTime::Local(y, m, d, now.hour(), now.minute(), now.seconds()); + const auto calendar_next_minute = calendar_now.add_full(0, 0, 0, 0, 1, -now.seconds()); std::vector upcoming; - for(const auto& a : m_state->planner->upcoming.get()) - if (next_minute <= a.begin) + for(const auto& a : m_state->calendar_upcoming->appointments().get()) + if (calendar_next_minute <= a.begin) upcoming.push_back(a); if (m_upcoming != upcoming) diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp index f47c2e9..f3f28ee 100644 --- a/src/planner-eds.cpp +++ b/src/planner-eds.cpp @@ -18,550 +18,49 @@ */ #include - -#include - -#include -#include -#include -#include - -#include // std::sort() -#include -#include +#include namespace unity { namespace indicator { namespace datetime { -/**** -***** -****/ +/*** +**** +***/ -class PlannerEds::Impl +EdsPlanner::EdsPlanner(const std::shared_ptr& engine, + const std::shared_ptr& timezone): + m_engine(engine), + m_timezone(timezone) { -public: - - Impl(PlannerEds& owner, - const std::shared_ptr& clock, - const std::shared_ptr& timezone): - m_owner(owner), - m_clock(clock), - m_timezone(timezone), - m_cancellable(g_cancellable_new()) - { - e_source_registry_new(m_cancellable, on_source_registry_ready, this); - - m_clock->minute_changed.connect([this](){ - g_debug("rebuilding upcoming because the clock's minute_changed"); - rebuild_soon(UPCOMING); - }); - - m_owner.time.changed().connect([this](const DateTime& dt) { - g_debug("planner's datetime property changed to %s; calling rebuild_soon()", dt.format("%F %T").c_str()); - rebuild_soon(MONTH); - }); - - rebuild_soon(ALL); - } - - ~Impl() - { - g_cancellable_cancel(m_cancellable); - g_clear_object(&m_cancellable); - - while(!m_sources.empty()) - remove_source(*m_sources.begin()); - - if (m_rebuild_tag) - g_source_remove(m_rebuild_tag); - - if (m_source_registry) - g_signal_handlers_disconnect_by_data(m_source_registry, this); - g_clear_object(&m_source_registry); - } - -private: - - static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself) - { - GError * error = nullptr; - auto r = e_source_registry_new_finish(res, &error); - if (error != nullptr) - { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning("indicator-datetime cannot show EDS appointments: %s", error->message); - - g_error_free(error); - } - else - { - g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), gself); - g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), gself); - g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), gself); - g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself); - g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), gself); - - auto self = static_cast(gself); - self->m_source_registry = r; - self->add_sources_by_extension(E_SOURCE_EXTENSION_CALENDAR); - self->add_sources_by_extension(E_SOURCE_EXTENSION_TASK_LIST); - } - } - - void add_sources_by_extension(const char* extension) - { - auto& r = m_source_registry; - auto sources = e_source_registry_list_sources(r, extension); - for (auto l=sources; l!=nullptr; l=l->next) - on_source_added(r, E_SOURCE(l->data), this); - g_list_free_full(sources, g_object_unref); - } - - static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself) - { - auto self = static_cast(gself); - - self->m_sources.insert(E_SOURCE(g_object_ref(source))); - - if (e_source_get_enabled(source)) - on_source_enabled(registry, source, gself); - } - - static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) - { - auto self = static_cast(gself); - ECalClientSourceType source_type; - bool client_wanted = false; - - if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR)) - { - source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; - client_wanted = true; - } - else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST)) - { - source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; - client_wanted = true; - } - - const auto source_uid = e_source_get_uid(source); - if (client_wanted) - { - g_debug("%s connecting a client to source %s", G_STRFUNC, source_uid); - e_cal_client_connect(source, - source_type, - self->m_cancellable, - on_client_connected, - gself); - } - else - { - g_debug("%s not using source %s -- no tasks/calendar", G_STRFUNC, source_uid); - } - } - - static void on_client_connected(GObject* /*source*/, GAsyncResult * res, gpointer gself) - { - GError * error = nullptr; - EClient * client = e_cal_client_connect_finish(res, &error); - if (error) - { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning("indicator-datetime cannot connect to EDS source: %s", error->message); - - g_error_free(error); - } - else - { - // add the client to our collection - auto self = static_cast(gself); - g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client))); - self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client); - - // now create a view for it so that we can listen for changes - e_cal_client_get_view (E_CAL_CLIENT(client), - "#t", // match all - self->m_cancellable, - on_client_view_ready, - self); - - g_debug("client connected; calling rebuild_soon()"); - self->rebuild_soon(ALL); - } - } - - static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself) - { - GError* error = nullptr; - ECalClientView* view = nullptr; - - if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error)) - { - // add the view to our collection - e_cal_client_view_start(view, &error); - g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client))); - auto self = static_cast(gself); - self->m_views[e_client_get_source(E_CLIENT(client))] = view; - - g_signal_connect(view, "objects-added", G_CALLBACK(on_view_objects_added), self); - g_signal_connect(view, "objects-modified", G_CALLBACK(on_view_objects_modified), self); - g_signal_connect(view, "objects-removed", G_CALLBACK(on_view_objects_removed), self); - g_debug("view connected; calling rebuild_soon()"); - self->rebuild_soon(ALL); - } - else if(error != nullptr) - { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning("indicator-datetime cannot get View to EDS client: %s", error->message); - - g_error_free(error); - } - } - - static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) - { - g_debug("%s", G_STRFUNC); - static_cast(gself)->rebuild_soon(ALL); - } - static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) - { - g_debug("%s", G_STRFUNC); - static_cast(gself)->rebuild_soon(ALL); - } - static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) - { - g_debug("%s", G_STRFUNC); - static_cast(gself)->rebuild_soon(ALL); - } - - static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) - { - static_cast(gself)->disable_source(source); - } - void disable_source(ESource* source) - { - // if an ECalClientView is associated with this source, remove it - auto vit = m_views.find(source); - if (vit != m_views.end()) - { - auto& view = vit->second; - e_cal_client_view_stop(view, nullptr); - const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this); - g_warn_if_fail(n_disconnected == 3); - g_object_unref(view); - m_views.erase(vit); - rebuild_soon(ALL); - } - - // if an ECalClient is associated with this source, remove it - auto cit = m_clients.find(source); - if (cit != m_clients.end()) - { - auto& client = cit->second; - g_object_unref(client); - m_clients.erase(cit); - rebuild_soon(ALL); - } - } - - static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) - { - static_cast(gself)->remove_source(source); - } - void remove_source(ESource* source) - { - disable_source(source); - - auto sit = m_sources.find(source); - if (sit != m_sources.end()) - { - g_object_unref(*sit); - m_sources.erase(sit); - rebuild_soon(ALL); - } - } - - static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself) - { - g_debug("source changed; calling rebuild_soon()"); - static_cast(gself)->rebuild_soon(ALL); - } - -private: - - typedef std::function&)> appointment_func; - - struct Task - { - Impl* p; - appointment_func func; - std::vector appointments; - Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {} - }; - - struct AppointmentSubtask - { - std::shared_ptr task; - ECalClient* client; - std::string color; - AppointmentSubtask(const std::shared_ptr& task_in, ECalClient* client_in, const char* color_in): - task(task_in), client(client_in) - { - if (color_in) - color = color_in; - } - }; - - void rebuild_soon(int rebuild_flags) - { - static const guint ARBITRARY_INTERVAL_SECS = 2; - - m_rebuild_flags |= rebuild_flags; - - if (m_rebuild_tag == 0) - m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuild_now_static, this); - } - - static gboolean rebuild_now_static(gpointer gself) - { - auto self = static_cast(gself); - const auto flags = self->m_rebuild_flags; - self->m_rebuild_tag = 0; - self->m_rebuild_flags = 0; - self->rebuild_now(flags); - return G_SOURCE_REMOVE; - } - - void rebuild_now(int rebuild_flags) - { - if (rebuild_flags & UPCOMING) - rebuild_upcoming(); - - if (rebuild_flags & MONTH) - rebuild_month(); - } - - void rebuild_month() - { - const auto ref = m_owner.time.get().get(); - auto month_begin = g_date_time_add_full(ref, - 0, // subtract no years - 0, // subtract no months - -(g_date_time_get_day_of_month(ref)-1), - -g_date_time_get_hour(ref), - -g_date_time_get_minute(ref), - -g_date_time_get_seconds(ref)); - auto month_end = g_date_time_add_full(month_begin, 0, 1, 0, 0, 0, -0.1); - - get_appointments(month_begin, month_end, [this](const std::vector& appointments) { - g_debug("got %d appointments in this calendar month", (int)appointments.size()); - m_owner.this_month.set(appointments); - }); - - g_date_time_unref(month_end); - g_date_time_unref(month_begin); - } - - void rebuild_upcoming() - { - const auto ref = m_clock->localtime(); - const auto begin = g_date_time_add_days(ref.get(),-1); - const auto end = g_date_time_add_months(begin,1); - - get_appointments(begin, end, [this](const std::vector& appointments) { - g_debug("got %d upcoming appointments", (int)appointments.size()); - m_owner.upcoming.set(appointments); - }); - - g_date_time_unref(end); - g_date_time_unref(begin); - } - - void get_appointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func) - { - const auto begin = g_date_time_to_unix(begin_dt); - const auto end = g_date_time_to_unix(end_dt); - - auto begin_str = g_date_time_format(begin_dt, "%F %T"); - auto end_str = g_date_time_format(end_dt, "%F %T"); - g_debug("getting all appointments from [%s ... %s]", begin_str, end_str); - g_free(begin_str); - g_free(end_str); - - /** - *** init the default timezone - **/ - - icaltimezone * default_timezone = nullptr; - const auto tz = m_timezone->timezone.get().c_str(); - if (tz && *tz) - { - default_timezone = icaltimezone_get_builtin_timezone(tz); - - if (default_timezone == nullptr) // maybe str is a tzid? - default_timezone = icaltimezone_get_builtin_timezone_from_tzid(tz); - - g_debug("default_timezone is %p", (void*)default_timezone); - } + m_engine->changed().connect([this](){ + g_debug("EdsPlanner %p rebuilding soon because EdsEngine %p emitted 'changed' signal%p", this, m_engine.get()); + rebuild_soon(); + }); +} - /** - *** walk through the sources to build the appointment list - **/ +EdsPlanner::~EdsPlanner() =default; - auto task_deleter = [](Task* task){ - // give the caller the (sorted) finished product - auto& a = task->appointments; - std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;}); - task->func(a); - // we're done; delete the task - g_debug("time to delete task %p", (void*)task); - delete task; - }; - - std::shared_ptr main_task(new Task(this, func), task_deleter); - - for (auto& kv : m_clients) - { - auto& client = kv.second; - if (default_timezone != nullptr) - e_cal_client_set_default_timezone(client, default_timezone); - - // start a new subtask to enumerate all the components in this client. - auto& source = kv.first; - 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); - e_cal_client_generate_instances(client, - begin, - end, - m_cancellable, - my_get_appointments_foreach, - new AppointmentSubtask (main_task, client, color), - [](gpointer g){delete static_cast(g);}); - } - } +void EdsPlanner::rebuild_now() +{ + const auto& r = range().get(); - struct UrlSubtask - { - std::shared_ptr task; - Appointment appointment; - UrlSubtask(const std::shared_ptr& task_in, const Appointment& appointment_in): - task(task_in), appointment(appointment_in) {} + auto on_appointments_fetched = [this](const std::vector& a){ + g_debug("EdsPlanner %p got %zu appointments", this, a.size()); + m_appointments.set(a); }; - static gboolean - my_get_appointments_foreach(ECalComponent* component, - time_t begin, - time_t end, - gpointer gsubtask) - { - const auto vtype = e_cal_component_get_vtype(component); - auto subtask = static_cast(gsubtask); - - if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO)) - { - const gchar* uid = nullptr; - e_cal_component_get_uid(component, &uid); - - auto status = ICAL_STATUS_NONE; - e_cal_component_get_status(component, &status); - - if ((uid != nullptr) && - (status != ICAL_STATUS_COMPLETED) && - (status != ICAL_STATUS_CANCELLED)) - { - Appointment appointment; - - /* Determine whether this is a recurring event. - NB: icalrecurrencetype supports complex recurrence patterns; - however, since design only allows daily recurrence, - that's all we support here. */ - GSList * recur_list; - e_cal_component_get_rrule_list(component, &recur_list); - for (auto l=recur_list; l!=nullptr; l=l->next) - { - const auto recur = static_cast(l->data); - appointment.is_daily |= ((recur->freq == ICAL_DAILY_RECURRENCE) - && (recur->interval == 1)); - } - e_cal_component_free_recur_list(recur_list); - - ECalComponentText text; - text.value = nullptr; - e_cal_component_get_summary(component, &text); - if (text.value) - appointment.summary = text.value; - - appointment.begin = DateTime(begin); - appointment.end = DateTime(end); - appointment.color = subtask->color; - appointment.is_event = vtype == E_CAL_COMPONENT_EVENT; - appointment.uid = uid; + m_engine->get_appointments(r.first, r.second, *m_timezone.get(), on_appointments_fetched); +} - GList * alarm_uids = e_cal_component_get_alarm_uids(component); - appointment.has_alarms = alarm_uids != nullptr; - cal_obj_uid_list_free(alarm_uids); - - e_cal_client_get_attachment_uris(subtask->client, - uid, - nullptr, - subtask->task->p->m_cancellable, - on_appointment_uris_ready, - new UrlSubtask(subtask->task, appointment)); - } - } - - return G_SOURCE_CONTINUE; - } - - static void on_appointment_uris_ready(GObject* client, GAsyncResult* res, gpointer gsubtask) - { - auto subtask = static_cast(gsubtask); - - GSList * uris = nullptr; - GError * error = nullptr; - e_cal_client_get_attachment_uris_finish(E_CAL_CLIENT(client), res, &uris, &error); - if (error != nullptr) - { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && - !g_error_matches(error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED)) - { - g_warning("Error getting appointment uris: %s", error->message); - } - - g_error_free(error); - } - else if (uris != nullptr) - { - subtask->appointment.url = (const char*) uris->data; // copy the first URL - g_debug("found url '%s' for appointment '%s'", subtask->appointment.url.c_str(), subtask->appointment.summary.c_str()); - e_client_util_free_string_slist(uris); - } - - g_debug("adding appointment '%s' '%s'", subtask->appointment.summary.c_str(), subtask->appointment.url.c_str()); - subtask->task->appointments.push_back(subtask->appointment); - delete subtask; - } - - PlannerEds& m_owner; - std::shared_ptr m_clock; - std::shared_ptr m_timezone; - std::set m_sources; - std::map m_clients; - std::map m_views; - GCancellable* m_cancellable = nullptr; - ESourceRegistry* m_source_registry = nullptr; - guint m_rebuild_tag = 0; - guint m_rebuild_flags = 0; - enum { UPCOMING=(1<<0), MONTH=(1<<1), ALL=UPCOMING|MONTH }; -}; - -PlannerEds::PlannerEds(const std::shared_ptr& clock, - const std::shared_ptr& timezone): - p(new Impl(*this, clock, timezone)) {} +core::Property>& EdsPlanner::appointments() +{ + return m_appointments; +} -PlannerEds::~PlannerEds() =default; +/*** +**** +***/ } // namespace datetime } // namespace indicator diff --git a/src/planner-month.cpp b/src/planner-month.cpp new file mode 100644 index 0000000..5920daa --- /dev/null +++ b/src/planner-month.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +MonthPlanner::MonthPlanner(const std::shared_ptr& range_planner, + const DateTime& month_in): + m_range_planner(range_planner) +{ + month().changed().connect([this](const DateTime& m){ + auto month_begin = m.add_full(0, // no years + 0, // no months + -(m.day_of_month()-1), + -m.hour(), + -m.minute(), + -m.seconds()); + auto month_end = month_begin.add_full(0, 1, 0, 0, 0, -0.1); + g_debug("PlannerMonth %p setting calendar month range: [%s..%s]", this, month_begin.format("%F %T").c_str(), month_end.format("%F %T").c_str()); + m_range_planner->range().set(std::pair(month_begin,month_end)); + }); + + month().set(month_in); +} + +core::Property& MonthPlanner::month() +{ + return m_month; +} + +core::Property>& MonthPlanner::appointments() +{ + return m_range_planner->appointments(); +} + + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/planner-range.cpp b/src/planner-range.cpp new file mode 100644 index 0000000..13a1492 --- /dev/null +++ b/src/planner-range.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +RangePlanner::RangePlanner(): + m_range(std::pair(DateTime::NowLocal(), DateTime::NowLocal())) +{ + range().changed().connect([this](const std::pair&){ + g_debug("rebuilding because the date range changed"); + rebuild_soon(); + }); +} + +RangePlanner::~RangePlanner() +{ + if (m_rebuild_tag) + g_source_remove(m_rebuild_tag); +} + +void RangePlanner::rebuild_soon() +{ + static const int ARBITRARY_BATCH_MSEC = 200; + + if (m_rebuild_tag == 0) + m_rebuild_tag = g_timeout_add(ARBITRARY_BATCH_MSEC, rebuild_now_static, this); +} + +gboolean RangePlanner::rebuild_now_static(gpointer gself) +{ + auto self = static_cast(gself); + self->m_rebuild_tag = 0; + self->rebuild_now(); + return G_SOURCE_REMOVE; +} + +core::Property>& RangePlanner::range() +{ + return m_range; +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/planner-upcoming.cpp b/src/planner-upcoming.cpp new file mode 100644 index 0000000..4e5af6f --- /dev/null +++ b/src/planner-upcoming.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +UpcomingPlanner::UpcomingPlanner(const std::shared_ptr& range_planner, + const DateTime& date_in): + m_range_planner(range_planner) +{ + date().changed().connect([this](const DateTime& dt){ + // set the range to the upcoming month + const auto b = dt.add_full(0, 0, -1, 0, 0, 0); + const auto e = dt.add_full(0, 1, 0, 0, 0, 0); + g_debug("%p setting date range to [%s..%s]", this, b.format("%F %T").c_str(), e.format("%F %T").c_str()); + m_range_planner->range().set(std::pair(b,e)); + }); + + date().set(date_in); +} + +core::Property& UpcomingPlanner::date() +{ + return m_date; +} + +core::Property>& UpcomingPlanner::appointments() +{ + return m_range_planner->appointments(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/tests/planner-mock.h b/tests/planner-mock.h index 44d30c7..67e550c 100644 --- a/tests/planner-mock.h +++ b/tests/planner-mock.h @@ -20,12 +20,13 @@ #ifndef INDICATOR_DATETIME_PLANNER_MOCK_H #define INDICATOR_DATETIME_PLANNER_MOCK_H -#include +#include namespace unity { namespace indicator { namespace datetime { +#if 0 /** * \brief Planner which does nothing on its own. * It requires its client must set its appointments property. @@ -35,7 +36,31 @@ class MockPlanner: public Planner public: MockPlanner() =default; virtual ~MockPlanner() =default; + core::Property>& appointments() { return m_appointments; } + +private: + core::Property> m_appointments; +}; +#endif + +/** + * \brief #RangePlanner which does nothing on its own. + * Its controller must set its appointments property. + */ +class MockRangePlanner: public RangePlanner +{ +public: + MockRangePlanner() =default; + ~MockRangePlanner() =default; + core::Property>& appointments() { return m_appointments; } + +protected: + void rebuild_now(){} + +private: + core::Property> m_appointments; }; + } // namespace datetime } // namespace indicator diff --git a/tests/state-mock.h b/tests/state-mock.h index 721b82f..792c60d 100644 --- a/tests/state-mock.h +++ b/tests/state-mock.h @@ -28,15 +28,21 @@ class MockState: public State { public: std::shared_ptr mock_clock; + std::shared_ptr mock_range_planner; MockState() { const DateTime now = DateTime::NowLocal(); mock_clock.reset(new MockClock(now)); - settings.reset(new Settings); clock = std::dynamic_pointer_cast(mock_clock); - planner.reset(new MockPlanner); - planner->time = now; + + settings.reset(new Settings); + + mock_range_planner.reset(new MockRangePlanner); + auto range_planner = std::dynamic_pointer_cast(mock_range_planner); + calendar_month.reset(new MonthPlanner(range_planner, now)); + calendar_upcoming.reset(new UpcomingPlanner(range_planner, now)); + locations.reset(new Locations); } }; diff --git a/tests/test-actions.cpp b/tests/test-actions.cpp index 1865cfd..5d1efd5 100644 --- a/tests/test-actions.cpp +++ b/tests/test-actions.cpp @@ -150,10 +150,10 @@ TEST_F(ActionsFixture, SetCalendarDate) // confirm that Planner.time gets changed to that date when we // activate the 'calendar' action with that date's time_t as the arg - EXPECT_NE (now, m_state->planner->time.get()); + EXPECT_NE (now, m_state->calendar_month->month().get()); auto v = g_variant_new_int64(now.to_unix()); g_action_group_activate_action (action_group, action_name, v); - EXPECT_EQ (now, m_state->planner->time.get()); + EXPECT_EQ (now, m_state->calendar_month->month().get()); } TEST_F(ActionsFixture, ActivatingTheCalendarResetsItsDate) @@ -171,11 +171,12 @@ TEST_F(ActionsFixture, ActivatingTheCalendarResetsItsDate) const auto now = m_state->clock->localtime(); auto next_week = g_date_time_add_weeks(now.get(), 1); const auto next_week_unix = g_date_time_to_unix(next_week); + g_date_time_unref(next_week); g_action_group_activate_action (action_group, "calendar", g_variant_new_int64(next_week_unix)); // confirm the planner and calendar action state moved a week into the future // but that m_state->clock is unchanged - EXPECT_EQ(next_week_unix, m_state->planner->time.get().to_unix()); + EXPECT_EQ(next_week_unix, m_state->calendar_month->month().get().to_unix()); EXPECT_EQ(now, m_state->clock->localtime()); auto calendar_state = g_action_group_get_action_state(action_group, "calendar"); EXPECT_TRUE(calendar_state != nullptr); @@ -196,7 +197,7 @@ TEST_F(ActionsFixture, ActivatingTheCalendarResetsItsDate) g_action_group_change_action_state(action_group, "calendar-active", g_variant_new_boolean(true)); // confirm the planner and calendar action state were reset back to m_state->clock's time - EXPECT_EQ(now.to_unix(), m_state->planner->time.get().to_unix()); + EXPECT_EQ(now.to_unix(), m_state->calendar_month->month().get().to_unix()); EXPECT_EQ(now, m_state->clock->localtime()); calendar_state = g_action_group_get_action_state(action_group, "calendar"); EXPECT_TRUE(calendar_state != nullptr); @@ -215,7 +216,8 @@ TEST_F(ActionsFixture, OpenAppointment) Appointment appt; appt.uid = "some arbitrary uid"; appt.url = "http://www.canonical.com/"; - m_state->planner->upcoming.set(std::vector({appt})); + appt.begin = m_state->clock->localtime(); + m_state->calendar_upcoming->appointments().set(std::vector({appt})); const auto action_name = "activate-appointment"; auto action_group = m_actions->action_group(); diff --git a/tests/test-clock-watcher.cpp b/tests/test-clock-watcher.cpp index 79b8485..2425fe8 100644 --- a/tests/test-clock-watcher.cpp +++ b/tests/test-clock-watcher.cpp @@ -35,12 +35,16 @@ protected: std::vector m_triggered; std::unique_ptr m_watcher; + std::shared_ptr m_range_planner; + std::shared_ptr m_upcoming; void SetUp() { super::SetUp(); - m_watcher.reset(new ClockWatcherImpl(m_state)); + m_range_planner.reset(new MockRangePlanner); + m_upcoming.reset(new UpcomingPlanner(m_range_planner, m_state->clock->localtime())); + m_watcher.reset(new ClockWatcherImpl(m_state->clock, m_upcoming)); m_watcher->alarm_reached().connect([this](const Appointment& appt){ m_triggered.push_back(appt.uid); }); @@ -52,6 +56,8 @@ protected: { m_triggered.clear(); m_watcher.reset(); + m_upcoming.reset(); + m_range_planner.reset(); super::TearDown(); } @@ -108,7 +114,7 @@ TEST_F(ClockWatcherFixture, AppointmentsChanged) // One of these matches our state's localtime, so that should get triggered. std::vector a = build_some_appointments(); a[0].begin = m_state->clock->localtime(); - m_state->planner->upcoming.set(a); + m_range_planner->appointments().set(a); // Confirm that it got fired EXPECT_EQ(1, m_triggered.size()); @@ -121,10 +127,10 @@ TEST_F(ClockWatcherFixture, TimeChanged) // Add some appointments to the planner. // Neither of these match the state's localtime, so nothing should be triggered. std::vector a = build_some_appointments(); - m_state->planner->upcoming.set(a); + m_range_planner->appointments().set(a); EXPECT_TRUE(m_triggered.empty()); - // Set the state's clock to a time that matches one of the appointments. + // Set the state's clock to a time that matches one of the appointments(). // That appointment should get triggered. m_mock_state->mock_clock->set_localtime(a[1].begin); EXPECT_EQ(1, m_triggered.size()); @@ -137,7 +143,7 @@ TEST_F(ClockWatcherFixture, MoreThanOne) const auto now = m_state->clock->localtime(); std::vector a = build_some_appointments(); a[0].begin = a[1].begin = now; - m_state->planner->upcoming.set(a); + m_range_planner->appointments().set(a); EXPECT_EQ(2, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); @@ -153,14 +159,14 @@ TEST_F(ClockWatcherFixture, NoDuplicates) std::vector a; a.push_back(appointments[0]); a[0].begin = now; - m_state->planner->upcoming.set(a); + m_range_planner->appointments().set(a); EXPECT_EQ(1, m_triggered.size()); EXPECT_EQ(a[0].uid, m_triggered[0]); // Now change the appointment vector by adding one to it. // Confirm that the ClockWatcher doesn't re-trigger a[0] a.push_back(appointments[1]); - m_state->planner->upcoming.set(a); + m_range_planner->appointments().set(a); EXPECT_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 eab8596..ffba4ce 100644 --- a/tests/test-live-actions.cpp +++ b/tests/test-live-actions.cpp @@ -295,7 +295,8 @@ TEST_F(LiveActionsFixture, CalendarState) const DateTime now (tmp); g_date_time_unref (tmp); m_mock_state->mock_clock->set_localtime (now); - m_state->planner->time.set(now); + m_state->calendar_month->month().set(now); + //m_state->planner->time.set(now); /// /// Test the default calendar state. @@ -315,7 +316,7 @@ TEST_F(LiveActionsFixture, CalendarState) // calendar-day should be in sync with m_state->calendar_day v = g_variant_lookup_value (calendar_state, "calendar-day", G_VARIANT_TYPE_INT64); EXPECT_TRUE (v != nullptr); - EXPECT_EQ (m_state->planner->time.get().to_unix(), g_variant_get_int64(v)); + EXPECT_EQ (m_state->calendar_month->month().get().to_unix(), g_variant_get_int64(v)); g_clear_pointer (&v, g_variant_unref); // show-week-numbers should be false because MockSettings defaults everything to 0 @@ -356,7 +357,7 @@ TEST_F(LiveActionsFixture, CalendarState) a2.begin = next_begin; a2.end = next_end; - m_state->planner->this_month.set(std::vector({a1, a2})); + m_state->calendar_month->appointments().set(std::vector({a1, a2})); /// /// Now test the calendar state again. diff --git a/tests/test-menus.cpp b/tests/test-menus.cpp index 73d6036..61d1295 100644 --- a/tests/test-menus.cpp +++ b/tests/test-menus.cpp @@ -255,7 +255,7 @@ private: const std::vector& appointments) { // try adding a few appointments and see if the menu updates itself - m_state->planner->upcoming.set(appointments); + m_state->calendar_upcoming->appointments().set(appointments); wait_msec(); // wait a moment for the menu to update //auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU); @@ -285,7 +285,7 @@ private: // there should be an "add event" button even if there aren't any appointments std::vector appointments; m_state->settings->show_events.set(true); - m_state->planner->upcoming.set(appointments); + m_state->calendar_upcoming->appointments().set(appointments); wait_msec(); section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION); EXPECT_EQ(1, g_menu_model_get_n_items(section)); @@ -299,7 +299,7 @@ private: // try adding a few appointments and see if the menu updates itself appointments = build_some_appointments(); - m_state->planner->upcoming.set(appointments); + m_state->calendar_upcoming->appointments().set(appointments); wait_msec(); // wait a moment for the menu to update section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION); EXPECT_EQ(3, g_menu_model_get_n_items(section)); @@ -316,7 +316,7 @@ private: // clear all the appointments std::vector appointments; - m_state->planner->upcoming.set(appointments); + m_state->calendar_upcoming->appointments().set(appointments); wait_msec(); // wait a moment for the menu to update // check that there's a "clock app" menuitem even when there are no appointments @@ -332,7 +332,7 @@ private: // add some appointments and test them appointments = build_some_appointments(); - m_state->planner->upcoming.set(appointments); + m_state->calendar_upcoming->appointments().set(appointments); wait_msec(); // wait a moment for the menu to update section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION); EXPECT_EQ(3, g_menu_model_get_n_items(section)); diff --git a/tests/test-planner.cpp b/tests/test-planner.cpp index 6f602f4..4694cf5 100644 --- a/tests/test-planner.cpp +++ b/tests/test-planner.cpp @@ -37,27 +37,6 @@ using namespace unity::indicator::datetime; typedef GlibFixture PlannerFixture; -TEST_F(PlannerFixture, EDS) -{ - auto tmp = g_date_time_new_now_local(); - const auto now = DateTime(tmp); - g_date_time_unref(tmp); - - std::shared_ptr clock(new MockClock(now)); - std::shared_ptr tz(new MockTimezone); - PlannerEds planner(clock, tz); - wait_msec(100); - - planner.time.set(now); - wait_msec(2500); - - std::vector this_month = planner.this_month.get(); - std::cerr << this_month.size() << " appointments this month" << std::endl; - for(const auto& a : this_month) - std::cerr << a.summary << std::endl; -} - - TEST_F(PlannerFixture, HelloWorld) { auto halloween = g_date_time_new_local(2020, 10, 31, 18, 30, 59); -- cgit v1.2.3