diff options
Diffstat (limited to 'src/planner-eds.cpp')
-rw-r--r-- | src/planner-eds.cpp | 555 |
1 files changed, 29 insertions, 526 deletions
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 |