aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/datetime/clock-watcher.h9
-rw-r--r--include/datetime/date-time.h5
-rw-r--r--include/datetime/engine-eds.h73
-rw-r--r--include/datetime/planner-eds.h25
-rw-r--r--include/datetime/planner-month.h56
-rw-r--r--include/datetime/planner-range.h62
-rw-r--r--include/datetime/planner-upcoming.h56
-rw-r--r--include/datetime/planner.h29
-rw-r--r--include/datetime/state.h14
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/actions-live.cpp6
-rw-r--r--src/actions.cpp13
-rw-r--r--src/clock-watcher.cpp26
-rw-r--r--src/date-time.cpp27
-rw-r--r--src/engine-eds.cpp531
-rw-r--r--src/main.cpp13
-rw-r--r--src/menu.cpp14
-rw-r--r--src/planner-eds.cpp555
-rw-r--r--src/planner-month.cpp66
-rw-r--r--src/planner-range.cpp72
-rw-r--r--src/planner-upcoming.cpp61
-rw-r--r--src/utils.c4
-rw-r--r--tests/planner-mock.h27
-rw-r--r--tests/state-mock.h12
-rw-r--r--tests/test-actions.cpp12
-rw-r--r--tests/test-clock-watcher.cpp20
-rw-r--r--tests/test-live-actions.cpp11
-rw-r--r--tests/test-menus.cpp10
-rw-r--r--tests/test-planner.cpp21
-rw-r--r--tests/timezone-mock.h40
30 files changed, 1236 insertions, 638 deletions
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 <datetime/state.h>
#include <datetime/appointment.h>
+#include <datetime/clock.h>
+#include <datetime/planner-upcoming.h>
#include <core/signal.h>
@@ -53,14 +54,16 @@ public:
class ClockWatcherImpl: public ClockWatcher
{
public:
- ClockWatcherImpl(const std::shared_ptr<const State>& state);
+ ClockWatcherImpl(const std::shared_ptr<Clock>& clock,
+ const std::shared_ptr<UpcomingPlanner>& upcoming_planner);
~ClockWatcherImpl() =default;
core::Signal<const Appointment&>& alarm_reached();
private:
void pulse();
std::set<std::string> m_triggered;
- std::shared_ptr<const State> m_state;
+ const std::shared_ptr<Clock> m_clock;
+ const std::shared_ptr<UpcomingPlanner> m_upcoming_planner;
core::Signal<const Appointment&> 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_ENGINE_EDS__H
+#define INDICATOR_DATETIME_ENGINE_EDS__H
+
+#include <datetime/appointment.h>
+#include <datetime/date-time.h>
+#include <datetime/timezone.h>
+
+#include <functional>
+#include <vector>
+
+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<void(const std::vector<Appointment>&)> appointment_func);
+
+ core::Signal<>& changed();
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> 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 a99f611..95b5d79 100644
--- a/include/datetime/planner-eds.h
+++ b/include/datetime/planner-eds.h
@@ -20,8 +20,10 @@
#ifndef INDICATOR_DATETIME_PLANNER_EDS_H
#define INDICATOR_DATETIME_PLANNER_EDS_H
-#include <datetime/clock.h>
-#include <datetime/planner.h>
+#include <datetime/planner-range.h>
+
+#include <datetime/engine-eds.h>
+#include <datetime/timezone.h>
#include <memory> // shared_ptr, unique_ptr
@@ -30,17 +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>& clock);
- virtual ~PlannerEds();
+ EdsPlanner(const std::shared_ptr<EdsEngine>& eds_engine,
+ const std::shared_ptr<Timezone>& timezone);
+ virtual ~EdsPlanner();
+
+ core::Property<std::vector<Appointment>>& appointments();
+
+protected:
+ void rebuild_now();
private:
- class Impl;
- std::unique_ptr<Impl> p;
+ std::shared_ptr<EdsEngine> m_engine;
+ std::shared_ptr<Timezone> m_timezone;
+ core::Property<std::vector<Appointment>> 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_PLANNER_MONTH_H
+#define INDICATOR_DATETIME_PLANNER_MONTH_H
+
+#include <datetime/planner.h>
+
+#include <datetime/date-time.h>
+#include <datetime/planner-range.h>
+
+#include <memory> // 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<RangePlanner>& range_planner,
+ const DateTime& month_in);
+ ~MonthPlanner() =default;
+
+ core::Property<std::vector<Appointment>>& appointments();
+ core::Property<DateTime>& month();
+
+private:
+ std::shared_ptr<RangePlanner> m_range_planner;
+ core::Property<DateTime> 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_PLANNER_RANGE_H
+#define INDICATOR_DATETIME_PLANNER_RANGE_H
+
+#include <datetime/planner.h>
+
+#include <datetime/date-time.h>
+
+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<std::pair<DateTime,DateTime>>& 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<std::pair<DateTime,DateTime>> 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_PLANNER_UPCOMING_H
+#define INDICATOR_DATETIME_PLANNER_UPCOMING_H
+
+#include <datetime/planner.h>
+
+#include <datetime/date-time.h>
+#include <datetime/planner-range.h>
+
+#include <memory> // 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<RangePlanner>& range_planner,
+ const DateTime& date);
+ ~UpcomingPlanner() =default;
+
+ core::Property<std::vector<Appointment>>& appointments();
+ core::Property<DateTime>& date();
+
+private:
+ std::shared_ptr<RangePlanner> m_range_planner;
+ core::Property<DateTime> 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<DateTime> time;
-
- /**
- * \brief The next few appointments that follow the time specified in the time property.
- */
- core::Property<std::vector<Appointment>> upcoming;
-
- /**
- * \brief The appointments that occur in the same month as the time property
- */
- core::Property<std::vector<Appointment>> this_month;
+ virtual core::Property<std::vector<Appointment>>& 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 <datetime/clock.h>
#include <datetime/locations.h>
-#include <datetime/planner.h>
+#include <datetime/planner-month.h>
+#include <datetime/planner-upcoming.h>
#include <datetime/settings.h>
#include <datetime/timezones.h>
@@ -60,9 +61,14 @@ struct State
section of the #Menu */
std::shared_ptr<Locations> locations;
- /** \brief The appointments to be displayed in the Calendar and
- Appointments sections of the #Menu */
- std::shared_ptr<Planner> planner;
+ /** \brief Appointments in the month that's being displayed
+ in the calendar section of the #Menu */
+ std::shared_ptr<MonthPlanner> calendar_month;
+
+ /** \brief The next appointments that follow highlighed date
+ highlighted in the calendar section of the #Menu
+ (default date = today) */
+ std::shared_ptr<UpcomingPlanner> calendar_upcoming;
/** \brief Configuration options that modify the view */
std::shared_ptr<Settings> 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..c179274 100644
--- a/src/actions-live.cpp
+++ b/src/actions-live.cpp
@@ -91,13 +91,15 @@ void LiveActions::open_phone_clock_app()
void LiveActions::open_planner_at(const DateTime& dt)
{
- auto cmd = dt.format("evolution \"calendar:///?startdate=%Y%m%d\"");
+ const auto day_begins = dt.add_full(0, 0, 0, -dt.hour(), -dt.minute(), -dt.seconds());
+ const auto gmt = day_begins.to_timezone("UTC");
+ auto cmd = gmt.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\"");
execute_command(cmd.c_str());
}
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>& 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>& 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>& 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<Appointment>&){
+ m_state->calendar_month->appointments().changed().connect([this](const std::vector<Appointment>&){
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<const State>& state):
- m_state(state)
+ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<Clock>& clock,
+ const std::shared_ptr<UpcomingPlanner>& upcoming_planner):
+ m_clock(clock),
+ m_upcoming_planner(upcoming_planner)
{
- m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){
- 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<Appointment>&){
+ g_debug("ClockWatcher %p calling pulse() due to appointments changed", this);
pulse();
});
+
pulse();
}
@@ -48,9 +58,9 @@ core::Signal<const Appointment&>& 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/engine-eds.h>
+
+#include <libical/ical.h>
+#include <libical/icaltime.h>
+#include <libecal/libecal.h>
+#include <libedataserver/libedataserver.h>
+
+#include <algorithm> // std::sort()
+#include <map>
+#include <set>
+
+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<void(const std::vector<Appointment>&)> 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<Task> 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<AppointmentSubtask*>(g);});
+ }
+ }
+
+private:
+
+ void set_dirty_now()
+ {
+ m_changed();
+ }
+
+ static gboolean set_dirty_now_static (gpointer gself)
+ {
+ auto self = static_cast<Impl*>(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<Impl*>(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<Impl*>(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<Impl*>(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<Impl*>(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<Impl*>(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<Impl*>(gself)->set_dirty_soon();
+ }
+ static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+ {
+ g_debug("%s", G_STRFUNC);
+ static_cast<Impl*>(gself)->set_dirty_soon();
+ }
+ static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+ {
+ g_debug("%s", G_STRFUNC);
+ static_cast<Impl*>(gself)->set_dirty_soon();
+ }
+
+ static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
+ {
+ static_cast<Impl*>(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<Impl*>(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<Impl*>(gself)->set_dirty_soon();
+ }
+
+private:
+
+ typedef std::function<void(const std::vector<Appointment>&)> appointment_func;
+
+ struct Task
+ {
+ Impl* p;
+ appointment_func func;
+ std::vector<Appointment> appointments;
+ Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {}
+ };
+
+ struct AppointmentSubtask
+ {
+ 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)
+ {
+ if (color_in)
+ color = color_in;
+ }
+ };
+
+ struct UrlSubtask
+ {
+ std::shared_ptr<Task> task;
+ Appointment appointment;
+ UrlSubtask(const std::shared_ptr<Task>& 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<AppointmentSubtask*>(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<struct icalrecurrencetype*>(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<UrlSubtask*>(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<ESource*> m_sources;
+ std::map<ESource*,ECalClient*> m_clients;
+ std::map<ESource*,ECalClientView*> 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<void(const std::vector<Appointment>&)> 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 31d9db6..3e16555 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -20,6 +20,7 @@
#include <datetime/actions-live.h>
#include <datetime/clock.h>
#include <datetime/clock-watcher.h>
+#include <datetime/engine-eds.h>
#include <datetime/exporter.h>
#include <datetime/locations-settings.h>
#include <datetime/menu.h>
@@ -27,6 +28,7 @@
#include <datetime/settings-live.h>
#include <datetime/snap.h>
#include <datetime/state.h>
+#include <datetime/timezone-file.h>
#include <datetime/timezones-live.h>
#include <glib/gi18n.h> // bindtextdomain()
@@ -56,16 +58,21 @@ main(int /*argc*/, char** /*argv*/)
std::shared_ptr<Settings> live_settings(new LiveSettings);
std::shared_ptr<Timezones> live_timezones(new LiveTimezones(live_settings, TIMEZONE_FILE));
std::shared_ptr<Clock> live_clock(new LiveClock(live_timezones));
+ std::shared_ptr<Timezone> file_timezone(new FileTimezone(TIMEZONE_FILE));
+ std::shared_ptr<EdsEngine> 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));
- state->planner->time = live_clock->localtime();
+ auto calendar_month = new MonthPlanner(std::shared_ptr<RangePlanner>(new EdsPlanner(eds_engine, file_timezone)), now);
+ state->calendar_month.reset(calendar_month);
+ state->calendar_upcoming.reset(new UpcomingPlanner(std::shared_ptr<RangePlanner>(new EdsPlanner(eds_engine, file_timezone)), now));
std::shared_ptr<Actions> actions(new LiveActions(state));
MenuFactory factory(actions, state);
// snap decisions
- ClockWatcherImpl clock_watcher(state);
+ std::shared_ptr<UpcomingPlanner> upcoming_planner(new UpcomingPlanner(std::shared_ptr<RangePlanner>(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<Appointment>&){
+ m_state->calendar_upcoming->appointments().changed().connect([this](const std::vector<Appointment>&){
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<Appointment> 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 a9eecf2..f3f28ee 100644
--- a/src/planner-eds.cpp
+++ b/src/planner-eds.cpp
@@ -18,546 +18,49 @@
*/
#include <datetime/planner-eds.h>
-
-#include <datetime/appointment.h>
-
-#include <libical/ical.h>
-#include <libical/icaltime.h>
-#include <libecal/libecal.h>
-#include <libedataserver/libedataserver.h>
-
-#include <algorithm> // std::sort()
-#include <map>
-#include <set>
+#include <datetime/engine-eds.h>
namespace unity {
namespace indicator {
namespace datetime {
-/****
-*****
-****/
+/***
+****
+***/
-class PlannerEds::Impl
+EdsPlanner::EdsPlanner(const std::shared_ptr<EdsEngine>& engine,
+ const std::shared_ptr<Timezone>& timezone):
+ m_engine(engine),
+ m_timezone(timezone)
{
-public:
-
- Impl(PlannerEds& owner, const std::shared_ptr<Clock>& clock):
- m_owner(owner),
- m_clock(clock),
- 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<Impl*>(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<Impl*>(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<Impl*>(gself);
- ECalClientSourceType source_type;
- bool client_wanted = false;
+ m_engine->changed().connect([this](){
+ g_debug("EdsPlanner %p rebuilding soon because EdsEngine %p emitted 'changed' signal%p", this, m_engine.get());
+ rebuild_soon();
+ });
+}
- 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;
- }
+EdsPlanner::~EdsPlanner() =default;
- 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<Impl*>(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<Impl*>(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<Impl*>(gself)->rebuild_soon(ALL);
- }
- static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
- {
- g_debug("%s", G_STRFUNC);
- static_cast<Impl*>(gself)->rebuild_soon(ALL);
- }
- static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
- {
- g_debug("%s", G_STRFUNC);
- static_cast<Impl*>(gself)->rebuild_soon(ALL);
- }
-
- static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
- {
- static_cast<Impl*>(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<Impl*>(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<Impl*>(gself)->rebuild_soon(ALL);
- }
-
-private:
-
- typedef std::function<void(const std::vector<Appointment>&)> appointment_func;
-
- struct Task
- {
- Impl* p;
- appointment_func func;
- std::vector<Appointment> appointments;
- Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {}
- };
-
- struct AppointmentSubtask
- {
- 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)
- {
- 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<Impl*>(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<Appointment>& 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_minutes(ref.get(),-10);
- const auto end = g_date_time_add_months(begin,1);
-
- get_appointments(begin, end, [this](const std::vector<Appointment>& 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 = g_date_time_get_timezone_abbreviation(m_owner.time.get().get());
- g_debug("%s tz is %s", G_STRLOC, tz);
- 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<Task> 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<AppointmentSubtask*>(g);});
- }
- }
+void EdsPlanner::rebuild_now()
+{
+ const auto& r = range().get();
- struct UrlSubtask
- {
- std::shared_ptr<Task> task;
- Appointment appointment;
- UrlSubtask(const std::shared_ptr<Task>& task_in, const Appointment& appointment_in):
- task(task_in), appointment(appointment_in) {}
+ auto on_appointments_fetched = [this](const std::vector<Appointment>& 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<AppointmentSubtask*>(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<struct icalrecurrencetype*>(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<UrlSubtask*>(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<Clock> m_clock;
- std::set<ESource*> m_sources;
- std::map<ESource*,ECalClient*> m_clients;
- std::map<ESource*,ECalClientView*> 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>& clock): p(new Impl(*this, clock)) {}
+core::Property<std::vector<Appointment>>& 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/planner-month.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+MonthPlanner::MonthPlanner(const std::shared_ptr<RangePlanner>& 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<DateTime,DateTime>(month_begin,month_end));
+ });
+
+ month().set(month_in);
+}
+
+core::Property<DateTime>& MonthPlanner::month()
+{
+ return m_month;
+}
+
+core::Property<std::vector<Appointment>>& 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/planner-range.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+RangePlanner::RangePlanner():
+ m_range(std::pair<DateTime,DateTime>(DateTime::NowLocal(), DateTime::NowLocal()))
+{
+ range().changed().connect([this](const std::pair<DateTime,DateTime>&){
+ 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<RangePlanner*>(gself);
+ self->m_rebuild_tag = 0;
+ self->rebuild_now();
+ return G_SOURCE_REMOVE;
+}
+
+core::Property<std::pair<DateTime,DateTime>>& 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/planner-upcoming.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+UpcomingPlanner::UpcomingPlanner(const std::shared_ptr<RangePlanner>& 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<DateTime,DateTime>(b,e));
+ });
+
+ date().set(date_in);
+}
+
+core::Property<DateTime>& UpcomingPlanner::date()
+{
+ return m_date;
+}
+
+core::Property<std::vector<Appointment>>& UpcomingPlanner::appointments()
+{
+ return m_range_planner->appointments();
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/utils.c b/src/utils.c
index f4eb53f..c9107ce 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -159,6 +159,10 @@ getDateProximity(GDateTime* now, GDateTime* time)
gint now_year, now_month, now_day;
gint time_year, time_month, time_day;
+ // did it already happen?
+ if (g_date_time_difference(time, now) < -G_USEC_PER_SEC)
+ return DATE_PROXIMITY_FAR;
+
// does it happen today?
g_date_time_get_ymd(now, &now_year, &now_month, &now_day);
g_date_time_get_ymd(time, &time_year, &time_month, &time_day);
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 <datetime/planner.h>
+#include <datetime/planner-range.h>
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<std::vector<Appointment>>& appointments() { return m_appointments; }
+
+private:
+ core::Property<std::vector<Appointment>> 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<std::vector<Appointment>>& appointments() { return m_appointments; }
+
+protected:
+ void rebuild_now(){}
+
+private:
+ core::Property<std::vector<Appointment>> 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<MockClock> mock_clock;
+ std::shared_ptr<MockRangePlanner> mock_range_planner;
MockState()
{
const DateTime now = DateTime::NowLocal();
mock_clock.reset(new MockClock(now));
- settings.reset(new Settings);
clock = std::dynamic_pointer_cast<Clock>(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<RangePlanner>(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<Appointment>({appt}));
+ appt.begin = m_state->clock->localtime();
+ m_state->calendar_upcoming->appointments().set(std::vector<Appointment>({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<std::string> m_triggered;
std::unique_ptr<ClockWatcher> m_watcher;
+ std::shared_ptr<RangePlanner> m_range_planner;
+ std::shared_ptr<UpcomingPlanner> 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<Appointment> 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<Appointment> 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<Appointment> 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<Appointment> 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..d6ef424 100644
--- a/tests/test-live-actions.cpp
+++ b/tests/test-live-actions.cpp
@@ -284,7 +284,9 @@ TEST_F(LiveActionsFixture, OpenPlannerAt)
{
const auto now = DateTime::NowLocal();
m_actions->open_planner_at(now);
- const std::string expected = now.format("evolution \"calendar:///?startdate=%Y%m%d\"");
+ const auto today_begins = now.add_full(0, 0, 0, -now.hour(), -now.minute(), -now.seconds());
+ const auto gmt = today_begins.to_timezone("UTC");
+ const auto expected = gmt.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\"");
EXPECT_EQ(expected, m_live_actions->last_cmd);
}
@@ -295,7 +297,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 +318,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 +359,7 @@ TEST_F(LiveActionsFixture, CalendarState)
a2.begin = next_begin;
a2.end = next_end;
- m_state->planner->this_month.set(std::vector<Appointment>({a1, a2}));
+ m_state->calendar_month->appointments().set(std::vector<Appointment>({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<Appointment>& 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<Appointment> 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<Appointment> 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 1923ba1..4694cf5 100644
--- a/tests/test-planner.cpp
+++ b/tests/test-planner.cpp
@@ -18,6 +18,7 @@
*/
#include "glib-fixture.h"
+#include "timezone-mock.h"
#include <datetime/appointment.h>
#include <datetime/clock-mock.h>
@@ -36,26 +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> clock(new MockClock(now));
- PlannerEds planner(clock);
- wait_msec(100);
-
- planner.time.set(now);
- wait_msec(2500);
-
- std::vector<Appointment> 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);
diff --git a/tests/timezone-mock.h b/tests/timezone-mock.h
new file mode 100644
index 0000000..67584cb
--- /dev/null
+++ b/tests/timezone-mock.h
@@ -0,0 +1,40 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_TIMEZONE_MOCK_H
+#define INDICATOR_DATETIME_TIMEZONE_MOCK_H
+
+#include <datetime/timezone.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+class MockTimezone: public Timezone
+{
+public:
+ MockTimezone() =default;
+ ~MockTimezone() =default;
+};
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_TIMEZONE_MOCK_H