diff options
author | Robert Tari <robert@tari.in> | 2021-08-30 01:26:19 +0200 |
---|---|---|
committer | Robert Tari <robert@tari.in> | 2021-08-30 01:26:19 +0200 |
commit | 22e66866c7b17fc655479ca911269b86cb80a744 (patch) | |
tree | e0aa6e6a8f50fd8451e03efc17b89d8c8c3de781 | |
parent | 1f8263dedf9b7e6f9e06492bd69f2436e36171a2 (diff) | |
parent | 38e5efecbb3154a83a70c1c762802ec7927b3caa (diff) | |
download | ayatana-indicator-datetime-22e66866c7b17fc655479ca911269b86cb80a744.tar.gz ayatana-indicator-datetime-22e66866c7b17fc655479ca911269b86cb80a744.tar.bz2 ayatana-indicator-datetime-22e66866c7b17fc655479ca911269b86cb80a744.zip |
Merge branch 'tari01-pr/ubports-patches'
Attributes GH PR #46: https://github.com/AyatanaIndicators/ayatana-indicator-datetime/pull/46
84 files changed, 3928 insertions, 1670 deletions
@@ -25,6 +25,9 @@ requires: - evolution-data-server - gsettings-desktop-schemas - properties-cpp +# - ayatana-indicator-messages + - gtk-doc-tools + - libaccountsservice debian: # Useful URL: https://salsa.debian.org/debian-ayatana-team/ayatana-ido @@ -51,6 +54,10 @@ requires: - libical-dev - libedataserver1.2-dev - libproperties-cpp-dev +# - libmessaging-menu-dev + - gtk-doc-tools + - libaccountsservice-dev + - liblomiri-url-dispatcher-dev # for the test harness: - libgtest-dev - libdbustest1-dev @@ -87,8 +94,10 @@ requires: - libecal2.0-dev - libical-dev - libedataserver1.2-dev - - liblomiri-url-dispatcher-dev - libproperties-cpp-dev +# - libmessaging-menu-dev + - gtk-doc-tools + - libaccountsservice-dev ubuntu:focal: - autopoint @@ -113,6 +122,9 @@ requires: - libical-dev - libedataserver1.2-dev - libproperties-cpp-dev +# - libmessaging-menu-dev + - gtk-doc-tools + - libaccountsservice-dev variables: - 'CHECKERS=" @@ -141,6 +153,15 @@ before_scripts: - cmake . -DCMAKE_INSTALL_PREFIX=/usr - make - make install + - cd ${START_DIR} + - if [ ! -d ayatana-indicator-messages-build ]; then + - git clone --depth 1 https://github.com/AyatanaIndicators/ayatana-indicator-messages.git ayatana-indicator-messages-build + - fi + - cd ayatana-indicator-messages-build + - NOCONFIGURE=1 ./autogen.sh + - ./configure --disable-tests --prefix=/usr + - make + - make install build_scripts: - if [ ${DISTRO_NAME} == "debian" ];then diff --git a/.travis.yml b/.travis.yml index 5382638..51db47f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ addons: before_install: # let's use the MATE project's docker build script... - - curl -Ls -o docker-build https://github.com/AyatanaIndicators/ayatana-dev-scripts/raw/master/travis/docker-build + - curl -Ls -o docker-build https://github.com/AyatanaIndicators/ayatana-dev-scripts/raw/main/travis/docker-build - chmod +x docker-build install: diff --git a/CMakeLists.txt b/CMakeLists.txt index 33781a0..8e682ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,16 +66,15 @@ pkg_check_modules (SERVICE_DEPS REQUIRED gstreamer-1.0>=1.2 libnotify>=0.7.6 properties-cpp>=0.0.1 - libaccounts-glib>=1.18) + libaccounts-glib>=1.18 + messaging-menu>=0.8.2) include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS}) -set(URL_DISPATCHER_REQUIRED_VERSION 0) -pkg_check_modules(URLDISPATCHER - lomiri-url-dispatcher>=${URL_DISPATCHER_REQUIRED_VERSION} -) +# lomiri-url-dispatcher + +pkg_check_modules(URLDISPATCHER lomiri-url-dispatcher>=0) include_directories(${URLDISPATCHER_INCLUDE_DIRS}) -# url-dispatcher support is optional... if(URLDISPATCHER_FOUND) add_definitions( -DHAS_URLDISPATCHER ) endif() @@ -96,9 +95,6 @@ add_custom_target (dist COMMAND bzr export --root=${ARCHIVE_NAME} ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.gz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) -add_custom_target (clean-coverage - COMMAND find ${CMAKE_BINARY_DIR} -name '*.gcda' | xargs rm -f) - add_custom_target (cppcheck COMMAND cppcheck --enable=all -q --error-exitcode=2 --inline-suppr ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/tests) @@ -123,32 +119,32 @@ endif() include_directories (${CMAKE_CURRENT_SOURCE_DIR}/include) include_directories (${CMAKE_CURRENT_BINARY_DIR}/include) -# testing & coverage -if (${ENABLE_TESTS}) - pkg_check_modules (DBUSTEST REQUIRED dbustest-1>=14.04.0) - - if (EXISTS /usr/src/googletest/src) - set (GTEST_SOURCE_DIR /usr/src/googletest/src) - else () - set (GTEST_SOURCE_DIR /usr/src/gtest/src) - endif () - - set (GTEST_INCLUDE_DIR ${GTEST_SOURCE_DIR}/..) - set (GTEST_LIBS -lpthread) - enable_testing () - if (${ENABLE_COVERAGE}) - include(GCov) - endif () -endif () - # actually build things add_subdirectory(include) add_subdirectory(src) add_subdirectory(data) add_subdirectory(po) -if (${ENABLE_TESTS}) - add_subdirectory(tests) -endif () + +# testing & coverage +if (ENABLE_TESTS) + + include(CTest) + pkg_check_modules (DBUSTEST REQUIRED dbustest-1>=14.04.0) + enable_testing() + add_subdirectory(tests) + + if (ENABLE_COVERAGE) + + find_package(CoverageReport) + ENABLE_COVERAGE_REPORT( + TARGETS indicatordatetimeservice ayatana-indicator-datetime-service + TESTS ${COVERAGE_TEST_TARGETS} + FILTER /usr/include ${CMAKE_BINARY_DIR}/* + ) + + endif() + +endif() # Display config info @@ -1,4 +1,4 @@ -# Ayatana System Indicator — DateTime [![Build Status](https://travis-ci.com/AyatanaIndicators/ayatana-indicator-datetime.svg)](https://travis-ci.com/AyatanaIndicators/ayatana-indicator-datetime) +# Ayatana System Indicator — DateTime [![Build Status](https://api.travis-ci.com/AyatanaIndicators/ayatana-indicator-datetime.svg)](https://travis-ci.com/github/AyatanaIndicators/ayatana-indicator-datetime) ## About Ayatana Indicators diff --git a/cmake/FindIntltool.cmake b/cmake/FindIntltool.cmake deleted file mode 100644 index 69ffab9..0000000 --- a/cmake/FindIntltool.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# FindIntltool.cmake -# -# Jim Nelson <jim@yorba.org> -# Copyright 2012 Yorba Foundation - -find_program (INTLTOOL_MERGE_EXECUTABLE intltool-merge) - -if (INTLTOOL_MERGE_EXECUTABLE) - set (INTLTOOL_MERGE_FOUND TRUE) -else (INTLTOOL_MERGE_EXECUTABLE) - set (INTLTOOL_MERGE_FOUND FALSE) -endif (INTLTOOL_MERGE_EXECUTABLE) - -if (INTLTOOL_MERGE_FOUND) - macro (INTLTOOL_MERGE_DESKTOP desktop_id po_dir) - add_custom_target (geary.desktop ALL - ${INTLTOOL_MERGE_EXECUTABLE} --desktop-style ${CMAKE_SOURCE_DIR}/${po_dir} - ${CMAKE_CURRENT_SOURCE_DIR}/${desktop_id}.desktop.in ${desktop_id}.desktop - ) - install (FILES ${CMAKE_CURRENT_BINARY_DIR}/geary.desktop DESTINATION /usr/share/applications) - endmacro (INTLTOOL_MERGE_DESKTOP desktop_id po_dir) -endif (INTLTOOL_MERGE_FOUND) - diff --git a/cmake/GCov.cmake b/cmake/GCov.cmake deleted file mode 100644 index 9a5086e..0000000 --- a/cmake/GCov.cmake +++ /dev/null @@ -1,51 +0,0 @@ -if (CMAKE_BUILD_TYPE MATCHES coverage) - set(GCOV_FLAGS "${GCOV_FLAGS} --coverage") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCOV_FLAGS}") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${GCOV_FLAGS}") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${GCOV_FLAGS}") - set(GCOV_LIBS ${GCOV_LIBS} gcov) - - find_program(GCOVR_EXECUTABLE gcovr HINTS ${GCOVR_ROOT} "${GCOVR_ROOT}/bin") - if (NOT GCOVR_EXECUTABLE) - message(STATUS "Gcovr binary was not found, can not generate XML coverage info.") - else () - message(STATUS "Gcovr found, can generate XML coverage info.") - add_custom_target (coverage-xml - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMAND "${GCOVR_EXECUTABLE}" --exclude="test.*" -x -r "${CMAKE_SOURCE_DIR}" - --object-directory=${CMAKE_BINARY_DIR} -o coverage.xml) - endif() - - find_program(LCOV_EXECUTABLE lcov HINTS ${LCOV_ROOT} "${GCOVR_ROOT}/bin") - find_program(GENHTML_EXECUTABLE genhtml HINTS ${GENHTML_ROOT}) - if (NOT LCOV_EXECUTABLE) - message(STATUS "Lcov binary was not found, can not generate HTML coverage info.") - else () - if(NOT GENHTML_EXECUTABLE) - message(STATUS "Genthml binary not found, can not generate HTML coverage info.") - else() - message(STATUS "Lcov and genhtml found, can generate HTML coverage info.") - add_custom_target (coverage-html - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMAND "${CMAKE_CTEST_COMMAND}" --force-new-ctest-process --verbose - COMMAND "${LCOV_EXECUTABLE}" --directory ${CMAKE_BINARY_DIR} --capture | ${CMAKE_SOURCE_DIR}/trim-lcov.py > dconf-lcov.info - COMMAND "${LCOV_EXECUTABLE}" -r dconf-lcov.info /usr/include/\\* -o nosys-lcov.info - COMMAND LANG=C "${GENHTML_EXECUTABLE}" --prefix ${CMAKE_BINARY_DIR} --output-directory lcov-html --legend --show-details nosys-lcov.info - COMMAND ${CMAKE_COMMAND} -E echo "" - COMMAND ${CMAKE_COMMAND} -E echo "file://${CMAKE_BINARY_DIR}/lcov-html/index.html" - COMMAND ${CMAKE_COMMAND} -E echo "") - #COMMAND "${LCOV_EXECUTABLE}" --directory ${CMAKE_BINARY_DIR} --capture --output-file coverage.info --no-checksum - #COMMAND "${GENHTML_EXECUTABLE}" --prefix ${CMAKE_BINARY_DIR} --output-directory coveragereport --title "Code Coverage" --legend --show-details coverage.info - #COMMAND ${CMAKE_COMMAND} -E echo "\\#define foo \\\"bar\\\"" - #) - endif() - endif() -endif() - - - #$(MAKE) $(AM_MAKEFLAGS) check - #lcov --directory $(top_builddir) --capture --test-name dconf | $(top_srcdir)/trim-lcov.py > dconf-lcov.info - #LANG=C genhtml --prefix $(top_builddir) --output-directory lcov-html --legend --show-details dconf-lcov.info - #@echo - #@echo " file://$(abs_top_builddir)/lcov-html/index.html" - #@echo diff --git a/cmake/Translations.cmake b/cmake/Translations.cmake deleted file mode 100644 index b51c39d..0000000 --- a/cmake/Translations.cmake +++ /dev/null @@ -1,37 +0,0 @@ -# Translations.cmake, CMake macros written for Marlin, feel free to re-use them - -macro(add_translations_directory NLS_PACKAGE) - add_custom_target (i18n ALL) - find_program (MSGFMT_EXECUTABLE msgfmt) - file (GLOB PO_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.po) - foreach (PO_INPUT ${PO_FILES}) - get_filename_component (PO_INPUT_BASE ${PO_INPUT} NAME_WE) - set (MO_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PO_INPUT_BASE}.mo) - add_custom_command (TARGET i18n COMMAND ${MSGFMT_EXECUTABLE} -o ${MO_OUTPUT} ${PO_INPUT}) - - install (FILES ${MO_OUTPUT} DESTINATION - ${CMAKE_INSTALL_LOCALEDIR}/${PO_INPUT_BASE}/LC_MESSAGES - RENAME ${NLS_PACKAGE}.mo) - endforeach (PO_INPUT ${PO_FILES}) -endmacro(add_translations_directory) - - -macro(add_translations_catalog NLS_PACKAGE) - add_custom_target (pot COMMENT “Building translation catalog.”) - find_program (XGETTEXT_EXECUTABLE xgettext) - - # init this list, which will hold all the sources across all dirs - set(SOURCES "") - - # add each directory's sources to the overall sources list - foreach(FILES_INPUT ${ARGN}) - set (DIR ${CMAKE_CURRENT_SOURCE_DIR}/${FILES_INPUT}) - file (GLOB_RECURSE DIR_SOURCES ${DIR}/*.c ${DIR}/*.cc ${DIR}/*.cpp ${DIR}/*.cxx ${DIR}/*.vala) - set (SOURCES ${SOURCES} ${DIR_SOURCES}) - endforeach() - - add_custom_command (TARGET pot COMMAND - ${XGETTEXT_EXECUTABLE} -d ${NLS_PACKAGE} -o ${CMAKE_CURRENT_SOURCE_DIR}/${NLS_PACKAGE}.pot - ${SOURCES} --keyword="_" --keyword="N_" --from-code=UTF-8 - ) -endmacro() diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index c460586..d7ab10f 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -55,7 +55,7 @@ set (XDG_AUTOSTART_FILE_IN "${CMAKE_CURRENT_SOURCE_DIR}/${XDG_AUTOSTART_NAME}.in set (pkglibexecdir "${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}") configure_file ("${XDG_AUTOSTART_FILE_IN}" "${XDG_AUTOSTART_FILE}") -# install it +# install XDG autostart install (FILES "${XDG_AUTOSTART_FILE}" DESTINATION "${XDG_AUTOSTART_DIR}") diff --git a/data/ayatana-indicator-datetime.desktop.in b/data/ayatana-indicator-datetime.desktop.in index 70df0d7..6d27166 100644 --- a/data/ayatana-indicator-datetime.desktop.in +++ b/data/ayatana-indicator-datetime.desktop.in @@ -6,3 +6,4 @@ OnlyShowIn=MATE;Unity;XFCE;Pantheon; NoDisplay=true StartupNotify=false Terminal=false +Icon=@messaging_menu_icon@ diff --git a/debian/control b/debian/control index 946b4c2..93d2b76 100644 --- a/debian/control +++ b/debian/control @@ -17,8 +17,8 @@ Build-Depends: cmake, libical-dev (>= 1.0), libedataserver1.2-dev (>= 3.5), accountsservice-ubuntu-schemas | hello, - liblomiri-url-dispatcher-dev | hello, libproperties-cpp-dev, + liblomiri-url-dispatcher-dev | hello, # for the test harness: libgtest-dev <!nocheck>, libdbustest1-dev <!nocheck>, diff --git a/include/datetime/actions-live.h b/include/datetime/actions-live.h index 2c348c6..6f536c4 100644 --- a/include/datetime/actions-live.h +++ b/include/datetime/actions-live.h @@ -40,21 +40,12 @@ public: virtual ~LiveActions() =default; bool desktop_has_calendar_app() const override; - void desktop_open_alarm_app() override; - void desktop_open_appointment(const Appointment&) override; - void desktop_open_calendar_app(const DateTime&) override; - void desktop_open_settings_app() override; - - void phone_open_alarm_app() override; - void phone_open_appointment(const Appointment&) override; - void phone_open_calendar_app(const DateTime&) override; - void phone_open_settings_app() override; + void open_alarm_app() override; + void open_appointment(const Appointment&, const DateTime&) override; + void open_calendar_app(const DateTime&) override; + void open_settings_app() override; void set_location(const std::string& zone, const std::string& name) override; - -protected: - virtual void execute_command(const std::string& command); - virtual void dispatch_url(const std::string& url); }; } // namespace datetime diff --git a/include/datetime/actions.h b/include/datetime/actions.h index 47931ac..d866b00 100644 --- a/include/datetime/actions.h +++ b/include/datetime/actions.h @@ -44,15 +44,11 @@ class Actions public: virtual bool desktop_has_calendar_app() const =0; - virtual void desktop_open_alarm_app() =0; - virtual void desktop_open_appointment(const Appointment&) =0; - virtual void desktop_open_calendar_app(const DateTime&) =0; - virtual void desktop_open_settings_app() =0; - virtual void phone_open_alarm_app() =0; - virtual void phone_open_appointment(const Appointment&) =0; - virtual void phone_open_calendar_app(const DateTime&) =0; - virtual void phone_open_settings_app() =0; + virtual void open_alarm_app() =0; + virtual void open_appointment(const Appointment&, const DateTime&) =0; + virtual void open_calendar_app(const DateTime&) =0; + virtual void open_settings_app() =0; virtual void set_location(const std::string& zone, const std::string& name)=0; diff --git a/include/datetime/appointment.h b/include/datetime/appointment.h index 950f4bb..faf8a18 100644 --- a/include/datetime/appointment.h +++ b/include/datetime/appointment.h @@ -43,7 +43,6 @@ struct Alarm bool has_text() const; }; - /** * \brief An instance of an appointment; e.g. a calendar event or clock-app alarm * diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h index fc83388..845716d 100644 --- a/include/datetime/date-time.h +++ b/include/datetime/date-time.h @@ -68,7 +68,9 @@ public: int64_t to_unix() const; bool operator<(const DateTime& that) const; + bool operator>(const DateTime& that) const; bool operator<=(const DateTime& that) const; + bool operator>=(const DateTime& that) const; bool operator!=(const DateTime& that) const; bool operator==(const DateTime& that) const; int64_t operator- (const DateTime& that) const; diff --git a/include/datetime/dbus-shared.h b/include/datetime/dbus-shared.h index 057ac6b..0b6aa95 100644 --- a/include/datetime/dbus-shared.h +++ b/include/datetime/dbus-shared.h @@ -24,9 +24,43 @@ #define BUS_DATETIME_NAME "org.ayatana.indicator.datetime" #define BUS_DATETIME_PATH "/org/ayatana/indicator/datetime" -#define BUS_POWERD_NAME "com.canonical.powerd" -#define BUS_POWERD_PATH "/com/canonical/powerd" -#define BUS_POWERD_INTERFACE "com.canonical.powerd" +#define BUS_POWERD_NAME "com.lomiri.Repowerd" +#define BUS_POWERD_PATH "/com/lomiri/Repowerd" +#define BUS_POWERD_INTERFACE "com.lomiri.Repowerd" +namespace Bus +{ + namespace Timedate1 + { + static constexpr char const * BUSNAME {"org.freedesktop.timedate1"}; + static constexpr char const * ADDR {"/org/freedesktop/timedate1"}; + static constexpr char const * IFACE {"org.freedesktop.timedate1"}; + + namespace Properties + { + static constexpr char const * TIMEZONE {"Timezone"}; + } + + namespace Methods + { + static constexpr char const * SET_TIMEZONE {"SetTimezone"}; + } + } + + namespace Properties + { + static constexpr char const * IFACE {"org.freedesktop.DBus.Properties"}; + + namespace Methods + { + static constexpr char const * GET {"Get"}; + } + + namespace Signals + { + static constexpr char const * PROPERTIES_CHANGED {"PropertiesChanged"}; + } + } +} #endif /* INDICATOR_DATETIME_DBUS_SHARED_H */ diff --git a/include/datetime/engine-eds.h b/include/datetime/engine-eds.h index 12425b3..96b0f76 100644 --- a/include/datetime/engine-eds.h +++ b/include/datetime/engine-eds.h @@ -47,8 +47,7 @@ class Myself; class EdsEngine: public Engine { public: - EdsEngine(); - explicit EdsEngine(const std::shared_ptr<Myself> &myself); + EdsEngine(const std::shared_ptr<Myself> &myself); ~EdsEngine(); void get_appointments(const DateTime& begin, diff --git a/include/datetime/menu.h b/include/datetime/menu.h index acd9ed8..0074ea5 100644 --- a/include/datetime/menu.h +++ b/include/datetime/menu.h @@ -21,6 +21,7 @@ #define INDICATOR_DATETIME_MENU_H #include <datetime/actions.h> +#include <datetime/appointment.h> #include <datetime/state.h> #include <memory> // std::shared_ptr @@ -49,6 +50,11 @@ public: Profile profile() const; GMenuModel* menu_model(); + static std::vector<Appointment> get_display_appointments( + const std::vector<Appointment>&, + const DateTime& start, + unsigned int max_items=5); + protected: Menu (Profile profile_in, const std::string& name_in); virtual ~Menu() =default; diff --git a/include/datetime/settings-live.h b/include/datetime/settings-live.h index ccf7122..85071ce 100644 --- a/include/datetime/settings-live.h +++ b/include/datetime/settings-live.h @@ -38,8 +38,12 @@ public: virtual ~LiveSettings(); private: - static void on_changed(GSettings*, gchar*, gpointer); - void update_key(const std::string& key); + static void on_changed_ccid(GSettings*, gchar*, gpointer); + static void on_changed_cal_notification(GSettings*, gchar*, gpointer); + static void on_changed_general_notification(GSettings*, gchar*, gpointer); + void update_key_ccid(const std::string& key); + void update_key_cal_notification(const std::string& key); + void update_key_general_notification(const std::string& key); void update_custom_time_format(); void update_locations(); @@ -60,8 +64,16 @@ private: void update_alarm_duration(); void update_alarm_haptic(); void update_snooze_duration(); + void update_cal_notification_enabled(); + void update_cal_notification_sounds(); + void update_cal_notification_vibrations(); + void update_cal_notification_bubbles(); + void update_cal_notification_list(); + void update_vibrate_silent_mode(); GSettings* m_settings; + GSettings* m_settings_cal_notification; + GSettings* m_settings_general_notification; // we've got a raw pointer here, so disable copying LiveSettings(const LiveSettings&) =delete; diff --git a/include/datetime/settings-shared.h b/include/datetime/settings-shared.h index bd84a2d..7280c16 100644 --- a/include/datetime/settings-shared.h +++ b/include/datetime/settings-shared.h @@ -51,4 +51,14 @@ TimeFormatMode; #define SETTINGS_ALARM_HAPTIC_S "alarm-haptic-feedback" #define SETTINGS_SNOOZE_DURATION_S "snooze-duration-minutes" +#define SETTINGS_NOTIFY_APPS_SCHEMA_ID "com.lomiri.notifications.settings.applications" +#define SETTINGS_VIBRATE_SILENT_KEY "vibrate-silent-mode" +#define SETTINGS_NOTIFY_SCHEMA_ID "com.lomiri.notifications.settings" +#define SETTINGS_NOTIFY_CALENDAR_PATH "/com/lomiri/NotificationSettings/com.lomiri.calendar/calendar/" +#define SETTINGS_NOTIFY_ENABLED_KEY "enable-notifications" +#define SETTINGS_NOTIFY_SOUNDS_KEY "use-sounds-notifications" +#define SETTINGS_NOTIFY_VIBRATIONS_KEY "use-vibrations-notifications" +#define SETTINGS_NOTIFY_BUBBLES_KEY "use-bubbles-notifications" +#define SETTINGS_NOTIFY_LIST_KEY "use-list-notifications" + #endif // INDICATOR_DATETIME_SETTINGS_SHARED diff --git a/include/datetime/settings.h b/include/datetime/settings.h index 253a00a..af9227d 100644 --- a/include/datetime/settings.h +++ b/include/datetime/settings.h @@ -61,6 +61,12 @@ public: core::Property<unsigned int> alarm_volume; core::Property<unsigned int> alarm_duration; core::Property<unsigned int> snooze_duration; + core::Property<bool> cal_notification_enabled; + core::Property<bool> cal_notification_sounds; + core::Property<bool> cal_notification_vibrations; + core::Property<bool> cal_notification_bubbles; + core::Property<bool> cal_notification_list; + core::Property<bool> vibrate_silent_mode; }; } // namespace datetime diff --git a/include/datetime/snap.h b/include/datetime/snap.h index a295d9f..baa765b 100644 --- a/include/datetime/snap.h +++ b/include/datetime/snap.h @@ -24,6 +24,9 @@ #include <datetime/settings.h> #include <notifications/notifications.h> +#include <notifications/sound.h> + +#include <gio/gio.h> // GDBusConnection #include <functional> #include <memory> @@ -39,14 +42,16 @@ class Snap { public: Snap(const std::shared_ptr<ayatana::indicator::notifications::Engine>& engine, - const std::shared_ptr<const Settings>& settings); + const std::shared_ptr<ayatana::indicator::notifications::SoundBuilder>& sound_builder, + const std::shared_ptr<const Settings>& settings, + GDBusConnection* system_bus); virtual ~Snap(); - typedef std::function<void(const Appointment&, const Alarm&)> appointment_func; + enum class Response { None, Snooze, ShowApp }; + typedef std::function<void(const Appointment&, const Alarm&, const Response&)> response_func; void operator()(const Appointment& appointment, const Alarm& alarm, - appointment_func snooze, - appointment_func ok); + response_func on_response); private: class Impl; diff --git a/include/datetime/timezone-timedated.h b/include/datetime/timezone-timedated.h index 336a148..e0af184 100644 --- a/include/datetime/timezone-timedated.h +++ b/include/datetime/timezone-timedated.h @@ -20,10 +20,10 @@ #ifndef INDICATOR_DATETIME_TIMEDATED_TIMEZONE_H #define INDICATOR_DATETIME_TIMEDATED_TIMEZONE_H -#define DEFAULT_FILENAME "/etc/timezone" - #include <datetime/timezone.h> // base class +#include <gio/gio.h> // GDBusConnection* + #include <string> // std::string namespace ayatana { @@ -31,12 +31,12 @@ namespace indicator { namespace datetime { /** - * \brief A #Timezone that gets its information from monitoring a file, such as /etc/timezone + * \brief A #Timezone that gets its information from org.freedesktop.timedate1 */ class TimedatedTimezone: public Timezone { public: - TimedatedTimezone(std::string filename = DEFAULT_FILENAME); + TimedatedTimezone(GDBusConnection* connection); ~TimedatedTimezone(); private: diff --git a/include/datetime/timezones-live.h b/include/datetime/timezones-live.h index e722576..8b8b5fa 100644 --- a/include/datetime/timezones-live.h +++ b/include/datetime/timezones-live.h @@ -23,7 +23,6 @@ #include <datetime/settings.h> #include <datetime/timezones.h> #include <datetime/timezone-geoclue.h> -#include <datetime/timezone-timedated.h> #include <memory> // shared_ptr<> @@ -38,13 +37,13 @@ namespace datetime { class LiveTimezones: public Timezones { public: - LiveTimezones(const std::shared_ptr<const Settings>& settings); + LiveTimezones(const std::shared_ptr<const Settings>& settings, const std::shared_ptr<Timezone>& primary_timezone); private: void update_geolocation(); void update_timezones(); - TimedatedTimezone m_file; + std::shared_ptr<Timezone> m_primary_timezone; std::shared_ptr<const Settings> m_settings; std::shared_ptr<GeoclueTimezone> m_geo; }; diff --git a/include/notifications/awake.h b/include/notifications/awake.h index b441692..d0b46eb 100644 --- a/include/notifications/awake.h +++ b/include/notifications/awake.h @@ -22,6 +22,8 @@ #include <memory> +#include <gio/gio.h> + namespace ayatana { namespace indicator { namespace notifications { @@ -36,7 +38,7 @@ namespace notifications { class Awake { public: - explicit Awake(const std::string& app_name); + explicit Awake(GDBusConnection* system_bus, const std::string& app_name); ~Awake(); private: diff --git a/include/notifications/dbus-shared.h b/include/notifications/dbus-shared.h index 523fb2a..31df219 100644 --- a/include/notifications/dbus-shared.h +++ b/include/notifications/dbus-shared.h @@ -25,9 +25,9 @@ #define BUS_SCREEN_PATH "/com/canonical/Unity/Screen" #define BUS_SCREEN_INTERFACE "com.canonical.Unity.Screen" -#define BUS_POWERD_NAME "com.canonical.powerd" -#define BUS_POWERD_PATH "/com/canonical/powerd" -#define BUS_POWERD_INTERFACE "com.canonical.powerd" +#define BUS_POWERD_NAME "com.lomiri.Repowerd" +#define BUS_POWERD_PATH "/com/lomiri/Repowerd" +#define BUS_POWERD_INTERFACE "com.lomiri.Repowerd" #define BUS_HAPTIC_NAME "com.canonical.usensord" #define BUS_HAPTIC_PATH "/com/canonical/usensord/haptic" diff --git a/include/notifications/haptic.h b/include/notifications/haptic.h index 535074d..2f4008a 100644 --- a/include/notifications/haptic.h +++ b/include/notifications/haptic.h @@ -41,7 +41,7 @@ public: MODE_PULSE }; - explicit Haptic(const Mode& mode = MODE_PULSE); + explicit Haptic(const Mode& mode = MODE_PULSE, bool repeat = false); ~Haptic(); private: diff --git a/include/notifications/notifications.h b/include/notifications/notifications.h index 0de1e23..af6d21f 100644 --- a/include/notifications/notifications.h +++ b/include/notifications/notifications.h @@ -50,6 +50,8 @@ public: void set_icon_name (const std::string& icon_name); + void set_start_time(uint64_t time); + /* Set an interval, after which the notification will automatically be closed. If not set, the notification server's default timeout is used. */ @@ -62,15 +64,26 @@ public: static constexpr char const * HINT_NONSHAPED_ICON {"x-canonical-non-shaped-icon"}; static constexpr char const * HINT_AFFIRMATIVE_HINT {"x-canonical-private-affirmative-tint"}; static constexpr char const * HINT_REJECTION_TINT {"x-canonical-private-rejection-tint"}; + static constexpr char const * HINT_INTERACTIVE {"x-canonical-switch-to-application"}; /* Add an action button. This may fail if the Engine doesn't support actions. @see Engine::supports_actions() */ void add_action (const std::string& action, const std::string& label); - /** Sets the closed callback. This will be called exactly once. */ + /** Sets the closed callback. This will be called exactly once. After notification disappears */ void set_closed_callback (std::function<void(const std::string& action)>); + /** Sets the time-out callback. This will be called exactly once. */ + void set_timeout_callback (std::function<void()>); + + /** Sets if a notification bubble should be displayed. */ + void set_show_notification_bubble (bool show); + + /** Sets if notification should be posted to messaging menu after it is closed. */ + void set_post_to_messaging_menu (bool post); + + private: friend class Engine; class Impl; diff --git a/m4/gcov.m4 b/m4/gcov.m4 deleted file mode 100644 index d243eeb..0000000 --- a/m4/gcov.m4 +++ /dev/null @@ -1,86 +0,0 @@ -# Checks for existence of coverage tools: -# * gcov -# * lcov -# * genhtml -# * gcovr -# -# Sets ac_cv_check_gcov to yes if tooling is present -# and reports the executables to the variables LCOV, GCOVR and GENHTML. -AC_DEFUN([AC_TDD_GCOV], -[ - AC_ARG_ENABLE(gcov, - AS_HELP_STRING([--enable-gcov], - [enable coverage testing with gcov]), - [use_gcov=$enableval], [use_gcov=no]) - - if test "x$use_gcov" = "xyes"; then - # we need gcc: - if test "$GCC" != "yes"; then - AC_MSG_ERROR([GCC is required for --enable-gcov]) - fi - - # Check if ccache is being used - AC_CHECK_PROG(SHTOOL, shtool, shtool) - case `$SHTOOL path $CC` in - *ccache*[)] gcc_ccache=yes;; - *[)] gcc_ccache=no;; - esac - - if test "$gcc_ccache" = "yes" && (test -z "$CCACHE_DISABLE" || test "$CCACHE_DISABLE" != "1"); then - AC_MSG_ERROR([ccache must be disabled when --enable-gcov option is used. You can disable ccache by setting environment variable CCACHE_DISABLE=1.]) - fi - - lcov_version_list="1.6 1.7 1.8 1.9" - AC_CHECK_PROG(LCOV, lcov, lcov) - AC_CHECK_PROG(GENHTML, genhtml, genhtml) - - if test "$LCOV"; then - AC_CACHE_CHECK([for lcov version], glib_cv_lcov_version, [ - glib_cv_lcov_version=invalid - lcov_version=`$LCOV -v 2>/dev/null | $SED -e 's/^.* //'` - for lcov_check_version in $lcov_version_list; do - if test "$lcov_version" = "$lcov_check_version"; then - glib_cv_lcov_version="$lcov_check_version (ok)" - fi - done - ]) - else - lcov_msg="To enable code coverage reporting you must have one of the following lcov versions installed: $lcov_version_list" - AC_MSG_ERROR([$lcov_msg]) - fi - - case $glib_cv_lcov_version in - ""|invalid[)] - lcov_msg="You must have one of the following versions of lcov: $lcov_version_list (found: $lcov_version)." - AC_MSG_ERROR([$lcov_msg]) - LCOV="exit 0;" - ;; - esac - - if test -z "$GENHTML"; then - AC_MSG_ERROR([Could not find genhtml from the lcov package]) - fi - - ac_cv_check_gcov=yes - ac_cv_check_lcov=yes - - # Remove all optimization flags from CFLAGS - changequote({,}) - CFLAGS=`echo "$CFLAGS" | $SED -e 's/-O[0-9]*//g'` - changequote([,]) - - # Add the special gcc flags - COVERAGE_CFLAGS="-O0 -fprofile-arcs -ftest-coverage" - COVERAGE_CXXFLAGS="-O0 -fprofile-arcs -ftest-coverage" - COVERAGE_LDFLAGS="-lgcov" - - # Check availability of gcovr - AC_CHECK_PROG(GCOVR, gcovr, gcovr) - if test -z "$GCOVR"; then - ac_cv_check_gcovr=no - else - ac_cv_check_gcovr=yes - fi - -fi -]) # AC_TDD_GCOV diff --git a/m4/gtest.m4 b/m4/gtest.m4 deleted file mode 100644 index 2de334c..0000000 --- a/m4/gtest.m4 +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (C) 2012 Canonical, Ltd. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice (including the next -# paragraph) shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Checks whether the gtest source is available on the system. Allows for -# adjusting the include and source path. Sets have_gtest=yes if the source is -# present. Sets GTEST_CPPFLAGS and GTEST_SOURCE to the preprocessor flags and -# source location respectively. -AC_DEFUN([CHECK_GTEST], -[ - AC_ARG_WITH([gtest-include-path], - [AS_HELP_STRING([--with-gtest-include-path], - [location of the Google test headers])], - [GTEST_CPPFLAGS="-I$withval"]) - - AC_ARG_WITH([gtest-source-path], - [AS_HELP_STRING([--with-gtest-source-path], - [location of the Google test sources, defaults to /usr/src/gtest])], - [GTEST_SOURCE="$withval"], - [GTEST_SOURCE="/usr/src/gtest"]) - - GTEST_CPPFLAGS="$GTEST_CPPFLAGS -I$GTEST_SOURCE" - - AC_LANG_PUSH([C++]) - - tmp_CPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $GTEST_CPPFLAGS" - - AC_CHECK_HEADER([gtest/gtest.h]) - - CPPFLAGS="$tmp_CPPFLAGS" - - AC_LANG_POP - - AC_CHECK_FILES([$GTEST_SOURCE/src/gtest-all.cc] - [$GTEST_SOURCE/src/gtest_main.cc], - [have_gtest_source=yes], - [have_gtest_source=no]) - - AS_IF([test "x$ac_cv_header_gtest_gtest_h" = xyes -a \ - "x$have_gtest_source" = xyes], - [have_gtest=yes] - [AC_SUBST(GTEST_CPPFLAGS)] - [AC_SUBST(GTEST_SOURCE)], - [have_gtest=no]) -]) # CHECK_GTEST diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt index 8325f6e..0b2b2a1 100644 --- a/po/CMakeLists.txt +++ b/po/CMakeLists.txt @@ -1,3 +1,6 @@ -include (Translations) -add_translations_directory ("${GETTEXT_PACKAGE}") -add_translations_catalog ("${GETTEXT_PACKAGE}" ../src/) +find_package(Intltool REQUIRED) + +intltool_install_translations( + ALL + GETTEXT_PACKAGE ${GETTEXT_PACKAGE} +) diff --git a/po/Makevars b/po/Makevars deleted file mode 100644 index e877468..0000000 --- a/po/Makevars +++ /dev/null @@ -1,7 +0,0 @@ -DOMAIN = $(PACKAGE) -subdir = po -top_builddir = .. -XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ --keyword=C_:1c,2 --keyword=T_ --from-code=UTF-8 -COPYRIGHT_HOLDER = Canonical Ltd. -MSGID_BUGS_ADDRESS = ted@canonical.com -EXTRA_LOCALE_CATEGORIES = diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 96284fb..a64a50e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -57,9 +57,9 @@ endif() # add warnings/coverage info on handwritten files # but not the autogenerated ones... set_source_files_properties(${SERVICE_CXX_SOURCES} - PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} ${GCOV_FLAGS} -g -std=c++11") + PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -std=c++11") set_source_files_properties(${SERVICE_C_SOURCES} - PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} ${GCOV_FLAGS} -g -std=c99") + PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -std=c99") # add the bin dir to our include path so our code can find the generated header files include_directories (${CMAKE_CURRENT_BINARY_DIR}) @@ -69,6 +69,6 @@ include_directories (${CMAKE_SOURCE_DIR}) link_directories (${SERVICE_DEPS_LIBRARY_DIRS}) add_executable (${SERVICE_EXEC} main.cpp) -set_source_files_properties(${SERVICE_SOURCES} main.cpp PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -g -std=c++11") -target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${GCOV_LIBS} ${URLDISPATCHER_LIBRARIES}) +set_source_files_properties(${SERVICE_SOURCES} main.cpp PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -std=c++11") +target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES}) install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}) diff --git a/src/actions-live.cpp b/src/actions-live.cpp index 271d2f3..5c49bc4 100644 --- a/src/actions-live.cpp +++ b/src/actions-live.cpp @@ -17,14 +17,18 @@ * Charles Kerr <charles.kerr@canonical.com> */ +#include <datetime/dbus-shared.h> #include <datetime/actions-live.h> -#ifdef HAS_URLDISPATCHER -#include <lomiri-url-dispatcher.h> -#endif - #include <glib.h> +#include <sstream> + +extern "C" +{ + #include <ayatana/common/utils.h> +} + namespace ayatana { namespace indicator { namespace datetime { @@ -38,53 +42,55 @@ LiveActions::LiveActions(const std::shared_ptr<State>& state_in): { } -void LiveActions::execute_command(const std::string& cmdstr) -{ - const auto cmd = cmdstr.c_str(); - g_debug("Issuing command '%s'", cmd); +/*** +**** +***/ - GError* error = nullptr; - if (!g_spawn_command_line_async(cmd, &error)) +void LiveActions::open_alarm_app() +{ + if (ayatana_common_utils_is_lomiri()) { - g_warning("Unable to start \"%s\": %s", cmd, error->message); - g_error_free(error); + ayatana_common_utils_open_url("alarm://"); + } + else + { + ayatana_common_utils_execute_command("evolution -c calendar"); } } -void LiveActions::dispatch_url(const std::string& url) +void LiveActions::open_calendar_app(const DateTime& dt) { - g_debug("Dispatching url '%s'", url.c_str()); -#ifdef HAS_URLDISPATCHER - lomiri_url_dispatch_send(url.c_str(), nullptr, nullptr); -#else - // FIXME: Deal with this, if we build without liburl-dispatcher... -#endif + if (ayatana_common_utils_is_lomiri()) + { + const auto utc = dt.to_timezone("UTC"); + auto cmd = utc.format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00"); + ayatana_common_utils_open_url(cmd.c_str()); + } + else + { + const auto utc = dt.start_of_day().to_timezone("UTC"); + auto cmd = utc.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\""); + ayatana_common_utils_execute_command(cmd.c_str()); + } } -/*** -**** -***/ - -void LiveActions::desktop_open_settings_app() +void LiveActions::open_settings_app() { - if (g_getenv ("MIR_SOCKET") != nullptr) + if (ayatana_common_utils_is_lomiri()) + { + ayatana_common_utils_open_url("settings:///system/time-date"); + } + else if (ayatana_common_utils_is_unity()) { - dispatch_url("settings:///system/time-date"); + ayatana_common_utils_execute_command("unity-control-center datetime"); + } + else if (ayatana_common_utils_is_mate()) + { + ayatana_common_utils_execute_command("mate-time-admin"); } else { - if ((g_strcmp0 (g_getenv ("XDG_CURRENT_DESKTOP"), "Unity") == 0)) - { - execute_command("unity-control-center datetime"); - } - else if ((g_strcmp0 (g_getenv ("XDG_CURRENT_DESKTOP"), "MATE") == 0)) - { - execute_command("mate-time-admin"); - } - else - { - execute_command("gnome-control-center datetime"); - } + ayatana_common_utils_execute_command("gnome-control-center datetime"); } } @@ -120,61 +126,25 @@ bool LiveActions::desktop_has_calendar_app() const return have_calendar; } -void LiveActions::desktop_open_alarm_app() -{ - execute_command("evolution -c calendar"); -} - -void LiveActions::desktop_open_appointment(const Appointment& appt) +void LiveActions::open_appointment(const Appointment& appt, const DateTime& date) { - desktop_open_calendar_app(appt.begin); -} - -void LiveActions::desktop_open_calendar_app(const DateTime& dt) -{ - const auto utc = dt.start_of_day().to_timezone("UTC"); - auto cmd = utc.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\""); - execute_command(cmd.c_str()); -} - -/*** -**** -***/ - -void LiveActions::phone_open_alarm_app() -{ - dispatch_url("appid://com.ubuntu.clock/clock/current-user-version"); -} - -void LiveActions::phone_open_appointment(const Appointment& appt) -{ - if (!appt.activation_url.empty()) { - dispatch_url(appt.activation_url); + ayatana_common_utils_open_url(appt.activation_url.c_str()); } else switch (appt.type) { case Appointment::UBUNTU_ALARM: - phone_open_alarm_app(); + open_alarm_app(); break; + case Appointment::EVENT: default: - phone_open_calendar_app(appt.begin); + open_calendar_app(date); + break; } } -void LiveActions::phone_open_calendar_app(const DateTime&) -{ - // does calendar app have a mechanism for specifying dates? - dispatch_url("appid://com.ubuntu.calendar/calendar/current-user-version"); -} - -void LiveActions::phone_open_settings_app() -{ - dispatch_url("settings:///system/time-date"); -} - /*** **** ***/ @@ -235,7 +205,7 @@ on_datetime1_proxy_ready (GObject * object G_GNUC_UNUSED, else { g_dbus_proxy_call(proxy, - "SetTimezone", + Bus::Timedate1::Methods::SET_TIMEZONE, g_variant_new ("(sb)", data->tzid.c_str(), TRUE), G_DBUS_CALL_FLAGS_NONE, -1, @@ -263,9 +233,9 @@ void LiveActions::set_location(const std::string& tzid, const std::string& name) g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", + Bus::Timedate1::BUSNAME, + Bus::Timedate1::ADDR, + Bus::Timedate1::IFACE, nullptr, on_datetime1_proxy_ready, data); diff --git a/src/actions.cpp b/src/actions.cpp index 93629a0..315340a 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -48,13 +48,8 @@ DateTime datetime_from_timet_variant(GVariant* v) return DateTime::NowLocal(); } -bool lookup_appointment_by_uid_variant(const std::shared_ptr<State>& state, GVariant* vuid, Appointment& setme) +bool lookup_appointment_by_uid(const std::shared_ptr<State>& state, const gchar* uid, Appointment& setme) { - g_return_val_if_fail(vuid != nullptr, false); - g_return_val_if_fail(g_variant_type_equal(G_VARIANT_TYPE_STRING,g_variant_get_type(vuid)), false); - const auto uid = g_variant_get_string(vuid, nullptr); - g_return_val_if_fail(uid && *uid, false); - for(const auto& appt : state->calendar_upcoming->appointments().get()) { if (appt.uid == uid) @@ -67,46 +62,28 @@ bool lookup_appointment_by_uid_variant(const std::shared_ptr<State>& state, GVar return false; } -void on_desktop_appointment_activated (GSimpleAction*, GVariant *vuid, gpointer gself) +void on_appointment_activated (GSimpleAction*, GVariant *vdata, gpointer gself) { auto self = static_cast<Actions*>(gself); Appointment appt; - if (lookup_appointment_by_uid_variant(self->state(), vuid, appt)) - self->desktop_open_appointment(appt); + const gchar* uid = nullptr; + gint64 time = 0; + g_variant_get(vdata, "(&sx)", &uid, &time); + if (lookup_appointment_by_uid(self->state(), uid, appt)) + self->open_appointment(appt, DateTime::Local(time)); } -void on_desktop_alarm_activated (GSimpleAction*, GVariant*, gpointer gself) +void on_alarm_activated (GSimpleAction*, GVariant*, gpointer gself) { - static_cast<Actions*>(gself)->desktop_open_alarm_app(); + static_cast<Actions*>(gself)->open_alarm_app(); } -void on_desktop_calendar_activated (GSimpleAction*, GVariant* vt, gpointer gself) +void on_calendar_activated (GSimpleAction*, GVariant* vt, gpointer gself) { const auto dt = datetime_from_timet_variant(vt); - static_cast<Actions*>(gself)->desktop_open_calendar_app(dt); + static_cast<Actions*>(gself)->open_calendar_app(dt); } -void on_desktop_settings_activated (GSimpleAction*, GVariant*, gpointer gself) +void on_settings_activated (GSimpleAction*, GVariant*, gpointer gself) { - static_cast<Actions*>(gself)->desktop_open_settings_app(); -} - -void on_phone_appointment_activated (GSimpleAction*, GVariant *vuid, gpointer gself) -{ - auto self = static_cast<Actions*>(gself); - Appointment appt; - if (lookup_appointment_by_uid_variant(self->state(), vuid, appt)) - self->phone_open_appointment(appt); -} -void on_phone_alarm_activated (GSimpleAction*, GVariant*, gpointer gself) -{ - static_cast<Actions*>(gself)->phone_open_alarm_app(); -} -void on_phone_calendar_activated (GSimpleAction*, GVariant* vt, gpointer gself) -{ - const auto dt = datetime_from_timet_variant(vt); - static_cast<Actions*>(gself)->phone_open_calendar_app(dt); -} -void on_phone_settings_activated (GSimpleAction*, GVariant*, gpointer gself) -{ - static_cast<Actions*>(gself)->phone_open_settings_app(); + static_cast<Actions*>(gself)->open_settings_app(); } void on_set_location(GSimpleAction * /*action*/, @@ -134,9 +111,9 @@ void on_calendar_active_changed(GSimpleAction * /*action*/, } } -void on_calendar_activated(GSimpleAction * /*action*/, - GVariant * state, - gpointer gself) +void on_calendar_date_activated(GSimpleAction * /*action*/, + GVariant * state, + gpointer gself) { const time_t t = g_variant_get_int64(state); @@ -198,15 +175,15 @@ Actions::Actions(const std::shared_ptr<State>& state): { GActionEntry entries[] = { - { "desktop.open-appointment", on_desktop_appointment_activated, "s", nullptr }, - { "desktop.open-alarm-app", on_desktop_alarm_activated }, - { "desktop.open-calendar-app", on_desktop_calendar_activated, "x", nullptr }, - { "desktop.open-settings-app", on_desktop_settings_activated }, + { "desktop.open-appointment", on_appointment_activated, "(sx)", nullptr }, + { "desktop.open-alarm-app", on_alarm_activated }, + { "desktop.open-calendar-app", on_calendar_activated, "x", nullptr }, + { "desktop.open-settings-app", on_settings_activated }, - { "phone.open-appointment", on_phone_appointment_activated, "s", nullptr }, - { "phone.open-alarm-app", on_phone_alarm_activated }, - { "phone.open-calendar-app", on_phone_calendar_activated, "x", nullptr }, - { "phone.open-settings-app", on_phone_settings_activated }, + { "phone.open-appointment", on_appointment_activated, "(sx)", nullptr }, + { "phone.open-alarm-app", on_alarm_activated }, + { "phone.open-calendar-app", on_calendar_activated, "x", nullptr }, + { "phone.open-settings-app", on_settings_activated }, { "calendar-active", nullptr, nullptr, "false", on_calendar_active_changed }, { "set-location", on_set_location, "s" } @@ -240,7 +217,7 @@ Actions::Actions(const std::shared_ptr<State>& state): v = create_calendar_state(state); a = g_simple_action_new_stateful("calendar", G_VARIANT_TYPE_INT64, v); g_action_map_add_action(gam, G_ACTION(a)); - g_signal_connect(a, "activate", G_CALLBACK(on_calendar_activated), this); + g_signal_connect(a, "activate", G_CALLBACK(on_calendar_date_activated), this); g_object_unref(a); /// diff --git a/src/awake.cpp b/src/awake.cpp index dd41c35..9ee337e 100644 --- a/src/awake.cpp +++ b/src/awake.cpp @@ -36,89 +36,38 @@ class Awake::Impl { public: - Impl(const std::string& app_name): + Impl(GDBusConnection* bus, const std::string& app_name): m_app_name(app_name), - m_cancellable(g_cancellable_new()) + m_cancellable(g_cancellable_new()), + m_system_bus{G_DBUS_CONNECTION(g_object_ref(bus))} { - g_bus_get(G_BUS_TYPE_SYSTEM, m_cancellable, on_system_bus_ready, this); + // ask repowerd to keep the system awake + static constexpr int32_t POWERD_SYS_STATE_ACTIVE = 1; + g_dbus_connection_call (m_system_bus, + BUS_POWERD_NAME, + BUS_POWERD_PATH, + BUS_POWERD_INTERFACE, + "requestSysState", + g_variant_new("(si)", m_app_name.c_str(), POWERD_SYS_STATE_ACTIVE), + G_VARIANT_TYPE("(s)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + m_cancellable, + on_force_awake_response, + this); + } ~Impl() { g_cancellable_cancel (m_cancellable); g_object_unref (m_cancellable); - - if (m_display_on_timer) - { - g_source_remove (m_display_on_timer); - m_display_on_timer = 0; - } - - if (m_system_bus != nullptr) - { - unforce_awake (); - remove_display_on_request (); - g_object_unref (m_system_bus); - } + unforce_awake (); + g_clear_object (&m_system_bus); } private: - static void on_system_bus_ready (GObject *, - GAsyncResult *res, - gpointer gself) - { - GError * error; - GDBusConnection * system_bus; - - error = nullptr; - system_bus = g_bus_get_finish (res, &error); - if (error != nullptr) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning ("Unable to get bus: %s", error->message); - - g_error_free (error); - } - else if (system_bus != nullptr) - { - auto self = static_cast<Impl*>(gself); - - self->m_system_bus = G_DBUS_CONNECTION (g_object_ref (system_bus)); - - // ask powerd to keep the system awake - static constexpr int32_t POWERD_SYS_STATE_ACTIVE = 1; - g_dbus_connection_call (system_bus, - BUS_POWERD_NAME, - BUS_POWERD_PATH, - BUS_POWERD_INTERFACE, - "requestSysState", - g_variant_new("(si)", self->m_app_name.c_str(), POWERD_SYS_STATE_ACTIVE), - G_VARIANT_TYPE("(s)"), - G_DBUS_CALL_FLAGS_NONE, - -1, - self->m_cancellable, - on_force_awake_response, - self); - - // ask unity-system-compositor to turn on the screen - g_dbus_connection_call (system_bus, - BUS_SCREEN_NAME, - BUS_SCREEN_PATH, - BUS_SCREEN_INTERFACE, - "keepDisplayOn", - nullptr, - G_VARIANT_TYPE("(i)"), - G_DBUS_CALL_FLAGS_NONE, - -1, - self->m_cancellable, - on_keep_display_on_response, - self); - - g_object_unref (system_bus); - } - } - static void on_force_awake_response (GObject * connection, GAsyncResult * res, gpointer gself) @@ -146,60 +95,11 @@ private: g_clear_pointer (&self->m_awake_cookie, g_free); g_variant_get (args, "(s)", &self->m_awake_cookie); - g_debug ("m_awake_cookie is now '%s'", self->m_awake_cookie); - - g_variant_unref (args); - } - } - - static void on_keep_display_on_response (GObject * connection, - GAsyncResult * res, - gpointer gself) - { - GError * error; - GVariant * args; - - error = nullptr; - args = g_dbus_connection_call_finish (G_DBUS_CONNECTION(connection), - res, - &error); - if (error != nullptr) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && - !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) - { - g_warning ("Unable to turn on the screen: %s", error->message); - } - - g_error_free (error); - } - else - { - auto self = static_cast<Impl*>(gself); - - self->m_display_on_cookie = NO_DISPLAY_ON_COOKIE; - g_variant_get (args, "(i)", &self->m_display_on_cookie); - g_debug ("m_display_on_cookie is now '%d'", self->m_display_on_cookie); - - self->m_display_on_timer = g_timeout_add_seconds (self->m_display_on_seconds, - on_display_on_timer, - gself); g_variant_unref (args); } } - static gboolean on_display_on_timer (gpointer gself) - { - auto self = static_cast<Impl*>(gself); - - self->m_display_on_timer = 0; - self->remove_display_on_request(); - - return G_SOURCE_REMOVE; - } - - void unforce_awake () { g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); @@ -215,7 +115,7 @@ private: nullptr, G_DBUS_CALL_FLAGS_NONE, -1, - nullptr, + m_cancellable, nullptr, nullptr); @@ -223,56 +123,18 @@ private: } } - void remove_display_on_request () - { - g_return_if_fail (G_IS_DBUS_CONNECTION(m_system_bus)); - - if (m_display_on_cookie != NO_DISPLAY_ON_COOKIE) - { - g_dbus_connection_call (m_system_bus, - BUS_SCREEN_NAME, - BUS_SCREEN_PATH, - BUS_SCREEN_INTERFACE, - "removeDisplayOnRequest", - g_variant_new("(i)", m_display_on_cookie), - nullptr, - G_DBUS_CALL_FLAGS_NONE, - -1, - nullptr, - nullptr, - nullptr); - - m_display_on_cookie = NO_DISPLAY_ON_COOKIE; - } - } - const std::string m_app_name; GCancellable * m_cancellable = nullptr; GDBusConnection * m_system_bus = nullptr; char * m_awake_cookie = nullptr; - - /** - * As described by bug #1434637, alarms should have the display turn on, - * dim, and turn off "just like it would if you'd woken it up yourself". - * USC may be adding an intent-based bus API to handle this use case, - * e.g. turnDisplayOnTemporarily(intent), but there's no timeframe for it. - * - * Until that's avaialble, we can get close to Design's specs by - * requesting a display-on cookie and then releasing the cookie - * a moment later. */ - const guint m_display_on_seconds = 1; - guint m_display_on_timer = 0; - int32_t m_display_on_cookie = NO_DISPLAY_ON_COOKIE; - - static constexpr int32_t NO_DISPLAY_ON_COOKIE { std::numeric_limits<int32_t>::min() }; }; /*** **** ***/ -Awake::Awake(const std::string& app_name): - impl(new Impl(app_name)) +Awake::Awake(GDBusConnection* system_bus, const std::string& app_name): + impl{new Impl{system_bus, app_name}} { } diff --git a/src/clock.cpp b/src/clock.cpp index a2ef387..47eebe6 100644 --- a/src/clock.cpp +++ b/src/clock.cpp @@ -155,7 +155,7 @@ private: } /** - *** DBus Chatter: com.canonical.powerd + *** DBus Chatter: com.lomiri.Repowerd *** *** Fire Clock::minute_changed() signal when powerd says the system's *** has awoken from sleep -- the old timestamp is likely out-of-date diff --git a/src/date-time.cpp b/src/date-time.cpp index 169426c..911fb7a 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -245,11 +245,21 @@ bool DateTime::operator<(const DateTime& that) const return g_date_time_compare(get(), that.get()) < 0; } +bool DateTime::operator>(const DateTime& that) const +{ + return g_date_time_compare(get(), that.get()) > 0; +} + bool DateTime::operator<=(const DateTime& that) const { return g_date_time_compare(get(), that.get()) <= 0; } +bool DateTime::operator>=(const DateTime& that) const +{ + return g_date_time_compare(get(), that.get()) >= 0; +} + bool DateTime::operator!=(const DateTime& that) const { // return true if this isn't set, or if it's not equal diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp index fc6a45b..4228258 100644 --- a/src/engine-eds.cpp +++ b/src/engine-eds.cpp @@ -1260,11 +1260,6 @@ private: **** ***/ -EdsEngine::EdsEngine(): - p(new Impl(std::shared_ptr<Myself>(new Myself))) -{ -} - EdsEngine::EdsEngine(const std::shared_ptr<Myself> &myself): p(new Impl(myself)) { diff --git a/src/haptic.cpp b/src/haptic.cpp index 7430c04..dc2cb82 100644 --- a/src/haptic.cpp +++ b/src/haptic.cpp @@ -37,9 +37,10 @@ class Haptic::Impl { public: - Impl(const Mode& mode): + Impl(const Mode& mode, bool repeat): m_mode(mode), - m_cancellable(g_cancellable_new()) + m_cancellable(g_cancellable_new()), + m_repeat(repeat) { g_bus_get (G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready, this); } @@ -93,11 +94,15 @@ private: // one second on, one second off. m_pattern = std::vector<uint32_t>({1000u, 1000u}); break; + } - // Set up a loop to keep repeating the pattern - auto msec = std::accumulate(m_pattern.begin(), m_pattern.end(), 0u); - m_tag = g_timeout_add(msec, call_vibrate_pattern_static, this); + if (m_repeat) + { + // Set up a loop to keep repeating the pattern + auto msec = std::accumulate(m_pattern.begin(), m_pattern.end(), 0u); + m_tag = g_timeout_add(msec, call_vibrate_pattern_static, this); + } call_vibrate_pattern(); } @@ -146,14 +151,15 @@ private: GDBusConnection * m_bus = nullptr; std::vector<uint32_t> m_pattern; guint m_tag = 0; + bool m_repeat = false; }; /*** **** ***/ -Haptic::Haptic(const Mode& mode): - impl(new Impl (mode)) +Haptic::Haptic(const Mode& mode, bool repeat): + impl(new Impl (mode, repeat)) { } diff --git a/src/main.cpp b/src/main.cpp index fdd84b5..6cad190 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,6 +25,7 @@ #include <datetime/exporter.h> #include <datetime/locations-settings.h> #include <datetime/menu.h> +#include <datetime/myself.h> #include <datetime/planner-aggregate.h> #include <datetime/planner-snooze.h> #include <datetime/planner-range.h> @@ -60,7 +61,7 @@ namespace if (!g_strcmp0("lightdm", g_get_user_name())) engine.reset(new MockEngine); else - engine.reset(new EdsEngine); + engine.reset(new EdsEngine(std::shared_ptr<Myself>(new Myself))); return engine; } @@ -70,7 +71,7 @@ namespace { // create the live objects auto live_settings = std::make_shared<LiveSettings>(); - auto live_timezones = std::make_shared<LiveTimezones>(live_settings); + auto live_timezones = std::make_shared<LiveTimezones>(live_settings, timezone_); auto live_clock = std::make_shared<LiveClock>(timezone_); // create a full-month planner currently pointing to the current month @@ -131,8 +132,17 @@ main(int /*argc*/, char** /*argv*/) bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); textdomain(GETTEXT_PACKAGE); + // get the system bus + GError* error {}; + auto system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &error); + if (error != nullptr) { + g_critical("Unable to get system bus: %s", error->message); + g_clear_error(&error); + return 0; + } + auto engine = create_engine(); - auto timezone_ = std::make_shared<TimedatedTimezone>(); + auto timezone_ = std::make_shared<TimedatedTimezone>(system_bus); auto state = create_state(engine, timezone_); auto actions = std::make_shared<LiveActions>(state); MenuFactory factory(actions, state); @@ -141,14 +151,23 @@ main(int /*argc*/, char** /*argv*/) // set up the snap decisions auto snooze_planner = std::make_shared<SnoozePlanner>(state->settings, state->clock); auto notification_engine = std::make_shared<ain::Engine>("ayatana-indicator-datetime-service"); - std::unique_ptr<Snap> snap (new Snap(notification_engine, state->settings)); + auto sound_builder = std::make_shared<uin::DefaultSoundBuilder>(); + std::unique_ptr<Snap> snap (new Snap(notification_engine, sound_builder, state->settings, system_bus)); auto alarm_queue = create_simple_alarm_queue(state->clock, snooze_planner, engine, timezone_); - auto on_snooze = [snooze_planner](const Appointment& appointment, const Alarm& alarm) { - snooze_planner->add(appointment, alarm); + auto on_response = [snooze_planner, actions](const Appointment& appointment, const Alarm& alarm, const Snap::Response& response) { + switch(response) { + case Snap::Response::Snooze: + snooze_planner->add(appointment, alarm); + break; + case Snap::Response::ShowApp: + actions->open_appointment(appointment, appointment.begin); + break; + case Snap::Response::None: + break; + } }; - auto on_ok = [](const Appointment&, const Alarm&){}; - auto on_alarm_reached = [&engine, &snap, &on_snooze, &on_ok](const Appointment& appointment, const Alarm& alarm) { - (*snap)(appointment, alarm, on_snooze, on_ok); + auto on_alarm_reached = [&engine, &snap, &on_response](const Appointment& appointment, const Alarm& alarm) { + (*snap)(appointment, alarm, on_response); engine->disable_ubuntu_alarm(appointment); }; alarm_queue->alarm_reached().connect(on_alarm_reached); @@ -170,5 +189,6 @@ main(int /*argc*/, char** /*argv*/) g_main_loop_run(loop); g_main_loop_unref(loop); + g_clear_object(&system_bus); return 0; } diff --git a/src/menu.cpp b/src/menu.cpp index d19ad73..b1ac75c 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -22,6 +22,8 @@ #include <datetime/state.h> #include <glib/gi18n.h> #include <gio/gio.h> +#include <algorithm> +#include <iterator> #include <vector> extern "C" @@ -58,11 +60,90 @@ GMenuModel* Menu::menu_model() return G_MENU_MODEL(m_menu); } +/** + * To avoid a giant menu on the PC, and to avoid pushing lower menu items + * off-screen on the phone, the menu should show the + * next five calendar events, if any. + * + * The list might include multiple occurrences of the same event (bug 1515821). + */ +std::vector<Appointment> +Menu::get_display_appointments(const std::vector<Appointment>& appointments_in, + const DateTime& now, + unsigned int max_items) +{ + std::vector<Appointment> appointments; + std::copy_if(appointments_in.begin(), + appointments_in.end(), + std::back_inserter(appointments), + [now](const Appointment& a){return a.end >= now;}); + + if (appointments.size() > max_items) + { + const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds()); + const auto start_of_day = now.start_of_day(); + const auto end_of_day = now.end_of_day(); + + /* + * If there are more than five, the events shown should be, in order of priority: + * 1. any events that start or end (bug 1329048) after the current minute today; + * 2. any full-day events that span all of today (bug 1302004); + * 3. any events that start or end tomorrow; + * 4. any events that start or end the day after tomorrow; and so on. + */ + auto compare = [next_minute, start_of_day, end_of_day]( + const Appointment& a, + const Appointment& b) + { + const bool a_later_today = (a.begin >= next_minute) || (a.end <= end_of_day); + const bool b_later_today = (b.begin >= next_minute) || (b.end <= end_of_day); + if (a_later_today != b_later_today) + return a_later_today; + + const bool a_full_day_today = (a.begin <= start_of_day) && (end_of_day <= a.end); + const bool b_full_day_today = (b.begin <= start_of_day) && (end_of_day <= b.end); + if (a_full_day_today != b_full_day_today) + return a_full_day_today; + + const bool a_after_today = (a.begin > end_of_day) || (a.end > end_of_day); + const bool b_after_today = (a.begin > end_of_day) || (a.end > end_of_day); + if (a_after_today != b_after_today) + return a_after_today; + if (a.begin != b.begin) + return a.begin < b.begin; + if (b.end != b.end) + return a.end < b.end; + + return false; + }; + std::sort(appointments.begin(), appointments.end(), compare); + appointments.resize(max_items); + } + + /* + * However, the display order should be the reverse: full-day events + * first (since they start first), part-day events afterward in + * chronological order. If multiple events have exactly the same start+end + * time, they should be sorted alphabetically. + */ + auto compare = [](const Appointment& a, const Appointment& b) + { + if (a.begin != b.begin) + return a.begin < b.begin; + + if (a.end != b.end) + return a.end < b.end; + + return a.summary < b.summary; + }; + std::sort(appointments.begin(), appointments.end(), compare); + return appointments; +} + /**** ***** ****/ - #define ALARM_ICON_NAME "alarm-clock" #define CALENDAR_ICON_NAME "calendar" @@ -150,25 +231,19 @@ protected: void update_upcoming() { - // The usual case is on desktop (and /only/ case on phone) - // is that we're looking at the current date and want to see - // "the next five calendar events, if any." - // - // However on the Desktop when the user clicks onto a different - // calendar date, show the next five calendar events starting - // from the beginning of that clicked day. - DateTime begin; + // The usual case is to show events germane to the current time. + // However when the user clicks onto a different calendar date, + // we pick events starting from the beginning of that clicked day. const auto now = m_state->clock->localtime(); const auto calendar_day = m_state->calendar_month->month().get(); - if ((profile() == Desktop) && !DateTime::is_same_day(now, calendar_day)) - begin = calendar_day.start_of_day(); - else - begin = now.start_of_minute(); + const auto begin = DateTime::is_same_day(now, calendar_day) + ? now.start_of_minute() + : calendar_day.start_of_day(); - std::vector<Appointment> upcoming; - for(const auto& a : m_state->calendar_upcoming->appointments().get()) - if (begin <= a.begin) - upcoming.push_back(a); + auto upcoming = get_display_appointments( + m_state->calendar_upcoming->appointments().get(), + begin + ); if (m_upcoming != upcoming) { @@ -339,9 +414,12 @@ private: if (!appt.color.empty()) g_menu_item_set_attribute (menu_item, "x-ayatana-color", "s", appt.color.c_str()); - if (action_name != nullptr) + if (action_name != nullptr) { g_menu_item_set_action_and_target_value (menu_item, action_name, - g_variant_new_string (appt.uid.c_str())); + g_variant_new ("(sx)", + appt.uid.c_str(), + unix_time)); + } g_menu_append_item (menu, menu_item); g_object_unref (menu_item); diff --git a/src/myself.cpp b/src/myself.cpp index 9c02054..ae2f061 100644 --- a/src/myself.cpp +++ b/src/myself.cpp @@ -25,6 +25,8 @@ #include <libaccounts-glib/accounts-glib.h> #endif +#include <libaccounts-glib/ag-account.h> + #include <algorithm> namespace ayatana { diff --git a/src/notifications.cpp b/src/notifications.cpp index 051653d..b36227b 100644 --- a/src/notifications.cpp +++ b/src/notifications.cpp @@ -21,10 +21,23 @@ #include <libnotify/notify.h> +#include <messaging-menu/messaging-menu-app.h> +#include <messaging-menu/messaging-menu-message.h> + +#ifdef HAS_URLDISPATCHER +#include <lomiri-url-dispatcher.h> +#endif + +#include <uuid/uuid.h> + +#include <gio/gdesktopappinfo.h> + #include <map> #include <set> #include <string> #include <vector> +#include <memory> + namespace ayatana { namespace indicator { @@ -45,9 +58,13 @@ public: std::string m_body; std::string m_icon_name; std::chrono::seconds m_duration; + gint64 m_start_time {}; std::set<std::string> m_string_hints; std::vector<std::pair<std::string,std::string>> m_actions; std::function<void(const std::string&)> m_closed_callback; + std::function<void()> m_timeout_callback; + bool m_show_notification_bubble; + bool m_post_to_messaging_menu; }; Builder::Builder(): @@ -101,6 +118,30 @@ Builder::set_closed_callback (std::function<void (const std::string&)> cb) impl->m_closed_callback.swap (cb); } +void +Builder::set_timeout_callback (std::function<void()> cb) +{ + impl->m_timeout_callback.swap (cb); +} + +void +Builder::set_start_time (uint64_t time) +{ + impl->m_start_time = time; +} + +void +Builder::set_show_notification_bubble (bool show) +{ + impl->m_show_notification_bubble = show; +} + +void +Builder::set_post_to_messaging_menu (bool post) +{ + impl->m_post_to_messaging_menu = post; +} + /*** **** ***/ @@ -110,7 +151,14 @@ class Engine::Impl struct notification_data { std::shared_ptr<NotifyNotification> nn; - std::function<void(const std::string&)> closed_callback; + Builder::Impl data; + }; + + struct messaging_menu_data + { + std::string msg_id; + std::function<void()> callback; + Engine::Impl *self; }; public: @@ -120,13 +168,23 @@ public: { if (!notify_init(app_name.c_str())) g_critical("Unable to initialize libnotify!"); + + // messaging menu + auto app_id = calendar_app_id(); + if (!app_id.empty()) { + m_messaging_app.reset(messaging_menu_app_new(app_id.c_str()), g_object_unref); + messaging_menu_app_register(m_messaging_app.get()); + } } ~Impl() { close_all (); + remove_all (); notify_uninit (); + if (m_messaging_app) + messaging_menu_app_unregister (m_messaging_app.get()); } const std::string& app_name() const @@ -217,10 +275,15 @@ public: notification_key_quark(), GINT_TO_POINTER(key)); - m_notifications[key] = { nn, info.m_closed_callback }; + m_notifications[key] = { nn, info }; g_signal_connect (nn.get(), "closed", G_CALLBACK(on_notification_closed), this); + if (!info.m_show_notification_bubble) { + post(info); + return ret; + } + GError * error = nullptr; if (notify_notification_show(nn.get(), &error)) { @@ -238,6 +301,76 @@ public: return ret; } + std::string post(const Builder::Impl& data) + { + if (!data.m_post_to_messaging_menu) { + return ""; + } + + if (!m_messaging_app) { + return std::string(); + } + uuid_t message_uuid; + uuid_generate(message_uuid); + + char uuid_buf[37]; + uuid_unparse(message_uuid, uuid_buf); + const std::string message_id(uuid_buf); + + // use full icon path name, "calendar-app" does not work with themed icons + auto icon_file = g_file_new_for_path(calendar_app_icon().c_str()); + // messaging_menu_message_new: will take control of icon object + GIcon *icon = g_file_icon_new(icon_file); + g_object_unref(icon_file); + + // check if source exists + if (!messaging_menu_app_has_source(m_messaging_app.get(), m_app_name.c_str())) + messaging_menu_app_append_source(m_messaging_app.get(), m_app_name.c_str(), nullptr, "Calendar"); + + auto msg = messaging_menu_message_new(message_id.c_str(), + icon, + data.m_title.c_str(), + nullptr, + data.m_body.c_str(), + data.m_start_time * G_USEC_PER_SEC); // secs -> microsecs + if (msg) + { + std::shared_ptr<messaging_menu_data> msg_data(new messaging_menu_data{message_id, data.m_timeout_callback, this}); + m_messaging_messages[message_id] = msg_data; + g_signal_connect(G_OBJECT(msg), "activate", + G_CALLBACK(on_message_activated), msg_data.get()); + messaging_menu_app_append_message(m_messaging_app.get(), msg, m_app_name.c_str(), false); + + // we use that to keep track of messaging, in case of message get cleared from menu + g_object_set_data_full(G_OBJECT(msg), "destroy-notify", msg_data.get(), on_message_destroyed); + // keep the message control with message_menu + g_object_unref(msg); + + return message_id; + } else { + g_warning("Fail to create messaging menu message"); + } + return ""; + } + + void remove (const std::string &key) + { + auto it = m_messaging_messages.find(key); + if (it != m_messaging_messages.end()) + { + // tell the server to remove message + messaging_menu_app_remove_message_by_id(m_messaging_app.get(), it->second->msg_id.c_str()); + // message will be remove by on_message_destroyed cb. + } + } + + void remove_all () + { + // call remove() on all our keys + while (!m_messaging_messages.empty()) + remove(m_messaging_messages.begin()->first); + } + private: const std::set<std::string>& server_caps() const @@ -279,6 +412,28 @@ private: static_cast<Impl*>(gself)->remove_closed_notification(GPOINTER_TO_INT(gkey)); } + static void on_message_activated (MessagingMenuMessage *, + const char *, + GVariant *, + gpointer data) + { + auto msg_data = static_cast<messaging_menu_data*>(data); + auto it = msg_data->self->m_messaging_messages.find(msg_data->msg_id); + g_return_if_fail (it != msg_data->self->m_messaging_messages.end()); + const auto& ndata = it->second; + + if (ndata->callback) + ndata->callback(); + } + + static void on_message_destroyed(gpointer data) + { + auto msg_data = static_cast<messaging_menu_data*>(data); + auto it = msg_data->self->m_messaging_messages.find(msg_data->msg_id); + if (it != msg_data->self->m_messaging_messages.end()) + msg_data->self->m_messaging_messages.erase(it); + } + void remove_closed_notification (int key) { auto it = m_notifications.find(key); @@ -286,25 +441,67 @@ private: const auto& ndata = it->second; auto nn = ndata.nn.get(); - if (ndata.closed_callback) + + if (ndata.data.m_closed_callback) { std::string action; - const GQuark q = notification_action_quark(); const gpointer p = g_object_get_qdata(G_OBJECT(nn), q); if (p != nullptr) action = static_cast<const char*>(p); - ndata.closed_callback (action); + ndata.data.m_closed_callback (action); + // empty action means that the notification got timeout + // post a message on messaging menu + if (action.empty()) + post(ndata.data); } m_notifications.erase(it); } + static std::string calendar_app_id() + { +#ifdef HAS_URLDISPATCHER + auto urls = g_strsplit("calendar://", ",", 0); + auto appids = lomiri_url_dispatch_url_appid(const_cast<const gchar**>(urls)); + g_strfreev(urls); + std::string result; + if (appids != nullptr) { + // Due the use of old API by messaging_menu we need append a extra ".desktop" to the app_id. + result = std::string(appids[0]) + ".desktop"; + g_strfreev(appids); + } + return result; +#else + return std::string(); +#endif + } + + static std::string calendar_app_icon() + { + auto app_desktop = g_desktop_app_info_new(calendar_app_id().c_str()); + if (app_desktop != nullptr) { + auto icon_name = g_desktop_app_info_get_string(app_desktop, "Icon"); + g_object_unref(app_desktop); + if (icon_name) { + std::string result(icon_name); + g_free(icon_name); + return result; + } + } + g_warning("Fail to get calendar icon"); + return std::string(); + } + /*** **** ***/ + // messaging menu + std::shared_ptr<MessagingMenuApp> m_messaging_app; + std::map<std::string, std::shared_ptr<messaging_menu_data> > m_messaging_messages; + const std::string m_app_name; // key-to-data diff --git a/src/planner-snooze.cpp b/src/planner-snooze.cpp index cb365ca..b81c912 100644 --- a/src/planner-snooze.cpp +++ b/src/planner-snooze.cpp @@ -59,7 +59,9 @@ public: appt.alarms.push_back(alarm); // reschedule the alarm to go off N minutes from now - const auto offset = std::chrono::minutes(m_settings->snooze_duration.get()); + // also take into count every whole minute since the alarm went off + const auto offset_to_now = std::chrono::duration_cast<std::chrono::minutes>(std::chrono::microseconds(DateTime::NowLocal() - appt.begin)); + const auto offset = offset_to_now + std::chrono::minutes(m_settings->snooze_duration.get()); appt.begin += offset; appt.end += offset; appt.alarms[0].time += offset; diff --git a/src/planner-upcoming.cpp b/src/planner-upcoming.cpp index 918ebbe..149ac09 100644 --- a/src/planner-upcoming.cpp +++ b/src/planner-upcoming.cpp @@ -33,7 +33,7 @@ UpcomingPlanner::UpcomingPlanner(const std::shared_ptr<RangePlanner>& range_plan { date().changed().connect([this](const DateTime& dt){ // set the range to the upcoming month - const auto b = dt.add_days(-1).start_of_day(); + const auto b = dt.start_of_day(); const auto e = b.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)); diff --git a/src/settings-live.cpp b/src/settings-live.cpp index 5c2addb..dc90d0e 100644 --- a/src/settings-live.cpp +++ b/src/settings-live.cpp @@ -19,6 +19,11 @@ #include <datetime/settings-live.h> +extern "C" +{ + #include <ayatana/common/utils.h> +} + namespace ayatana { namespace indicator { namespace datetime { @@ -29,13 +34,15 @@ namespace datetime { LiveSettings::~LiveSettings() { + g_clear_object(&m_settings_general_notification); + g_clear_object(&m_settings_cal_notification); g_clear_object(&m_settings); } LiveSettings::LiveSettings(): m_settings(g_settings_new(SETTINGS_INTERFACE)) { - g_signal_connect (m_settings, "changed", G_CALLBACK(on_changed), this); + g_signal_connect (m_settings, "changed", G_CALLBACK(on_changed_ccid), this); // init the Properties from the GSettings backend update_custom_time_format(); @@ -58,6 +65,25 @@ LiveSettings::LiveSettings(): update_alarm_haptic(); update_snooze_duration(); + if (ayatana_common_utils_is_lomiri()) + { + m_settings_cal_notification = g_settings_new_with_path(SETTINGS_NOTIFY_SCHEMA_ID, SETTINGS_NOTIFY_CALENDAR_PATH); + m_settings_general_notification = g_settings_new(SETTINGS_NOTIFY_APPS_SCHEMA_ID); + g_signal_connect (m_settings_cal_notification, "changed", G_CALLBACK(on_changed_cal_notification), this); + g_signal_connect (m_settings_general_notification, "changed", G_CALLBACK(on_changed_general_notification), this); + update_cal_notification_enabled(); + update_cal_notification_sounds(); + update_cal_notification_vibrations(); + update_cal_notification_bubbles(); + update_cal_notification_list(); + update_vibrate_silent_mode(); + } + else + { + m_settings_cal_notification = NULL; + m_settings_general_notification = NULL; + } + // now listen for clients to change the properties s.t. we can sync update GSettings custom_time_format.changed().connect([this](const std::string& value){ @@ -140,6 +166,30 @@ LiveSettings::LiveSettings(): snooze_duration.changed().connect([this](unsigned int value){ g_settings_set_uint(m_settings, SETTINGS_SNOOZE_DURATION_S, value); }); + + cal_notification_enabled.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_ENABLED_KEY, value); + }); + + cal_notification_sounds.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_SOUNDS_KEY, value); + }); + + cal_notification_vibrations.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_VIBRATIONS_KEY, value); + }); + + cal_notification_bubbles.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_BUBBLES_KEY, value); + }); + + cal_notification_list.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_LIST_KEY, value); + }); + + vibrate_silent_mode.changed().connect([this](bool value){ + g_settings_set_boolean(m_settings_general_notification, SETTINGS_VIBRATE_SILENT_KEY, value); + }); } /*** @@ -261,18 +311,91 @@ void LiveSettings::update_snooze_duration() snooze_duration.set(g_settings_get_uint(m_settings, SETTINGS_SNOOZE_DURATION_S)); } +void LiveSettings::update_cal_notification_enabled() +{ + cal_notification_enabled.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_ENABLED_KEY)); +} + +void LiveSettings::update_cal_notification_sounds() +{ + cal_notification_sounds.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_SOUNDS_KEY)); +} + +void LiveSettings::update_cal_notification_vibrations() +{ + cal_notification_vibrations.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_VIBRATIONS_KEY)); +} + +void LiveSettings::update_cal_notification_bubbles() +{ + cal_notification_bubbles.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_BUBBLES_KEY)); +} + +void LiveSettings::update_cal_notification_list() +{ + cal_notification_list.set(g_settings_get_boolean(m_settings_cal_notification, SETTINGS_NOTIFY_LIST_KEY)); +} + +void LiveSettings::update_vibrate_silent_mode() +{ + vibrate_silent_mode.set(g_settings_get_boolean(m_settings_general_notification, SETTINGS_VIBRATE_SILENT_KEY)); +} + +/*** +**** +***/ + +void LiveSettings::on_changed_cal_notification(GSettings* /*settings*/, + gchar* key, + gpointer gself) +{ + static_cast<LiveSettings*>(gself)->update_key_cal_notification(key); +} + + +void LiveSettings::update_key_cal_notification(const std::string& key) +{ + if (key == SETTINGS_NOTIFY_ENABLED_KEY) + update_cal_notification_enabled(); + else if (key == SETTINGS_NOTIFY_SOUNDS_KEY) + update_cal_notification_sounds(); + else if (key == SETTINGS_NOTIFY_VIBRATIONS_KEY) + update_cal_notification_vibrations(); + else if (key == SETTINGS_NOTIFY_BUBBLES_KEY) + update_cal_notification_bubbles(); + else if (key == SETTINGS_NOTIFY_LIST_KEY) + update_cal_notification_list(); +} + +/*** +**** +***/ + +void LiveSettings::on_changed_general_notification(GSettings* /*settings*/, + gchar* key, + gpointer gself) +{ + static_cast<LiveSettings*>(gself)->update_key_general_notification(key); +} + +void LiveSettings::update_key_general_notification(const std::string& key) +{ + if (key == SETTINGS_VIBRATE_SILENT_KEY) + update_vibrate_silent_mode(); +} + /*** **** ***/ -void LiveSettings::on_changed(GSettings* /*settings*/, - gchar* key, - gpointer gself) +void LiveSettings::on_changed_ccid(GSettings* /*settings*/, + gchar* key, + gpointer gself) { - static_cast<LiveSettings*>(gself)->update_key(key); + static_cast<LiveSettings*>(gself)->update_key_ccid(key); } -void LiveSettings::update_key(const std::string& key) +void LiveSettings::update_key_ccid(const std::string& key) { if (key == SETTINGS_LOCATIONS_S) update_locations(); diff --git a/src/snap.cpp b/src/snap.cpp index f0300af..00c4743 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -53,19 +53,25 @@ class Snap::Impl public: Impl(const std::shared_ptr<ayatana::indicator::notifications::Engine>& engine, - const std::shared_ptr<const Settings>& settings): + const std::shared_ptr<ayatana::indicator::notifications::SoundBuilder>& sound_builder, + const std::shared_ptr<const Settings>& settings, + GDBusConnection* system_bus): m_engine(engine), + m_sound_builder(sound_builder), m_settings(settings), - m_cancellable(g_cancellable_new()) + m_cancellable(g_cancellable_new()), + m_system_bus{G_DBUS_CONNECTION(g_object_ref(system_bus))} { auto object_path = g_strdup_printf("/org/freedesktop/Accounts/User%lu", (gulong)getuid()); - accounts_service_sound_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, - G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, - "org.freedesktop.Accounts", - object_path, - m_cancellable, - on_sound_proxy_ready, - this); + + + accounts_service_sound_proxy_new(m_system_bus, + G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES, + "org.freedesktop.Accounts", + object_path, + m_cancellable, + on_sound_proxy_ready, + this); g_free(object_path); } @@ -74,6 +80,7 @@ public: g_cancellable_cancel(m_cancellable); g_clear_object(&m_cancellable); g_clear_object(&m_accounts_service_sound_proxy); + g_clear_object(&m_system_bus); for (const auto& key : m_notifications) m_engine->close (key); @@ -81,9 +88,14 @@ public: void operator()(const Appointment& appointment, const Alarm& alarm, - appointment_func snooze, - appointment_func ok) + response_func on_response) { + // If calendar notifications are disabled, don't show them + if (!appointment.is_ubuntu_alarm() && !calendar_notifications_are_enabled()) { + g_debug("Skipping disabled calendar event '%s' notification", appointment.summary.c_str()); + return; + } + /* Alarms and calendar events are treated differently. Alarms should require manual intervention to dismiss. Calendar events are less urgent and shouldn't require manual @@ -91,11 +103,14 @@ public: const bool interactive = appointment.is_ubuntu_alarm() && m_engine->supports_actions(); // force the system to stay awake - auto awake = std::make_shared<ain::Awake>(m_engine->app_name()); + std::shared_ptr<ain::Awake> awake; + if (appointment.is_ubuntu_alarm() || calendar_bubbles_enabled() || calendar_list_enabled()) { + awake = std::make_shared<ain::Awake>(m_system_bus, m_engine->app_name()); + } // calendar events are muted in silent mode; alarm clocks never are std::shared_ptr<ain::Sound> sound; - if (appointment.is_ubuntu_alarm() || !silent_mode()) { + if (appointment.is_ubuntu_alarm() || (calendar_sounds_enabled() && !silent_mode())) { // create the sound. const auto role = appointment.is_ubuntu_alarm() ? "alarm" : "alert"; const auto uri = get_alarm_uri(appointment, alarm, m_settings); @@ -106,18 +121,22 @@ public: // create the haptic feedback... std::shared_ptr<ain::Haptic> haptic; - if (should_vibrate()) { - const auto haptic_mode = m_settings->alarm_haptic.get(); - if (haptic_mode == "pulse") - haptic = std::make_shared<ain::Haptic>(ain::Haptic::MODE_PULSE); + if (should_vibrate() && (appointment.is_ubuntu_alarm() || calendar_vibrations_enabled())) { + // when in silent mode should only vibrate if user defined so + if (!silent_mode() || vibrate_in_silent_mode_enabled()) { + const auto haptic_mode = m_settings->alarm_haptic.get(); + if (haptic_mode == "pulse") + haptic = std::make_shared<ain::Haptic>(ain::Haptic::MODE_PULSE, appointment.is_ubuntu_alarm()); + } } // show a notification... const auto minutes = std::chrono::minutes(m_settings->alarm_duration.get()); ain::Builder b; b.set_body (appointment.summary); - b.set_icon_name ("alarm-clock"); + b.set_icon_name (appointment.is_ubuntu_alarm() ? "alarm-clock" : "calendar-app"); b.add_hint (ain::Builder::HINT_NONSHAPED_ICON); + b.set_start_time (appointment.begin.to_unix()); const char * timefmt; if (is_locale_12h()) { @@ -130,28 +149,53 @@ public: timefmt = _("%a, %H:%M"); } const auto timestr = appointment.begin.format(timefmt); - auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str()); + + const char * titlefmt; + if (appointment.is_ubuntu_alarm()) { + titlefmt = _("Alarm %s"); + } else { + titlefmt = _("Event %s"); + } + auto title = g_strdup_printf(titlefmt, timestr.c_str()); b.set_title (title); g_free (title); b.set_timeout (std::chrono::duration_cast<std::chrono::seconds>(minutes)); if (interactive) { b.add_hint (ain::Builder::HINT_SNAP); b.add_hint (ain::Builder::HINT_AFFIRMATIVE_HINT); - b.add_action ("ok", _("OK")); - b.add_action ("snooze", _("Snooze")); + b.add_action (ACTION_NONE, _("OK")); + b.add_action (ACTION_SNOOZE, _("Snooze")); + } else { + b.add_hint (ain::Builder::HINT_INTERACTIVE); + b.add_action (ACTION_SHOW_APP, _("OK")); } // add 'sound', 'haptic', and 'awake' objects to the capture so // they stay alive until the closed callback is called; i.e., // for the lifespan of the notficiation - b.set_closed_callback([appointment, alarm, snooze, ok, sound, awake, haptic] + b.set_closed_callback([appointment, alarm, on_response, sound, awake, haptic] (const std::string& action){ - if (action == "snooze") - snooze(appointment, alarm); + Snap::Response response; + if ((action == ACTION_SNOOZE) || (appointment.is_ubuntu_alarm() && action.empty())) + response = Snap::Response::Snooze; + else if (action == ACTION_SHOW_APP) + response = Snap::Response::ShowApp; else - ok(appointment, alarm); + response = Snap::Response::None; + + on_response(appointment, alarm, response); }); + //TODO: we need to extend it to support alarms appointments + if (!appointment.is_ubuntu_alarm()) { + b.set_timeout_callback([appointment, alarm, on_response](){ + on_response(appointment, alarm, Snap::Response::ShowApp); + }); + } + + b.set_show_notification_bubble(appointment.is_ubuntu_alarm() || calendar_bubbles_enabled()); + b.set_post_to_messaging_menu(appointment.is_ubuntu_alarm() || calendar_list_enabled()); + const auto key = m_engine->show(b); if (key) m_notifications.insert (key); @@ -159,12 +203,42 @@ public: private: + bool calendar_notifications_are_enabled() const + { + return m_settings->cal_notification_enabled.get(); + } + + bool calendar_sounds_enabled() const + { + return m_settings->cal_notification_sounds.get(); + } + + bool calendar_vibrations_enabled() const + { + return m_settings->cal_notification_vibrations.get(); + } + + bool calendar_bubbles_enabled() const + { + return m_settings->cal_notification_bubbles.get(); + } + + bool calendar_list_enabled() const + { + return m_settings->cal_notification_list.get(); + } + + bool vibrate_in_silent_mode_enabled() const + { + return m_settings->vibrate_silent_mode.get(); + } + static void on_sound_proxy_ready(GObject* /*source_object*/, GAsyncResult* res, gpointer gself) { GError * error; error = nullptr; - auto proxy = accounts_service_sound_proxy_new_for_bus_finish (res, &error); + auto proxy = accounts_service_sound_proxy_new_finish (res, &error); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) @@ -230,6 +304,11 @@ private: std::set<int> m_notifications; GCancellable * m_cancellable {nullptr}; AccountsServiceSound * m_accounts_service_sound_proxy {nullptr}; + GDBusConnection * m_system_bus {nullptr}; + + static constexpr char const * ACTION_NONE {"none"}; + static constexpr char const * ACTION_SNOOZE {"snooze"}; + static constexpr char const * ACTION_SHOW_APP {"show-app"}; }; /*** @@ -237,8 +316,10 @@ private: ***/ Snap::Snap(const std::shared_ptr<ayatana::indicator::notifications::Engine>& engine, - const std::shared_ptr<const Settings>& settings): - impl(new Impl(engine, settings)) + const std::shared_ptr<ayatana::indicator::notifications::SoundBuilder>& sound_builder, + const std::shared_ptr<const Settings>& settings, + GDBusConnection* system_bus): + impl(new Impl(engine, sound_builder, settings, system_bus)) { } @@ -249,10 +330,9 @@ Snap::~Snap() void Snap::operator()(const Appointment& appointment, const Alarm& alarm, - appointment_func show, - appointment_func ok) + response_func on_response) { - (*impl)(appointment, alarm, show, ok); + (*impl)(appointment, alarm, on_response); } /*** diff --git a/src/timezone-timedated.cpp b/src/timezone-timedated.cpp index d38557b..1b6497e 100644 --- a/src/timezone-timedated.cpp +++ b/src/timezone-timedated.cpp @@ -17,6 +17,7 @@ * Charles Kerr <charles.kerr@canonical.com> */ +#include <datetime/dbus-shared.h> #include <datetime/timezone-timedated.h> #include <gio/gio.h> @@ -36,166 +37,169 @@ class TimedatedTimezone::Impl { public: - Impl(TimedatedTimezone& owner, std::string filename): - m_owner(owner), - m_filename(filename) + Impl(TimedatedTimezone& owner, GDBusConnection* connection): + m_owner{owner}, + m_connection{G_DBUS_CONNECTION(g_object_ref(G_OBJECT(connection)))}, + m_cancellable{g_cancellable_new()} { - g_debug("Filename is '%s'", filename.c_str()); - monitor_timezone_property(); + // set the fallback value + m_owner.timezone.set("Etc/Utc"); + + // watch for timedate1 on the bus + m_watcher_id = g_bus_watch_name_on_connection( + m_connection, + Bus::Timedate1::BUSNAME, + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + on_timedate1_appeared, + on_timedate1_vanished, + this, + nullptr); + + // listen for changed properties + m_signal_subscription_id = g_dbus_connection_signal_subscribe( + m_connection, + Bus::Timedate1::IFACE, + Bus::Properties::IFACE, + Bus::Properties::Signals::PROPERTIES_CHANGED, + Bus::Timedate1::ADDR, + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + on_properties_changed, + this, + nullptr); } ~Impl() { - clear(); - } + g_cancellable_cancel(m_cancellable); + g_clear_object(&m_cancellable); -private: + g_bus_unwatch_name(m_watcher_id); - void clear() - { - if (m_connection && m_signal_subscription_id) - { - g_dbus_connection_signal_unsubscribe (m_connection, m_signal_subscription_id); - m_signal_subscription_id = 0; - } + g_dbus_connection_signal_unsubscribe(m_connection, m_signal_subscription_id); g_clear_object(&m_connection); } - static void on_properties_changed (GDBusConnection *connection G_GNUC_UNUSED, - const gchar *sender_name G_GNUC_UNUSED, - const gchar *object_path G_GNUC_UNUSED, - const gchar *interface_name G_GNUC_UNUSED, - const gchar *signal_name G_GNUC_UNUSED, - GVariant *parameters, - gpointer gself) - { - auto self = static_cast<Impl*>(gself); - const char *tz; - GVariant *changed_properties; - gchar **invalidated_properties; +private: - g_variant_get (parameters, "(s@a{sv}^as)", NULL, &changed_properties, &invalidated_properties); + static void on_timedate1_appeared(GDBusConnection * /*connection*/, + const gchar * name, + const gchar * /*name_owner*/, + gpointer gself) + { + g_debug("%s appeared on bus", name); - if (g_variant_lookup(changed_properties, "Timezone", "&s", &tz, NULL)) - self->notify_timezone(tz); - else if (g_strv_contains (invalidated_properties, "Timezone")) - self->notify_timezone(self->get_timezone_from_file(self->m_filename)); + static_cast<Impl*>(gself)->ask_for_timezone(); + } - g_variant_unref (changed_properties); - g_strfreev (invalidated_properties); + static void on_timedate1_vanished(GDBusConnection * /*connection*/, + const gchar * name, + gpointer /*gself*/) + { + g_debug("%s not present on bus", name); } - void monitor_timezone_property() + static void on_properties_changed(GDBusConnection * /*connection*/, + const gchar * /*sender_name*/, + const gchar * /*object_path*/, + const gchar * /*interface_name*/, + const gchar * /*signal_name*/, + GVariant * parameters, + gpointer gself) { - GError *err = nullptr; - - /* - * There is an unlikely race which happens if there is an activation - * and timezone change before our match rule is added. - */ - notify_timezone(get_timezone_from_file(m_filename)); - - /* - * Make sure the bus is around at least until we add the match rules, - * otherwise things (tests) are sad. - */ - m_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, - nullptr, - &err); - - if (err) + auto self = static_cast<Impl*>(gself); + + GVariant* changed_properties {}; + gchar** invalidated_properties {}; + g_variant_get(parameters, "(s@a{sv}^as)", NULL, &changed_properties, &invalidated_properties); + + const char* tz {}; + if (g_variant_lookup(changed_properties, Bus::Timedate1::Properties::TIMEZONE, "&s", &tz, NULL)) + { + if (tz != nullptr) + self->set_timezone(tz); + else + g_warning("%s no timezone found", G_STRLOC); + } + else if (g_strv_contains(invalidated_properties, Bus::Timedate1::Properties::TIMEZONE)) { - g_warning("Couldn't get bus connection: '%s'", err->message); - g_error_free(err); - return; + self->ask_for_timezone(); } - m_signal_subscription_id = g_dbus_connection_signal_subscribe(m_connection, - "org.freedesktop.timedate1", - "org.freedesktop.DBus.Properties", - "PropertiesChanged", - "/org/freedesktop/timedate1", - NULL, G_DBUS_SIGNAL_FLAGS_NONE, - on_properties_changed, - this, nullptr); + g_variant_unref(changed_properties); + g_strfreev(invalidated_properties); } - void notify_timezone(std::string new_timezone) + void ask_for_timezone() { - g_debug("notify_timezone '%s'", new_timezone.c_str()); - if (!new_timezone.empty()) - m_owner.timezone.set(new_timezone); + g_dbus_connection_call( + m_connection, + Bus::Timedate1::BUSNAME, + Bus::Timedate1::ADDR, + Bus::Properties::IFACE, + Bus::Properties::Methods::GET, + g_variant_new("(ss)", Bus::Timedate1::IFACE, Bus::Timedate1::Properties::TIMEZONE), + G_VARIANT_TYPE("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + m_cancellable, + on_get_timezone_ready, + this); } - std::string get_timezone_from_file(const std::string& filename) + static void on_get_timezone_ready(GObject * connection, + GAsyncResult * res, + gpointer gself) { - GError * error; - GIOChannel * io_channel; - std::string ret; - - // read through filename line-by-line until we fine a nonempty non-comment line - error = nullptr; - io_channel = g_io_channel_new_file(filename.c_str(), "r", &error); - if (error == nullptr) + GError* error {}; + GVariant* v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(connection), res, &error); + if (error != nullptr) { - auto line = g_string_new(nullptr); - - while(ret.empty()) - { - const auto io_status = g_io_channel_read_line_string(io_channel, line, nullptr, &error); - if ((io_status == G_IO_STATUS_EOF) || (io_status == G_IO_STATUS_ERROR)) - break; - if (error != nullptr) - break; - - g_strstrip(line->str); - - if (!line->len) // skip empty lines - continue; - - if (*line->str=='#') // skip comments - continue; - - ret = line->str; - } + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("%s Couldn't get timezone: %s", G_STRLOC, error->message); + } + else if (v != nullptr) + { + GVariant* tzv {}; + g_variant_get(v, "(v)", &tzv); + const char* tz = g_variant_get_string(tzv, nullptr); - g_string_free(line, true); - } else - /* Default to UTC */ - ret = "Etc/Utc"; + if (tz != nullptr) + static_cast<Impl*>(gself)->set_timezone(tz); + else + g_warning("%s no timezone found", G_STRLOC); - if (io_channel != nullptr) - { - g_io_channel_shutdown(io_channel, false, nullptr); - g_io_channel_unref(io_channel); + g_clear_pointer(&tzv, g_variant_unref); + g_clear_pointer(&v, g_variant_unref); } + } - if (error != nullptr) - { - g_warning("%s Unable to read timezone file '%s': %s", G_STRLOC, filename.c_str(), error->message); - g_error_free(error); - } + void set_timezone(const std::string& tz) + { + g_return_if_fail(!tz.empty()); - return ret; + g_debug("set timezone: '%s'", tz.c_str()); + m_owner.timezone.set(tz); } /*** **** ***/ - TimedatedTimezone & m_owner; - GDBusConnection *m_connection = nullptr; - unsigned long m_signal_subscription_id = 0; - std::string m_filename; + TimedatedTimezone& m_owner; + GDBusConnection* m_connection {}; + GCancellable* m_cancellable {}; + unsigned long m_signal_subscription_id {}; + unsigned int m_watcher_id {}; }; /*** **** ***/ -TimedatedTimezone::TimedatedTimezone(std::string filename): - impl(new Impl{*this, filename}) +TimedatedTimezone::TimedatedTimezone(GDBusConnection* connection): + impl{new Impl{*this, connection}} { } diff --git a/src/timezones-live.cpp b/src/timezones-live.cpp index 2979036..f3bd02d 100644 --- a/src/timezones-live.cpp +++ b/src/timezones-live.cpp @@ -25,11 +25,14 @@ namespace ayatana { namespace indicator { namespace datetime { -LiveTimezones::LiveTimezones(const std::shared_ptr<const Settings>& settings): - m_file(), +LiveTimezones::LiveTimezones( + const std::shared_ptr<const Settings>& settings, + const std::shared_ptr<Timezone>& primary_timezone +): + m_primary_timezone(primary_timezone), m_settings(settings) { - m_file.timezone.changed().connect([this](const std::string&){update_timezones();}); + m_primary_timezone->timezone.changed().connect([this](const std::string&){update_timezones();}); m_settings->show_detected_location.changed().connect([this](bool){update_geolocation();}); update_geolocation(); @@ -53,7 +56,7 @@ void LiveTimezones::update_geolocation() void LiveTimezones::update_timezones() { - const auto a = m_file.timezone.get(); + const auto a = m_primary_timezone->timezone.get(); const auto b = m_geo ? m_geo->timezone.get() : ""; timezone.set(a.empty() ? b : a); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5206259..4a38890 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,11 +1,6 @@ -# build libgtest -add_library (gtest STATIC - ${GTEST_SOURCE_DIR}/gtest-all.cc - ${GTEST_SOURCE_DIR}/gtest_main.cc) -set_target_properties (gtest PROPERTIES INCLUDE_DIRECTORIES ${INCLUDE_DIRECTORIES} ${GTEST_INCLUDE_DIR}) -set_target_properties (gtest PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -w") +find_package(GMock REQUIRED) -SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${COMPILE_FLAGS}") +SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${COMPILE_FLAGS}") # dbustest pkg_check_modules(DBUSTEST REQUIRED @@ -42,13 +37,17 @@ add_definitions (-DG_LOG_DOMAIN="ayatana-indicator-datetime") function(add_test_by_name name) set (TEST_NAME ${name}) + set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME} PARENT_SCOPE) add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) + target_link_options(${TEST_NAME} PRIVATE -no-pie) add_test (${TEST_NAME} ${TEST_NAME}) - target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS} ${URLDISPATCHER_LIBRARIES}) + target_link_libraries (${TEST_NAME} indicatordatetimeservice ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) endfunction() add_test_by_name(test-datetime) if(HAVE_UT_ACCTSERVICE_SYSTEMSOUND_SETTINGS) -add_test_by_name(test-snap) +add_test_by_name(test-sound) +add_test_by_name(test-notification) +add_test_by_name(test-notification-response) endif() add_test_by_name(test-actions) add_test_by_name(test-alarm-queue) @@ -58,6 +57,7 @@ add_test_by_name(test-exporter) add_test_by_name(test-formatter) add_test_by_name(test-live-actions) add_test_by_name(test-locations) +add_test_by_name(test-menu-appointments) add_test_by_name(test-menus) add_test_by_name(test-planner) add_test_by_name(test-settings) @@ -66,8 +66,10 @@ add_test_by_name(test-utils) if(HAVE_UT_ACCTSERVICE_SYSTEMSOUND_SETTINGS) set (TEST_NAME manual-test-snap) + set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME}) add_executable (${TEST_NAME} ${TEST_NAME}.cpp) - target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) + target_link_options(${TEST_NAME} PRIVATE -no-pie) + target_link_libraries (${TEST_NAME} indicatordatetimeservice ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) endif() ## @@ -78,17 +80,20 @@ find_program(DBUS_RUNNER dbus-test-runner) function(add_eds_ics_test_by_name name) set (TEST_NAME ${name}) + set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME} PARENT_SCOPE) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}.ics.in" "${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.ics") add_executable(${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) - target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) + target_link_options(${TEST_NAME} PRIVATE -no-pie) + target_link_libraries (${TEST_NAME} indicatordatetimeservice ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) add_test (${TEST_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/run-eds-ics-test.sh ${DBUS_RUNNER} # arg1: dbus-test-runner exec ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} # arg2: test executable path ${TEST_NAME} # arg3: test name ${CMAKE_CURRENT_SOURCE_DIR}/test-eds-ics-config-files # arg4: base directory for config file template - ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.ics) # arg5: the ical file for this test + ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.ics # arg5: the ical file for this test + ${CMAKE_CURRENT_SOURCE_DIR}/accounts.db) # arg6: online accounts database endfunction() add_eds_ics_test_by_name(test-eds-ics-all-day-events) add_eds_ics_test_by_name(test-eds-ics-repeating-events) @@ -97,15 +102,25 @@ add_eds_ics_test_by_name(test-eds-ics-repeating-valarms) add_eds_ics_test_by_name(test-eds-ics-missing-trigger) add_eds_ics_test_by_name(test-eds-ics-tzids) add_eds_ics_test_by_name(test-eds-ics-tzids-2) +add_eds_ics_test_by_name(test-eds-ics-tzids-utc) +add_eds_ics_test_by_name(test-eds-ics-non-attending-alarms) +add_eds_ics_test_by_name(test-eds-ics-repeating-events-with-individual-change) # disabling the timezone unit tests because they require # https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724 # which hasn't landed yet. These can be re-enabled as soon as that lands. #function(add_dbusmock_test_by_name name) # set (TEST_NAME ${name}) +# set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME} PARENT_SCOPE) # add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) # add_test (${TEST_NAME} ${TEST_NAME}) -# target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GTEST_LIBS}) +# target_link_libraries (${TEST_NAME} indicatordatetimeservice ${SERVICE_DEPS_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) #endfunction() #add_dbusmock_test_by_name(test-timezone-geoclue) #add_dbusmock_test_by_name(test-timezones) + +set( + COVERAGE_TEST_TARGETS + ${COVERAGE_TEST_TARGETS} + PARENT_SCOPE +) diff --git a/tests/accounts.db b/tests/accounts.db Binary files differnew file mode 100644 index 0000000..ece5b2f --- /dev/null +++ b/tests/accounts.db diff --git a/tests/actions-mock.h b/tests/actions-mock.h index 59a0912..a02a7e2 100644 --- a/tests/actions-mock.h +++ b/tests/actions-mock.h @@ -34,14 +34,10 @@ public: explicit MockActions(const std::shared_ptr<State>& state_in): Actions(state_in) {} ~MockActions() =default; - enum Action { DesktopOpenAlarmApp, - DesktopOpenAppt, - DesktopOpenCalendarApp, - DesktopOpenSettingsApp, - PhoneOpenAlarmApp, - PhoneOpenAppt, - PhoneOpenCalendarApp, - PhoneOpenSettingsApp, + enum Action { OpenAlarmApp, + OpenAppt, + OpenCalendarApp, + OpenSettingsApp, SetLocation }; const std::vector<Action>& history() const { return m_history; } @@ -54,34 +50,20 @@ public: bool desktop_has_calendar_app() const { return m_desktop_has_calendar_app; } - void desktop_open_alarm_app() { - m_history.push_back(DesktopOpenAlarmApp); + void open_alarm_app() { + m_history.push_back(OpenAlarmApp); } - void desktop_open_appointment(const Appointment& appt) { + void open_appointment(const Appointment& appt, const DateTime& dt) { m_appt = appt; - m_history.push_back(DesktopOpenAppt); - } - void desktop_open_calendar_app(const DateTime& dt) { m_date_time = dt; - m_history.push_back(DesktopOpenCalendarApp); - } - void desktop_open_settings_app() { - m_history.push_back(DesktopOpenSettingsApp); - } - - void phone_open_alarm_app() { - m_history.push_back(PhoneOpenAlarmApp); - } - void phone_open_appointment(const Appointment& appt) { - m_appt = appt; - m_history.push_back(PhoneOpenAppt); + m_history.push_back(OpenAppt); } - void phone_open_calendar_app(const DateTime& dt) { + void open_calendar_app(const DateTime& dt) { m_date_time = dt; - m_history.push_back(PhoneOpenCalendarApp); + m_history.push_back(OpenCalendarApp); } - void phone_open_settings_app() { - m_history.push_back(PhoneOpenSettingsApp); + void open_settings_app() { + m_history.push_back(OpenSettingsApp); } void set_location(const std::string& zone_, const std::string& name_) { diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h index 4d309e6..88ee384 100644 --- a/tests/glib-fixture.h +++ b/tests/glib-fixture.h @@ -1,5 +1,5 @@ /* - * Copyright 2013 Canonical Ltd. + * Copyright 2013-2016 Canonical Ltd. * * Authors: * Charles Kerr <charles.kerr@canonical.com> @@ -17,10 +17,12 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef INDICATOR_DATETIME_TESTS_GLIB_FIXTURE_H -#define INDICATOR_DATETIME_TESTS_GLIB_FIXTURE_H +#pragma once +#include <chrono> +#include <functional> // std::function #include <map> +#include <memory> // std::shared_ptr #include <glib.h> #include <glib/gstdio.h> @@ -109,7 +111,115 @@ class GlibFixture : public ::testing::Test g_source_remove(id); } - GMainLoop * loop; + bool wait_for(std::function<bool()> test_function, guint timeout_msec=1000) + { + auto timer = std::shared_ptr<GTimer>(g_timer_new(), [](GTimer* t){g_timer_destroy(t);}); + const auto timeout_sec = timeout_msec / 1000.0; + for (;;) { + if (test_function()) + return true; + //g_message("%f ... %f", g_timer_elapsed(timer.get(), nullptr), timeout_sec); + if (g_timer_elapsed(timer.get(), nullptr) >= timeout_sec) + return false; + wait_msec(); + } + } + + bool wait_for_name_owned(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + struct Data { + GMainLoop* loop = nullptr; + bool owned = false; + }; + Data data; + + auto on_name_appeared = [](GDBusConnection* /*connection*/, + const gchar* /*name_*/, + const gchar* name_owner, + gpointer gdata) + { + if (name_owner == nullptr) + return; + auto tmp = static_cast<Data*>(gdata); + tmp->owned = true; + g_main_loop_quit(tmp->loop); + }; + + const auto timeout_id = g_timeout_add(timeout_msec, wait_msec__timeout, loop); + data.loop = loop; + const auto watch_id = g_bus_watch_name_on_connection(connection, + name, + flags, + on_name_appeared, + nullptr, /* name_vanished */ + &data, + nullptr); /* user_data_free_func */ + g_main_loop_run(loop); + + g_bus_unwatch_name(watch_id); + g_source_remove(timeout_id); + + return data.owned; + } + + void EXPECT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + EXPECT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + void EXPECT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + EXPECT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + void ASSERT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + ASSERT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + void ASSERT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + ASSERT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + using source_func = std::function<gboolean()>; + + guint idle_add(source_func&& func) + { + return g_idle_add_full( + G_PRIORITY_DEFAULT_IDLE, + [](gpointer gf){return (*static_cast<source_func*>(gf))();}, + new std::function<gboolean()>(func), + [](gpointer gf){delete static_cast<source_func*>(gf);} + ); + } + + guint timeout_add(source_func&& func, std::chrono::milliseconds msec) + { + return g_timeout_add_full( + G_PRIORITY_DEFAULT, + msec.count(), + [](gpointer gf){return (*static_cast<source_func*>(gf))();}, + new std::function<gboolean()>(func), + [](gpointer gf){delete static_cast<source_func*>(gf);} + ); + } + + GMainLoop* loop {}; }; -#endif /* INDICATOR_DATETIME_TESTS_GLIB_FIXTURE_H */ diff --git a/tests/libdbusmock-fixture.h b/tests/libdbusmock-fixture.h new file mode 100644 index 0000000..7301042 --- /dev/null +++ b/tests/libdbusmock-fixture.h @@ -0,0 +1,148 @@ +/* + * Copyright 2014-2016 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> + */ + +#pragma once + +#include "glib-fixture.h" + +#include <libdbustest/dbus-test.h> + +/*** +**** +***/ + +class LibdbusmockFixture: public GlibFixture +{ +private: + + typedef GlibFixture super; + +protected: + + GDBusConnection * system_bus {}; + GDBusConnection * session_bus {}; + DbusTestService * service {}; + + void SetUp() override + { + + super::SetUp(); + + service = dbus_test_service_new(nullptr); + } + + void startDbusMock() + { + // start 'em up. + // make the system bus work off the mock bus too, since that's + // where the upower and screen are on the system bus... + + dbus_test_service_start_tasks(service); + g_setenv("DBUS_SYSTEM_BUS_ADDRESS", g_getenv("DBUS_SESSION_BUS_ADDRESS"), TRUE); + + session_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); + ASSERT_NE(nullptr, session_bus); + g_dbus_connection_set_exit_on_close(session_bus, false); + g_object_add_weak_pointer(G_OBJECT(session_bus), (gpointer *)&session_bus); + + system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr); + ASSERT_NE(nullptr, system_bus); + g_dbus_connection_set_exit_on_close(system_bus, FALSE); + g_object_add_weak_pointer(G_OBJECT(system_bus), (gpointer *)&system_bus); + } + + void TearDown() override + { + g_clear_object(&service); + g_object_unref(session_bus); + g_object_unref(system_bus); + + // wait a little while for the scaffolding to shut down, + // but don't block on it forever... + wait_for([this](){return system_bus==nullptr && session_bus==nullptr;}, 5000); + + super::TearDown(); + } + + bool wait_for_method_call(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=100) + { + if (params != nullptr) + g_variant_ref_sink(params); + + auto test_function = [mock, obj, method, params]() { + GError* error {}; + const auto called = dbus_test_dbus_mock_object_check_method_call(mock, + obj, + method, + params, + &error); + if (error != nullptr) { + g_critical("Error looking for method call '%s': %s", method, error->message); + g_clear_error(&error); + } + + return called; + }; + + const auto ret = wait_for(test_function, timeout_msec); + g_clear_pointer(¶ms, g_variant_unref); + return ret; + } + + void EXPECT_METHOD_CALLED_EVENTUALLY(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=1000) + { + EXPECT_TRUE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method; + } + + void EXPECT_METHOD_NOT_CALLED_EVENTUALLY(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=1000) + { + EXPECT_FALSE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method; + } + + void ASSERT_METHOD_CALLED_EVENTUALLY(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=1000) + { + ASSERT_TRUE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method; + } + + void ASSERT_METHOD_NOT_CALLED_EVENTUALLY(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=1000) + { + ASSERT_FALSE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method; + } +}; + diff --git a/tests/manual-test-snap.cpp b/tests/manual-test-snap.cpp index a0f80f2..72fd374 100644 --- a/tests/manual-test-snap.cpp +++ b/tests/manual-test-snap.cpp @@ -74,12 +74,14 @@ int main(int argc, const char* argv[]) a.alarms.push_back(Alarm{"Alarm Text", "", a.begin}); auto loop = g_main_loop_new(nullptr, false); - auto on_snooze = [loop](const Appointment& appt, const Alarm&){ - g_message("You clicked 'Snooze' for appt url '%s'", appt.summary.c_str()); - g_idle_add(quit_idle, loop); - }; - auto on_ok = [loop](const Appointment&, const Alarm&){ - g_message("You clicked 'OK'"); + auto on_response = [loop](const Appointment& appt, const Alarm&, const Snap::Response& response){ + const char* str {""}; + switch(response) { + case Snap::Response::ShowApp: str = "show-app"; break; + case Snap::Response::Snooze: str = "snooze"; break; + case Snap::Response::None: str = "no-action"; break; + }; + g_message("You clicked '%s' for appt url '%s'", str, appt.summary.c_str()); g_idle_add(quit_idle, loop); }; @@ -92,10 +94,13 @@ int main(int argc, const char* argv[]) settings->alarm_volume.set(volume); auto notification_engine = std::make_shared<ain::Engine>("ayatana-indicator-datetime-service"); - Snap snap (notification_engine, settings); - snap(a, a.alarms.front(), on_snooze, on_ok); + auto sound_builder = std::make_shared<ain::DefaultSoundBuilder>(); + auto system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr); + Snap snap (notification_engine, sound_builder, settings, system_bus); + snap(a, a.alarms.front(), on_response); g_main_loop_run(loop); g_main_loop_unref(loop); + g_clear_object(&system_bus); return 0; } diff --git a/tests/test-snap.cpp b/tests/notification-fixture.h index afee297..cbce9ff 100644 --- a/tests/test-snap.cpp +++ b/tests/notification-fixture.h @@ -1,8 +1,5 @@ /* - * Copyright 2014 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> + * Copyright 2014-2016 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 @@ -15,8 +12,15 @@ * * 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> */ +#pragma once + +#include "libdbusmock-fixture.h" + #include <datetime/appointment.h> #include <datetime/dbus-shared.h> #include <datetime/settings.h> @@ -27,38 +31,25 @@ #include <libdbustest/dbus-test.h> -#include <glib.h> - #include <unistd.h> // getuid() #include <sys/types.h> // getuid() -using namespace ayatana::indicator::datetime; - -#include "glib-fixture.h" - /*** **** ***/ -namespace -{ - static constexpr char const * APP_NAME {"ayatana-indicator-datetime-service"}; -} - -using namespace ayatana::indicator::datetime; - -class SnapFixture: public GlibFixture +class NotificationFixture: public LibdbusmockFixture { private: - typedef GlibFixture super; + typedef LibdbusmockFixture super; + +protected: static constexpr char const * NOTIFY_BUSNAME {"org.freedesktop.Notifications"}; static constexpr char const * NOTIFY_INTERFACE {"org.freedesktop.Notifications"}; static constexpr char const * NOTIFY_PATH {"/org/freedesktop/Notifications"}; -protected: - static constexpr char const * HAPTIC_METHOD_VIBRATE_PATTERN {"VibratePattern"}; static constexpr int SCREEN_COOKIE {8675309}; @@ -87,15 +78,13 @@ protected: static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"}; static constexpr char const * AS_BUSNAME {"org.freedesktop.Accounts"}; - static constexpr char const * AS_INTERFACE {"com.ubuntu.touch.AccountsService.Sound"}; + static constexpr char const * AS_INTERFACE {"com.lomiri.touch.AccountsService.Sound"}; static constexpr char const * PROP_OTHER_VIBRATIONS {"OtherVibrate"}; static constexpr char const * PROP_SILENT_MODE {"SilentMode"}; - Appointment appt; - Appointment ualarm; - GDBusConnection * system_bus = nullptr; - GDBusConnection * session_bus = nullptr; - DbusTestService * service = nullptr; + ayatana::indicator::datetime::Appointment appt; + ayatana::indicator::datetime::Appointment ualarm; + DbusTestDbusMock * as_mock = nullptr; DbusTestDbusMock * notify_mock = nullptr; DbusTestDbusMock * powerd_mock = nullptr; @@ -118,23 +107,21 @@ protected: appt.color = "green"; appt.summary = "Christmas"; appt.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; - appt.type = Appointment::EVENT; - const auto christmas = DateTime::Local(2015,12,25,0,0,0); + appt.type = ayatana::indicator::datetime::Appointment::EVENT; + const auto christmas = ayatana::indicator::datetime::DateTime::Local(2015,12,25,0,0,0); appt.begin = christmas.start_of_day(); appt.end = christmas.end_of_day(); - appt.alarms.push_back(Alarm{"Ho Ho Ho!", "", appt.begin}); + appt.alarms.push_back(ayatana::indicator::datetime::Alarm{"Ho Ho Ho!", CALENDAR_DEFAULT_SOUND, appt.begin}); - // init an Ubuntu Alarm + // init a Lomiri Alarm ualarm.color = "red"; ualarm.summary = "Wakeup"; ualarm.uid = "E4B57D50247291478ED31DED17FF0A9838DED403"; - ualarm.type = Appointment::UBUNTU_ALARM; - const auto tomorrow = DateTime::NowLocal().add_days(1); + ualarm.type = ayatana::indicator::datetime::Appointment::UBUNTU_ALARM; + const auto tomorrow = ayatana::indicator::datetime::DateTime::NowLocal().add_days(1); ualarm.begin = tomorrow; ualarm.end = tomorrow; - ualarm.alarms.push_back(Alarm{"It's Tomorrow!", "", appt.begin}); - - service = dbus_test_service_new(nullptr); + ualarm.alarms.push_back(ayatana::indicator::datetime::Alarm{"It's Tomorrow!", "", appt.begin}); /// /// Add the AccountsService mock @@ -311,23 +298,7 @@ protected: g_assert_no_error (error); dbus_test_service_add_task(service, DBUS_TEST_TASK(haptic_mock)); - - // start 'em up. - // make the system bus work off the mock bus too, since that's - // where the upower and screen are on the system bus... - - dbus_test_service_start_tasks(service); - g_setenv("DBUS_SYSTEM_BUS_ADDRESS", g_getenv("DBUS_SESSION_BUS_ADDRESS"), TRUE); - - session_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); - ASSERT_NE(nullptr, session_bus); - g_dbus_connection_set_exit_on_close(session_bus, false); - g_object_add_weak_pointer(G_OBJECT(session_bus), (gpointer *)&session_bus); - - system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr); - ASSERT_NE(nullptr, system_bus); - g_dbus_connection_set_exit_on_close(system_bus, FALSE); - g_object_add_weak_pointer(G_OBJECT(system_bus), (gpointer *)&system_bus); + startDbusMock(); } void TearDown() override @@ -337,20 +308,6 @@ protected: g_clear_object(&powerd_mock); g_clear_object(¬ify_mock); g_clear_object(&as_mock); - g_clear_object(&service); - g_object_unref(session_bus); - g_object_unref(system_bus); - - // wait a little while for the scaffolding to shut down, - // but don't block on it forever... - unsigned int cleartry = 0; - while (((system_bus != nullptr) || (session_bus != nullptr)) && (cleartry < 50)) - { - g_usleep(100000); - while (g_main_context_pending(nullptr)) - g_main_context_iteration(nullptr, true); - cleartry++; - } super::TearDown(); } @@ -371,213 +328,15 @@ protected: &error); g_assert_no_error (error); } -}; -/*** -**** -***/ - -namespace -{ - gboolean quit_idle (gpointer gloop) + std::shared_ptr<ayatana::indicator::datetime::Snap> + create_snap(const std::shared_ptr<ayatana::indicator::notifications::Engine>& ne, + const std::shared_ptr<ayatana::indicator::notifications::SoundBuilder>& sb, + const std::shared_ptr<ayatana::indicator::datetime::Settings>& settings) { - g_main_loop_quit(static_cast<GMainLoop*>(gloop)); - return G_SOURCE_REMOVE; - }; -} - -TEST_F(SnapFixture, InteractiveDuration) -{ - static constexpr int duration_minutes = 120; - auto settings = std::make_shared<Settings>(); - settings->alarm_duration.set(duration_minutes); - auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); - Snap snap (ne, settings); - - make_interactive(); - - // call the Snap Decision - auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; - snap(appt, appt.alarms.front(), func, func); - - // confirm that Notify got called once - guint len = 0; - GError * error = nullptr; - const auto calls = dbus_test_dbus_mock_object_get_method_calls (notify_mock, - notify_obj, - METHOD_NOTIFY, - &len, - &error); - g_assert_no_error(error); - ASSERT_EQ(1, len); - - // confirm that the app_name passed to Notify was APP_NAME - const auto& params = calls[0].params; - ASSERT_EQ(8, g_variant_n_children(params)); - const char * str = nullptr; - g_variant_get_child (params, 0, "&s", &str); - ASSERT_STREQ(APP_NAME, str); - - // confirm that the icon passed to Notify was "alarm-clock" - g_variant_get_child (params, 2, "&s", &str); - ASSERT_STREQ("alarm-clock", str); - - // confirm that the hints passed to Notify included a timeout matching duration_minutes - int32_t i32; - bool b; - auto hints = g_variant_get_child_value (params, 6); - b = g_variant_lookup (hints, HINT_TIMEOUT, "i", &i32); - EXPECT_TRUE(b); - const auto duration = std::chrono::minutes(duration_minutes); - EXPECT_EQ(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(), i32); - g_variant_unref(hints); - ne.reset(); -} - -/*** -**** -***/ - -TEST_F(SnapFixture, InhibitSleep) -{ - auto settings = std::make_shared<Settings>(); - auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); - auto snap = new Snap (ne, settings); - - make_interactive(); - - // invoke the notification - auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; - (*snap)(appt, appt.alarms.front(), func, func); - - wait_msec(1000); - - // confirm that sleep got inhibited - GError * error = nullptr; - EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock, - powerd_obj, - POWERD_METHOD_REQUEST_SYS_STATE, - g_variant_new("(si)", APP_NAME, POWERD_SYS_STATE_ACTIVE), - &error)); - - // confirm that the screen got forced on - EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (screen_mock, - screen_obj, - SCREEN_METHOD_KEEP_DISPLAY_ON, - nullptr, - &error)); - - // force-close the snap - wait_msec(100); - delete snap; - wait_msec(100); - - // confirm that sleep got uninhibted - EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock, - powerd_obj, - POWERD_METHOD_CLEAR_SYS_STATE, - g_variant_new("(s)", POWERD_COOKIE), - &error)); - - // confirm that the screen's no longer forced on - EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (screen_mock, - screen_obj, - SCREEN_METHOD_REMOVE_DISPLAY_ON_REQUEST, - g_variant_new("(i)", SCREEN_COOKIE), - &error)); - - g_assert_no_error (error); -} - -/*** -**** -***/ - -TEST_F(SnapFixture, ForceScreen) -{ - auto settings = std::make_shared<Settings>(); - auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); - auto snap = new Snap (ne, settings); - - make_interactive(); - - // invoke the notification - auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; - (*snap)(appt, appt.alarms.front(), func, func); - - wait_msec(1000); - - // confirm that sleep got inhibited - GError * error = nullptr; - EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock, - powerd_obj, - POWERD_METHOD_REQUEST_SYS_STATE, - g_variant_new("(si)", APP_NAME, POWERD_SYS_STATE_ACTIVE), - &error)); - g_assert_no_error(error); - - // force-close the snap - wait_msec(100); - delete snap; - wait_msec(100); - - // confirm that sleep got uninhibted - EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock, - powerd_obj, - POWERD_METHOD_CLEAR_SYS_STATE, - g_variant_new("(s)", POWERD_COOKIE), - &error)); - g_assert_no_error(error); -} - -/*** -**** -***/ - -TEST_F(SnapFixture,Vibrate) -{ - auto settings = std::make_shared<Settings>(); - auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); - auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; - GError * error = nullptr; - - struct { - bool other_vibrations; // the com.ubuntu.touch.AccountsService.Sound "other vibrations" setting - const char* haptic_mode; // supported values: "none", "pulse" - bool expected_vibrate_called; // do we expect the phone to vibrate? - } test_cases[] = { - { false, "none", false }, - { true, "none", false }, - { false, "pulse", false }, - { true, "pulse", true } - }; - - auto snap = std::make_shared<Snap>(ne, settings); - - for(const auto& test_case : test_cases) - { - // clear out any previous iterations' noise - dbus_test_dbus_mock_object_clear_method_calls(haptic_mock, haptic_obj, &error); - - // set the properties to match the test case - settings->alarm_haptic.set(test_case.haptic_mode); - dbus_test_dbus_mock_object_update_property(as_mock, - as_obj, - PROP_OTHER_VIBRATIONS, - g_variant_new_boolean(test_case.other_vibrations), - &error); - g_assert_no_error(error); - wait_msec(100); - - // run the test - (*snap)(appt, appt.alarms.front(), func, func); - wait_msec(100); - const bool vibrate_called = dbus_test_dbus_mock_object_check_method_call(haptic_mock, - haptic_obj, - HAPTIC_METHOD_VIBRATE_PATTERN, - nullptr, - &error); - g_assert_no_error(error); - EXPECT_EQ(test_case.expected_vibrate_called, vibrate_called); + auto snap = std::make_shared<ayatana::indicator::datetime::Snap>(ne, sb, settings, system_bus); + wait_msec(100); // wait a moment for the Snap to finish its async dbus bootstrapping + return snap; } -} +}; + diff --git a/tests/print-to.h b/tests/print-to.h index 19367ac..652da52 100644 --- a/tests/print-to.h +++ b/tests/print-to.h @@ -21,6 +21,7 @@ #define INDICATOR_DATETIME_TESTS_PRINT_TO #include <algorithm> +#include <vector> #include <datetime/appointment.h> @@ -71,6 +72,15 @@ PrintTo(const Appointment& appointment, std::ostream* os) *os << '}'; } +void +PrintTo(const std::vector<Appointment>& appointments, std::ostream* os) +{ + *os << '{'; + for (const auto& appointment : appointments) + PrintTo(appointment, os); + *os << '}'; +} + } // namespace datetime } // namespace indicator } // namespace ayatana diff --git a/tests/run-eds-ics-test.sh b/tests/run-eds-ics-test.sh index 13c1617..b38fe77 100755 --- a/tests/run-eds-ics-test.sh +++ b/tests/run-eds-ics-test.sh @@ -6,6 +6,7 @@ TEST_EXEC=$2 # full executable path of test app TEST_NAME=$3 # test name CONFIG_DIR=$4 # config files ICS_FILE=$5 # ical file holding test data +ACCOUNTS_DB=$6 # online account database echo "this script: ${SELF}" echo "test-runner: ${TEST_RUNNER}" @@ -55,6 +56,13 @@ if [ -e ${ICS_FILE} ]; then cp --verbose --archive ${ICS_FILE} ${XDG_DATA_HOME}/evolution/tasks/system/tasks.ics fi +# prepare online accounts database +if [ -e ${ACCOUNTS_DB} ]; then + echo "copying ${ACCOUNTS_DB} into $HOME" + mkdir -p ${XDG_CONFIG_HOME}/libaccounts-glib/ + cp --verbose --archive ${ACCOUNTS_DB} ${XDG_CONFIG_HOME}/libaccounts-glib/accounts.db +fi + # run the test ${TEST_RUNNER} --keep-env --max-wait=90 --task ${TEST_EXEC} --task-name ${TEST_NAME} --wait-until-complete rv=$? diff --git a/tests/test-actions.cpp b/tests/test-actions.cpp index aa608a8..a01fb83 100644 --- a/tests/test-actions.cpp +++ b/tests/test-actions.cpp @@ -116,7 +116,7 @@ protected: m_mock_state->mock_range_planner->appointments().set(appointments); // activate the action - auto v = g_variant_new_string(appointments[0].uid.c_str()); + auto v = g_variant_new("(sx)", appointments[0].uid.c_str(), 0); g_action_group_activate_action(action_group, action_name, v); // test the results @@ -134,7 +134,7 @@ protected: EXPECT_TRUE(m_mock_actions->history().empty()); // activate the action - v = g_variant_new_string("this-uid-is-not-one-that-we-have"); + v = g_variant_new("(sx)", "this-uid-is-not-one-that-we-have", 0); g_action_group_activate_action(action_group, action_name, v); // test the results @@ -176,25 +176,25 @@ TEST_F(ActionsFixture, ActionsExist) TEST_F(ActionsFixture, DesktopOpenAlarmApp) { test_action_with_no_args("desktop.open-alarm-app", - MockActions::DesktopOpenAlarmApp); + MockActions::OpenAlarmApp); } TEST_F(ActionsFixture, DesktopOpenAppointment) { test_action_with_appt_arg("desktop.open-appointment", - MockActions::DesktopOpenAppt); + MockActions::OpenAppt); } TEST_F(ActionsFixture, DesktopOpenCalendarApp) { test_action_with_time_arg("desktop.open-calendar-app", - MockActions::DesktopOpenCalendarApp); + MockActions::OpenCalendarApp); } TEST_F(ActionsFixture, DesktopOpenSettingsApp) { test_action_with_no_args("desktop.open-settings-app", - MockActions::DesktopOpenSettingsApp); + MockActions::OpenSettingsApp); } /*** @@ -204,25 +204,25 @@ TEST_F(ActionsFixture, DesktopOpenSettingsApp) TEST_F(ActionsFixture, PhoneOpenAlarmApp) { test_action_with_no_args("phone.open-alarm-app", - MockActions::PhoneOpenAlarmApp); + MockActions::OpenAlarmApp); } TEST_F(ActionsFixture, PhoneOpenAppointment) { test_action_with_appt_arg("phone.open-appointment", - MockActions::PhoneOpenAppt); + MockActions::OpenAppt); } TEST_F(ActionsFixture, PhoneOpenCalendarApp) { test_action_with_time_arg("phone.open-calendar-app", - MockActions::PhoneOpenCalendarApp); + MockActions::OpenCalendarApp); } TEST_F(ActionsFixture, PhoneOpenSettingsApp) { test_action_with_no_args("phone.open-settings-app", - MockActions::PhoneOpenSettingsApp); + MockActions::OpenSettingsApp); } /*** diff --git a/tests/test-eds-ics-all-day-events.cpp b/tests/test-eds-ics-all-day-events.cpp index 68a3c95..5d7cdc6 100644 --- a/tests/test-eds-ics-all-day-events.cpp +++ b/tests/test-eds-ics-all-day-events.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; diff --git a/tests/test-eds-ics-missing-trigger.cpp b/tests/test-eds-ics-missing-trigger.cpp index 0aa00c6..70bbccb 100644 --- a/tests/test-eds-ics-missing-trigger.cpp +++ b/tests/test-eds-ics-missing-trigger.cpp @@ -21,9 +21,10 @@ #include <algorithm> -#include <datetime/engine-eds.h> #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> +#include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MissingTriggers) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; @@ -62,7 +63,7 @@ TEST_F(VAlarmFixture, MissingTriggers) // make a planner that looks at the first half of 2015 in EDS auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; - const DateTime range_end {gtz, 2015,6,30,23,59,59.5}; + const DateTime range_end {gtz, 2015,7,1,23,59,59.5}; planner->range().set(std::make_pair(range_begin, range_end)); // give EDS a moment to load diff --git a/tests/test-eds-ics-non-attending-alarms.cpp b/tests/test-eds-ics-non-attending-alarms.cpp new file mode 100644 index 0000000..bfa3ac3 --- /dev/null +++ b/tests/test-eds-ics-non-attending-alarms.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2015 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 <algorithm> + +#include <datetime/alarm-queue-simple.h> +#include <datetime/clock-mock.h> +#include <datetime/engine-eds.h> +#include <datetime/myself.h> +#include <datetime/planner-range.h> + +#include <gtest/gtest.h> + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace ayatana::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, NonAttendingEvent) +{ + // start the EDS engine + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Recife"}; + auto tz = std::make_shared<MockTimezone>(zone_str); + + #if GLIB_CHECK_VERSION(2, 68, 0) + auto gtz = g_time_zone_new_identifier(zone_str); + + if (gtz == NULL) + { + gtz = g_time_zone_new_utc(); + } + #else + auto gtz = g_time_zone_new(zone_str); + #endif + + // make a planner that looks at the first half of 2016 in EDS + auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); + const DateTime range_begin {gtz, 2016,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2016,6,30,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // the planner should match what we've got in the calendar.ics file + const auto appts = planner->appointments().get(); + EXPECT_EQ(2, appts.size()); + EXPECT_EQ(appts[0].begin, DateTime(gtz, 2016, 4, 4, 16, 0, 0)); + EXPECT_EQ(appts[1].begin, DateTime(gtz, 2016, 4, 6, 16, 0, 0)); + + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-non-attending-alarms.ics.in b/tests/test-eds-ics-non-attending-alarms.ics.in new file mode 100644 index 0000000..7adc8ab --- /dev/null +++ b/tests/test-eds-ics-non-attending-alarms.ics.in @@ -0,0 +1,53 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2015-04-05T21:32:47.354433Z(2) +BEGIN:VEVENT +STATUS:CONFIRMED +DTSTAMP:20160405T152128Z +CREATED:20160405T152128Z +UID:ddtvl069dn2cquo8dhg3j9c360@google.com +SEQUENCE:1 +TRANSP:OPAQUE +SUMMARY:Every day at 4PM +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160404T160000 +RRULE:FREQ=DAILY;UNTIL=20160406T190000Z +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160404T170000 +ATTENDEE;CN=Uphablet;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT; + CUTYPE=INDIVIDUAL:mailto:uphablet@lomiri.com +LAST-MODIFIED:20160405T152128Z +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-PT30M +ACTION:EMAIL +DESCRIPTION:This is an event reminder +X-EVOLUTION-ALARM-UID:20160405T152128Z-2848-32011-1844-65@lomiri-phablet +END:VALARM +END:VEVENT +BEGIN:VEVENT +STATUS:CONFIRMED +DTSTAMP:20160405T152128Z +CREATED:20160405T151054Z +UID:ddtvl069dn2cquo8dhg3j9c360@google.com +SEQUENCE:1 +TRANSP:OPAQUE +SUMMARY::Every day at 4PM +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Fortaleza: + 20160405T160000 +RECURRENCE-ID;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160405T160000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Fortaleza: + 20160405T170000 +ATTENDEE;CN=Uphablet;PARTSTAT=DECLINED;ROLE=REQ-PARTICIPANT; + CUTYPE=INDIVIDUAL:mailto:uphablet@lomiri.com +LAST-MODIFIED:20160405T152128Z +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-PT30M +ACTION:EMAIL +DESCRIPTION:This is an event reminder +X-EVOLUTION-ALARM-UID:20160405T152128Z-2848-32011-1844-66@lomiri-phablet +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/tests/test-eds-ics-non-selected-source.cpp b/tests/test-eds-ics-non-selected-source.cpp new file mode 100644 index 0000000..5101b32 --- /dev/null +++ b/tests/test-eds-ics-non-selected-source.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2015 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: + * Renato Araujo Oliveira Filh <renato.filho@canonical.com> + */ + +#include <algorithm> + +#include <datetime/engine-eds.h> +#include <datetime/myself.h> +#include <datetime/planner-range.h> + +#include <libedataserver/libedataserver.h> + +#include <gtest/gtest.h> + +#include "glib-fixture.h" +#include "timezone-mock.h" + +using namespace ayatana::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, NonSelectedSources) +{ + // start the EDS engine + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Chicago"}; + auto tz = std::make_shared<MockTimezone>(zone_str); + auto gtz = g_time_zone_new(zone_str); + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); + const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // appointmes are visible + auto appts = planner->appointments().get(); + EXPECT_TRUE(appts.size() > 0); + + // Unselect all sources + auto registry = e_source_registry_new_sync(NULL, NULL); + auto sources = e_source_registry_list_sources(registry, E_SOURCE_EXTENSION_TASK_LIST); + for (auto l=sources; l!=nullptr; l=l->next) { + auto source = static_cast<ESource*>(l->data); + auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR); + e_source_selectable_set_selected(E_SOURCE_SELECTABLE(extension), FALSE); + e_source_write_sync(source, NULL, NULL); + } + + g_list_free_full(sources, g_object_unref); + g_object_unref(registry); + + // give some time to planner update + wait_msec(5 * G_TIME_SPAN_MILLISECOND); + + // the planner should be empty at this point + appts = planner->appointments().get(); + EXPECT_TRUE(appts.size() == 0); + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-non-selected-source.ics.in b/tests/test-eds-ics-non-selected-source.ics.in new file mode 100644 index 0000000..19f93d7 --- /dev/null +++ b/tests/test-eds-ics-non-selected-source.ics.in @@ -0,0 +1,28 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0) +BEGIN:VTODO +UID:20150507T211449Z-4262-32011-1418-1@lomiri-phablet +DTSTAMP:20150508T211449Z +DTSTART:20150508T164000 +RRULE:FREQ=WEEKLY;BYDAY=FR +SUMMARY:Alarm +CATEGORIES:x-lomiri-alarm +CREATED:20150507T211449Z +LAST-MODIFIED:20150507T211449Z +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-2@lomiri-phablet +ACTION:AUDIO +ATTACH:file://@ALARM_DEFAULT_SOUND@ +TRIGGER;VALUE=DURATION;RELATED=START:PT0S +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-3@lomiri-phablet +ACTION:DISPLAY +DESCRIPTION:Alarm +TRIGGER;VALUE=DURATION;RELATED=START:PT0S +END:VALARM +END:VTODO +END:VCALENDAR diff --git a/tests/test-eds-ics-nonrepeating-events.cpp b/tests/test-eds-ics-nonrepeating-events.cpp index e79ab1a..8aa2b82 100644 --- a/tests/test-eds-ics-nonrepeating-events.cpp +++ b/tests/test-eds-ics-nonrepeating-events.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; @@ -80,7 +81,7 @@ TEST_F(VAlarmFixture, MultipleAppointments) // what we expect to get... Appointment expected_appt; - expected_appt.uid = "20150520T000726Z-3878-32011-1770-81@ubuntu-phablet"; + expected_appt.uid = "20150520T000726Z-3878-32011-1770-81@lomiri-phablet"; expected_appt.color = "#becedd"; expected_appt.summary = "Alarm"; std::array<Alarm,1> expected_alarms = { diff --git a/tests/test-eds-ics-repeating-events-with-individual-change.cpp b/tests/test-eds-ics-repeating-events-with-individual-change.cpp new file mode 100644 index 0000000..a4a85c0 --- /dev/null +++ b/tests/test-eds-ics-repeating-events-with-individual-change.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2016 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: + * Renato Araujo Oliveira Filho <renato.filho@canonical.com> + */ + +#include <algorithm> + +#include <datetime/alarm-queue-simple.h> +#include <datetime/clock-mock.h> +#include <datetime/engine-eds.h> +#include <datetime/myself.h> +#include <datetime/planner-range.h> + +#include <gtest/gtest.h> + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace ayatana::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, RepeatingEventsWithIndividualChange) +{ + // start the EDS engine + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Recife"}; + auto tz = std::make_shared<MockTimezone>(zone_str); + #if GLIB_CHECK_VERSION(2, 68, 0) + auto gtz = g_time_zone_new_identifier(zone_str); + + if (gtz == NULL) + { + gtz = g_time_zone_new_utc(); + } + #else + auto gtz = g_time_zone_new(zone_str); + #endif + + // make a planner that looks at the year of 2016 in EDS + auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); + const DateTime range_begin {gtz, 2016,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2016,12,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // what we expect to get... + Appointment expected_appt; + expected_appt.summary = "Alarm"; + std::array<DateTime,10> expected_times = { + DateTime(gtz,2016,6, 20,10,00,0), + DateTime(gtz,2016,6, 21,10,00,0), + DateTime(gtz,2016,6, 22,10,00,0), + DateTime(gtz,2016,6, 23,10,00,0), + DateTime(gtz,2016,6, 24,20,00,0), + DateTime(gtz,2016,6, 25,10,00,0), + DateTime(gtz,2016,6, 26,10,00,0), + DateTime(gtz,2016,6, 27,10,00,0), + DateTime(gtz,2016,6, 28,10,00,0), + DateTime(gtz,2016,6, 29,10,00,0) + }; + + // compare it to what we actually loaded... + const auto appts = planner->appointments().get(); + EXPECT_EQ(expected_times.size(), appts.size()); + for (size_t i=0, n=expected_times.size(); i<n; i++) { + const auto& appt = appts[i]; + if (i != 4) + EXPECT_EQ("Every day and every night", appt.summary); + else + EXPECT_EQ("At night", appt.summary); + EXPECT_EQ(expected_times[i], appt.begin); + } + + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-repeating-events-with-individual-change.ics.in b/tests/test-eds-ics-repeating-events-with-individual-change.ics.in new file mode 100644 index 0000000..3723399 --- /dev/null +++ b/tests/test-eds-ics-repeating-events-with-individual-change.ics.in @@ -0,0 +1,969 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2016-06-21T21:42:08.507434Z(7) +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/Recife +X-LIC-LOCATION:America/Recife +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19680301T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19851102T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19860315T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19861025T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19870214T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19871025T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19880207T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19881016T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19890129T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19891015T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19900211T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19991003T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:20000227T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:20001008T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:20001015T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:20011014T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:20020217T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +END:VTIMEZONE +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/New_York +X-LIC-LOCATION:America/New_York +BEGIN:STANDARD +TZNAME:EST +DTSTART:19691026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19700426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19701025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19710425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19711031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19720430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19721029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19730429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19731028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19740106T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19741027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19750223T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19751026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19760425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19761031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19770424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19771030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19780430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19781029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19790429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19791028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19800427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19801026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19810426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19811025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19820425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19821031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19830424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19831030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19840429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19841028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19850428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19851027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19860427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19861026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19870405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19871025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19880403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19881030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19890402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19891029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19900401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19901028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19910407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19911027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19920405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19921025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19930404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19931031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19940403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19941030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19950402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19951029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19960407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19961027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19970406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19971026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19980405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19981025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19990404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19991031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20000402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20001029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20010401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20011028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20020407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20021027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20030406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20031026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20040404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20041031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20050403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20051030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20060402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20061029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20070311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20071104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20080309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20081102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20090308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20091101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20100314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20101107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20110313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20111106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20120311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20121104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20130310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20131103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20140309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20141102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20150308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20151101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20160313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20161106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20170312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20171105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20180311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20181104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20190310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20191103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20200308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20201101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20210314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20211107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20220313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20221106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20230312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20231105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20240310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20241103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20250309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20251102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20260308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20261101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20270314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20271107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20280312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20281105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20290311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20291104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20300310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20301103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20310309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20311102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20320314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20321107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20330313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20331106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20340312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20341105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20350311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20351104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20360309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20361102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20370308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20371101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:20160621T214054Z-31488-1000-4151-27@rento-lomiri +DTSTAMP:20160621T123718Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160620T100000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160620T103000 +TRANSP:OPAQUE +SEQUENCE:2 +SUMMARY:Every day and every night +CLASS:PUBLIC +RRULE;X-EVOLUTION-ENDDATE=20160629T130000Z:FREQ=DAILY;COUNT=10 +CREATED:20160621T214124Z +LAST-MODIFIED:20160621T214124Z +END:VEVENT +BEGIN:VEVENT +UID:20160621T214054Z-31488-1000-4151-27@rento-lomiri +DTSTAMP:20160621T123718Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160624T200000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160624T203000 +TRANSP:OPAQUE +SEQUENCE:3 +SUMMARY:At night +CLASS:PUBLIC +CREATED:20160621T214124Z +LAST-MODIFIED:20160621T214208Z +RECURRENCE-ID;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160624T100000 +END:VEVENT +END:VCALENDAR diff --git a/tests/test-eds-ics-repeating-events.cpp b/tests/test-eds-ics-repeating-events.cpp index d4f0026..4125623 100644 --- a/tests/test-eds-ics-repeating-events.cpp +++ b/tests/test-eds-ics-repeating-events.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; @@ -80,7 +81,7 @@ TEST_F(VAlarmFixture, MultipleAppointments) // what we expect to get... Appointment expected_appt; - expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@ubuntu-phablet"; + expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@lomiri-phablet"; expected_appt.color = "#becedd"; expected_appt.summary = "Alarm"; std::array<Alarm,8> expected_alarms = { diff --git a/tests/test-eds-ics-repeating-valarms.cpp b/tests/test-eds-ics-repeating-valarms.cpp index 2132b71..1584983 100644 --- a/tests/test-eds-ics-repeating-valarms.cpp +++ b/tests/test-eds-ics-repeating-valarms.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; diff --git a/tests/test-eds-ics-tzids-2.cpp b/tests/test-eds-ics-tzids-2.cpp index c8b0370..a1d2f5a 100644 --- a/tests/test-eds-ics-tzids-2.cpp +++ b/tests/test-eds-ics-tzids-2.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Los_Angeles"}; diff --git a/tests/test-eds-ics-tzids-utc.cpp b/tests/test-eds-ics-tzids-utc.cpp new file mode 100644 index 0000000..f79bf3e --- /dev/null +++ b/tests/test-eds-ics-tzids-utc.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2015 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 <algorithm> + +#include <datetime/alarm-queue-simple.h> +#include <datetime/clock-mock.h> +#include <datetime/engine-eds.h> +#include <datetime/myself.h> +#include <datetime/planner-range.h> + +#include <gtest/gtest.h> + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace ayatana::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, UTCAppointments) +{ + // start the EDS engine + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Recife"}; + auto tz = std::make_shared<MockTimezone>(zone_str); + + #if GLIB_CHECK_VERSION(2, 68, 0) + auto gtz = g_time_zone_new_identifier(zone_str); + + if (gtz == NULL) + { + gtz = g_time_zone_new_utc(); + } + #else + auto gtz = g_time_zone_new(zone_str); + #endif + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); + const DateTime range_begin {gtz, 2016,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2017,1, 1, 0, 0, 0.0}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // what we expect to get... + std::array<Appointment,1> expected_appts; + auto appt = &expected_appts[0]; + appt->uid = "20160322T132738Z"; + appt->color = "#becedd"; + appt->summary = "UTC event"; + appt->begin = DateTime{gtz,2016,3,22,15,0,0}; + appt->end = DateTime{gtz,2016,3,22,16,0,0}; + + // compare it to what we actually loaded... + const auto appts = planner->appointments().get(); + EXPECT_EQ(expected_appts.size(), appts.size()); + for (size_t i=0, n=std::min(appts.size(),expected_appts.size()); i<n; i++) + EXPECT_EQ(expected_appts[i], appts[i]); + + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-tzids-utc.ics.in b/tests/test-eds-ics-tzids-utc.ics.in new file mode 100644 index 0000000..da607d6 --- /dev/null +++ b/tests/test-eds-ics-tzids-utc.ics.in @@ -0,0 +1,15 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2016-03-22T16:13:53.714952Z(2) +BEGIN:VEVENT +UID:20160322T132738Z +DTSTAMP:20160322T133534Z +DTSTART:20160322T180000Z +DTEND:20160322T190000Z +SUMMARY:UTC event +SEQUENCE:1 +LAST-MODIFIED:20160322T133534Z +END:VEVENT +END:VCALENDAR diff --git a/tests/test-eds-ics-tzids.cpp b/tests/test-eds-ics-tzids.cpp index c80feb2..11d44b7 100644 --- a/tests/test-eds-ics-tzids.cpp +++ b/tests/test-eds-ics-tzids.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"Europe/Berlin"}; diff --git a/tests/test-formatter.cpp b/tests/test-formatter.cpp index 855d4c5..87c6475 100644 --- a/tests/test-formatter.cpp +++ b/tests/test-formatter.cpp @@ -66,15 +66,14 @@ class FormatterFixture: public GlibFixture bool SetLocale(const char* expected_locale, const char* name) { - setlocale(LC_TIME, expected_locale); - const auto actual_locale = setlocale(LC_TIME, nullptr); - if (!g_strcmp0(expected_locale, actual_locale)) + if (setlocale(LC_TIME, expected_locale) != nullptr) { return true; } else { - g_message("Unable to set locale to %s, actual locale is %s; skipping %s locale tests.", expected_locale, actual_locale, name); + g_warning("Unable to set locale to %s; skipping %s locale tests. (Current LC_TIME: %s)", + expected_locale, name, setlocale(LC_TIME, nullptr)); return false; } } @@ -87,7 +86,7 @@ class FormatterFixture: public GlibFixture /** * Test the phone header format */ -TEST_F(FormatterFixture, TestPhoneHeader) +TEST_F(FormatterFixture, DISABLED_TestPhoneHeader) { auto now = DateTime::Local(2020, 10, 31, 18, 30, 59); auto clock = std::make_shared<MockClock>(now); @@ -114,7 +113,7 @@ TEST_F(FormatterFixture, TestPhoneHeader) /** * Test the default values of the desktop header format */ -TEST_F(FormatterFixture, TestDesktopHeader) +TEST_F(FormatterFixture, DISABLED_TestDesktopHeader) { struct { bool is_12h; diff --git a/tests/test-live-actions.cpp b/tests/test-live-actions.cpp index 9f17001..2d6ac9b 100644 --- a/tests/test-live-actions.cpp +++ b/tests/test-live-actions.cpp @@ -17,18 +17,72 @@ * Charles Kerr <charles.kerr@canonical.com> */ +#include "state-mock.h" #include "timedated-fixture.h" +#include <datetime/actions-live.h> + +using namespace ayatana::indicator::datetime; + +class MockLiveActions: public LiveActions +{ +public: + std::string last_cmd; + std::string last_url; + + explicit MockLiveActions(const std::shared_ptr<State>& state_in): LiveActions(state_in) {} + ~MockLiveActions() {} +}; + +class TestLiveActionsFixture: public TimedatedFixture +{ +private: + + using super = TimedatedFixture; + +protected: + + std::shared_ptr<MockState> m_mock_state; + std::shared_ptr<State> m_state; + std::shared_ptr<MockLiveActions> m_live_actions; + std::shared_ptr<Actions> m_actions; + + void SetUp() override + { + super::SetUp(); + + // create the State and Actions + m_mock_state.reset(new MockState); + m_mock_state->settings.reset(new Settings); + m_state = std::dynamic_pointer_cast<State>(m_mock_state); + m_live_actions.reset(new MockLiveActions(m_state)); + m_actions = std::dynamic_pointer_cast<Actions>(m_live_actions); + + // start the timedate1 dbusmock + start_timedate1("Etc/Utc"); + } + + void TearDown() override + { + m_actions.reset(); + m_live_actions.reset(); + m_state.reset(); + m_mock_state.reset(); + + super::TearDown(); + } +}; + /*** **** ***/ -TEST_F(TimedateFixture, HelloWorld) +TEST_F(TestLiveActionsFixture, HelloWorld) { EXPECT_TRUE(true); } -TEST_F(TimedateFixture, SetLocation) +TEST_F(TestLiveActionsFixture, SetLocation) { const std::string tzid = "America/Chicago"; const std::string name = "Oklahoma City"; @@ -36,49 +90,50 @@ TEST_F(TimedateFixture, SetLocation) EXPECT_NE(expected, m_state->settings->timezone_name.get()); - m_actions->set_location(tzid, name); + std::string new_name; m_state->settings->timezone_name.changed().connect( - [this](const std::string&){ - g_main_loop_quit(loop); - }); - g_main_loop_run(loop); - EXPECT_EQ(attempted_tzid, tzid); - wait_msec(); + [&new_name](const std::string& n){new_name = n;} + ); + + m_actions->set_location(tzid, name); + EXPECT_TRUE(wait_for([&new_name](){return !new_name.empty();})); + EXPECT_EQ(expected, new_name); EXPECT_EQ(expected, m_state->settings->timezone_name.get()); + EXPECT_EQ(tzid, get_timedate1_timezone()); } /*** **** ***/ -TEST_F(TimedateFixture, DesktopOpenAlarmApp) +TEST_F(TestLiveActionsFixture, DesktopOpenAlarmApp) { - m_actions->desktop_open_alarm_app(); + m_actions->open_alarm_app(); const std::string expected = "evolution -c calendar"; EXPECT_EQ(expected, m_live_actions->last_cmd); } -TEST_F(TimedateFixture, DesktopOpenAppointment) +TEST_F(TestLiveActionsFixture, DesktopOpenAppointment) { Appointment a; a.uid = "some-uid"; a.begin = DateTime::NowLocal(); - m_actions->desktop_open_appointment(a); + m_actions->open_appointment(a, a.begin); const std::string expected_substr = "evolution \"calendar:///?startdate="; EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos); } -TEST_F(TimedateFixture, DesktopOpenCalendarApp) +TEST_F(TestLiveActionsFixture, DesktopOpenCalendarApp) { - m_actions->desktop_open_calendar_app(DateTime::NowLocal()); + m_actions->open_calendar_app(DateTime::NowLocal()); const std::string expected_substr = "evolution \"calendar:///?startdate="; EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos); } -TEST_F(TimedateFixture, DesktopOpenSettingsApp) +TEST_F(TestLiveActionsFixture, DesktopOpenSettingsApp) { - m_actions->desktop_open_settings_app(); + m_actions->open_settings_app(); const std::string expected_substr = "control-center"; EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos); } @@ -89,42 +144,45 @@ TEST_F(TimedateFixture, DesktopOpenSettingsApp) namespace { - const std::string clock_app_url = "appid://com.ubuntu.clock/clock/current-user-version"; - - const std::string calendar_app_url = "appid://com.ubuntu.calendar/calendar/current-user-version"; + const std::string clock_app_url = "alarm://"; } -TEST_F(TimedateFixture, PhoneOpenAlarmApp) +TEST_F(TestLiveActionsFixture, PhoneOpenAlarmApp) { - m_actions->phone_open_alarm_app(); + m_actions->open_alarm_app(); EXPECT_EQ(clock_app_url, m_live_actions->last_url); } -TEST_F(TimedateFixture, PhoneOpenAppointment) +TEST_F(TestLiveActionsFixture, PhoneOpenAppointment) { Appointment a; - a.uid = "some-uid"; + a.uid = "event-uid"; + a.source_uid = "source-uid"; a.begin = DateTime::NowLocal(); a.type = Appointment::EVENT; - m_actions->phone_open_appointment(a); - EXPECT_EQ(calendar_app_url, m_live_actions->last_url); + auto ocurrenceDate = DateTime::Local(2014, 1, 1, 0, 0, 0); + m_actions->open_appointment(a, ocurrenceDate); + const std::string appointment_app_url = ocurrenceDate.to_timezone("UTC").format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00"); + EXPECT_EQ(appointment_app_url, m_live_actions->last_url); a.type = Appointment::UBUNTU_ALARM; - m_actions->phone_open_appointment(a); + m_actions->open_appointment(a, a.begin); EXPECT_EQ(clock_app_url, m_live_actions->last_url); } -TEST_F(TimedateFixture, PhoneOpenCalendarApp) +TEST_F(TestLiveActionsFixture, PhoneOpenCalendarApp) { - m_actions->phone_open_calendar_app(DateTime::NowLocal()); - const std::string expected = "appid://com.ubuntu.calendar/calendar/current-user-version"; + auto now = DateTime::NowLocal(); + m_actions->open_calendar_app(now); + const std::string expected = now.to_timezone("UTC").format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00"); EXPECT_EQ(expected, m_live_actions->last_url); } -TEST_F(TimedateFixture, PhoneOpenSettingsApp) + +TEST_F(TestLiveActionsFixture, PhoneOpenSettingsApp) { - m_actions->phone_open_settings_app(); + m_actions->open_settings_app(); const std::string expected = "settings:///system/time-date"; EXPECT_EQ(expected, m_live_actions->last_url); } @@ -133,7 +191,7 @@ TEST_F(TimedateFixture, PhoneOpenSettingsApp) **** ***/ -TEST_F(TimedateFixture, CalendarState) +TEST_F(TestLiveActionsFixture, CalendarState) { // init the clock auto now = DateTime::Local(2014, 1, 1, 0, 0, 0); diff --git a/tests/test-menu-appointments.cpp b/tests/test-menu-appointments.cpp new file mode 100644 index 0000000..91a4b42 --- /dev/null +++ b/tests/test-menu-appointments.cpp @@ -0,0 +1,263 @@ +/* + * Copyright 2016 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 "glib-fixture.h" +#include "print-to.h" + +#include <datetime/appointment.h> +#include <datetime/menu.h> + +#include <vector> + +using MenuAppointmentFixture = GlibFixture; + +using namespace ayatana::indicator::datetime; + +namespace +{ + Appointment create_appointment( + const Appointment::Type& type, + const std::string& uid, + const std::string& summary, + const DateTime& begin, + const DateTime& end) + { + Appointment a; + a.type = type; + a.uid = uid; + a.summary = summary; + a.begin = begin; + a.end = end; + return a; + } +} + +TEST_F(MenuAppointmentFixture, DisplayEvents) +{ + const auto airport = create_appointment( + Appointment::UBUNTU_ALARM, + "uid-airport", + "Pick Aunt Mabel up at the airport", + DateTime::Local(2016,12,24,10,0,0), + DateTime::Local(2016,12,24,10,0,0) + ); + + const auto christmas_eve_candle_service = create_appointment( + Appointment::EVENT, + "uid-christmas-eve-candle-service", + "Christmas Eve Candle Service", + DateTime::Local(2016,12,24,22,0,0), + DateTime::Local(2016,12,24,23,0,0) + ); + + const auto christmas = create_appointment( + Appointment::EVENT, + "uid-christmas", + "Christmas", + DateTime::Local(2016,12,25,0,0,0), + DateTime::Local(2016,12,26,0,0,0) + ); + + const auto santa = create_appointment( + Appointment::UBUNTU_ALARM, + "uid-santa", + "Time to set out cookies and milk for Santa", + DateTime::Local(2016,12,25,1,0,0), + DateTime::Local(2016,12,25,1,0,0) + ); + + const auto bike = create_appointment( + Appointment::UBUNTU_ALARM, + "uid-bike", + "Remember to put out the bike, it's in the garage", + DateTime::Local(2016,12,25,1,0,0), + DateTime::Local(2016,12,25,1,0,0) + ); + + + const auto christmas_lunch = create_appointment( + Appointment::EVENT, + "uid-christmas-lunch", + "Christmas Lunch at Grandma's", + DateTime::Local(2016,12,25,12,0,0), + DateTime::Local(2016,12,25,14,0,0) + ); + + const auto boxing_day = create_appointment( + Appointment::EVENT, + "uid-boxing-day", + "Boxing Day", + DateTime::Local(2016,12,26,0,0,0), + DateTime::Local(2016,12,27,0,0,0) + ); + + const auto new_years_eve = create_appointment( + Appointment::EVENT, + "uid-new-years-eve", + "New Years' Eve", + DateTime::Local(2016,12,31,0,0,0), + DateTime::Local(2017,1,1,0,0,0) + ); + + const auto nye_party = create_appointment( + Appointment::EVENT, + "uid-new-years-party", + "New Year Party at Ted's", + DateTime::Local(2016,12,31,19,0,0), + DateTime::Local(2017, 1, 1, 2,0,0) + ); + + const auto new_years_day = create_appointment( + Appointment::EVENT, + "uid-new-years-day", + "New Years' Day", + DateTime::Local(2017,1,1,0,0,0), + DateTime::Local(2017,1,2,0,0,0) + ); + + const auto weekday_wakeup_alarm = create_appointment( + Appointment::UBUNTU_ALARM, + "wakeup-alarm", + "Wake Up", + DateTime::Local(2017,1,3,6,0,0), + DateTime::Local(2017,1,3,6,0,0) + ); + + const auto dentist_appointment = create_appointment( + Appointment::EVENT, + "dentist", + "Dentist", + DateTime::Local(2017,1,5,14,0,0), + DateTime::Local(2017,1,5,15,0,0) + ); + + const auto marcus_birthday = create_appointment( + Appointment::EVENT, + "uid-mlk", + "Marcus' Birthday", + DateTime::Local(2017,1,8,0,0,0), + DateTime::Local(2017,1,9,0,0,0) + ); + + const auto mlk_day = create_appointment( + Appointment::EVENT, + "uid-mlk", + "Martin Luther King Day", + DateTime::Local(2017,1,16,0,0,0), + DateTime::Local(2017,1,17,0,0,0) + ); + + const auto rodney_party = create_appointment( + Appointment::EVENT, + "uid-rodney", + "Rodney's Party", + DateTime::Local(2017,1,30,19,0,0), + DateTime::Local(2017,1,30,23,0,0) + ); + + const auto pub_with_pawel = create_appointment( + Appointment::UBUNTU_ALARM, + "uid-pawel", + "Meet Pawel at the Pub", + DateTime::Local(2017,2,4,19,0,0), + DateTime::Local(2017,2,4,19,0,0) + ); + + const struct + { + const char* const description; + DateTime start; + std::vector<Appointment> appointments; + std::vector<Appointment> expected_display_appointments; + int max_items; + } + tests[] = + { + { + "test presentation order: full-day events come before part-day events", + DateTime::Local(2016,12,25,6,0,0), + std::vector<Appointment>({christmas, christmas_lunch, boxing_day, new_years_eve}), + std::vector<Appointment>({christmas, christmas_lunch, boxing_day, new_years_eve}), + 5 + }, + { + "test presentation order: part-day events in chronological order", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({airport, christmas_eve_candle_service, christmas, santa, christmas_lunch}), + std::vector<Appointment>({airport, christmas_eve_candle_service, christmas, santa, christmas_lunch}), + 5 + }, + { + "test presentation order: multiple events with the same start+end sorted alphabetically", + DateTime::Local(2016,12,25,0,0,0), + std::vector<Appointment>({christmas, bike, santa, christmas_lunch}), + std::vector<Appointment>({christmas, bike, santa, christmas_lunch}), + 5 + }, + { + "test culling priority: today's part-day events outrank today's full-day events", + DateTime::Local(2016,12,25,1,0,0), + std::vector<Appointment>({christmas, santa, christmas_lunch}), + std::vector<Appointment>({santa, christmas_lunch}), + 2 + }, + { + "test culling priority: events later today outrank both tomorrow's full-day and part-day events", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({christmas_eve_candle_service, christmas, santa}), + std::vector<Appointment>({christmas_eve_candle_service}), + 1 + }, + { + "test edge cases: confirm <max events works ok", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({christmas, christmas_lunch}), + std::vector<Appointment>({christmas, christmas_lunch}), + 5 + }, + { + "test edge cases: confirm 0 events works ok", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({}), + std::vector<Appointment>({}), + 5 + }, + { + "test edge cases: confirm max-events of 0 doesn't crash", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({christmas, bike, santa, christmas_lunch}), + std::vector<Appointment>({}), + 0 + } + }; + + for (const auto& test : tests) + { + g_debug("running test: %s", test.description); + + // run the test... + ASSERT_EQ(test.expected_display_appointments, Menu::get_display_appointments(test.appointments, test.start, test.max_items)); + + // ...and again with a reversed vector to confirm input order doesn't matter + auto reversed = test.appointments; + std::reverse(reversed.begin(), reversed.end()); + ASSERT_EQ(test.expected_display_appointments, Menu::get_display_appointments(reversed, test.start, test.max_items)); + } +} + diff --git a/tests/test-notification-response.cpp b/tests/test-notification-response.cpp new file mode 100644 index 0000000..fd40ed8 --- /dev/null +++ b/tests/test-notification-response.cpp @@ -0,0 +1,144 @@ +/* + * Copyright 2014-2016 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/appointment.h> +#include <datetime/settings.h> +#include <datetime/snap.h> + +#include "notification-fixture.h" + +/*** +**** +***/ + +using namespace ayatana::indicator::datetime; + +namespace +{ + static constexpr char const * APP_NAME {"indicator-datetime-service"}; + + gboolean quit_idle (gpointer gloop) + { + g_main_loop_quit(static_cast<GMainLoop*>(gloop)); + return G_SOURCE_REMOVE; + } +} + +/*** +**** +***/ + +TEST_F(NotificationFixture,Response) +{ + // create the world + make_interactive(); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>(); + auto settings = std::make_shared<Settings>(); + int next_notification_id {FIRST_NOTIFY_ID}; + + // set a response callback that remembers what response we got + bool on_response_called {}; + Snap::Response response {}; + auto on_response = [this, &on_response_called, &response] + (const Appointment&, const Alarm&, const Snap::Response& r){ + on_response_called = true; + response = r; + g_idle_add(quit_idle, loop); + }; + + // our tests! + const struct { + Appointment appt; + std::vector<std::string> expected_actions; + std::string action; + Snap::Response expected_response; + } tests[] = { + { appt, {"show-app"}, "show-app", Snap::Response::ShowApp }, + { ualarm, {"none", "snooze"}, "snooze", Snap::Response::Snooze }, + { ualarm, {"none", "snooze"}, "none", Snap::Response::None } + }; + + + settings->cal_notification_enabled.set(true); + settings->cal_notification_sounds.set(true); + settings->cal_notification_vibrations.set(true); + settings->cal_notification_bubbles.set(true); + settings->cal_notification_list.set(true); + + // walk through the tests + for (const auto& test : tests) + { + // wait for previous iterations' bus noise to finish and reset the counters + GError* error {}; + wait_msec(500); + dbus_test_dbus_mock_object_clear_method_calls(notify_mock, notify_obj, &error); + g_assert_no_error(error); + on_response_called = false; + + // create a snap decision + auto snap = create_snap(ne, sb, settings); + (*snap)(test.appt, test.appt.alarms.front(), on_response); + + // wait for the notification to show up + EXPECT_METHOD_CALLED_EVENTUALLY(notify_mock, notify_obj, METHOD_NOTIFY); + + // test that Notify was called the right number of times + static constexpr int expected_num_notify_calls {1}; + guint num_notify_calls {}; + const auto notify_calls = dbus_test_dbus_mock_object_get_method_calls( + notify_mock, + notify_obj, + METHOD_NOTIFY, + &num_notify_calls, + &error); + g_assert_no_error(error); + EXPECT_EQ(expected_num_notify_calls, num_notify_calls); + + // test that Notify was called with the correct list of actions + if (num_notify_calls > 0) { + std::vector<std::string> actions; + const gchar** as {nullptr}; + g_variant_get_child(notify_calls[0].params, 5, "^a&s", &as); + for (int i=0; as && as[i]; i+=2) // actions are in pairs of (name, i18n), skip the i18n + actions.push_back(as[i]); + EXPECT_EQ(test.expected_actions, actions); + } + + // make the notification mock tell the world that the user invoked an action + const auto notification_id = next_notification_id++; + idle_add([this, notification_id, test](){ + GError* err {}; + dbus_test_dbus_mock_object_emit_signal(notify_mock, notify_obj, "ActionInvoked", + G_VARIANT_TYPE("(us)"), + g_variant_new("(us)", guint(notification_id), test.action.c_str()), + &err); + dbus_test_dbus_mock_object_emit_signal(notify_mock, notify_obj, "NotificationClosed", + G_VARIANT_TYPE("(uu)"), + g_variant_new("(uu)", guint(notification_id), guint(NOTIFICATION_CLOSED_DISMISSED)), + &err); + g_assert_no_error(err); + return G_SOURCE_REMOVE; + }); + + // confirm that the response callback got the right response + EXPECT_TRUE(wait_for([&on_response_called](){return on_response_called;})); + EXPECT_EQ(int(test.expected_response), int(response)) << "notification_id " << notification_id; + } +} diff --git a/tests/test-notification.cpp b/tests/test-notification.cpp new file mode 100644 index 0000000..83a021f --- /dev/null +++ b/tests/test-notification.cpp @@ -0,0 +1,198 @@ +/* + * Copyright 2014-2016 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/appointment.h> +#include <datetime/settings.h> +#include <datetime/snap.h> + +#include "notification-fixture.h" + +/*** +**** +***/ + +using namespace ayatana::indicator::datetime; + +namespace +{ + static constexpr char const * APP_NAME {"indicator-datetime-service"}; + + gboolean quit_idle (gpointer gloop) + { + g_main_loop_quit(static_cast<GMainLoop*>(gloop)); + return G_SOURCE_REMOVE; + } +} + +/*** +**** +***/ + +TEST_F(NotificationFixture,Notification) +{ + // Feed different combinations of system settings, + // indicator-datetime settings, and event types, + // then see if notifications and haptic feedback behave as expected. + + auto settings = std::make_shared<Settings>(); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>(); + auto func = [this](const Appointment&, const Alarm&, const Snap::Response&){g_idle_add(quit_idle, loop);}; + + // combinatorial factor #1: event type + struct { + Appointment appt; + const char* icon_name; + const char* prefix; + bool expected_notify_called; + bool expected_vibrate_called; + } test_appts[] = { + { appt, "calendar-app", "Event", true, true }, + { ualarm, "alarm-clock", "Alarm", true, true } + }; + + // combinatorial factor #2: indicator-datetime's haptic mode + struct { + const char* haptic_mode; + bool expected_notify_called; + bool expected_vibrate_called; + } test_haptics[] = { + { "none", true, false }, + { "pulse", true, true } + }; + + // combinatorial factor #3: system settings' "other vibrations" enabled + struct { + bool other_vibrations; + bool expected_notify_called; + bool expected_vibrate_called; + } test_other_vibrations[] = { + { true, true, true }, + { false, true, false } + }; + + // combinatorial factor #4: system settings' notifications disabled + struct { + bool cal_notification_enabled; // calendar app can trigger notifications + std::set<Appointment::Type> expected_notify_called; // do we expect the notification to show? + std::set<Appointment::Type> expected_vibrate_called; // do we expect the phone to vibrate? + } test_cal_disabled[] = { + { true, std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM, Appointment::Type::EVENT }, + std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM, Appointment::Type::EVENT } }, + { false, std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM }, + std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM } } + }; + + for (const auto& test_appt : test_appts) + { + for (const auto& test_haptic : test_haptics) + { + for (const auto& test_vibes : test_other_vibrations) + { + for (const auto& test_disabled : test_cal_disabled) + { + const bool expected_notify_called = test_appt.expected_notify_called + && test_vibes.expected_notify_called + && (test_disabled.expected_notify_called.count(test_appt.appt.type) > 0) + && test_haptic.expected_notify_called; + + const bool expected_vibrate_called = test_appt.expected_vibrate_called + && test_vibes.expected_vibrate_called + && (test_disabled.expected_vibrate_called.count(test_appt.appt.type) > 0) + && test_haptic.expected_vibrate_called; + + // set test case properties: cal_notification_enabled + settings->cal_notification_enabled.set(test_disabled.cal_notification_enabled); + settings->cal_notification_sounds.set(test_disabled.cal_notification_enabled); + settings->cal_notification_vibrations.set(test_disabled.cal_notification_enabled); + settings->cal_notification_bubbles.set(test_disabled.cal_notification_enabled); + settings->cal_notification_list.set(test_disabled.cal_notification_enabled); + + // set test case properties: haptic mode + settings->alarm_haptic.set(test_haptic.haptic_mode); + + // set test case properties: other-vibrations flag + // (and wait for the PropertiesChanged signal so we know the dbusmock got it) + GError * error {}; + dbus_test_dbus_mock_object_update_property(as_mock, + as_obj, + PROP_OTHER_VIBRATIONS, + g_variant_new_boolean(test_vibes.other_vibrations), + &error); + g_assert_no_error(error); + + // wait for previous iterations' bus noise to finish and reset the counters + wait_msec(500); + dbus_test_dbus_mock_object_clear_method_calls(haptic_mock, haptic_obj, &error); + dbus_test_dbus_mock_object_clear_method_calls(notify_mock, notify_obj, &error); + g_assert_no_error(error); + + // run the test + auto snap = create_snap(ne, sb, settings); + (*snap)(test_appt.appt, appt.alarms.front(), func); + + // confirm that the notification was as expected + if (expected_notify_called) { + EXPECT_METHOD_CALLED_EVENTUALLY(notify_mock, notify_obj, METHOD_NOTIFY); + } else { + EXPECT_METHOD_NOT_CALLED_EVENTUALLY(notify_mock, notify_obj, METHOD_NOTIFY); + } + + // confirm that the vibration was as expected + if (expected_vibrate_called) { + EXPECT_METHOD_CALLED_EVENTUALLY(haptic_mock, haptic_obj, HAPTIC_METHOD_VIBRATE_PATTERN); + } else { + EXPECT_METHOD_NOT_CALLED_EVENTUALLY(haptic_mock, haptic_obj, HAPTIC_METHOD_VIBRATE_PATTERN); + } + + // confirm that the notification was as expected + guint num_notify_calls = 0; + const auto notify_calls = dbus_test_dbus_mock_object_get_method_calls(notify_mock, + notify_obj, + METHOD_NOTIFY, + &num_notify_calls, + &error); + g_assert_no_error(error); + if (num_notify_calls > 0) + { + // test that Notify was called with the app_name + const gchar* app_name {nullptr}; + g_variant_get_child(notify_calls[0].params, 0, "&s", &app_name); + ASSERT_STREQ(APP_NAME, app_name); + + // test that Notify was called with the type-appropriate icon + const gchar* icon_name {nullptr}; + g_variant_get_child(notify_calls[0].params, 2, "&s", &icon_name); + ASSERT_STREQ(test_appt.icon_name, icon_name); + + // test that the Notification title has the correct prefix + const gchar* title {nullptr}; + g_variant_get_child(notify_calls[0].params, 3, "&s", &title); + ASSERT_TRUE(g_str_has_prefix(title, test_appt.prefix)); + + // test that Notify was called with the appointment's body + const gchar* body {nullptr}; + g_variant_get_child(notify_calls[0].params, 4, "&s", &body); + ASSERT_STREQ(test_appt.appt.summary.c_str(), body); + } + } + } + } + } +} diff --git a/tests/test-settings.cpp b/tests/test-settings.cpp index 3af9eab..b9658f4 100644 --- a/tests/test-settings.cpp +++ b/tests/test-settings.cpp @@ -22,6 +22,11 @@ #include <datetime/settings-live.h> #include <datetime/settings-shared.h> +extern "C" +{ + #include <ayatana/common/utils.h> +} + using namespace ayatana::indicator::datetime; /*** @@ -37,19 +42,23 @@ protected: std::shared_ptr<LiveSettings> m_live; std::shared_ptr<Settings> m_settings; - GSettings * m_gsettings; + GSettings * m_gsettings {}; + GSettings * m_gsettings_cal_notification {}; void SetUp() override { super::SetUp(); m_gsettings = g_settings_new(SETTINGS_INTERFACE); + m_gsettings_cal_notification = g_settings_new_with_path(SETTINGS_NOTIFY_SCHEMA_ID, SETTINGS_NOTIFY_CALENDAR_PATH); + m_live.reset(new LiveSettings); m_settings = std::dynamic_pointer_cast<Settings>(m_live); } void TearDown() override { + g_clear_object(&m_gsettings_cal_notification); g_clear_object(&m_gsettings); m_settings.reset(); m_live.reset(); @@ -57,62 +66,62 @@ protected: super::TearDown(); } - void TestBoolProperty(core::Property<bool>& property, const gchar* key) + void TestBoolProperty(GSettings* gsettings, core::Property<bool>& property, const gchar* key) { - EXPECT_EQ(g_settings_get_boolean(m_gsettings, key), property.get()); - g_settings_set_boolean(m_gsettings, key, false); + EXPECT_EQ(g_settings_get_boolean(gsettings, key), property.get()); + g_settings_set_boolean(gsettings, key, false); EXPECT_FALSE(property.get()); - g_settings_set_boolean(m_gsettings, key, true); + g_settings_set_boolean(gsettings, key, true); EXPECT_TRUE(property.get()); property.set(false); - EXPECT_FALSE(g_settings_get_boolean(m_gsettings, key)); + EXPECT_FALSE(g_settings_get_boolean(gsettings, key)); property.set(true); - EXPECT_TRUE(g_settings_get_boolean(m_gsettings, key)); + EXPECT_TRUE(g_settings_get_boolean(gsettings, key)); } - void TestStringProperty(core::Property<std::string>& property, const gchar* key) + void TestStringProperty(GSettings* gsettings, core::Property<std::string>& property, const gchar* key) { gchar* tmp; std::string str; - tmp = g_settings_get_string(m_gsettings, key); + tmp = g_settings_get_string(gsettings, key); EXPECT_EQ(tmp, property.get()); g_clear_pointer(&tmp, g_free); str = "a"; - g_settings_set_string(m_gsettings, key, str.c_str()); + g_settings_set_string(gsettings, key, str.c_str()); EXPECT_EQ(str, property.get()); str = "b"; - g_settings_set_string(m_gsettings, key, str.c_str()); + g_settings_set_string(gsettings, key, str.c_str()); EXPECT_EQ(str, property.get()); str = "a"; property.set(str); - tmp = g_settings_get_string(m_gsettings, key); + tmp = g_settings_get_string(gsettings, key); EXPECT_EQ(str, tmp); g_clear_pointer(&tmp, g_free); str = "b"; property.set(str); - tmp = g_settings_get_string(m_gsettings, key); + tmp = g_settings_get_string(gsettings, key); EXPECT_EQ(str, tmp); g_clear_pointer(&tmp, g_free); } - void TestUIntProperty(core::Property<unsigned int>& property, const gchar* key) + void TestUIntProperty(GSettings* gsettings, core::Property<unsigned int>& property, const gchar* key) { - EXPECT_EQ(g_settings_get_uint(m_gsettings, key), property.get()); + EXPECT_EQ(g_settings_get_uint(gsettings, key), property.get()); unsigned int expected_values[] = { 1, 2, 3 }; // modify GSettings and confirm that the new value is propagated for(const auto& expected_value : expected_values) { - g_settings_set_uint(m_gsettings, key, expected_value); + g_settings_set_uint(gsettings, key, expected_value); EXPECT_EQ(expected_value, property.get()); - EXPECT_EQ(expected_value, g_settings_get_uint(m_gsettings, key)); + EXPECT_EQ(expected_value, g_settings_get_uint(gsettings, key)); } // modify the property and confirm that the new value is propagated @@ -120,7 +129,7 @@ protected: { property.set(expected_value); EXPECT_EQ(expected_value, property.get()); - EXPECT_EQ(expected_value, g_settings_get_uint(m_gsettings, key)); + EXPECT_EQ(expected_value, g_settings_get_uint(gsettings, key)); } } }; @@ -136,31 +145,31 @@ TEST_F(SettingsFixture, HelloWorld) TEST_F(SettingsFixture, BoolProperties) { - TestBoolProperty(m_settings->show_seconds, SETTINGS_SHOW_SECONDS_S); - TestBoolProperty(m_settings->show_calendar, SETTINGS_SHOW_CALENDAR_S); - TestBoolProperty(m_settings->show_date, SETTINGS_SHOW_DATE_S); - TestBoolProperty(m_settings->show_day, SETTINGS_SHOW_DAY_S); - TestBoolProperty(m_settings->show_detected_location, SETTINGS_SHOW_DETECTED_S); - TestBoolProperty(m_settings->show_events, SETTINGS_SHOW_EVENTS_S); - TestBoolProperty(m_settings->show_locations, SETTINGS_SHOW_LOCATIONS_S); - TestBoolProperty(m_settings->show_week_numbers, SETTINGS_SHOW_WEEK_NUMBERS_S); - TestBoolProperty(m_settings->show_year, SETTINGS_SHOW_YEAR_S); + TestBoolProperty(m_gsettings, m_settings->show_seconds, SETTINGS_SHOW_SECONDS_S); + TestBoolProperty(m_gsettings, m_settings->show_calendar, SETTINGS_SHOW_CALENDAR_S); + TestBoolProperty(m_gsettings, m_settings->show_date, SETTINGS_SHOW_DATE_S); + TestBoolProperty(m_gsettings, m_settings->show_day, SETTINGS_SHOW_DAY_S); + TestBoolProperty(m_gsettings, m_settings->show_detected_location, SETTINGS_SHOW_DETECTED_S); + TestBoolProperty(m_gsettings, m_settings->show_events, SETTINGS_SHOW_EVENTS_S); + TestBoolProperty(m_gsettings, m_settings->show_locations, SETTINGS_SHOW_LOCATIONS_S); + TestBoolProperty(m_gsettings, m_settings->show_week_numbers, SETTINGS_SHOW_WEEK_NUMBERS_S); + TestBoolProperty(m_gsettings, m_settings->show_year, SETTINGS_SHOW_YEAR_S); } TEST_F(SettingsFixture, UIntProperties) { - TestUIntProperty(m_settings->alarm_duration, SETTINGS_ALARM_DURATION_S); - TestUIntProperty(m_settings->alarm_volume, SETTINGS_ALARM_VOLUME_S); - TestUIntProperty(m_settings->snooze_duration, SETTINGS_SNOOZE_DURATION_S); + TestUIntProperty(m_gsettings, m_settings->alarm_duration, SETTINGS_ALARM_DURATION_S); + TestUIntProperty(m_gsettings, m_settings->alarm_volume, SETTINGS_ALARM_VOLUME_S); + TestUIntProperty(m_gsettings, m_settings->snooze_duration, SETTINGS_SNOOZE_DURATION_S); } TEST_F(SettingsFixture, StringProperties) { - TestStringProperty(m_settings->custom_time_format, SETTINGS_CUSTOM_TIME_FORMAT_S); - TestStringProperty(m_settings->timezone_name, SETTINGS_TIMEZONE_NAME_S); - TestStringProperty(m_settings->alarm_sound, SETTINGS_ALARM_SOUND_S); - TestStringProperty(m_settings->calendar_sound, SETTINGS_CALENDAR_SOUND_S); - TestStringProperty(m_settings->alarm_haptic, SETTINGS_ALARM_HAPTIC_S); + TestStringProperty(m_gsettings, m_settings->custom_time_format, SETTINGS_CUSTOM_TIME_FORMAT_S); + TestStringProperty(m_gsettings, m_settings->timezone_name, SETTINGS_TIMEZONE_NAME_S); + TestStringProperty(m_gsettings, m_settings->alarm_sound, SETTINGS_ALARM_SOUND_S); + TestStringProperty(m_gsettings, m_settings->calendar_sound, SETTINGS_CALENDAR_SOUND_S); + TestStringProperty(m_gsettings, m_settings->alarm_haptic, SETTINGS_ALARM_HAPTIC_S); } TEST_F(SettingsFixture, TimeFormatMode) @@ -221,3 +230,16 @@ TEST_F(SettingsFixture, Locations) g_strfreev(tmp); EXPECT_EQ(bv, vtmp); } + +TEST_F(SettingsFixture, MutedApps) +{ + if (!m_gsettings_cal_notification) { + return; + } + + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_enabled, SETTINGS_NOTIFY_ENABLED_KEY); + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_sounds, SETTINGS_NOTIFY_SOUNDS_KEY); + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_vibrations, SETTINGS_NOTIFY_VIBRATIONS_KEY); + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_bubbles, SETTINGS_NOTIFY_BUBBLES_KEY); + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_list, SETTINGS_NOTIFY_LIST_KEY); +} diff --git a/tests/test-sound.cpp b/tests/test-sound.cpp new file mode 100644 index 0000000..f808db6 --- /dev/null +++ b/tests/test-sound.cpp @@ -0,0 +1,182 @@ +/* + * Copyright 2014-2016 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/appointment.h> +#include <datetime/settings.h> +#include <datetime/snap.h> + +#include "notification-fixture.h" + +using namespace ayatana::indicator::datetime; + +namespace uin = ayatana::indicator::notifications; + +/*** +**** +***/ + +namespace +{ + static constexpr char const * APP_NAME {"indicator-datetime-service"}; +} + + +namespace +{ + gboolean quit_idle (gpointer gloop) + { + g_main_loop_quit(static_cast<GMainLoop*>(gloop)); + return G_SOURCE_REMOVE; + }; +} + +/*** +**** +***/ + +TEST_F(NotificationFixture, InteractiveDuration) +{ + static constexpr int duration_minutes = 120; + auto settings = std::make_shared<Settings>(); + settings->alarm_duration.set(duration_minutes); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>(); + auto snap = create_snap(ne, sb, settings); + + settings->cal_notification_enabled.set(true); + settings->cal_notification_sounds.set(true); + settings->cal_notification_vibrations.set(true); + settings->cal_notification_bubbles.set(true); + settings->cal_notification_list.set(true); + + make_interactive(); + + // call the Snap Decision + auto func = [this](const Appointment&, const Alarm&, const Snap::Response&){g_idle_add(quit_idle, loop);}; + (*snap)(appt, appt.alarms.front(), func); + + // confirm that Notify got called once + guint len = 0; + GError * error = nullptr; + const auto calls = dbus_test_dbus_mock_object_get_method_calls (notify_mock, + notify_obj, + METHOD_NOTIFY, + &len, + &error); + g_assert_no_error(error); + ASSERT_EQ(1, len); + + // confirm that the app_name passed to Notify was APP_NAME + const auto& params = calls[0].params; + ASSERT_EQ(8, g_variant_n_children(params)); + const char * str = nullptr; + g_variant_get_child (params, 0, "&s", &str); + ASSERT_STREQ(APP_NAME, str); + + // confirm that the icon passed to Notify was "alarm-clock" + g_variant_get_child (params, 2, "&s", &str); + ASSERT_STREQ("calendar-app", str); + + // confirm that the hints passed to Notify included a timeout matching duration_minutes + int32_t i32; + bool b; + auto hints = g_variant_get_child_value (params, 6); + b = g_variant_lookup (hints, HINT_TIMEOUT, "i", &i32); + EXPECT_TRUE(b); + const auto duration = std::chrono::minutes(duration_minutes); + EXPECT_EQ(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(), i32); + g_variant_unref(hints); + ne.reset(); +} + +/*** +**** +***/ + +/** + * A DefaultSoundBuilder wrapper which remembers the parameters of the last sound created. + */ +class TestSoundBuilder: public uin::SoundBuilder +{ +public: + TestSoundBuilder() =default; + ~TestSoundBuilder() =default; + + virtual std::shared_ptr<uin::Sound> create(const std::string& role, const std::string& uri, unsigned int volume, bool loop) override { + m_role = role; + m_uri = uri; + m_volume = volume; + m_loop = loop; + return m_impl.create(role, uri, volume, loop); + } + + const std::string& role() { return m_role; } + const std::string& uri() { return m_uri; } + unsigned int volume() { return m_volume; } + bool loop() { return m_loop; } + +private: + std::string m_role; + std::string m_uri; + unsigned int m_volume; + bool m_loop; + uin::DefaultSoundBuilder m_impl; +}; + +std::string path_to_uri(const std::string& path) +{ + auto file = g_file_new_for_path(path.c_str()); + auto uri_cstr = g_file_get_uri(file); + std::string uri = uri_cstr; + g_free(uri_cstr); + g_clear_pointer(&file, g_object_unref); + return uri; +} + +TEST_F(NotificationFixture,DefaultSounds) +{ + auto settings = std::make_shared<Settings>(); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<TestSoundBuilder>(); + auto func = [this](const Appointment&, const Alarm&, const Snap::Response&){g_idle_add(quit_idle, loop);}; + + settings->cal_notification_enabled.set(true); + settings->cal_notification_sounds.set(true); + settings->cal_notification_vibrations.set(true); + settings->cal_notification_bubbles.set(true); + settings->cal_notification_list.set(true); + + const struct { + Appointment appointment; + std::string expected_role; + std::string expected_uri; + } test_cases[] = { + { ualarm, "alarm", path_to_uri(ALARM_DEFAULT_SOUND) } + // No sound for appointments + // { appt, "alert", path_to_uri(CALENDAR_DEFAULT_SOUND) } + }; + + auto snap = create_snap(ne, sb, settings); + for(const auto& test_case : test_cases) + { + (*snap)(test_case.appointment, test_case.appointment.alarms.front(), func); + EXPECT_EQ(test_case.expected_uri, sb->uri()); + EXPECT_EQ(test_case.expected_role, sb->role()); + } +} diff --git a/tests/test-timezone-timedated.cpp b/tests/test-timezone-timedated.cpp index 2fdec12..c686c39 100644 --- a/tests/test-timezone-timedated.cpp +++ b/tests/test-timezone-timedated.cpp @@ -1,9 +1,5 @@ - /* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> + * Copyright © 2014-2016 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 @@ -16,127 +12,76 @@ * * 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> + * Ted Gould <ted.gould@canonical.com> */ #include "timedated-fixture.h" #include <datetime/timezone-timedated.h> -using ayatana::indicator::datetime::TimedatedTimezone; +using namespace ayatana::indicator::datetime; + +using TestTimedatedFixture = TimedatedFixture; /*** **** ***/ -#define TIMEZONE_FILE (SANDBOX"/timezone") - -class TimezoneFixture: public TimedateFixture +TEST_F(TestTimedatedFixture, HelloWorld) { - private: - - typedef TimedateFixture super; - - protected: - - void SetUp() override - { - super::SetUp(); - } - - void TearDown() override - { - super::TearDown(); - } - - public: - - /* convenience func to set the timezone file */ - void set_file(const std::string& text) - { - g_debug("set_file %s %s", TIMEZONE_FILE, text.c_str()); - auto fp = fopen(TIMEZONE_FILE, "w+"); - fprintf(fp, "%s\n", text.c_str()); - fclose(fp); - sync(); - } -}; +} /** - * Test that timezone-timedated warns, but doesn't crash, if the timezone file doesn't exist + * Test that the tzid is right if timedated isn't available */ -TEST_F(TimezoneFixture, NoFile) +TEST_F(TestTimedatedFixture, DefaultTimezone) { - remove(TIMEZONE_FILE); - ASSERT_FALSE(g_file_test(TIMEZONE_FILE, G_FILE_TEST_EXISTS)); + const std::string expected_tzid{"Etc/Utc"}; - expectLogMessage(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*No such file or directory*"); - TimedatedTimezone tz(TIMEZONE_FILE); + TimedatedTimezone tmp {m_bus}; + EXPECT_TZID(expected_tzid, tmp); } /** - * Test that timezone-timedated gives a default of UTC if the file doesn't exist + * Test that the tzid is right if timedated shows BEFORE we start */ -TEST_F(TimezoneFixture, DefaultValueNoFile) +TEST_F(TestTimedatedFixture, Timedate1First) { - const std::string expected_timezone = "Etc/Utc"; - remove(TIMEZONE_FILE); - ASSERT_FALSE(g_file_test(TIMEZONE_FILE, G_FILE_TEST_EXISTS)); + const std::string expected_tzid{"America/Chicago"}; - expectLogMessage(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*No such file or directory*"); - TimedatedTimezone tz(TIMEZONE_FILE); - ASSERT_EQ(expected_timezone, tz.timezone.get()); + start_timedate1(expected_tzid); + TimedatedTimezone tmp {m_bus}; + EXPECT_TZID(expected_tzid, tmp); } /** - * Test that timezone-timedated picks up the initial value + * Test that the tzid is right if timedated shows AFTER we start */ -TEST_F(TimezoneFixture, InitialValue) +TEST_F(TestTimedatedFixture, Timedate1Last) { - const std::string expected_timezone = "America/Chicago"; - set_file(expected_timezone); - TimedatedTimezone tz(TIMEZONE_FILE); + const std::string expected_tzid("America/Los_Angeles"); + + TimedatedTimezone tmp {m_bus}; + start_timedate1(expected_tzid); + EXPECT_TZID(expected_tzid, tmp); } /** - * Test that changing the tz after we are running works. + * Test that the tzid is right if timedated's property changes */ -TEST_F(TimezoneFixture, ChangedValue) +TEST_F(TestTimedatedFixture, TimezoneChange) { - const std::string initial_timezone = "America/Chicago"; - const std::string changed_timezone = "America/New_York"; - - set_file(initial_timezone); - - TimedatedTimezone tz(TIMEZONE_FILE); - ASSERT_EQ(initial_timezone, tz.timezone.get()); - - bool changed = false; - tz.timezone.changed().connect( - [&changed, this](const std::string& s){ - g_message("timezone changed to %s", s.c_str()); - changed = true; - g_main_loop_quit(loop); - }); + const std::vector<std::string> expected_tzids{"America/Los_Angeles", "America/Chicago", "Etc/Utc"}; - g_idle_add([](gpointer gself){ - static_cast<TimedateFixture*>(gself)->set_timezone("America/New_York"); - return G_SOURCE_REMOVE; - }, this); + TimedatedTimezone tmp {m_bus}; + start_timedate1("America/New_York"); - g_main_loop_run(loop); - - ASSERT_TRUE(changed); - ASSERT_EQ(changed_timezone, tz.timezone.get()); -} - -/** - * Test that timezone-timedated picks up the initial value - */ -TEST_F(TimezoneFixture, IgnoreComments) -{ - const std::string comment = "# Created by cloud-init v. 0.7.5 on Thu, 24 Apr 2014 14:03:29 +0000"; - const std::string expected_timezone = "Europe/Berlin"; - set_file(comment + "\n" + expected_timezone); - TimedatedTimezone tz(TIMEZONE_FILE); - ASSERT_EQ(expected_timezone, tz.timezone.get()); + for(const auto& expected_tzid : expected_tzids) + { + set_timedate1_timezone(expected_tzid); + EXPECT_TZID(expected_tzid, tmp); + } } diff --git a/tests/timedated-fixture.h b/tests/timedated-fixture.h index 00269e0..3aff986 100644 --- a/tests/timedated-fixture.h +++ b/tests/timedated-fixture.h @@ -17,285 +17,132 @@ * Charles Kerr <charles.kerr@canonical.com> */ -#ifndef INDICATOR_DATETIME_TESTS_TIMEDATED_FIXTURE_H -#define INDICATOR_DATETIME_TESTS_TIMEDATED_FIXTURE_H +#pragma once #include <datetime/actions-live.h> -#include "state-mock.h" #include "glib-fixture.h" -using namespace ayatana::indicator::datetime; - -class MockLiveActions: public LiveActions -{ -public: - std::string last_cmd; - std::string last_url; - explicit MockLiveActions(const std::shared_ptr<State>& state_in): LiveActions(state_in) {} - ~MockLiveActions() {} - -protected: - void dispatch_url(const std::string& url) override { last_url = url; } - void execute_command(const std::string& cmd) override { last_cmd = cmd; } -}; +#include <datetime/dbus-shared.h> +#include <datetime/timezone.h> /*** **** ***/ -using namespace ayatana::indicator::datetime; - -class TimedateFixture: public GlibFixture +struct TimedatedFixture: public GlibFixture { private: - typedef GlibFixture super; - - static GVariant * timedate1_get_properties (GDBusConnection * /*connection*/ , - const gchar * /*sender*/, - const gchar * /*object_path*/, - const gchar * /*interface_name*/, - const gchar *property_name, - GError ** /*error*/, - gpointer gself) + using super = GlibFixture; - { - auto self = static_cast<TimedateFixture*>(gself); - g_debug("get_properties called"); - if (g_strcmp0(property_name, "Timezone") == 0) - { - g_debug("timezone requested, giving '%s'", - self->attempted_tzid.c_str()); - return g_variant_new_string(self->attempted_tzid.c_str()); - } - return nullptr; - } +protected: + GDBusConnection* m_bus {}; + GTestDBus* m_test_bus {}; - static void on_bus_acquired(GDBusConnection* conn, - const gchar* name, - gpointer gself) + virtual void SetUp() override { - auto self = static_cast<TimedateFixture*>(gself); - g_debug("bus acquired: %s, connection is %p", name, conn); - - /* Set up a fake timedated which handles setting and getting the - ** timezone - */ - static const GDBusInterfaceVTable vtable = { - timedate1_handle_method_call, - timedate1_get_properties, /* GetProperty */ - nullptr, /* SetProperty */ - }; + super::SetUp(); - self->connection = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(conn))); + // use a fake bus + m_test_bus = g_test_dbus_new(G_TEST_DBUS_NONE); + g_test_dbus_up(m_test_bus); + const char * address = g_test_dbus_get_bus_address(m_test_bus); + g_setenv("DBUS_SYSTEM_BUS_ADDRESS", address, true); + g_setenv("DBUS_SESSION_BUS_ADDRESS", address, true); + g_debug("test_dbus's address is %s", address); - GError* error = nullptr; - self->object_register_id = g_dbus_connection_register_object( - conn, - "/org/freedesktop/timedate1", - self->node_info->interfaces[0], - &vtable, - self, - nullptr, - &error); - g_assert_no_error(error); + // get the bus + m_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); + g_dbus_connection_set_exit_on_close(m_bus, FALSE); + g_object_add_weak_pointer(G_OBJECT(m_bus), (gpointer*)&m_bus); } - static void on_name_acquired(GDBusConnection* conn, - const gchar* name, - gpointer gself) + virtual void TearDown() override { - g_debug("on_name_acquired"); - auto self = static_cast<TimedateFixture*>(gself); - self->name_acquired = true; - self->proxy = g_dbus_proxy_new_sync(conn, - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, - nullptr, - name, - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - nullptr, - nullptr); - g_main_loop_quit(self->loop); - } + g_clear_object(&m_bus); + g_clear_object(&m_test_bus); - static void on_name_lost(GDBusConnection* /*conn*/, - const gchar* /*name*/, - gpointer gself) - { - g_debug("on_name_lost"); - auto self = static_cast<TimedateFixture*>(gself); - self->name_acquired = false; + super::TearDown(); } - static void on_bus_closed(GObject* /*object*/, - GAsyncResult* res, - gpointer gself) + void start_timedate1(const std::string& tzid) { - g_debug("on_bus_closed"); - auto self = static_cast<TimedateFixture*>(gself); - GError* err = nullptr; - g_dbus_connection_close_finish(self->connection, res, &err); - g_assert_no_error(err); - g_main_loop_quit(self->loop); + // start dbusmock with the timedated template + auto json_parameters = g_strdup_printf("{\"Timezone\": \"%s\"}", tzid.c_str()); + const gchar* child_argv[] = { + "python3", "-m", "dbusmock", + "--template", "timedated", + "--parameters", json_parameters, + nullptr + }; + GError* error = nullptr; + g_spawn_async(nullptr, (gchar**)child_argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error); + g_assert_no_error(error); + g_free(json_parameters); + + // wait for it to appear on the bus + wait_for_name_owned(m_bus, Bus::Timedate1::BUSNAME); } - static void - timedate1_handle_method_call(GDBusConnection * connection, - const gchar * /*sender*/, - const gchar * object_path, - const gchar * interface_name, - const gchar * method_name, - GVariant * parameters, - GDBusMethodInvocation * invocation, - gpointer gself) + bool wait_for_tzid(const std::string& tzid, ayatana::indicator::datetime::Timezone& tz) { - g_assert(!g_strcmp0(method_name, "SetTimezone")); - g_assert(g_variant_is_of_type(parameters, G_VARIANT_TYPE_TUPLE)); - g_assert(2 == g_variant_n_children(parameters)); - - auto child = g_variant_get_child_value(parameters, 0); - g_assert(g_variant_is_of_type(child, G_VARIANT_TYPE_STRING)); - auto self = static_cast<TimedateFixture*>(gself); - self->attempted_tzid = g_variant_get_string(child, nullptr); - g_debug("set tz (dbus side): '%s'", self->attempted_tzid.c_str()); - g_dbus_method_invocation_return_value(invocation, nullptr); - - /* Send PropertiesChanged */ - GError * local_error = nullptr; - auto builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); - g_variant_builder_add (builder, - "{sv}", - "Timezone", - g_variant_new_string( - self->attempted_tzid.c_str())); - g_dbus_connection_emit_signal (connection, - NULL, - object_path, - "org.freedesktop.DBus.Properties", - "PropertiesChanged", - g_variant_new ("(sa{sv}as)", - interface_name, - builder, - NULL), - &local_error); - g_assert_no_error (local_error); - g_variant_unref(child); + return wait_for([&tzid, &tz](){return tzid == tz.timezone.get();}); } -protected: - - std::shared_ptr<MockState> m_mock_state; - std::shared_ptr<State> m_state; - std::shared_ptr<MockLiveActions> m_live_actions; - std::shared_ptr<Actions> m_actions; - - bool name_acquired; - std::string attempted_tzid; - - GTestDBus* bus; - guint own_name; - GDBusConnection* connection; - GDBusNodeInfo* node_info; - int object_register_id; - GDBusProxy *proxy; - - void SetUp() + void set_timedate1_timezone(const std::string& tzid) { - super::SetUp(); - g_debug("SetUp"); - - name_acquired = false; - attempted_tzid.clear(); - connection = nullptr; - node_info = nullptr; - object_register_id = 0; - own_name = 0; - proxy = nullptr; - - // bring up the test bus - bus = g_test_dbus_new(G_TEST_DBUS_NONE); - g_test_dbus_up(bus); - const auto address = g_test_dbus_get_bus_address(bus); - g_setenv("DBUS_SYSTEM_BUS_ADDRESS", address, true); - g_setenv("DBUS_SESSION_BUS_ADDRESS", address, true); - g_debug("test_dbus's address is %s", address); - - // parse the org.freedesktop.timedate1 interface - const gchar introspection_xml[] = - "<node>" - " <interface name='org.freedesktop.timedate1'>" - " <property name='Timezone' type='s' access='read' />" - " <method name='SetTimezone'>" - " <arg name='timezone' type='s' direction='in'/>" - " <arg name='user_interaction' type='b' direction='in'/>" - " </method>" - " </interface>" - "</node>"; - node_info = g_dbus_node_info_new_for_xml(introspection_xml, nullptr); - ASSERT_TRUE(node_info != nullptr); - ASSERT_TRUE(node_info->interfaces != nullptr); - ASSERT_TRUE(node_info->interfaces[0] != nullptr); - ASSERT_TRUE(node_info->interfaces[1] == nullptr); - ASSERT_STREQ("org.freedesktop.timedate1", node_info->interfaces[0]->name); - - // own the bus - own_name = g_bus_own_name(G_BUS_TYPE_SYSTEM, - "org.freedesktop.timedate1", - G_BUS_NAME_OWNER_FLAGS_NONE, - on_bus_acquired, on_name_acquired, on_name_lost, - this, nullptr); - ASSERT_TRUE(object_register_id == 0); - ASSERT_FALSE(name_acquired); - ASSERT_TRUE(connection == nullptr); - g_main_loop_run(loop); - ASSERT_TRUE(object_register_id != 0); - ASSERT_TRUE(name_acquired); - ASSERT_TRUE(G_IS_DBUS_CONNECTION(connection)); + GError* error {}; + auto v = g_dbus_connection_call_sync( + m_bus, + Bus::Timedate1::BUSNAME, + Bus::Timedate1::ADDR, + Bus::Timedate1::IFACE, + Bus::Timedate1::Methods::SET_TIMEZONE, + g_variant_new("(sb)", tzid.c_str(), FALSE), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &error); + g_assert_no_error(error); - // create the State and Actions - m_mock_state.reset(new MockState); - m_mock_state->settings.reset(new Settings); - m_state = std::dynamic_pointer_cast<State>(m_mock_state); - m_live_actions.reset(new MockLiveActions(m_state)); - m_actions = std::dynamic_pointer_cast<Actions>(m_live_actions); + g_clear_pointer(&v, g_variant_unref); } - void TearDown() + std::string get_timedate1_timezone() { - g_debug("TearDown"); - m_actions.reset(); - m_live_actions.reset(); - m_state.reset(); - m_mock_state.reset(); - g_dbus_connection_unregister_object(connection, object_register_id); - g_object_unref(proxy); - g_dbus_node_info_unref(node_info); - g_bus_unown_name(own_name); - g_dbus_connection_close(connection, nullptr, on_bus_closed, this); - g_main_loop_run(loop); - g_clear_object(&connection); - g_test_dbus_down(bus); - g_clear_object(&bus); + GError* error {}; + auto v = g_dbus_connection_call_sync( + m_bus, + Bus::Timedate1::BUSNAME, + Bus::Timedate1::ADDR, + Bus::Properties::IFACE, + Bus::Properties::Methods::GET, + g_variant_new("(ss)", Bus::Timedate1::IFACE, Bus::Timedate1::Properties::TIMEZONE), + G_VARIANT_TYPE("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &error); + g_assert_no_error(error); - super::TearDown(); - } -public: - void set_timezone(std::string tz) - { - g_debug("set_timezone: '%s'", tz.c_str()); - g_dbus_proxy_call_sync(proxy, - "SetTimezone", - g_variant_new("(sb)", - tz.c_str(), - FALSE), - G_DBUS_CALL_FLAGS_NONE, - 500, - nullptr, - nullptr); + GVariant* tzv {}; + g_variant_get(v, "(v)", &tzv); + std::string tzid; + const char* tz = g_variant_get_string(tzv, nullptr); + if (tz != nullptr) + tzid = tz; + + g_clear_pointer(&tzv, g_variant_unref); + g_clear_pointer(&v, g_variant_unref); + return tzid; } }; -#endif +#define EXPECT_TZID(expected_tzid, tmp) \ + EXPECT_TRUE(wait_for_tzid(expected_tzid, tmp)) \ + << "expected " << expected_tzid \ + << " got " << tmp.timezone.get(); + diff --git a/trim-lcov.py b/trim-lcov.py deleted file mode 100755 index 2cd8dee..0000000 --- a/trim-lcov.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# This script removes branch and/or line coverage data for lines that -# contain a particular substring. -# -# In the interest of "fairness" it removes all branch or coverage data -# when a match is found -- not just negative data. It is therefore -# likely that running this script will actually reduce the total number -# of lines and branches that are marked as covered (in absolute terms). -# -# This script intentionally avoids checking for errors. Any exceptions -# will trigger make to fail. -# -# Author: Ryan Lortie <desrt@desrt.ca> - -import sys - -line_suppress = ['g_assert_not_reached'] -branch_suppress = ['g_assert', 'g_return_if_fail', 'g_clear_object', 'g_clear_pointer', 'g_return_val_if_fail', 'G_DEFINE_TYPE'] - -def check_suppress(suppressions, source, data): - line, _, rest = data.partition(',') - line = int(line) - 1 - - assert line < len(source) - - for suppression in suppressions: - if suppression in source[line]: - return True - - return False - -source = [] -for line in sys.stdin: - line = line[:-1] - - keyword, _, rest = line.partition(':') - - # Source file - if keyword == 'SF': - - with open(rest, 'r') as pFile: - - source = pFile.readlines() - - # Branch coverage data - elif keyword == 'BRDA': - if check_suppress(branch_suppress, source, rest): - continue - - # Line coverage data - elif keyword == 'DA': - if check_suppress(line_suppress, source, rest): - continue - - print(line) |