~aleteoryx/muditaos

84e08c242ff8bd90187890b255a35e6a27662e58 — tomaszrogala 5 years ago 96c9cfa
[EGD-3512] Create ICS Parser (#892)

-Create application desktop endpoints for calendar events
-Modify Calendar Database Interface
-Create ParserICS lib
-Create calendar events endpoints test
-Create UT for ParserICS
34 files changed, 2674 insertions(+), 469 deletions(-)

M module-apps/application-calendar/ApplicationCalendar.cpp
M module-apps/application-calendar/data/dateCommon.hpp
M module-apps/application-calendar/widgets/RepeatAndReminderItem.cpp
M module-db/CMakeLists.txt
M module-db/Interface/EventsRecord.cpp
M module-db/Interface/EventsRecord.hpp
M module-db/Tables/EventsTable.cpp
M module-db/Tables/EventsTable.hpp
M module-db/queries/calendar/QueryEventsAdd.hpp
M module-db/queries/calendar/QueryEventsEdit.hpp
A module-db/queries/calendar/QueryEventsEditICS.cpp
A module-db/queries/calendar/QueryEventsEditICS.hpp
M module-db/queries/calendar/QueryEventsRemove.cpp
M module-db/queries/calendar/QueryEventsRemove.hpp
A module-db/queries/calendar/QueryEventsRemoveICS.cpp
A module-db/queries/calendar/QueryEventsRemoveICS.hpp
M module-db/tests/EventsRecord_tests.cpp
M module-db/tests/EventsTable_tests.cpp
M module-services/service-desktop/CMakeLists.txt
A module-services/service-desktop/endpoints/CMakeLists.txt
M module-services/service-desktop/endpoints/EndpointFactory.hpp
A module-services/service-desktop/endpoints/calendarEvents/CalendarEventsEndpoint.cpp
A module-services/service-desktop/endpoints/calendarEvents/CalendarEventsEndpoint.hpp
A module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.cpp
A module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.hpp
M module-services/service-desktop/parser/ParserUtils.hpp
M module-utils/CMakeLists.txt
A module-utils/ical/ParserICS.cpp
A module-utils/ical/ParserICS.hpp
M module-utils/test/CMakeLists.txt
A module-utils/test/test_ParserICS.cpp
A test/service-desktop-test/defs.py
M test/service-desktop-test/main.py
A test/service-desktop-test/tests/calendarEvents.py
M module-apps/application-calendar/ApplicationCalendar.cpp => module-apps/application-calendar/ApplicationCalendar.cpp +3 -4
@@ 38,10 38,9 @@ namespace app
        {Repeat::never, "app_calendar_repeat_never"},
        {Repeat::daily, "app_calendar_repeat_daily"},
        {Repeat::weekly, "app_calendar_repeat_weekly"},
        {Repeat::two_weeks, "app_calendar_repeat_two_weeks"},
        {Repeat::month, "app_calendar_repeat_month"},
        {Repeat::year, "app_calendar_repeat_year"},
        {Repeat::custom, "app_calendar_repeat_custom"}};
        {Repeat::biweekly, "app_calendar_repeat_two_weeks"},
        {Repeat::monthly, "app_calendar_repeat_month"},
        {Repeat::yearly, "app_calendar_repeat_year"}};

    ApplicationCalendar::ApplicationCalendar(std::string name,
                                             std::string parent,

M module-apps/application-calendar/data/dateCommon.hpp => module-apps/application-calendar/data/dateCommon.hpp +22 -4
@@ 6,6 6,7 @@

#include <module-utils/date/include/date/date.h>
#include <time/time_conversion.hpp>
#include <random>

using namespace std::chrono;
using namespace std::chrono_literals;


@@ 36,13 37,14 @@ enum class Repeat
    never,
    daily,
    weekly,
    two_weeks,
    month,
    year,
    custom
    biweekly,
    monthly,
    yearly
};

inline constexpr TimePoint TIME_POINT_INVALID = date::sys_days{date::January / 1 / 1970};
inline constexpr uint32_t yearDigitsNumb = 4, monthDigitsNumb = 2, dayDigitsNumb = 2, HourDigitsNumb = 2,
                          MinDigitsNumb = 2, SecDigitsNumb = 2;

inline std::tm CreateTmStruct(int year, int month, int day, int hour, int minutes, int seconds)
{


@@ 254,5 256,21 @@ inline unsigned int WeekdayIndexFromTimePoint(const TimePoint &tp)
    return ymw.weekday().iso_encoding() - 1;
}

inline std::string createUID()
{
    constexpr uint32_t bufferLimit = 16;
    char Buffer[bufferLimit];
    utils::time::Timestamp timestamp;
    std::string UID{timestamp.str("%Y%m%dT%H%M%S")};
    UID += '-';
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distrib(1, 100);
    sprintf(Buffer, "%d", distrib(gen));
    UID += Buffer;

    return UID;
}

#endif
// DATECOMMON_H

M module-apps/application-calendar/widgets/RepeatAndReminderItem.cpp => module-apps/application-calendar/widgets/RepeatAndReminderItem.cpp +1 -1
@@ 103,7 103,7 @@ namespace gui
        repeatTitle->setText(utils::localize.get("app_calendar_event_detail_repeat"));
        reminderTitle->setText(utils::localize.get("app_calendar_event_detail_reminder"));
        onLoadCallback = [&](std::shared_ptr<EventsRecord> event) {
            if (event->repeat >= app::ApplicationCalendar::repeatOptions.size()) {
            if (event->repeat > app::ApplicationCalendar::repeatOptions.size()) {
                repeat->setText("app_calendar_custom_repeat_title");
            }
            else {

M module-db/CMakeLists.txt => module-db/CMakeLists.txt +2 -0
@@ 103,10 103,12 @@ set(SOURCES
        queries/phonebook/QueryContactUpdate.cpp
        queries/phonebook/QueryContactRemove.cpp
        queries/phonebook/QueryNumberGetByID.cpp
        queries/calendar/QueryEventsRemoveICS.cpp
        queries/calendar/QueryEventsRemove.cpp
        queries/calendar/QueryEventsGet.cpp
        queries/calendar/QueryEventsGetAll.cpp
        queries/calendar/QueryEventsAdd.cpp
        queries/calendar/QueryEventsEditICS.cpp
        queries/calendar/QueryEventsEdit.cpp
        queries/calendar/QueryEventsGetFiltered.cpp
        queries/calendar/QueryEventsGetAllLimited.cpp

M module-db/Interface/EventsRecord.cpp => module-db/Interface/EventsRecord.cpp +131 -47
@@ 6,8 6,9 @@
#include "module-db/queries/calendar/QueryEventsGetAll.hpp"
#include "module-db/queries/calendar/QueryEventsAdd.hpp"
#include "module-db/queries/calendar/QueryEventsRemove.hpp"
#include "module-db/queries/calendar/QueryEventsRemoveICS.hpp"
#include "module-db/queries/calendar/QueryEventsEdit.hpp"
#include <module-db/queries/calendar/QueryEventsEdit.hpp>
#include <module-db/queries/calendar/QueryEventsEditICS.hpp>
#include <module-db/queries/calendar/QueryEventsGetFiltered.hpp>
#include <module-db/queries/calendar/QueryEventsGetAllLimited.hpp>
#include <module-db/queries/calendar/QueryEventsSelectFirstUpcoming.hpp>


@@ 17,8 18,9 @@
#include <vector>

EventsRecord::EventsRecord(const EventsTableRow &tableRow)
    : Record{tableRow.ID}, title{tableRow.title}, date_from{tableRow.date_from}, date_till{tableRow.date_till},
      reminder{tableRow.reminder}, repeat{tableRow.repeat}, reminder_fired{tableRow.reminder_fired}
    : Record{tableRow.ID}, UID{tableRow.UID}, title{tableRow.title}, date_from{tableRow.date_from},
      date_till{tableRow.date_till}, reminder{tableRow.reminder}, repeat{tableRow.repeat}, reminder_fired{
                                                                                               tableRow.reminder_fired}
{}

EventsRecordInterface::EventsRecordInterface(EventsDB *eventsDb) : eventsDb(eventsDb)


@@ 27,6 29,7 @@ EventsRecordInterface::EventsRecordInterface(EventsDB *eventsDb) : eventsDb(even
bool EventsRecordInterface::Add(const EventsRecord &rec)
{
    auto entry = EventsTableRow{{.ID = rec.ID},
                                .UID            = rec.UID,
                                .title          = rec.title,
                                .date_from      = rec.date_from,
                                .date_till      = rec.date_till,


@@ 34,29 37,33 @@ bool EventsRecordInterface::Add(const EventsRecord &rec)
                                .repeat         = rec.repeat,
                                .reminder_fired = rec.reminder_fired};

    switch (RepeatOption(rec.repeat)) {
    case RepeatOption::Never: {
    Repeat repeatOption = Repeat(rec.repeat);
    if (repeatOption > Repeat::yearly) {
        return (eventsDb->events.addCustom(entry));
    }

    switch (repeatOption) {
    case Repeat::never: {
        return eventsDb->events.add(entry);
    }
    case RepeatOption::Daily: {
    case Repeat::daily: {
        return eventsDb->events.addDaily(entry);
    }
    case RepeatOption::Weekly: {
    case Repeat::weekly: {
        return eventsDb->events.addWeekly(entry);
    }
    case RepeatOption::TwoWeeks: {
    case Repeat::biweekly: {
        return eventsDb->events.addTwoWeeks(entry);
    }
    case RepeatOption::Month: {
    case Repeat::monthly: {
        return eventsDb->events.addMonth(entry);
    }
    case RepeatOption::Year: {
    case Repeat::yearly: {
        return eventsDb->events.addYear(entry);
    }
    default: {
        return eventsDb->events.addCustom(entry);
    }
    }

    return false;
}

std::unique_ptr<std::vector<EventsRecord>> EventsRecordInterface::Select(TimePoint filter_from,


@@ 123,11 130,12 @@ bool EventsRecordInterface::Update(const EventsRecord &rec)
{
    auto record = eventsDb->events.getById(rec.ID);
    if (!record.isValid()) {
        LOG_DEBUG("IS NOT VALID");
        LOG_DEBUG("Event record is not valid");
        return false;
    }

    auto entry = EventsTableRow{{.ID = rec.ID},
                                .UID            = rec.UID,
                                .title          = rec.title,
                                .date_from      = rec.date_from,
                                .date_till      = rec.date_till,


@@ 137,29 145,81 @@ bool EventsRecordInterface::Update(const EventsRecord &rec)

    bool result = eventsDb->events.update(entry);

    switch (RepeatOption(rec.repeat)) {
    case RepeatOption::Never: {
        return (eventsDb->events.add(entry) && result);
    Repeat repeatOption = Repeat(rec.repeat);
    if (repeatOption > Repeat::yearly) {
        return (eventsDb->events.addCustom(entry) && result);
    }

    switch (repeatOption) {
    case Repeat::never: {
        return result;
    }
    case RepeatOption::Daily: {
    case Repeat::daily: {
        return (eventsDb->events.addDaily(entry) && result);
    }
    case RepeatOption::Weekly: {
    case Repeat::weekly: {
        return (eventsDb->events.addWeekly(entry) && result);
    }
    case RepeatOption::TwoWeeks: {
    case Repeat::biweekly: {
        return (eventsDb->events.addTwoWeeks(entry) && result);
    }
    case RepeatOption::Month: {
    case Repeat::monthly: {
        return (eventsDb->events.addMonth(entry) && result);
    }
    case RepeatOption::Year: {
    case Repeat::yearly: {
        return (eventsDb->events.addYear(entry) && result);
    }
    default: {
        return eventsDb->events.addCustom(entry);
    }

    return false;
}

bool EventsRecordInterface::UpdateByUID(const EventsRecord &rec)
{
    auto record = eventsDb->events.getByUID(rec.UID);
    if (!record.isValid()) {
        LOG_DEBUG("Event record is not valid");
        return false;
    }

    auto entry = EventsTableRow{{.ID = rec.ID},
                                .UID            = rec.UID,
                                .title          = rec.title,
                                .date_from      = rec.date_from,
                                .date_till      = rec.date_till,
                                .reminder       = rec.reminder,
                                .repeat         = rec.repeat,
                                .reminder_fired = rec.reminder_fired};

    bool result = eventsDb->events.updateByUID(entry);

    Repeat repeatOption = Repeat(rec.repeat);
    if (repeatOption > Repeat::yearly) {
        return (eventsDb->events.addCustom(entry) && result);
    }

    switch (Repeat(rec.repeat)) {
    case Repeat::never: {
        return result;
    }
    case Repeat::daily: {
        return (eventsDb->events.addDaily(entry) && result);
    }
    case Repeat::weekly: {
        return (eventsDb->events.addWeekly(entry) && result);
    }
    case Repeat::biweekly: {
        return (eventsDb->events.addTwoWeeks(entry) && result);
    }
    case Repeat::monthly: {
        return (eventsDb->events.addMonth(entry) && result);
    }
    case Repeat::yearly: {
        return (eventsDb->events.addYear(entry) && result);
    }
    }

    return false;
}

bool EventsRecordInterface::RemoveByID(uint32_t id)


@@ 167,6 227,11 @@ bool EventsRecordInterface::RemoveByID(uint32_t id)
    return eventsDb->events.removeById(id);
}

bool EventsRecordInterface::RemoveByUID(const std::string &UID)
{
    return eventsDb->events.removeByUID(UID);
}

bool EventsRecordInterface::RemoveByField(EventsRecordField field, const char *str)
{
    assert(0 && "Not implemented");


@@ 223,9 288,15 @@ std::unique_ptr<db::QueryResult> EventsRecordInterface::runQuery(std::shared_ptr
    if (typeid(*query) == typeid(db::query::events::Remove)) {
        return runQueryImplRemove(query);
    }
    if (typeid(*query) == typeid(db::query::events::RemoveICS)) {
        return runQueryImplRemoveICS(query);
    }
    if (typeid(*query) == typeid(db::query::events::Edit)) {
        return runQueryImplEdit(query);
    }
    if (typeid(*query) == typeid(db::query::events::EditICS)) {
        return runQueryImplEditICS(query);
    }
    if (typeid(*query) == typeid(db::query::events::SelectFirstUpcoming)) {
        return runQueryImplSelectFirstUpcoming(query);
    }


@@ 235,8 306,7 @@ std::unique_ptr<db::QueryResult> EventsRecordInterface::runQuery(std::shared_ptr
std::unique_ptr<db::query::events::GetResult> EventsRecordInterface::runQueryImplGetResult(
    std::shared_ptr<db::Query> query)
{
    auto getQuery = dynamic_cast<db::query::events::Get *>(query.get());
    assert(getQuery != nullptr);
    auto getQuery = static_cast<db::query::events::Get *>(query.get());
    auto records  = GetByID(getQuery->id);
    auto response = std::make_unique<db::query::events::GetResult>(records);
    response->setRequestQuery(query);


@@ 257,10 327,9 @@ std::unique_ptr<db::query::events::GetAllLimitedResult> EventsRecordInterface::r
    std::shared_ptr<db::Query> query)
{

    auto getAllLimitedQuery = dynamic_cast<db::query::events::GetAllLimited *>(query.get());
    assert(getAllLimitedQuery != nullptr);
    auto records = GetLimitOffsetByDate(getAllLimitedQuery->offset, getAllLimitedQuery->limit);
    auto count   = GetCount();
    auto getAllLimitedQuery = static_cast<db::query::events::GetAllLimited *>(query.get());
    auto records            = GetLimitOffsetByDate(getAllLimitedQuery->offset, getAllLimitedQuery->limit);
    auto count              = GetCount();
    auto response =
        std::make_unique<db::query::events::GetAllLimitedResult>(std::move(records), std::make_unique<uint32_t>(count));
    response->setRequestQuery(query);


@@ 270,22 339,20 @@ std::unique_ptr<db::query::events::GetAllLimitedResult> EventsRecordInterface::r
std::unique_ptr<db::query::events::GetFilteredResult> EventsRecordInterface::runQueryImplGetFilteredResult(
    std::shared_ptr<db::Query> query)
{
    auto getFilteredQuery = dynamic_cast<db::query::events::GetFiltered *>(query.get());
    assert(getFilteredQuery != nullptr);
    auto records        = Select(getFilteredQuery->filter_from,
    auto getFilteredQuery = static_cast<db::query::events::GetFiltered *>(query.get());
    auto records          = Select(getFilteredQuery->filter_from,
                          getFilteredQuery->filter_till,
                          getFilteredQuery->offset,
                          getFilteredQuery->limit);
    auto numberOfEvents = GetCountFiltered(getFilteredQuery->filter_from, getFilteredQuery->filter_till);
    auto response       = std::make_unique<db::query::events::GetFilteredResult>(std::move(records), numberOfEvents);
    auto numberOfEvents   = GetCountFiltered(getFilteredQuery->filter_from, getFilteredQuery->filter_till);
    auto response         = std::make_unique<db::query::events::GetFilteredResult>(std::move(records), numberOfEvents);
    response->setRequestQuery(query);
    return response;
}

std::unique_ptr<db::query::events::AddResult> EventsRecordInterface::runQueryImplAdd(std::shared_ptr<db::Query> query)
{
    auto addQuery = dynamic_cast<db::query::events::Add *>(query.get());
    assert(addQuery != nullptr);
    auto addQuery = static_cast<db::query::events::Add *>(query.get());
    bool ret      = Add(addQuery->getRecord());
    auto response = std::make_unique<db::query::events::AddResult>(ret);
    response->setRequestQuery(query);


@@ 295,20 362,38 @@ std::unique_ptr<db::query::events::AddResult> EventsRecordInterface::runQueryImp
std::unique_ptr<db::query::events::RemoveResult> EventsRecordInterface::runQueryImplRemove(
    std::shared_ptr<db::Query> query)
{
    auto removeQuery = dynamic_cast<db::query::events::Remove *>(query.get());
    assert(removeQuery != nullptr);
    bool ret      = RemoveByID(removeQuery->id);
    auto response = std::make_unique<db::query::events::RemoveResult>(ret);
    auto removeQuery = static_cast<db::query::events::Remove *>(query.get());
    bool ret         = RemoveByID(removeQuery->id);
    auto response    = std::make_unique<db::query::events::RemoveResult>(ret);
    response->setRequestQuery(query);
    return response;
}

std::unique_ptr<db::query::events::RemoveICSResult> EventsRecordInterface::runQueryImplRemoveICS(
    std::shared_ptr<db::Query> query)
{
    auto removeQuery = static_cast<db::query::events::RemoveICS *>(query.get());
    bool ret         = RemoveByUID(removeQuery->UID);
    auto response    = std::make_unique<db::query::events::RemoveICSResult>(ret);
    response->setRequestQuery(query);
    return response;
}

std::unique_ptr<db::query::events::EditResult> EventsRecordInterface::runQueryImplEdit(std::shared_ptr<db::Query> query)
{
    auto editQuery = dynamic_cast<db::query::events::Edit *>(query.get());
    assert(editQuery != nullptr);
    bool ret      = Update(editQuery->getRecord());
    auto response = std::make_unique<db::query::events::EditResult>(ret);
    auto editQuery = static_cast<db::query::events::Edit *>(query.get());
    bool ret       = Update(editQuery->getRecord());
    auto response  = std::make_unique<db::query::events::EditResult>(ret);
    response->setRequestQuery(query);
    return response;
}

std::unique_ptr<db::query::events::EditICSResult> EventsRecordInterface::runQueryImplEditICS(
    std::shared_ptr<db::Query> query)
{
    auto editQuery = static_cast<db::query::events::EditICS *>(query.get());
    bool ret       = UpdateByUID(editQuery->getRecord());
    auto response  = std::make_unique<db::query::events::EditICSResult>(ret);
    response->setRequestQuery(query);
    return response;
}


@@ 316,8 401,7 @@ std::unique_ptr<db::query::events::EditResult> EventsRecordInterface::runQueryIm
std::unique_ptr<db::query::events::SelectFirstUpcomingResult> EventsRecordInterface::runQueryImplSelectFirstUpcoming(
    std::shared_ptr<db::Query> query)
{
    auto getFirstUpcomingQuery = dynamic_cast<db::query::events::SelectFirstUpcoming *>(query.get());
    assert(getFirstUpcomingQuery != nullptr);
    auto getFirstUpcomingQuery = static_cast<db::query::events::SelectFirstUpcoming *>(query.get());

    auto records  = SelectFirstUpcoming(getFirstUpcomingQuery->filter_from, getFirstUpcomingQuery->filter_till);
    auto response = std::make_unique<db::query::events::SelectFirstUpcomingResult>(std::move(records));

M module-db/Interface/EventsRecord.hpp => module-db/Interface/EventsRecord.hpp +13 -13
@@ 3,12 3,13 @@

#pragma once

#include "Common/Common.hpp"
#include "Databases/EventsDB.hpp"
#include "module-db/Common/Common.hpp"
#include "module-db/Databases/EventsDB.hpp"
#include "Record.hpp"
#include <utf8/UTF8.hpp>
#include <cstdint>
#include <vector>
#include <variant>
#include <module-apps/application-calendar/data/dateCommon.hpp>

// fw declarations


@@ 26,29 27,24 @@ namespace db::query::events
    class AddResult;
    class Remove;
    class RemoveResult;
    class RemoveICS;
    class RemoveICSResult;
    class Edit;
    class EditResult;
    class EditICS;
    class EditICSResult;
    class SelectFirstUpcoming;
    class SelectFirstUpcomingResult;
} // namespace db::query::events

enum class RepeatOption
{
    Never    = 0,
    Daily    = 1,
    Weekly   = 2,
    TwoWeeks = 3,
    Month    = 4,
    Year     = 5
};

struct EventsRecord : public Record
{
    std::string UID;
    std::string title;
    TimePoint date_from;
    TimePoint date_till;
    uint32_t reminder = 0;
    uint32_t repeat    = 0;
    uint32_t repeat   = 0;
    TimePoint reminder_fired;

    EventsRecord()  = default;


@@ 71,8 67,10 @@ class EventsRecordInterface : public RecordInterface<EventsRecord, EventsRecordF

    bool Add(const EventsRecord &rec) override final;
    bool RemoveByID(uint32_t id) override final;
    bool RemoveByUID(const std::string &UID);
    bool RemoveByField(EventsRecordField field, const char *str) override final;
    bool Update(const EventsRecord &rec) override final;
    bool UpdateByUID(const EventsRecord &rec);
    EventsRecord GetByID(uint32_t id) override final;
    uint32_t GetCount() override final;
    uint32_t GetCountFiltered(TimePoint from, TimePoint till);


@@ 101,7 99,9 @@ class EventsRecordInterface : public RecordInterface<EventsRecord, EventsRecordF
        std::shared_ptr<db::Query> query);
    std::unique_ptr<db::query::events::AddResult> runQueryImplAdd(std::shared_ptr<db::Query> query);
    std::unique_ptr<db::query::events::RemoveResult> runQueryImplRemove(std::shared_ptr<db::Query> query);
    std::unique_ptr<db::query::events::RemoveICSResult> runQueryImplRemoveICS(std::shared_ptr<db::Query> query);
    std::unique_ptr<db::query::events::EditResult> runQueryImplEdit(std::shared_ptr<db::Query> query);
    std::unique_ptr<db::query::events::EditICSResult> runQueryImplEditICS(std::shared_ptr<db::Query> query);
    std::unique_ptr<db::query::events::SelectFirstUpcomingResult> runQueryImplSelectFirstUpcoming(
        std::shared_ptr<db::Query> query);
};

M module-db/Tables/EventsTable.cpp => module-db/Tables/EventsTable.cpp +390 -303
@@ 16,7 16,7 @@ EventsTable::EventsTable(Database *db) : Table(db)

bool EventsTable::create()
{
    if (!db->execute(createTableQuery.c_str())) {
    if (!db->execute(createTableQuery)) {
        return false;
    }



@@ 26,287 26,323 @@ bool EventsTable::create()
bool EventsTable::add(EventsTableRow entry)
{
    // Prevent duplicates using ANDs:
    return db->execute("INSERT or IGNORE INTO events "
                       "(title, date_from, date_till, reminder, repeat, reminder_fired) "
                       "SELECT '%q', '%q','%q', %lu, %lu, '%q' "
                       "WHERE NOT EXISTS "
                       "(SELECT 1 FROM events e "
                       "WHERE e.title='%q' "
                       "AND e.date_from='%q' "
                       "AND e.date_till='%q' "
                       "AND e.reminder=%lu "
                       "AND e.repeat=%lu );",
                       entry.title.c_str(),
                       TimePointToString(entry.date_from).c_str(),
                       TimePointToString(entry.date_till).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from).c_str(),
                       TimePointToString(entry.date_till).c_str(),
                       entry.reminder,
                       entry.repeat);
    if (entry.UID.empty()) {
        entry.UID = createUID();
    }
    return db->execute(
        "INSERT or IGNORE INTO events (uid, title, date_from, date_till, reminder, repeat, reminder_fired) "
        "SELECT '%q','%q', '%q','%q', %lu, %lu, '%q' "
        "WHERE NOT EXISTS "
        "(SELECT 1 FROM events e "
        "WHERE e.title='%q' "
        "AND e.date_from='%q' "
        "AND e.date_till='%q' "
        "AND e.reminder=%lu "
        "AND e.repeat=%lu );",
        entry.UID.c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from).c_str(),
        TimePointToString(entry.date_till).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from).c_str(),
        TimePointToString(entry.date_till).c_str(),
        entry.reminder,
        entry.repeat);
}

bool EventsTable::addDaily(EventsTableRow entry)
{
    return db->execute("INSERT or IGNORE INTO events "
                       "(title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q');",
                       entry.title.c_str(),
                       TimePointToString(entry.date_from).c_str(),
                       TimePointToString(entry.date_till).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{1}).c_str(),
                       TimePointToString(entry.date_till + date::days{1}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{2}).c_str(),
                       TimePointToString(entry.date_till + date::days{2}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{3}).c_str(),
                       TimePointToString(entry.date_till + date::days{3}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{4}).c_str(),
                       TimePointToString(entry.date_till + date::days{4}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{5}).c_str(),
                       TimePointToString(entry.date_till + date::days{5}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{6}).c_str(),
                       TimePointToString(entry.date_till + date::days{6}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str());
    return db->execute(
        "INSERT or IGNORE INTO events (uid, title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q');",
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from).c_str(),
        TimePointToString(entry.date_till).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{1}).c_str(),
        TimePointToString(entry.date_till + date::days{1}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{2}).c_str(),
        TimePointToString(entry.date_till + date::days{2}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{3}).c_str(),
        TimePointToString(entry.date_till + date::days{3}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{4}).c_str(),
        TimePointToString(entry.date_till + date::days{4}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{5}).c_str(),
        TimePointToString(entry.date_till + date::days{5}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{6}).c_str(),
        TimePointToString(entry.date_till + date::days{6}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str());
}

bool EventsTable::addWeekly(EventsTableRow entry)
{
    return db->execute("INSERT or IGNORE INTO events "
                       "(title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q');",
                       entry.title.c_str(),
                       TimePointToString(entry.date_from).c_str(),
                       TimePointToString(entry.date_till).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{7}).c_str(),
                       TimePointToString(entry.date_till + date::days{7}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{14}).c_str(),
                       TimePointToString(entry.date_till + date::days{14}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{21}).c_str(),
                       TimePointToString(entry.date_till + date::days{21}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str());
    return db->execute(
        "INSERT or IGNORE INTO events (uid, title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q');",
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from).c_str(),
        TimePointToString(entry.date_till).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{7}).c_str(),
        TimePointToString(entry.date_till + date::days{7}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{14}).c_str(),
        TimePointToString(entry.date_till + date::days{14}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{21}).c_str(),
        TimePointToString(entry.date_till + date::days{21}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str());
}

bool EventsTable::addTwoWeeks(EventsTableRow entry)
{
    return db->execute("INSERT or IGNORE INTO events "
                       "(title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q');",
                       entry.title.c_str(),
                       TimePointToString(entry.date_from).c_str(),
                       TimePointToString(entry.date_till).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{14}).c_str(),
                       TimePointToString(entry.date_till + date::days{14}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{28}).c_str(),
                       TimePointToString(entry.date_till + date::days{28}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::days{42}).c_str(),
                       TimePointToString(entry.date_till + date::days{42}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str());
    return db->execute(
        "INSERT or IGNORE INTO events (uid, title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q');",
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from).c_str(),
        TimePointToString(entry.date_till).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{14}).c_str(),
        TimePointToString(entry.date_till + date::days{14}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{28}).c_str(),
        TimePointToString(entry.date_till + date::days{28}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::days{42}).c_str(),
        TimePointToString(entry.date_till + date::days{42}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str());
}

bool EventsTable::addMonth(EventsTableRow entry)
{
    return db->execute("INSERT or IGNORE INTO events "
                       "(title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q');",
                       entry.title.c_str(),
                       TimePointToString(entry.date_from).c_str(),
                       TimePointToString(entry.date_till).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{1}).c_str(),
                       TimePointToString(entry.date_till, date::months{1}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{2}).c_str(),
                       TimePointToString(entry.date_till, date::months{2}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{3}).c_str(),
                       TimePointToString(entry.date_till, date::months{3}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{4}).c_str(),
                       TimePointToString(entry.date_till, date::months{4}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{5}).c_str(),
                       TimePointToString(entry.date_till, date::months{5}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{6}).c_str(),
                       TimePointToString(entry.date_till, date::months{6}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{7}).c_str(),
                       TimePointToString(entry.date_till, date::months{7}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{8}).c_str(),
                       TimePointToString(entry.date_till, date::months{8}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{9}).c_str(),
                       TimePointToString(entry.date_till, date::months{9}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{10}).c_str(),
                       TimePointToString(entry.date_till, date::months{10}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{11}).c_str(),
                       TimePointToString(entry.date_till, date::months{11}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from, date::months{12}).c_str(),
                       TimePointToString(entry.date_till, date::months{12}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str());
    return db->execute(
        "INSERT or IGNORE INTO events (uid, title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q');",
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from).c_str(),
        TimePointToString(entry.date_till).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{1}).c_str(),
        TimePointToString(entry.date_till, date::months{1}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{2}).c_str(),
        TimePointToString(entry.date_till, date::months{2}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{3}).c_str(),
        TimePointToString(entry.date_till, date::months{3}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{4}).c_str(),
        TimePointToString(entry.date_till, date::months{4}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{5}).c_str(),
        TimePointToString(entry.date_till, date::months{5}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{6}).c_str(),
        TimePointToString(entry.date_till, date::months{6}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{7}).c_str(),
        TimePointToString(entry.date_till, date::months{7}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{8}).c_str(),
        TimePointToString(entry.date_till, date::months{8}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{9}).c_str(),
        TimePointToString(entry.date_till, date::months{9}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{10}).c_str(),
        TimePointToString(entry.date_till, date::months{10}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{11}).c_str(),
        TimePointToString(entry.date_till, date::months{11}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from, date::months{12}).c_str(),
        TimePointToString(entry.date_till, date::months{12}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str());
}

bool EventsTable::addYear(EventsTableRow entry)
{
    return db->execute("INSERT or IGNORE INTO events "
                       "(title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q'),"
                       "('%q', '%q', '%q', %u, %u, '%q');",
                       entry.title.c_str(),
                       TimePointToString(entry.date_from).c_str(),
                       TimePointToString(entry.date_till).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::years{1}).c_str(),
                       TimePointToString(entry.date_till + date::years{1}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::years{2}).c_str(),
                       TimePointToString(entry.date_till + date::years{2}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::years{3}).c_str(),
                       TimePointToString(entry.date_till + date::years{3}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.title.c_str(),
                       TimePointToString(entry.date_from + date::years{4}).c_str(),
                       TimePointToString(entry.date_till + date::years{4}).c_str(),
                       entry.reminder,
                       entry.repeat,
                       TimePointToString(entry.reminder_fired).c_str());
    return db->execute(
        "INSERT or IGNORE INTO events (uid, title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q'),"
        "('%q','%q', '%q','%q', %u, %u,'%q');",
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from).c_str(),
        TimePointToString(entry.date_till).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::years{1}).c_str(),
        TimePointToString(entry.date_till + date::years{1}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::years{2}).c_str(),
        TimePointToString(entry.date_till + date::years{2}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::years{3}).c_str(),
        TimePointToString(entry.date_till + date::years{3}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str(),
        createUID().c_str(),
        entry.title.c_str(),
        TimePointToString(entry.date_from + date::years{4}).c_str(),
        TimePointToString(entry.date_till + date::years{4}).c_str(),
        entry.reminder,
        entry.repeat,
        TimePointToString(entry.reminder_fired).c_str());
}

std::vector<bool> parseOptions(const uint32_t &dataDB)


@@ 336,13 372,18 @@ bool EventsTable::addCustom(EventsTableRow entry)
    weekDayOptions          = parseOptions(entry.repeat);
    uint32_t incrementation = 0;

    result = result && db->execute("INSERT or IGNORE INTO events (title, date_from, date_till, reminder, repeat) VALUES"
                                   "('%q', '%q','%q', %u, %u);",
                                   entry.title.c_str(),
                                   TimePointToString(entry.date_from).c_str(),
                                   TimePointToString(entry.date_till).c_str(),
                                   entry.reminder,
                                   entry.repeat);
    result =
        result &&
        db->execute(
            "INSERT or IGNORE INTO events (uid, title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
            "('%q','%q', '%q','%q', %u, %u, '%q');",
            createUID().c_str(),
            entry.title.c_str(),
            TimePointToString(entry.date_from).c_str(),
            TimePointToString(entry.date_till).c_str(),
            entry.reminder,
            entry.repeat,
            TimePointToString(entry.reminder_fired).c_str());

    auto dateFrom = getFirstWeekDay(entry.date_from);
    auto dateTill = getFirstWeekDay(entry.date_till);


@@ 350,9 391,10 @@ bool EventsTable::addCustom(EventsTableRow entry)
    for (uint32_t i = 1; i <= numberOfWeeks; i++) {
        for (auto option : weekDayOptions) {
            if (option) {
                result = result && db->execute("INSERT or IGNORE INTO events "
                                               "(title, date_from, date_till, reminder, repeat, reminder_fired) VALUES"
                                               "('%q', '%q','%q', %u, %u);",
                result = result && db->execute("INSERT or IGNORE INTO events (uid, title, date_from, date_till, "
                                               "reminder, repeat, reminder_fired) VALUES"
                                               "('%q','%q', '%q','%q', %u, %u, '%q');",
                                               createUID().c_str(),
                                               entry.title.c_str(),
                                               TimePointToString(dateFrom + date::days{incrementation}).c_str(),
                                               TimePointToString(dateTill + date::days{incrementation}).c_str(),


@@ 372,6 414,11 @@ bool EventsTable::removeById(uint32_t id)
    return db->execute("DELETE FROM events where _id = %u;", id);
}

bool EventsTable::removeByUID(const std::string &UID)
{
    return db->execute("DELETE FROM events where uid = '%q';", UID.c_str());
}

bool EventsTable::removeByField(EventsTableFields field, const char *str)
{
    std::string fieldName;


@@ 403,11 450,24 @@ bool EventsTable::update(EventsTableRow entry)
                       entry.ID);
}

bool EventsTable::updateByUID(EventsTableRow entry)
{
    return db->execute("UPDATE events SET title= '%q', date_from = '%q', date_till = '%q', reminder "
                       "= %u, repeat = %u, reminder_fired = '%q' WHERE uid = '%q';",
                       entry.title.c_str(),
                       TimePointToString(entry.date_from).c_str(),
                       TimePointToString(entry.date_till).c_str(),
                       entry.reminder,
                       static_cast<uint32_t>(entry.repeat),
                       TimePointToString(entry.reminder_fired).c_str(),
                       entry.UID.c_str());
}

EventsTableRow EventsTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM events WHERE _id= %u;", id);

    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
    if (retQuery == nullptr || retQuery->getRowCount() == 0) {
        return EventsTableRow();
    }



@@ 415,13 475,35 @@ EventsTableRow EventsTable::getById(uint32_t id)

    return EventsTableRow{
        (*retQuery)[0].getUInt32(),                              // ID
        (*retQuery)[1].getString(),                              // title
        TimePointFromString((*retQuery)[2].getString().c_str()), // date_from
        TimePointFromString((*retQuery)[3].getString().c_str()), // date_till
        (*retQuery)[4].getUInt32(),                              // reminder
        (*retQuery)[5].getUInt32(),                              // repeat
        TimePointFromString((*retQuery)[6].getString().c_str())  // date_till
        (*retQuery)[1].getString(),                              // UID
        (*retQuery)[2].getString(),                              // title
        TimePointFromString((*retQuery)[3].getString().c_str()), // date_from
        TimePointFromString((*retQuery)[4].getString().c_str()), // date_till
        (*retQuery)[5].getUInt32(),                              // reminder
        (*retQuery)[6].getUInt32(),                              // repeat
        TimePointFromString((*retQuery)[7].getString().c_str())  // reminder_fired
    };
}

EventsTableRow EventsTable::getByUID(const std::string &UID)
{
    auto retQuery = db->query("SELECT * FROM events WHERE uid= '%q';", UID.c_str());

    if (retQuery == nullptr || retQuery->getRowCount() == 0) {
        return EventsTableRow();
    }

    assert(retQuery->getRowCount() == 1);

    return EventsTableRow{
        (*retQuery)[0].getUInt32(),                              // ID
        (*retQuery)[1].getString(),                              // UID
        (*retQuery)[2].getString(),                              // title
        TimePointFromString((*retQuery)[3].getString().c_str()), // date_from
        TimePointFromString((*retQuery)[4].getString().c_str()), // date_till
        (*retQuery)[5].getUInt32(),                              // reminder
        (*retQuery)[6].getUInt32(),                              // repeat
        TimePointFromString((*retQuery)[7].getString().c_str())  // reminder_fired
    };
}



@@ 437,7 519,7 @@ std::vector<EventsTableRow> EventsTable::selectByDatePeriod(TimePoint date_filte
                              limit,
                              offset);

    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
    if (retQuery == nullptr || retQuery->getRowCount() == 0) {
        return std::vector<EventsTableRow>();
    }



@@ 446,12 528,13 @@ std::vector<EventsTableRow> EventsTable::selectByDatePeriod(TimePoint date_filte
    do {
        ret.push_back(EventsTableRow{
            (*retQuery)[0].getUInt32(),                              // ID
            (*retQuery)[1].getString(),                              // title
            TimePointFromString((*retQuery)[2].getString().c_str()), // date_from
            TimePointFromString((*retQuery)[3].getString().c_str()), // date_till
            (*retQuery)[4].getUInt32(),                              // reminder
            (*retQuery)[5].getUInt32(),                              // repeat
            TimePointFromString((*retQuery)[6].getString().c_str())  // date_till
            (*retQuery)[1].getString(),                              // UID
            (*retQuery)[2].getString(),                              // title
            TimePointFromString((*retQuery)[3].getString().c_str()), // date_from
            TimePointFromString((*retQuery)[4].getString().c_str()), // date_till
            (*retQuery)[5].getUInt32(),                              // reminder
            (*retQuery)[6].getUInt32(),                              // repeat
            TimePointFromString((*retQuery)[7].getString().c_str())  // reminder_fired
        });

    } while (retQuery->nextRow());


@@ 464,7 547,7 @@ std::vector<EventsTableRow> EventsTable::getLimitOffset(uint32_t offset, uint32_
    auto retQuery = db->query("SELECT * from events LIMIT %lu OFFSET %lu;", limit, offset);
    //"SELECT * FROM event WHERE id = "

    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
    if (retQuery == nullptr || retQuery->getRowCount() == 0) {
        return std::vector<EventsTableRow>();
    }



@@ 473,12 556,14 @@ std::vector<EventsTableRow> EventsTable::getLimitOffset(uint32_t offset, uint32_
    do {
        ret.push_back(EventsTableRow{
            (*retQuery)[0].getUInt32(),                              // ID
            (*retQuery)[1].getString(),                              // title
            TimePointFromString((*retQuery)[2].getString().c_str()), // date_from
            TimePointFromString((*retQuery)[3].getString().c_str()), // date_till
            (*retQuery)[4].getUInt32(),                              // reminder
            (*retQuery)[5].getUInt32(),                              // repeat
            TimePointFromString((*retQuery)[6].getString().c_str())  // date_till
            (*retQuery)[1].getString(),                              // UID
            (*retQuery)[2].getString(),                              // title
            TimePointFromString((*retQuery)[3].getString().c_str()), // date_from
            TimePointFromString((*retQuery)[4].getString().c_str()), // date_till
            (*retQuery)[5].getUInt32(),                              // reminder
            (*retQuery)[6].getUInt32(),                              // repeat
            TimePointFromString((*retQuery)[7].getString().c_str())  // reminder_fired

        });
    } while (retQuery->nextRow());



@@ 490,7 575,7 @@ std::vector<EventsTableRow> EventsTable::getLimitOffsetByDate(uint32_t offset, u

    auto retQuery = db->query("SELECT * from events ORDER BY datetime(date_from) LIMIT %u OFFSET %u;", limit, offset);

    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
    if (retQuery == nullptr || retQuery->getRowCount() == 0) {
        return std::vector<EventsTableRow>();
    }



@@ 499,12 584,13 @@ std::vector<EventsTableRow> EventsTable::getLimitOffsetByDate(uint32_t offset, u
    do {
        ret.push_back(EventsTableRow{
            (*retQuery)[0].getUInt32(),                              // ID
            (*retQuery)[1].getString(),                              // title
            TimePointFromString((*retQuery)[2].getString().c_str()), // date_from
            TimePointFromString((*retQuery)[3].getString().c_str()), // date_till
            (*retQuery)[4].getUInt32(),                              // reminder
            (*retQuery)[5].getUInt32(),                              // repeat
            TimePointFromString((*retQuery)[6].getString().c_str())  // date_till
            (*retQuery)[1].getString(),                              // UID
            (*retQuery)[2].getString(),                              // title
            TimePointFromString((*retQuery)[3].getString().c_str()), // date_from
            TimePointFromString((*retQuery)[4].getString().c_str()), // date_till
            (*retQuery)[5].getUInt32(),                              // reminder
            (*retQuery)[6].getUInt32(),                              // repeat
            TimePointFromString((*retQuery)[7].getString().c_str())  // reminder_fired
        });
    } while (retQuery->nextRow());



@@ 567,7 653,7 @@ std::vector<EventsTableRow> EventsTable::SelectFirstUpcoming(TimePoint filter_fr
                              TimePointToString(filter_from).c_str(),
                              TimePointToString(TIME_POINT_INVALID).c_str());

    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
    if (retQuery == nullptr || retQuery->getRowCount() == 0) {
        return std::vector<EventsTableRow>();
    }



@@ 575,13 661,14 @@ std::vector<EventsTableRow> EventsTable::SelectFirstUpcoming(TimePoint filter_fr

    do {
        ret.push_back(EventsTableRow{
            (*retQuery)[1].getUInt32(),                              // ID
            (*retQuery)[0].getUInt32(),                              // ID
            (*retQuery)[1].getString(),                              // UID
            (*retQuery)[2].getString(),                              // title
            TimePointFromString((*retQuery)[3].getString().c_str()), // date_from
            TimePointFromString((*retQuery)[4].getString().c_str()), // date_till
            (*retQuery)[5].getUInt32(),                              // reminder
            (*retQuery)[6].getUInt32(),                              // repeat
            TimePointFromString((*retQuery)[7].getString().c_str())  // date_till
            TimePointFromString((*retQuery)[7].getString().c_str())  // reminder_fired
        });
    } while (retQuery->nextRow());


M module-db/Tables/EventsTable.hpp => module-db/Tables/EventsTable.hpp +18 -13
@@ 5,16 5,17 @@

#include "Table.hpp"
#include "Record.hpp"
#include <Database/Database.hpp>
#include <Common/Common.hpp>
#include <module-db/Database/Database.hpp>
#include <module-db/Common/Common.hpp>
#include <utf8/UTF8.hpp>
#include <module-apps/application-calendar/data/dateCommon.hpp>

struct EventsTableRow : public Record
{
    std::string UID;
    std::string title;
    TimePoint date_from = TIME_POINT_INVALID;
    TimePoint date_till = TIME_POINT_INVALID;
    TimePoint date_from      = TIME_POINT_INVALID;
    TimePoint date_till      = TIME_POINT_INVALID;
    uint32_t reminder        = 0;
    uint32_t repeat          = 0;
    TimePoint reminder_fired = TIME_POINT_INVALID;


@@ 41,8 42,11 @@ class EventsTable : public Table<EventsTableRow, EventsTableFields>
    bool addYear(EventsTableRow entry);
    bool addCustom(EventsTableRow entry);
    bool removeById(uint32_t id) override final;
    bool removeByUID(const std::string &UID);
    bool removeByField(EventsTableFields field, const char *str) override final;
    bool update(EventsTableRow entry) override final;
    bool updateByUID(EventsTableRow entry);
    EventsTableRow getByUID(const std::string &UID);
    EventsTableRow getById(uint32_t id) override final;
    std::vector<EventsTableRow> selectByDatePeriod(TimePoint filter_from,
                                                   TimePoint filter_till,


@@ 61,13 65,14 @@ class EventsTable : public Table<EventsTableRow, EventsTableFields>
    std::vector<EventsTableRow> SelectFirstUpcoming(TimePoint filter_from, TimePoint filter_till);

  private:
    const std::string createTableQuery = "CREATE TABLE IF NOT EXISTS events("
                                         "_id INTEGER PRIMARY KEY AUTOINCREMENT,"
                                         "title TEXT,"
                                         "date_from DATETIME,"
                                         "date_till DATETIME,"
                                         "reminder INTEGER,"
                                         "repeat INTEGER,"
                                         "reminder_fired DATETIME,"
                                         "UNIQUE (title, date_from, date_till));";
    const char *createTableQuery = "CREATE TABLE IF NOT EXISTS events("
                                   "_id INTEGER PRIMARY KEY AUTOINCREMENT,"
                                   "uid TEXT,"
                                   "title TEXT,"
                                   "date_from DATETIME,"
                                   "date_till DATETIME,"
                                   "reminder INTEGER,"
                                   "repeat INTEGER,"
                                   "reminder_fired DATETIME,"
                                   "UNIQUE (title, date_from, date_till));";
};

M module-db/queries/calendar/QueryEventsAdd.hpp => module-db/queries/calendar/QueryEventsAdd.hpp +1 -1
@@ 23,7 23,7 @@ namespace db::query::events
    class AddResult : public QueryResult
    {
      public:
        AddResult(bool ret);
        explicit AddResult(bool ret);
        [[nodiscard]] auto getResult() const -> bool;

        [[nodiscard]] auto debugInfo() const -> std::string override;

M module-db/queries/calendar/QueryEventsEdit.hpp => module-db/queries/calendar/QueryEventsEdit.hpp +1 -1
@@ 23,7 23,7 @@ namespace db::query::events
    class EditResult : public QueryResult
    {
      public:
        EditResult(bool ret);
        explicit EditResult(bool ret);
        [[nodiscard]] auto getResult() const -> bool;

        [[nodiscard]] auto debugInfo() const -> std::string override;

A module-db/queries/calendar/QueryEventsEditICS.cpp => module-db/queries/calendar/QueryEventsEditICS.cpp +33 -0
@@ 0,0 1,33 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryEventsEditICS.hpp"

namespace db::query::events
{
    EditICS::EditICS(EventsRecord record) : Query(Query::Type::Update), record(record)
    {}

    auto EditICS::getRecord() const -> EventsRecord
    {
        return record;
    }

    auto EditICS::debugInfo() const -> std::string
    {
        return "Edit";
    }

    EditICSResult::EditICSResult(bool ret) : ret(ret)
    {}

    auto EditICSResult::getResult() const -> bool
    {
        return ret;
    }

    auto EditICSResult::debugInfo() const -> std::string
    {
        return "EditResult";
    }
} // namespace db::query::events

A module-db/queries/calendar/QueryEventsEditICS.hpp => module-db/queries/calendar/QueryEventsEditICS.hpp +34 -0
@@ 0,0 1,34 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "module-db/Interface/EventsRecord.hpp"
#include <Common/Query.hpp>
#include <string>

namespace db::query::events
{
    class EditICS : public Query
    {
        EventsRecord record;

      public:
        EditICS(EventsRecord record);

        [[nodiscard]] auto getRecord() const -> EventsRecord;
        [[nodiscard]] auto debugInfo() const -> std::string override;
    };

    class EditICSResult : public QueryResult
    {
      public:
        explicit EditICSResult(bool ret);
        [[nodiscard]] auto getResult() const -> bool;

        [[nodiscard]] auto debugInfo() const -> std::string override;

        bool ret;
    };

} // namespace db::query::events

M module-db/queries/calendar/QueryEventsRemove.cpp => module-db/queries/calendar/QueryEventsRemove.cpp +1 -1
@@ 13,7 13,7 @@ namespace db::query::events
        return "Remove";
    }

    RemoveResult::RemoveResult(const bool &ret) : ret(ret)
    RemoveResult::RemoveResult(bool ret) : ret(ret)
    {}

    auto RemoveResult::getResult() const -> bool

M module-db/queries/calendar/QueryEventsRemove.hpp => module-db/queries/calendar/QueryEventsRemove.hpp +2 -2
@@ 21,10 21,10 @@ namespace db::query::events

    class RemoveResult : public QueryResult
    {
        const bool ret = true;
        bool ret;

      public:
        RemoveResult(const bool &ret);
        explicit RemoveResult(bool ret);
        [[nodiscard]] auto getResult() const -> bool;

        [[nodiscard]] auto debugInfo() const -> std::string override;

A module-db/queries/calendar/QueryEventsRemoveICS.cpp => module-db/queries/calendar/QueryEventsRemoveICS.cpp +28 -0
@@ 0,0 1,28 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryEventsRemoveICS.hpp"

namespace db::query::events
{
    RemoveICS::RemoveICS(const std::string &UID) : Query(Query::Type::Delete), UID(UID)
    {}

    auto RemoveICS::debugInfo() const -> std::string
    {
        return "Remove";
    }

    RemoveICSResult::RemoveICSResult(bool ret) : ret(ret)
    {}

    auto RemoveICSResult::getResult() const -> bool
    {
        return ret;
    }

    auto RemoveICSResult::debugInfo() const -> std::string
    {
        return "RemoveResult";
    }
} // namespace db::query::events

A module-db/queries/calendar/QueryEventsRemoveICS.hpp => module-db/queries/calendar/QueryEventsRemoveICS.hpp +33 -0
@@ 0,0 1,33 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "module-db/Interface/EventsRecord.hpp"
#include <Common/Query.hpp>
#include <string>

namespace db::query::events
{

    class RemoveICS : public Query
    {
      public:
        RemoveICS(const std::string &UID);
        [[nodiscard]] auto debugInfo() const -> std::string override;

        std::string UID;
    };

    class RemoveICSResult : public QueryResult
    {
        bool ret;

      public:
        explicit RemoveICSResult(bool ret);
        [[nodiscard]] auto getResult() const -> bool;

        [[nodiscard]] auto debugInfo() const -> std::string override;
    };

} // namespace db::query::events

M module-db/tests/EventsRecord_tests.cpp => module-db/tests/EventsRecord_tests.cpp +16 -6
@@ 44,11 44,13 @@ TEST_CASE("Events Record tests")
    SECTION("Constructor from EventsTableRow")
    {
        EventsTableRow tableRow{{.ID = 10},
                                "test",
                                "Event3",
                                TimePointFromString("2019-10-20 14:24:00"),
                                TimePointFromString("2019-10-20 15:36:00"),
                                1,
                                2};
                                2,
                                TIME_POINT_INVALID};
        EventsRecord testRec(tableRow);
        REQUIRE(testRec.title == "Event3");
        REQUIRE(testRec.date_from == TimePointFromString("2019-10-20 14:24:00"));


@@ 65,11 67,14 @@ TEST_CASE("Events Record tests")
    REQUIRE(numberOfEvents == 0);

    EventsTableRow tableRow{{.ID = 10},
                            "test",
                            "Event1",
                            TimePointFromString("2019-10-20 14:24:00"),
                            TimePointFromString("2019-10-20 15:36:00"),
                            1,
                            2};
                            2,
                            TIME_POINT_INVALID};

    auto rec = EventsRecord(tableRow);
    REQUIRE(rec.title == "Event1");
    REQUIRE(rec.date_from == TimePointFromString("2019-10-20 14:24:00"));


@@ 108,11 113,14 @@ TEST_CASE("Events Record tests")
    }

    EventsTableRow tableRow2{{.ID = 10},
                             "test",
                             "Event2",
                             TimePointFromString("2025-10-20 14:24:00"),
                             TimePointFromString("2025-10-20 15:36:00"),
                             1,
                             2};
                             2,
                             TIME_POINT_INVALID};

    auto rec2 = EventsRecord(tableRow2);
    REQUIRE(rec2.title == "Event2");
    REQUIRE(rec2.date_from == TimePointFromString("2025-10-20 14:24:00"));


@@ 194,11 202,13 @@ TEST_CASE("Events Record tests")
    }

    EventsTableRow tableRow3{{.ID = 3},
                             "test",
                             "Event3",
                             TimePointFromString("2021-10-20 14:24:00"),
                             TimePointFromString("2021-10-20 15:36:00"),
                             1,
                             2};
                             2,
                             TIME_POINT_INVALID};
    auto rec3 = EventsRecord(tableRow3);
    REQUIRE(rec3.title == "Event3");
    REQUIRE(rec3.date_from == TimePointFromString("2021-10-20 14:24:00"));


@@ 247,7 257,7 @@ TEST_CASE("Events Record tests")
    };

    auto AddQuery = [&](uint32_t id, std::string title, TimePoint date_from, TimePoint date_till) {
        EventsTableRow tableRow2{{.ID = id}, title, date_from, date_till, 1, 2};
        EventsTableRow tableRow2{{.ID = id}, "test", title, date_from, date_till, 1, 2, TIME_POINT_INVALID};
        auto record = EventsRecord(tableRow2);
        auto query  = std::make_shared<db::query::events::Add>(record);
        auto ret    = eventsRecordInterface.runQuery(query);


@@ 270,7 280,7 @@ TEST_CASE("Events Record tests")
                         TimePoint date_till,
                         uint32_t reminder,
                         uint32_t repeat) {
        EventsTableRow tableRow2{{.ID = id}, title, date_from, date_till, reminder, repeat};
        EventsTableRow tableRow2{{.ID = id}, "test", title, date_from, date_till, reminder, repeat, TIME_POINT_INVALID};
        auto record = EventsRecord(tableRow2);
        auto query  = std::make_shared<db::query::events::Edit>(record);
        auto ret    = eventsRecordInterface.runQuery(query);

M module-db/tests/EventsTable_tests.cpp => module-db/tests/EventsTable_tests.cpp +109 -71
@@ 30,6 30,7 @@ TEST_CASE("Events Table tests")
    {
        EventsTableRow testRow;
        REQUIRE(testRow.ID == DB_ID_NONE);
        REQUIRE(testRow.UID == "");
        REQUIRE(testRow.title == "");
        REQUIRE(testRow.date_from == TIME_POINT_INVALID);
        REQUIRE(testRow.date_till == TIME_POINT_INVALID);


@@ 40,17 41,22 @@ TEST_CASE("Events Table tests")
    }

    REQUIRE(eventsTbl.add({{.ID = 0},
                           .title     = "Event3",
                           .date_from = TimePointFromString("2019-10-20 14:24:00"),
                           .date_till = TimePointFromString("2019-10-20 15:36:00"),
                           .reminder  = 1,
                           .repeat    = 2}));
                           .UID            = "test",
                           .title          = "Event3",
                           .date_from      = TimePointFromString("2019-10-20 14:24:00"),
                           .date_till      = TimePointFromString("2019-10-20 15:36:00"),
                           .reminder       = 1,
                           .repeat         = 2,
                           .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));

    REQUIRE(eventsTbl.add({{.ID = 0},
                           .title     = "Event4",
                           .date_from = TimePointFromString("2021-10-20 12:24:00"),
                           .date_till = TimePointFromString("2021-10-20 15:36:00"),
                           .reminder  = 0,
                           .repeat    = 3}));
                           .UID            = "test",
                           .title          = "Event4",
                           .date_from      = TimePointFromString("2021-10-20 12:24:00"),
                           .date_till      = TimePointFromString("2021-10-20 15:36:00"),
                           .reminder       = 0,
                           .repeat         = 3,
                           .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));

    REQUIRE(eventsTbl.count() == 2);



@@ 85,11 91,13 @@ TEST_CASE("Events Table tests")
    SECTION("Update entry by ID")
    {
        auto entry = EventsTableRow({{.ID = 2},
                                     "test",
                                     "TestUpdateEvent",
                                     TimePointFromString("2019-10-20 15:00:00"),
                                     TimePointFromString("2019-10-20 18:54:00"),
                                     0,
                                     2});
                                     2,
                                     TIME_POINT_INVALID});
        REQUIRE(eventsTbl.update(entry));
        auto record = eventsTbl.getById(2);
        REQUIRE(record.ID == 2);


@@ 104,41 112,53 @@ TEST_CASE("Events Table tests")
    SECTION("Select entry by date")
    {
        REQUIRE(eventsTbl.add({{.ID = 0},
                               .title     = "Event5",
                               .date_from = TimePointFromString("2019-10-20 14:24:00"),
                               .date_till = TimePointFromString("2019-10-20 15:36:00"),
                               .reminder  = 1,
                               .repeat    = 2}));
                               .UID            = "test",
                               .title          = "Event5",
                               .date_from      = TimePointFromString("2019-10-20 14:24:00"),
                               .date_till      = TimePointFromString("2019-10-20 15:36:00"),
                               .reminder       = 1,
                               .repeat         = 2,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 1},
                               .title     = "Event6",
                               .date_from = TimePointFromString("2021-04-19 12:24:00"),
                               .date_till = TimePointFromString("2021-04-20 15:36:00"),
                               .reminder  = 0,
                               .repeat    = 3}));
                               .UID            = "test",
                               .title          = "Event6",
                               .date_from      = TimePointFromString("2021-04-19 12:24:00"),
                               .date_till      = TimePointFromString("2021-04-20 15:36:00"),
                               .reminder       = 0,
                               .repeat         = 3,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 2},
                               .title     = "Event7",
                               .date_from = TimePointFromString("2019-10-20 15:24:00"),
                               .date_till = TimePointFromString("2019-10-20 15:36:00"),
                               .reminder  = 1,
                               .repeat    = 2}));
                               .UID            = "test",
                               .title          = "Event7",
                               .date_from      = TimePointFromString("2019-10-20 15:24:00"),
                               .date_till      = TimePointFromString("2019-10-20 15:36:00"),
                               .reminder       = 1,
                               .repeat         = 2,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 3},
                               .title     = "Event8",
                               .date_from = TimePointFromString("2021-04-19 12:24:00"),
                               .date_till = TimePointFromString("2019-10-20 15:36:00"),
                               .reminder  = 0,
                               .repeat    = 3}));
                               .UID            = "test",
                               .title          = "Event8",
                               .date_from      = TimePointFromString("2021-04-19 12:24:00"),
                               .date_till      = TimePointFromString("2019-10-20 15:36:00"),
                               .reminder       = 0,
                               .repeat         = 3,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 4},
                               .title     = "Event9",
                               .date_from = TimePointFromString("2025-10-20 15:24:00"),
                               .date_till = TimePointFromString("2025-10-20 15:36:00"),
                               .reminder  = 1,
                               .repeat    = 2}));
                               .UID            = "test",
                               .title          = "Event9",
                               .date_from      = TimePointFromString("2025-10-20 15:24:00"),
                               .date_till      = TimePointFromString("2025-10-20 15:36:00"),
                               .reminder       = 1,
                               .repeat         = 2,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 5},
                               .title     = "Event10",
                               .date_from = TimePointFromString("2021-12-19 12:24:00"),
                               .date_till = TimePointFromString("2021-12-20 15:36:00"),
                               .reminder  = 0,
                               .repeat    = 3}));
                               .UID            = "test",
                               .title          = "Event10",
                               .date_from      = TimePointFromString("2021-12-19 12:24:00"),
                               .date_till      = TimePointFromString("2021-12-20 15:36:00"),
                               .reminder       = 0,
                               .repeat         = 3,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));

        auto entries = eventsTbl.selectByDatePeriod(
            TimePointFromString("2000-01-01 00:00:00"), TimePointFromString("2012-12-31 23:59:00"), 0, UINT32_MAX);


@@ 168,41 188,53 @@ TEST_CASE("Events Table tests")
        const std::array<uint32_t, 6> paramID{3, 4, 5, 6, 7, 8};
        const std::array<std::string, 6> paramName{"Event1", "Event2", "Event3", "Event4", "Event5", "Event6"};
        REQUIRE(eventsTbl.add({{.ID = 1},
                               .title     = "Event1",
                               .date_from = paramDate[0],
                               .date_till = TimePointFromString("2030-10-20 15:24"),
                               .reminder  = 0,
                               .repeat    = 0}));
                               .UID            = "test",
                               .title          = "Event1",
                               .date_from      = paramDate[0],
                               .date_till      = TimePointFromString("2030-10-20 15:24"),
                               .reminder       = 0,
                               .repeat         = 0,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 2},
                               .title     = "Event2",
                               .date_from = paramDate[5],
                               .date_till = TimePointFromString("2030-10-20 15:24"),
                               .reminder  = 0,
                               .repeat    = 0}));
                               .UID            = "test",
                               .title          = "Event2",
                               .date_from      = paramDate[5],
                               .date_till      = TimePointFromString("2030-10-20 15:24"),
                               .reminder       = 0,
                               .repeat         = 0,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 3},
                               .title     = "Event3",
                               .date_from = paramDate[2],
                               .date_till = TimePointFromString("2030-10-20 15:24"),
                               .reminder  = 0,
                               .repeat    = 0}));
                               .UID            = "test",
                               .title          = "Event3",
                               .date_from      = paramDate[2],
                               .date_till      = TimePointFromString("2030-10-20 15:24"),
                               .reminder       = 0,
                               .repeat         = 0,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 4},
                               .title     = "Event4",
                               .date_from = paramDate[3],
                               .date_till = TimePointFromString("2030-10-20 15:24"),
                               .reminder  = 0,
                               .repeat    = 0}));
                               .UID            = "test",
                               .title          = "Event4",
                               .date_from      = paramDate[3],
                               .date_till      = TimePointFromString("2030-10-20 15:24"),
                               .reminder       = 0,
                               .repeat         = 0,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 5},
                               .title     = "Event5",
                               .date_from = paramDate[4],
                               .date_till = TimePointFromString("2030-10-20 15:24"),
                               .reminder  = 0,
                               .repeat    = 0}));
                               .UID            = "test",
                               .title          = "Event5",
                               .date_from      = paramDate[4],
                               .date_till      = TimePointFromString("2030-10-20 15:24"),
                               .reminder       = 0,
                               .repeat         = 0,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));
        REQUIRE(eventsTbl.add({{.ID = 6},
                               .title     = "Event6",
                               .date_from = paramDate[1],
                               .date_till = TimePointFromString("2030-10-20 15:24"),
                               .reminder  = 0,
                               .repeat    = 0}));
                               .UID            = "test",
                               .title          = "Event6",
                               .date_from      = paramDate[1],
                               .date_till      = TimePointFromString("2030-10-20 15:24"),
                               .reminder       = 0,
                               .repeat         = 0,
                               .reminder_fired = TimePointFromString("2019-10-20 15:36:00")}));

        auto entries = eventsTbl.getLimitOffsetByDate(0, 6);
        REQUIRE(entries.size() == 6);


@@ 240,6 272,7 @@ TEST_CASE("Events Table tests")
        TimePoint firedDate = TimePointFromString("2018-10-20 14:24:00");

        REQUIRE(eventsTbl.add({{.ID = 1},
                               .UID            = "test",
                               .title          = "Event1",
                               .date_from      = paramDate[0],
                               .date_till      = tillDate,


@@ 247,6 280,7 @@ TEST_CASE("Events Table tests")
                               .repeat         = 0,
                               .reminder_fired = TIME_POINT_INVALID}));
        REQUIRE(eventsTbl.add({{.ID = 2},
                               .UID            = "test",
                               .title          = "Event2",
                               .date_from      = paramDate[1],
                               .date_till      = tillDate,


@@ 254,6 288,7 @@ TEST_CASE("Events Table tests")
                               .repeat         = 0,
                               .reminder_fired = TIME_POINT_INVALID}));
        REQUIRE(eventsTbl.add({{.ID = 3},
                               .UID            = "test",
                               .title          = "Event3",
                               .date_from      = paramDate[2],
                               .date_till      = tillDate,


@@ 261,6 296,7 @@ TEST_CASE("Events Table tests")
                               .repeat         = 0,
                               .reminder_fired = firedDate}));
        REQUIRE(eventsTbl.add({{.ID = 4},
                               .UID            = "test",
                               .title          = "Event4",
                               .date_from      = paramDate[3],
                               .date_till      = tillDate,


@@ 268,6 304,7 @@ TEST_CASE("Events Table tests")
                               .repeat         = 0,
                               .reminder_fired = firedDate}));
        REQUIRE(eventsTbl.add({{.ID = 5},
                               .UID            = "test",
                               .title          = "Event5",
                               .date_from      = paramDate[4],
                               .date_till      = tillDate,


@@ 275,6 312,7 @@ TEST_CASE("Events Table tests")
                               .repeat         = 0,
                               .reminder_fired = TIME_POINT_INVALID}));
        REQUIRE(eventsTbl.add({{.ID = 6},
                               .UID            = "test",
                               .title          = "Event6",
                               .date_from      = paramDate[5],
                               .date_till      = tillDate,

M module-services/service-desktop/CMakeLists.txt => module-services/service-desktop/CMakeLists.txt +2 -0
@@ 19,6 19,8 @@ set(SOURCES
    endpoints/update/UpdateEndpoint.cpp
    endpoints/update/UpdateMuditaOS.cpp
    endpoints/filesystem/FilesystemEndpoint.cpp
    endpoints/calendarEvents/CalendarEventsHelper.cpp
    endpoints/calendarEvents/CalendarEventsEndpoint.cpp

    parser/ParserUtils.cpp
    parser/ParserFSM.cpp

A module-services/service-desktop/endpoints/CMakeLists.txt => module-services/service-desktop/endpoints/CMakeLists.txt +0 -0
M module-services/service-desktop/endpoints/EndpointFactory.hpp => module-services/service-desktop/endpoints/EndpointFactory.hpp +3 -0
@@ 6,6 6,7 @@
#include "Endpoint.hpp"

#include "Service/Service.hpp"
#include "calendarEvents/CalendarEventsEndpoint.hpp"
#include "backup/BackupEndpoint.hpp"
#include "deviceInfo/DeviceInfoEndpoint.hpp"
#include "update/UpdateEndpoint.hpp"


@@ 47,6 48,8 @@ class EndpointFactory
            return std::make_unique<CalllogEndpoint>(ownerServicePtr);
        case EndpointType::developerMode:
            return std::make_unique<DeveloperModeEndpoint>(ownerServicePtr);
        case EndpointType::calendarEvents:
            return std::make_unique<CalendarEventsEndpoint>(ownerServicePtr);
        default:
            return nullptr;
        }

A module-services/service-desktop/endpoints/calendarEvents/CalendarEventsEndpoint.cpp => module-services/service-desktop/endpoints/calendarEvents/CalendarEventsEndpoint.cpp +27 -0
@@ 0,0 1,27 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CalendarEventsEndpoint.hpp"
#include "queries/phonebook/QueryContactGet.hpp"
#include <memory>
#include <string>

using namespace parserFSM;

auto CalendarEventsEndpoint::handle(Context &context) -> void
{
    switch (context.getMethod()) {
    case http::Method::get:
        helper->requestDataFromDB(context);
        break;
    case http::Method::post: // update entry
        helper->updateDBEntry(context);
        break;
    case http::Method::put:
        helper->createDBEntry(context);
        break;
    case http::Method::del:
        helper->deleteDBEntry(context);
        break;
    }
}

A module-services/service-desktop/endpoints/calendarEvents/CalendarEventsEndpoint.hpp => module-services/service-desktop/endpoints/calendarEvents/CalendarEventsEndpoint.hpp +24 -0
@@ 0,0 1,24 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <endpoints/Endpoint.hpp>
#include "Service/Service.hpp"
#include "CalendarEventsHelper.hpp"

using namespace parserFSM;

class CalendarEventsEndpoint : public Endpoint
{
  private:
    std::string debugName = "CalendarEventsEndpoint";
    std::unique_ptr<CalendarEventsHelper> helper;

  public:
    CalendarEventsEndpoint(sys::Service *_ownerServicePtr) : Endpoint(_ownerServicePtr)
    {
        helper = std::make_unique<CalendarEventsHelper>(ownerServicePtr);
    }
    void handle(Context &context) override;
};

A module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.cpp => module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.cpp +334 -0
@@ 0,0 1,334 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "EventsRecord.hpp"
#include "CalendarEventsHelper.hpp"
#include "Common/Query.hpp"
#include <parser/ParserUtils.hpp>
#include "Service/Common.hpp"
#include <service-db/DBServiceAPI.hpp>
#include "log/log.hpp"
#include "json/json11.hpp"
#include <queries/calendar/QueryEventsGetAll.hpp>
#include <queries/calendar/QueryEventsGet.hpp>
#include <queries/calendar/QueryEventsAdd.hpp>
#include <queries/calendar/QueryEventsEdit.hpp>
#include <queries/calendar/QueryEventsEditICS.hpp>
#include <queries/calendar/QueryEventsRemove.hpp>
#include <queries/calendar/QueryEventsRemoveICS.hpp>
#include <queries/calendar/QueryEventsGetAllLimited.hpp>
#include <variant>

namespace parserFSM
{
    namespace ical
    {
        namespace duration
        {
            const inline auto five_minutes_before    = Duration(0, 0, 0, 5);
            const inline auto fifteen_minutes_before = Duration(0, 0, 0, 15);
            const inline auto thirty_minutes_before  = Duration(0, 0, 0, 30);
            const inline auto one_hour_before        = Duration(0, 0, 1, 0);
            const inline auto two_hours_before       = Duration(0, 0, 2, 0);
            const inline auto one_day_before         = Duration(0, 1, 0, 0);
            const inline auto two_days_before        = Duration(0, 2, 0, 0);
            const inline auto one_week_before        = Duration(1, 0, 0, 0);
            const inline auto event_time             = Duration(0, 0, 0, 0);
        } // namespace duration
    }     // namespace ical

    namespace json::calendar::events
    {
        constexpr inline auto UID    = "UID";
        constexpr inline auto data   = "data";
        constexpr inline auto offset = "offset";
        constexpr inline auto limit  = "limit";
        constexpr inline auto count  = "count";
    } // namespace json::calendar::events
} // namespace parserFSM
using namespace parserFSM;

auto CalendarEventsHelper::frequencyFromCustomRepeat(Repeat repeat) const -> Frequency
{
    return Frequency(static_cast<uint32_t>(repeat));
}

auto CalendarEventsHelper::recurrenceRuleFrom(Repeat repeat) const -> RecurrenceRule
{
    if (repeat > Repeat::yearly) {
        uint32_t count = 1, interval = 1;
        return RecurrenceRule(frequencyFromCustomRepeat(repeat), count, interval);
    }
    switch (repeat) {
    case Repeat::never: {
        return RecurrenceRule();
    }
    case Repeat::daily: {
        uint32_t count = 7, interval = 1;
        return RecurrenceRule(Frequency::daily, count, interval);
    }
    case Repeat::weekly: {
        uint32_t count = 4, interval = 1;
        return RecurrenceRule(Frequency::weekly, count, interval);
    }
    case Repeat::biweekly: {
        uint32_t count = 4, interval = 2;
        return RecurrenceRule(Frequency::weekly, count, interval);
    }
    case Repeat::monthly: {
        uint32_t count = 12, interval = 1;
        return RecurrenceRule(Frequency::monthly, count, interval);
    }
    case Repeat::yearly: {
        uint32_t count = 4, interval = 1;
        return RecurrenceRule(Frequency::yearly, count, interval);
    }
    }

    return RecurrenceRule();
}

auto CalendarEventsHelper::alarmFrom(Reminder reminder) const -> Alarm
{
    switch (reminder) {
    case Reminder::never: {
        return Alarm();
    }
    case Reminder::five_min_before: {
        auto beforeEvent = ical::duration::five_minutes_before;
        return Alarm(beforeEvent, Action::display);
    }
    case Reminder::fifteen_min_before: {
        auto beforeEvent = ical::duration::fifteen_minutes_before;
        return Alarm(beforeEvent, Action::display);
    }
    case Reminder::thirty_min_before: {
        auto beforeEvent = ical::duration::thirty_minutes_before;
        return Alarm(beforeEvent, Action::display);
    }
    case Reminder::one_hour_before: {
        auto beforeEvent = ical::duration::one_hour_before;
        return Alarm(beforeEvent, Action::display);
    }
    case Reminder::two_hour_before: {
        auto beforeEvent = ical::duration::two_hours_before;
        return Alarm(beforeEvent, Action::display);
    }
    case Reminder::one_day_before: {
        auto beforeEvent = ical::duration::one_day_before;
        return Alarm(beforeEvent, Action::display);
    }
    case Reminder::two_days_before: {
        auto beforeEvent = ical::duration::two_days_before;
        return Alarm(beforeEvent, Action::display);
    }
    case Reminder::one_week_before: {
        auto beforeEvent = ical::duration::one_week_before;
        return Alarm(beforeEvent, Action::display);
    }
    case Reminder::event_time: {
        auto beforeEvent = ical::duration::event_time;
        return Alarm(beforeEvent, Action::display);
    }
    }
    return Alarm();
}

auto CalendarEventsHelper::icalEventFrom(const EventsRecord &record) const -> ICalEvent
{

    auto event = Event(record.title, record.date_from, record.date_till, record.UID);
    auto alarm = alarmFrom(Reminder(record.reminder));
    auto rrule = recurrenceRuleFrom(Repeat(record.repeat));
    return ICalEvent{event, alarm, rrule};
}

auto CalendarEventsHelper::requestDataFromDB(Context &context) -> sys::ReturnCodes
{
    auto obj        = context.getBody();
    uint32_t offset = obj[json::calendar::events::offset].int_value();
    uint32_t limit  = obj[json::calendar::events::limit].int_value();
    auto query      = std::make_unique<db::query::events::GetAllLimited>(offset, limit);

    auto listener = std::make_unique<db::EndpointListener>(
        [&](db::QueryResult *result, Context context) {
            if (auto EventsResult = dynamic_cast<db::query::events::GetAllLimitedResult *>(result)) {
                auto records = EventsResult->getResult();
                assert(records != nullptr);
                auto parser = std::make_unique<ParserICS>();
                std::vector<ICalEvent> icalEvents;
                for (auto rec : *records) {
                    icalEvents.push_back(icalEventFrom(rec));
                }
                parser->importEvents(icalEvents);
                auto jsonObj = json11::Json::object({{json::calendar::events::data, parser->getIcsData()},
                                                     {json::calendar::events::count, std::to_string(records->size())}});

                context.setResponseBody(jsonObj);
                MessageHandler::putToSendQueue(context.createSimpleResponse());
                return true;
            }
            return false;
        },
        context);

    query->setQueryListener(std::move(listener));
    auto ret = DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));

    if (ret) {
        return sys::ReturnCodes::Success;
    }
    else {
        return sys::ReturnCodes::Failure;
    }
}

auto CalendarEventsHelper::frequencyToCustomRepeat(Frequency freq) const -> uint32_t
{
    return static_cast<uint32_t>(freq);
}

auto CalendarEventsHelper::repeatFrom(RecurrenceRule &rrule) const -> Repeat
{
    auto freq = rrule.getFrequencyValue();
    switch (freq) {
    case Frequency::daily: {
        return Repeat::daily;
    }
    case Frequency::weekly: {
        if (rrule.getIntervalValue() == 1) {
            return Repeat::weekly;
        }
        else if (rrule.getIntervalValue() == 2) {
            return Repeat::biweekly;
        }
        else {
            LOG_ERROR("Interval invalid");
            return Repeat::never;
        }
    }
    case Frequency::monthly: {
        return Repeat::monthly;
    }
    case Frequency::yearly: {
        return Repeat::yearly;
    }
    case Frequency::invalid: {
        LOG_ERROR("Frequency invalid");
        return Repeat::never;
    }
    }
    return Repeat::never;
}

auto CalendarEventsHelper::eventsRecordFrom(ICalEvent &icalEvent) const -> EventsRecord
{

    auto record      = EventsRecord();
    record.UID       = icalEvent.event.getUID();
    record.title     = icalEvent.event.getSummary();
    record.date_from = icalEvent.event.getDTStartTimePoint();
    record.date_till = icalEvent.event.getDTEndTimePoint();
    if (icalEvent.rrule.getFrequencyValue() > Frequency::yearly) {
        record.repeat = frequencyToCustomRepeat(icalEvent.rrule.getFrequencyValue());
    }
    record.repeat   = static_cast<uint32_t>(repeatFrom(icalEvent.rrule));
    record.reminder = icalEvent.alarm.getTriggerValue().getDurationInMinutes();

    return record;
}

auto CalendarEventsHelper::createDBEntry(Context &context) -> sys::ReturnCodes
{
    auto parser = std::make_unique<ParserICS>();
    parser->loadData(context.getBody()[json::calendar::events::data].string_value());
    auto icalEvents = parser->exportEvents();

    bool ret = true;
    for (auto event : icalEvents) {
        auto record = eventsRecordFrom(event);
        auto query  = std::make_unique<db::query::events::Add>(record);

        auto listener = std::make_unique<db::EndpointListener>(
            [=](db::QueryResult *result, Context context) {
                if (auto EventResult = dynamic_cast<db::query::events::AddResult *>(result)) {
                    context.setResponseStatus(EventResult->getResult() ? http::Code::OK
                                                                       : http::Code::InternalServerError);
                    MessageHandler::putToSendQueue(context.createSimpleResponse());
                    return true;
                }
                return false;
            },
            context);

        query->setQueryListener(std::move(listener));
        ret = ret && DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
    }

    if (ret) {
        return sys::ReturnCodes::Success;
    }
    else {
        return sys::ReturnCodes::Failure;
    }
}

auto CalendarEventsHelper::updateDBEntry(Context &context) -> sys::ReturnCodes
{
    auto parser = std::make_unique<ParserICS>();
    parser->loadData(context.getBody()[json::calendar::events::data].string_value());
    auto icalEvents = parser->exportEvents();

    bool ret = true;
    for (auto event : icalEvents) {

        auto record   = eventsRecordFrom(event);
        auto query    = std::make_unique<db::query::events::EditICS>(record);
        auto listener = std::make_unique<db::EndpointListener>(
            [](db::QueryResult *result, Context context) {
                if (auto EventResult = dynamic_cast<db::query::events::EditICSResult *>(result)) {
                    context.setResponseStatus(EventResult->getResult() ? http::Code::OK
                                                                       : http::Code::InternalServerError);
                    MessageHandler::putToSendQueue(context.createSimpleResponse());
                    return true;
                }
                return false;
            },
            context);

        query->setQueryListener(std::move(listener));
        ret = ret && DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
    }
    if (ret) {
        return sys::ReturnCodes::Success;
    }
    else {
        return sys::ReturnCodes::Failure;
    }
}

auto CalendarEventsHelper::deleteDBEntry(Context &context) -> sys::ReturnCodes
{
    auto UID      = context.getBody()[json::calendar::events::UID].string_value();
    auto query    = std::make_unique<db::query::events::RemoveICS>(UID);
    auto listener = std::make_unique<db::EndpointListener>(
        [=](db::QueryResult *result, Context context) {
            if (auto EventResult = dynamic_cast<db::query::events::RemoveICSResult *>(result)) {
                context.setResponseStatus(EventResult->getResult() ? http::Code::OK : http::Code::InternalServerError);
                MessageHandler::putToSendQueue(context.createSimpleResponse());
                return true;
            }
            return false;
        },
        context);

    query->setQueryListener(std::move(listener));
    auto ret = DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));

    if (ret) {
        return sys::ReturnCodes::Success;
    }
    else {
        return sys::ReturnCodes::Failure;
    }
}

A module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.hpp => module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.hpp +41 -0
@@ 0,0 1,41 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "Common/Query.hpp"
#include "EventsRecord.hpp"
#include "Service/Service.hpp"
#include "Service/Common.hpp"
#include <memory>
#include <service-db/DBServiceAPI.hpp>
#include <endpoints/Endpoint.hpp>
#include <endpoints/Context.hpp>
#include <endpoints/DBHelper.hpp>

#include <module-utils/ical/ParserICS.hpp>

namespace parserFSM
{

    class CalendarEventsHelper : public DBHelper
    {
        [[nodiscard]] auto frequencyFromCustomRepeat(Repeat repeat) const -> Frequency;
        [[nodiscard]] auto recurrenceRuleFrom(Repeat repeat) const -> RecurrenceRule;
        [[nodiscard]] auto alarmFrom(Reminder reminder) const -> Alarm;
        [[nodiscard]] auto icalEventFrom(const EventsRecord &record) const -> ICalEvent;

        [[nodiscard]] auto frequencyToCustomRepeat(Frequency freq) const -> uint32_t;
        [[nodiscard]] auto repeatFrom(RecurrenceRule &rrule) const -> Repeat;
        [[nodiscard]] auto eventsRecordFrom(ICalEvent &icalEvent) const -> EventsRecord;

      public:
        CalendarEventsHelper(sys::Service *_ownerServicePtr) : DBHelper(_ownerServicePtr)
        {}

        auto createDBEntry(Context &context) -> sys::ReturnCodes override;
        auto requestDataFromDB(Context &context) -> sys::ReturnCodes override;
        auto updateDBEntry(Context &context) -> sys::ReturnCodes override;
        auto deleteDBEntry(Context &context) -> sys::ReturnCodes override;
    };
} // namespace parserFSM

M module-services/service-desktop/parser/ParserUtils.hpp => module-services/service-desktop/parser/ParserUtils.hpp +2 -1
@@ 25,7 25,8 @@ namespace parserFSM
        contacts,
        messages,
        calllog,
        developerMode,
        calendarEvents,
        developerMode
    };

    inline constexpr auto lastEndpoint = static_cast<int>(EndpointType::developerMode);

M module-utils/CMakeLists.txt => module-utils/CMakeLists.txt +1 -0
@@ 33,6 33,7 @@ set (SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/NumberHolderMatcher.hpp
        ${CMAKE_CURRENT_SOURCE_DIR}/country.hpp
        ${CMAKE_CURRENT_SOURCE_DIR}/state/ServiceState.hpp
        ${CMAKE_CURRENT_SOURCE_DIR}/ical/ParserICS.cpp
)

add_library(${PROJECT_NAME} STATIC ${SOURCES} ${BOARD_SOURCES})

A module-utils/ical/ParserICS.cpp => module-utils/ical/ParserICS.cpp +610 -0
@@ 0,0 1,610 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ParserICS.hpp"
//#include <exception>

namespace ical
{
    constexpr inline auto begin = "BEGIN:VCALENDAR";

    namespace with_linefeed
    {
        constexpr inline auto begin   = "BEGIN:VCALENDAR\r\n";
        constexpr inline auto version = "VERSION:2.0\r\n";
        constexpr inline auto end     = "END:VCALENDAR\r\n";
    } // namespace with_linefeed

    namespace event
    {
        namespace with_linefeed
        {
            constexpr inline auto begin = "BEGIN:VEVENT\r\n";
            constexpr inline auto end   = "END:VEVENT\r\n";
        } // namespace with_linefeed
        constexpr inline auto begin = "BEGIN:VEVENT";
        constexpr inline auto end   = "END:VEVENT";

        constexpr inline auto uid     = "UID:";
        constexpr inline auto summary = "SUMMARY:";
        constexpr inline auto dtstart = "DTSTART:";
        constexpr inline auto dtend   = "DTEND:";
    } // namespace event

    namespace alarm
    {
        namespace with_linefeed
        {
            constexpr inline auto begin = "BEGIN:VALARM\r\n";
            constexpr inline auto end   = "END:VALARM\r\n";
        } // namespace with_linefeed
        constexpr inline auto begin = "BEGIN:VALARM";
        constexpr inline auto end   = "END:VALARM";

        constexpr inline auto trigger = "TRIGGER:";
        constexpr inline auto action  = "ACTION:";

        namespace value
        {
            // Time, 0, Minutes
            namespace action
            {
                constexpr inline auto display   = "DISPLAY";
                constexpr inline auto procedure = "PROCEDURE";
                constexpr inline auto audio     = "AUDIO";
            } // namespace action
            constexpr inline auto trigger_invalid = "T0M";
            constexpr inline auto before          = "-P";
            constexpr inline auto after           = "P";
        } // namespace value
    }     // namespace alarm

    namespace rrule
    {
        constexpr inline auto rrule = "RRULE:";
        constexpr inline auto freq  = "FREQ=";
        namespace frequency
        {
            constexpr inline auto daily   = "DAILY";
            constexpr inline auto weekly  = "WEEKLY";
            constexpr inline auto monthly = "MONTHLY";
            constexpr inline auto yearly  = "YEARLY";
        } // namespace frequency
        constexpr inline auto count    = "COUNT=";
        constexpr inline auto interval = "INTERVAL=";
    } // namespace rrule

    namespace duration
    {
        constexpr inline auto minutesInHour = 60;
        constexpr inline auto minutesInDay  = 24 * minutesInHour;
        constexpr inline auto minutesInWeek = 7 * minutesInDay;
    } // namespace duration
} // namespace ical

Duration::Duration(uint32_t week, uint32_t day, uint32_t hour, uint32_t minute)
{
    this->week   = week;
    this->day    = day;
    this->hour   = hour;
    this->minute = minute;
}

Duration::Duration(const std::string &property)
{
    uint32_t i = 0;
    if (property.empty() || (property[0] != 'P' && property[1] != 'P')) {
        LOG_ERROR("Duration constructor: Invalid format provided: %s", property.c_str());
        return;
    }
    while (property[i] != '\0' && i < property.length()) {
        if (isdigit(property[i])) {
            std::string value;
            while (isdigit(property[i])) {
                value += property[i];
                ++i;
            }
            try {
                switch (property[i]) {
                case 'W':
                    this->week = stoi(value);
                    break;
                case 'D':
                    this->day = stoi(value);
                    break;
                case 'H':
                    this->hour = stoi(value);
                    break;
                case 'M':
                    this->minute = stoi(value);
                    break;
                }
            }
            catch (std::exception &e) {
                LOG_DEBUG("Duration conversion from string to int failed with exception:%s", e.what());
            }
        }
        ++i;
    }
}

auto Duration::getDurationInMinutes() const -> uint32_t
{
    auto weeksMinutes = week * ical::duration::minutesInWeek;
    auto daysMinutes  = day * ical::duration::minutesInDay;
    auto hoursMinutes = hour * ical::duration::minutesInHour;

    return weeksMinutes + daysMinutes + hoursMinutes + minute;
}

auto Duration::getDurationString() const -> std::string
{
    std::string durationString;
    if (week) {
        durationString += std::to_string(week) + 'W';
    }
    if (day) {
        durationString += std::to_string(day) + 'D';
    }
    if (hour || minute) {
        durationString += 'T';
    }
    if (hour) {
        durationString += std::to_string(hour) + 'H';
    }
    if (minute) {
        durationString += std::to_string(minute) + 'M';
    }
    if (week == 0 && day == 0 && hour == 0 && minute == 0) {
        durationString += "T0M";
    }
    return durationString;
}

Alarm::Alarm(Duration &beforeEvent, Action action)
{
    if (beforeEvent.getDurationString().empty()) {
        LOG_ERROR("Got empty duration value");
        return;
    }
    this->trigger = beforeEvent;
    this->action  = action;
}

void Alarm::setAction(const std::string &action)
{
    if (action == ical::alarm::value::action::display) {
        this->action = Action::display;
    }
    else if (action == ical::alarm::value::action::procedure) {
        this->action = Action::procedure;
    }
    else if (action == ical::alarm::value::action::audio) {
        this->action = Action::audio;
    }
    else {
        this->action = Action::invalid;
    }
}

void Alarm::setTrigger(const std::string &duration)
{
    this->trigger = Duration(duration);
}

auto Alarm::getTriggerValue() const -> Duration
{
    return this->trigger;
}

auto Alarm::getActionValue() const -> Action
{
    return this->action;
}

auto Alarm::getTriggerString() const -> std::string
{
    return ical::alarm::value::before + trigger.getDurationString();
}

auto Alarm::getActionString() const -> std::string
{
    std::string actionStr = ical::alarm::action;
    switch (this->action) {
    case Action::audio: {
        return ical::alarm::value::action::audio;
    }
    case Action::display: {
        return ical::alarm::value::action::display;
    }
    case Action::procedure: {
        return ical::alarm::value::action::procedure;
    }
    case Action::invalid: {
        LOG_ERROR("Alarm with no action");
        return "";
    }
    }
    return "";
}

RecurrenceRule::RecurrenceRule(Frequency freq, uint32_t count, uint32_t interval)
{
    if (count == 0 || interval == 0) {
        LOG_ERROR("Invalid count or interval value!");
        return;
    }

    this->frequency = freq;
    this->count     = count;
    this->interval  = interval;
}

void RecurrenceRule::setFrequency(const std::string &property)
{
    if (property == ical::rrule::frequency::daily) {
        this->frequency = Frequency::daily;
    }
    else if (property == ical::rrule::frequency::weekly) {
        this->frequency = Frequency::weekly;
    }
    else if (property == ical::rrule::frequency::monthly) {
        this->frequency = Frequency::monthly;
    }
    else if (property == ical::rrule::frequency::yearly) {
        this->frequency = Frequency::yearly;
    }
    else {
        LOG_ERROR("Invalid frequency!");
        this->frequency = Frequency::invalid;
    }
}
void RecurrenceRule::setCount(const std::string &property)
{
    try {
        this->count = stoi(property);
    }
    catch (...) {
        LOG_ERROR("Count value not conversionable to int!");
    }
}

void RecurrenceRule::setInterval(const std::string &property)
{
    try {
        this->interval = stoi(property);
    }
    catch (...) {
        LOG_ERROR("Interval value not conversionable to int!");
    }
}

auto RecurrenceRule::getFrequencyValue() const -> Frequency
{
    return this->frequency;
}

auto RecurrenceRule::getCountValue() const -> uint32_t
{
    return this->count;
}

auto RecurrenceRule::getIntervalValue() const -> uint32_t
{
    return this->interval;
}

auto RecurrenceRule::getFrequencyString() const -> std::string
{
    std::string frequencyStr = ical::rrule::freq;
    switch (this->frequency) {
    case Frequency::daily: {
        frequencyStr = ical::rrule::frequency::daily;
        break;
    }
    case Frequency::weekly: {
        frequencyStr = ical::rrule::frequency::weekly;
        break;
    }
    case Frequency::monthly: {
        frequencyStr = ical::rrule::frequency::monthly;
        break;
    }
    case Frequency::yearly: {
        frequencyStr = ical::rrule::frequency::yearly;
        break;
    }
    case Frequency::invalid: {
        LOG_ERROR("Frequency invalid");
        return "";
    }
    }
    return frequencyStr;
}

auto RecurrenceRule::getCountString() const -> std::string
{
    if (this->frequency == Frequency::invalid) {
        LOG_ERROR("Frequency value is invalid!");
        return "";
    }
    return std::to_string(this->count);
}

auto RecurrenceRule::getIntervalString() const -> std::string
{
    if (this->frequency == Frequency::invalid) {
        LOG_ERROR("Frequency value is invalid!");
        return "";
    }

    return std::to_string(this->interval);
}

auto Event::getDateFromIcalFormat(const std::string &icalDateTime) const -> std::string
{
    return icalDateTime.substr(0, icalDateTime.find_first_of('T'));
}

auto Event::getTimeFromIcalFormat(const std::string &icalDateTime) const -> std::string
{
    return icalDateTime.substr(icalDateTime.find_first_of('T') + 1);
}

auto Event::dateStringFrom(const std::string &icalDate) const -> std::string
{
    return icalDate.substr(0, yearDigitsNumb) + "-" + icalDate.substr(yearDigitsNumb, monthDigitsNumb) + "-" +
           icalDate.substr(yearDigitsNumb + monthDigitsNumb, dayDigitsNumb);
}

auto Event::timeStringFrom(const std::string &icalTime) const -> std::string
{
    return icalTime.substr(0, HourDigitsNumb) + ":" + icalTime.substr(HourDigitsNumb, MinDigitsNumb) + ":" +
           icalTime.substr(HourDigitsNumb + MinDigitsNumb, SecDigitsNumb);
}

auto Event::TimePointFromIcalDate(const std::string &icalDateTime) const -> TimePoint
{
    std::string icalDate(getDateFromIcalFormat(icalDateTime));
    std::string icalTime(getTimeFromIcalFormat(icalDateTime));

    std::string date(dateStringFrom(icalDate));
    std::string time(timeStringFrom(icalTime));

    std::string dateTime(date + " " + time);

    return TimePointFromString(dateTime.c_str());
}

auto Event::TimePointToIcalDate(const TimePoint &tp) const -> std::string
{
    constexpr uint32_t bufferLimit = 16;
    auto time                      = TimePointToTimeT(tp);
    auto utcTime                   = gmtime(&time);
    char Buffer[bufferLimit];
    strftime(Buffer, bufferLimit, "%Y%m%dT%H%M%S", utcTime);
    std::string IcalDate = Buffer;
    return IcalDate;
}

Event::Event(const std::string &summary, const TimePoint from, TimePoint till, const std::string &uid)
{
    this->summary = summary;
    this->dtstart = from;
    this->dtend   = till;
    this->uid     = uid;
}

void Event::setUID(const std::string &property)
{
    this->uid = property;
}
void Event::setSummary(const std::string &property)
{
    this->summary = property;
}
void Event::setDTStart(const std::string &property)
{
    this->dtstart = TimePointFromIcalDate(property);
}
void Event::setDTEnd(const std::string &property)
{
    this->dtend = TimePointFromIcalDate(property);
}

auto Event::getUID() const -> std::string
{
    return uid;
}

auto Event::getSummary() const -> std::string
{
    return summary;
}

auto Event::getDTStartTimePoint() const -> TimePoint
{
    return dtstart;
}

auto Event::getDTEndTimePoint() const -> TimePoint
{
    return dtend;
}

auto Event::getDTStartString() const -> std::string
{
    return TimePointToIcalDate(dtstart);
}
auto Event::getDTEndString() const -> std::string
{
    return TimePointToIcalDate(dtend);
}

void ParserICS::parseFrom(const Event &event)
{
    icsData += ical::event::uid + event.getUID() + "\r\n";
    icsData += ical::event::summary + event.getSummary() + "\r\n";
    icsData += ical::event::dtstart + event.getDTStartString() + "\r\n";
    icsData += ical::event::dtend + event.getDTEndString() + "\r\n";
}

void ParserICS::parseFrom(const Alarm &alarm)
{
    auto triggerValue  = alarm.getTriggerString();
    std::string prefix = ical::alarm::value::before;
    if (triggerValue == (prefix + ical::alarm::value::trigger_invalid)) {
        LOG_DEBUG("Got Alarm with empty trigger");
        return;
    }
    icsData += ical::alarm::with_linefeed::begin;
    icsData += ical::alarm::trigger + triggerValue + "\r\n";
    icsData += ical::alarm::action + alarm.getActionString() + "\r\n";
    icsData += ical::alarm::with_linefeed::end;
}

void ParserICS::parseFrom(const RecurrenceRule &rrule)
{
    auto freq = rrule.getFrequencyString();
    if (!freq.empty()) {
        icsData += std::string(ical::rrule::rrule) + ical::rrule::freq + freq + ";" + ical::rrule::count +
                   rrule.getCountString() + ";" + ical::rrule::interval + rrule.getIntervalString() + "\r\n";
    }
    else {
        LOG_DEBUG("Empty Alarm provided");
    }
}

void ParserICS::parseFrom(const ICalEvent &icalEvent)
{
    if (icalEvent.event.getUID() == "") {
        LOG_ERROR("Empty event provided to ical parser");
        return;
    }
    icsData += ical::event::with_linefeed::begin;
    parseFrom(icalEvent.event);
    parseFrom(icalEvent.rrule);
    parseFrom(icalEvent.alarm);
    icsData += ical::event::with_linefeed::end;
}

void ParserICS::eventPropertiesFromLine(ICalEvent &icalEvent, const std::string &line) const
{
    if (startsWith(line, ical::event::begin)) {
        icalEvent.event = Event();
    }
    else if (startsWith(line, ical::event::uid)) {
        icalEvent.event.setUID(getProperty(line));
    }
    else if (startsWith(line, ical::event::summary)) {
        icalEvent.event.setSummary(getProperty(line));
    }
    else if (startsWith(line, ical::event::dtstart)) {
        icalEvent.event.setDTStart(getProperty(line));
    }
    else if (startsWith(line, ical::event::dtend)) {
        icalEvent.event.setDTEnd(getProperty(line));
    }
}

void ParserICS::rrulePropertiesFromLine(ICalEvent &icalEvent, const std::string &line) const
{
    icalEvent.rrule    = RecurrenceRule();
    auto formattedLine = getProperty(line);
    icalEvent.rrule.setFrequency(getSubProperty(formattedLine, ical::rrule::freq));
    icalEvent.rrule.setCount(getSubProperty(formattedLine, ical::rrule::count));
    icalEvent.rrule.setInterval(getSubProperty(formattedLine, ical::rrule::interval));
}

void ParserICS::alarmPropertiesFromLine(ICalEvent &icalEvent, std::istringstream &input, std::string &line) const
{
    icalEvent.alarm = Alarm();

    while (getline(input, line)) {
        if (startsWith(line, ical::alarm::trigger)) {
            icalEvent.alarm.setTrigger(getProperty(line));
        }
        else if (startsWith(line, ical::alarm::action)) {
            icalEvent.alarm.setAction(getProperty(line));
        }
        else if (startsWith(line, ical::alarm::end)) {
            break;
        }
    }
}

auto ParserICS::startsWith(const std::string &line, const char *text) const -> bool
{
    return (line.find(text) == 0);
}

auto ParserICS::getProperty(const std::string &line) const -> std::string
{
    std::string Temp    = line.substr(line.find_first_of(':') + 1);
    unsigned int Length = Temp.length();
    if (Length > 0 && Temp[Length - 1] == '\r')
        Temp.resize(Length - 1);
    return Temp;
}

auto ParserICS::getSubProperty(const std::string &Line, const char *propertyName) const -> std::string
{
    size_t pos = Line.find(propertyName);
    if (pos == std::string::npos)
        return "";
    pos += strlen(propertyName);
    return Line.substr(pos, Line.find_first_of(';', pos) - pos);
}

void ParserICS::importEvents(std::vector<ICalEvent> events)
{
    this->icsData = ical::with_linefeed::begin;
    this->icsData += +ical::with_linefeed::version;

    for (auto event : events) {
        parseFrom(event);
    }

    this->icsData += ical::with_linefeed::end;
}

auto ParserICS::exportEvents() const -> std::vector<ICalEvent>
{
    std::vector<ICalEvent> events;
    ICalEvent icalEvent;

    std::istringstream input;
    input.str(icsData);
    std::string line;

    while (getline(input, line)) {
        eventPropertiesFromLine(icalEvent, line);

        if (startsWith(line, ical::rrule::rrule)) {
            rrulePropertiesFromLine(icalEvent, line);
        }
        else if (startsWith(line, ical::alarm::begin)) {
            alarmPropertiesFromLine(icalEvent, input, line);
        }
        else if (startsWith(line, ical::event::end)) {
            events.push_back(icalEvent);
        }
    }

    return events;
}

void ParserICS::loadData(const std::string &data)
{
    std::istringstream input;
    input.str(data);
    std::string line;

    getline(input, line);
    if (!startsWith(line, ical::begin)) {
        LOG_ERROR("Wrong format");
        return;
    }

    this->icsData = data;
}

A module-utils/ical/ParserICS.hpp => module-utils/ical/ParserICS.hpp +154 -0
@@ 0,0 1,154 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once
#include <module-apps/application-calendar/data/dateCommon.hpp>
#include "json/json11.hpp"
#include <memory>

/**
 * Icalendar Parser complies with:
 * @version RFC5545
 * @link https://tools.ietf.org/html/rfc5545
 */

class Duration
{
    uint32_t week = 0, day = 0, hour = 0, minute = 0;

  public:
    Duration(uint32_t week = 0, uint32_t day = 0, uint32_t hour = 0, uint32_t minute = 0);
    explicit Duration(const std::string &property);

    [[nodiscard]] auto getDurationInMinutes() const -> uint32_t;
    [[nodiscard]] auto getDurationString() const -> std::string;
};

enum class Action
{
    invalid,
    audio,
    display,
    procedure
};

class Alarm
{
    Duration trigger;
    Action action;

  public:
    Alarm() = default;
    explicit Alarm(Duration &beforeEvent, Action action = Action::invalid);

    void setAction(const std::string &action);
    void setTrigger(const std::string &duration);

    [[nodiscard]] auto getTriggerValue() const -> Duration;
    [[nodiscard]] auto getActionValue() const -> Action;

    [[nodiscard]] auto getTriggerString() const -> std::string;
    [[nodiscard]] auto getActionString() const -> std::string;
};

enum class Frequency
{
    invalid,
    daily,
    weekly,
    monthly,
    yearly

};

class RecurrenceRule
{
    Frequency frequency = Frequency::invalid;
    uint32_t count      = 0;
    uint32_t interval   = 0;

  public:
    RecurrenceRule() = default;
    RecurrenceRule(Frequency freq, uint32_t count, uint32_t interval);

    void setFrequency(const std::string &property);
    void setCount(const std::string &property);
    void setInterval(const std::string &property);

    [[nodiscard]] auto getFrequencyValue() const -> Frequency;
    [[nodiscard]] auto getCountValue() const -> uint32_t;
    [[nodiscard]] auto getIntervalValue() const -> uint32_t;

    [[nodiscard]] auto getFrequencyString() const -> std::string;
    [[nodiscard]] auto getCountString() const -> std::string;
    [[nodiscard]] auto getIntervalString() const -> std::string;
};

class Event
{
    std::string uid;
    std::string summary;
    TimePoint dtstart;
    TimePoint dtend;

    [[nodiscard]] auto getDateFromIcalFormat(const std::string &icalDateTime) const -> std::string;
    [[nodiscard]] auto getTimeFromIcalFormat(const std::string &icalDateTime) const -> std::string;
    [[nodiscard]] auto dateStringFrom(const std::string &icalDate) const -> std::string;
    [[nodiscard]] auto timeStringFrom(const std::string &icalTime) const -> std::string;

    [[nodiscard]] auto TimePointFromIcalDate(const std::string &icalDateTime) const -> TimePoint;
    [[nodiscard]] auto TimePointToIcalDate(const TimePoint &tp) const -> std::string;

  public:
    Event() = default;
    Event(const std::string &summary, TimePoint from, TimePoint till, const std::string &uid);

    void setUID(const std::string &property);
    void setSummary(const std::string &property);
    void setDTStart(const std::string &property);
    void setDTEnd(const std::string &property);

    [[nodiscard]] auto getUID() const -> std::string;
    [[nodiscard]] auto getSummary() const -> std::string;
    [[nodiscard]] auto getDTStartTimePoint() const -> TimePoint;
    [[nodiscard]] auto getDTEndTimePoint() const -> TimePoint;

    [[nodiscard]] auto getDTStartString() const -> std::string;
    [[nodiscard]] auto getDTEndString() const -> std::string;
};

struct ICalEvent
{
    Event event;
    Alarm alarm;
    RecurrenceRule rrule;
};

class ParserICS
{
    std::string icsData;

    void parseFrom(const Event &event);
    void parseFrom(const Alarm &alarm);
    void parseFrom(const RecurrenceRule &rrule);
    void parseFrom(const ICalEvent &icalEvent);

    auto startsWith(const std::string &line, const char *text) const -> bool;
    auto getProperty(const std::string &line) const -> std::string;
    auto getSubProperty(const std::string &Line, const char *propertyName) const -> std::string;

    void eventPropertiesFromLine(ICalEvent &icalEvent, const std::string &line) const;
    void rrulePropertiesFromLine(ICalEvent &icalEvent, const std::string &line) const;
    void alarmPropertiesFromLine(ICalEvent &icalEvent, std::istringstream &input, std::string &line) const;

  public:
    ParserICS() = default;
    void importEvents(std::vector<ICalEvent> events);
    [[nodiscard]] auto exportEvents() const -> std::vector<ICalEvent>;
    void loadData(const std::string &data);

    [[nodiscard]] auto getIcsData() const -> std::string
    {
        return icsData;
    }
};

M module-utils/test/CMakeLists.txt => module-utils/test/CMakeLists.txt +10 -0
@@ 75,6 75,16 @@ add_catch2_executable(
        module-utils
)

# ParserICS tests
#add_catch2_executable(
#    NAME
#        utils-parserIcs
#    SRCS
#        test_ParserICS.cpp
#    LIBS
#        module-utils
#)

###########################################
# Log functional tests project
project(test_module-utils_log VERSION 1.0

A module-utils/test/test_ParserICS.cpp => module-utils/test/test_ParserICS.cpp +498 -0
@@ 0,0 1,498 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN

#include <catch2/catch.hpp>
#include "ical/ParserICS.hpp"

TEST_CASE("Testing Duration class object", "getDurationString method")
{

    SECTION("Test regular week values")
    {
        int test_values[]             = {1, 2, 4, 52, 123};
        std::string expected_values[] = {"1W", "2W", "4W", "52W", "123W"};
        int index                     = 0;
        for (auto value : test_values) {
            auto duration = Duration(value, 0, 0, 0);
            CHECK(duration.getDurationString() == expected_values[index]);
            index++;
        }
    }

    SECTION("Test regular day values")
    {
        int test_values[]             = {1, 2, 3, 7, 30, 333};
        std::string expected_values[] = {"1D", "2D", "3D", "7D", "30D", "333D"};
        int index                     = 0;
        for (auto value : test_values) {
            auto duration = Duration(0, value, 0, 0);
            CHECK(duration.getDurationString() == expected_values[index]);
            index++;
        }
    }

    SECTION("Test regular hour values")
    {
        int test_values[]             = {1, 2, 3, 8, 24, 999};
        std::string expected_values[] = {"T1H", "T2H", "T3H", "T8H", "T24H", "T999H"};
        int index                     = 0;
        for (auto value : test_values) {
            auto duration = Duration(0, 0, value, 0);
            CHECK(duration.getDurationString() == expected_values[index]);
            index++;
        }
    }

    SECTION("Test regular minutes values")
    {
        int test_values[]             = {1, 2, 5, 8, 60, 941683};
        std::string expected_values[] = {"T1M", "T2M", "T5M", "T8M", "T60M", "T941683M"};
        int index                     = 0;
        for (auto value : test_values) {
            auto duration = Duration(0, 0, 0, value);
            CHECK(duration.getDurationString() == expected_values[index]);
            index++;
        }
    }

    SECTION("Test regular mixed week and day values")
    {
        int week_test_value        = 12;
        int day_test_value         = 34;
        std::string expected_value = "12W34D";

        auto duration = Duration(week_test_value, day_test_value, 0, 0);
        CHECK(duration.getDurationString() == expected_value);
    }

    SECTION("Test regular mixed hours and minutes values")
    {
        int hours_test_value       = 3;
        int minutes_test_value     = 5;
        std::string expected_value = "T3H5M";

        auto duration = Duration(0, 0, hours_test_value, minutes_test_value);
        CHECK(duration.getDurationString() == expected_value);
    }

    SECTION("Test regular mixed weeks and minutes values")
    {
        int week_test_value        = 4;
        int day_test_value         = 0;
        int hours_test_value       = 0;
        int minutes_test_value     = 5;
        std::string expected_value = "4WT5M";

        auto duration = Duration(week_test_value, day_test_value, hours_test_value, minutes_test_value);
        CHECK(duration.getDurationString() == expected_value);
    }

    SECTION("Test regular mixed all values")
    {
        int week_test_value        = 1;
        int day_test_value         = 0;
        int hours_test_value       = 8;
        int minutes_test_value     = 23;
        std::string expected_value = "1WT8H23M";

        auto duration = Duration(week_test_value, day_test_value, hours_test_value, minutes_test_value);
        CHECK(duration.getDurationString() == expected_value);
    }

    SECTION("Test getDurationInMinutes() method")
    {
        int week_test_value    = 2;
        int day_test_value     = 1;
        int hours_test_value   = 2;
        int minutes_test_value = 30;
        uint32_t time_in_minutes =
            week_test_value * 7 * 24 * 60 + day_test_value * 24 * 60 + hours_test_value * 60 + minutes_test_value;

        auto duration = Duration(week_test_value, day_test_value, hours_test_value, minutes_test_value);
        CHECK(duration.getDurationInMinutes() == time_in_minutes);
    }

    SECTION("Test Duration(string) constructor")
    {

        std::string duration_value = "P1WT8H23M";
        std::string expected_value = "1WT8H23M";
        auto duration              = Duration(duration_value);
        CHECK(duration.getDurationString() == expected_value);

        std::string duration_value2 = "-P1W1DT8H23M";
        std::string expected_value2 = "1W1DT8H23M";
        auto duration2              = Duration(duration_value2);
        CHECK(duration2.getDurationString() == expected_value2);

        std::string duration_value3 = "-PT8H23M";
        std::string expected_value3 = "T8H23M";
        auto duration3              = Duration(duration_value3);
        CHECK(duration3.getDurationString() == expected_value3);

        std::string duration_value4 = "PT8H23M";
        std::string expected_value4 = "T8H23M";
        auto duration4              = Duration(duration_value4);
        CHECK(duration4.getDurationString() == expected_value4);
    }

    SECTION("Test Duration(string) constructor invalid")
    {
        std::string duration_value = "";
        std::string expected_value = "T0M";
        auto duration              = Duration(duration_value);
        CHECK(duration.getDurationString() == expected_value);

        std::string duration_value1 = "DTSTART:20201020T1536";
        std::string expected_value1 = "T0M";
        auto duration1              = Duration(duration_value1);
        CHECK(duration1.getDurationString() == expected_value1);
    }
}

TEST_CASE("Testing Alarm class object", "")
{

    SECTION("Test regural trigger and action audio")
    {
        auto action                        = Action::audio;
        auto duration                      = Duration(0, 0, 0, 5);
        std::string expected_action_value  = "AUDIO";
        std::string expected_trigger_value = "-PT5M";

        auto alarm = Alarm(duration, action);
        CHECK(alarm.getActionString() == expected_action_value);
        CHECK(alarm.getTriggerString() == expected_trigger_value);
    }

    SECTION("Test regural trigger and action audio")
    {
        auto action                        = Action::procedure;
        auto duration                      = Duration(0, 0, 1, 0);
        std::string expected_action_value  = "PROCEDURE";
        std::string expected_trigger_value = "-PT1H";

        auto alarm = Alarm(duration, action);
        CHECK(alarm.getActionString() == expected_action_value);
        CHECK(alarm.getTriggerString() == expected_trigger_value);
    }

    SECTION("Test regural trigger and action display")
    {
        auto action                        = Action::display;
        auto duration                      = Duration(0, 1, 1, 0);
        std::string expected_action_value  = "DISPLAY";
        std::string expected_trigger_value = "-P1DT1H";

        auto alarm = Alarm(duration, action);
        CHECK(alarm.getActionString() == expected_action_value);
        CHECK(alarm.getTriggerString() == expected_trigger_value);
    }

    SECTION("Test invalid trigger and action")
    {
        auto invalid_action                = Action::invalid;
        auto invalid_duration              = Duration(0, 0, 0, 0);
        std::string expected_action_value  = "";
        std::string expected_trigger_value = "-PT0M";

        auto alarm = Alarm(invalid_duration, invalid_action);
        CHECK(alarm.getActionString() == expected_action_value);
        CHECK(alarm.getTriggerString() == expected_trigger_value);
    }
}

TEST_CASE("Testing RecurrenceRule class object", "")
{

    SECTION("Test frequency daily, count 7 value")
    {
        auto freq                            = Frequency::daily;
        auto rrule                           = RecurrenceRule(freq, 7, 1);
        std::string expected_frequency_value = "DAILY";
        std::string expected_count_value     = "7";
        std::string expected_interval_value  = "1";
        CHECK(rrule.getFrequencyString() == expected_frequency_value);
        CHECK(rrule.getCountString() == expected_count_value);
        CHECK(rrule.getIntervalString() == expected_interval_value);
    }

    SECTION("Test frequency weekly, count 4 value")
    {
        auto freq                            = Frequency::weekly;
        auto rrule                           = RecurrenceRule(freq, 4, 1);
        std::string expected_frequency_value = "WEEKLY";
        std::string expected_count_value     = "4";
        std::string expected_interval_value  = "1";
        CHECK(rrule.getFrequencyString() == expected_frequency_value);
        CHECK(rrule.getCountString() == expected_count_value);
        CHECK(rrule.getIntervalString() == expected_interval_value);
    }

    SECTION("Test frequency biweekly, count 4 value")
    {
        auto freq                            = Frequency::weekly;
        auto rrule                           = RecurrenceRule(freq, 4, 2);
        std::string expected_frequency_value = "WEEKLY";
        std::string expected_count_value     = "4";
        std::string expected_interval_value  = "2";
        CHECK(rrule.getFrequencyString() == expected_frequency_value);
        CHECK(rrule.getCountString() == expected_count_value);
        CHECK(rrule.getIntervalString() == expected_interval_value);
    }

    SECTION("Test frequency monthly, count 12 value")
    {
        auto freq                            = Frequency::monthly;
        auto rrule                           = RecurrenceRule(freq, 12, 1);
        std::string expected_frequency_value = "MONTHLY";
        std::string expected_count_value     = "12";
        std::string expected_interval_value  = "1";
        CHECK(rrule.getFrequencyString() == expected_frequency_value);
        CHECK(rrule.getCountString() == expected_count_value);
        CHECK(rrule.getIntervalString() == expected_interval_value);
    }

    SECTION("Test frequency yearly, count 4 value")
    {
        auto freq                            = Frequency::yearly;
        auto rrule                           = RecurrenceRule(freq, 4, 1);
        std::string expected_frequency_value = "YEARLY";
        std::string expected_count_value     = "4";
        std::string expected_interval_value  = "1";
        CHECK(rrule.getFrequencyString() == expected_frequency_value);
        CHECK(rrule.getCountString() == expected_count_value);
        CHECK(rrule.getIntervalString() == expected_interval_value);
    }

    SECTION("Test frequency invalid")
    {
        auto freq                            = Frequency::invalid;
        auto rrule                           = RecurrenceRule(freq, 1, 1);
        std::string expected_frequency_value = "";
        std::string expected_count_value     = "";
        std::string expected_interval_value  = "";
        CHECK(rrule.getFrequencyString() == expected_frequency_value);
        CHECK(rrule.getCountString() == expected_count_value);
        CHECK(rrule.getIntervalString() == expected_interval_value);
    }

    SECTION("Test count invalid")
    {
        auto freq                            = Frequency::daily;
        auto rrule                           = RecurrenceRule(freq, 1, 0);
        std::string expected_frequency_value = "";
        std::string expected_count_value     = "";
        std::string expected_interval_value  = "";
        CHECK(rrule.getFrequencyString() == expected_frequency_value);
        CHECK(rrule.getCountString() == expected_count_value);
        CHECK(rrule.getIntervalString() == expected_interval_value);
    }

    SECTION("Test interval invalid")
    {
        auto freq                            = Frequency::yearly;
        auto rrule                           = RecurrenceRule(freq, 0, 1);
        std::string expected_frequency_value = "";
        std::string expected_count_value     = "";
        std::string expected_interval_value  = "";
        CHECK(rrule.getFrequencyString() == expected_frequency_value);
        CHECK(rrule.getCountString() == expected_count_value);
        CHECK(rrule.getIntervalString() == expected_interval_value);
    }
}

TEST_CASE("Testing Event class object", "")
{

    SECTION("Test get DTStart and DTEnd in TimePoint value")
    {
        auto summary = "test";
        auto DTstart = TimePointFromString("2020-11-05 15:00:00");
        auto DTend   = TimePointFromString("2020-11-05 16:00:00");

        auto event = Event(summary, DTstart, DTend, "test_uid");

        std::string expected_summary_value = "test";
        std::string expected_DTstart_value = "20201105T150000";
        std::string expected_DTend_value   = "20201105T160000";
        CHECK(event.getSummary() == expected_summary_value);
        CHECK(event.getDTStartString() == expected_DTstart_value);
        CHECK(event.getDTEndString() == expected_DTend_value);

        auto dtstart_timepoint              = event.getDTStartTimePoint();
        auto dtend_timepoint                = event.getDTEndTimePoint();
        std::string expected_dtstart_string = "2020-11-05 15:00:00";
        std::string expected_dtend_string   = "2020-11-05 16:00:00";
        CHECK(TimePointToString(dtstart_timepoint) == expected_dtstart_string);
        CHECK(TimePointToString(dtend_timepoint) == expected_dtend_string);
    }

    SECTION("Test event with invalid timepoints values")
    {
        auto summary = "invalid";
        auto DTstart = TimePointFromString("2020-11-05 15:00");
        auto DTend   = TimePointFromString("2020-11 16:00:00");

        auto event = Event(summary, DTstart, DTend, "test_uid");

        std::string expected_summary_value = "invalid";
        std::string expected_DTstart_value = "19700101T000000";
        std::string expected_DTend_value   = "19700101T000000";
        CHECK(event.getSummary() == expected_summary_value);
        CHECK(event.getDTStartString() == expected_DTstart_value);
        CHECK(event.getDTEndString() == expected_DTend_value);

        auto dtstart_timepoint              = event.getDTStartTimePoint();
        auto dtend_timepoint                = event.getDTEndTimePoint();
        std::string expected_dtstart_string = "1970-01-01 00:00:00";
        std::string expected_dtend_string   = "1970-01-01 00:00:00";
        CHECK(TimePointToString(dtstart_timepoint) == expected_dtstart_string);
        CHECK(TimePointToString(dtend_timepoint) == expected_dtend_string);
    }
}

TEST_CASE("Testing ParserICS class methods", "")
{
    SECTION("Test importEvents and exportEvents")
    {
        /// Event1
        auto summary = "test";
        auto DTstart = TimePointFromString("2020-11-05 15:00:00");
        auto DTend   = TimePointFromString("2020-11-05 16:00:00");
        auto UID     = createUID();
        auto event   = Event(summary, DTstart, DTend, UID);

        auto freq  = Frequency::weekly;
        auto rrule = RecurrenceRule(freq, 4, 2);

        auto action     = Action::audio;
        auto duration   = Duration(0, 0, 0, 5);
        auto alarm      = Alarm(duration, action);
        auto icalEvent1 = ICalEvent{event, alarm, rrule};

        /// Event2
        auto summary2 = "test2";
        auto DTstart2 = TimePointFromString("2019-09-05 10:00:00");
        auto DTend2   = TimePointFromString("2019-09-05 14:30:00");
        auto UID2     = createUID();
        auto event2   = Event(summary2, DTstart2, DTend2, UID2);

        ICalEvent icalEvent2;
        icalEvent2.event = event2;

        /// Event3
        auto summary3 = "test3";
        auto DTstart3 = TimePointFromString("2021-01-01 00:00:00");
        auto DTend3   = TimePointFromString("2021-01-01 10:45:00");
        auto UID3     = createUID();
        auto event3   = Event(summary3, DTstart3, DTend3, UID3);

        auto freq3  = Frequency::daily;
        auto rrule3 = RecurrenceRule(freq3, 7, 1);

        ICalEvent icalEvent3;
        icalEvent3.event = event3;
        icalEvent3.rrule = rrule3;

        /// check
        auto IcalEventsINPUT = std::vector<ICalEvent>();
        IcalEventsINPUT.push_back(icalEvent1);
        IcalEventsINPUT.push_back(icalEvent2);
        IcalEventsINPUT.push_back(icalEvent3);

        auto parser = ParserICS();
        parser.importEvents(IcalEventsINPUT);

        [[maybe_unused]] auto icsFormat = parser.getIcsData();

        std::string expected_uid[]     = {UID, UID2, UID3};
        std::string expected_summary[] = {summary, summary2, summary3};
        std::string expected_DTstart[] = {"20201105T150000", "20190905T100000", "20210101T000000"};
        std::string expected_DTend[]   = {"20201105T160000", "20190905T143000", "20210101T104500"};

        auto icalEventsOUTPUT = parser.exportEvents();

        CHECK(icalEventsOUTPUT[0].event.getUID() == expected_uid[0]);
        CHECK(icalEventsOUTPUT[0].event.getSummary() == expected_summary[0]);
        CHECK(icalEventsOUTPUT[0].event.getDTStartString() == expected_DTstart[0]);
        CHECK(icalEventsOUTPUT[0].event.getDTEndString() == expected_DTend[0]);

        CHECK(icalEventsOUTPUT[1].event.getUID() == expected_uid[1]);
        CHECK(icalEventsOUTPUT[1].event.getSummary() == expected_summary[1]);
        CHECK(icalEventsOUTPUT[1].event.getDTStartString() == expected_DTstart[1]);
        CHECK(icalEventsOUTPUT[1].event.getDTEndString() == expected_DTend[1]);

        CHECK(icalEventsOUTPUT[2].event.getUID() == expected_uid[2]);
        CHECK(icalEventsOUTPUT[2].event.getSummary() == expected_summary[2]);
        CHECK(icalEventsOUTPUT[2].event.getDTStartString() == expected_DTstart[2]);
        CHECK(icalEventsOUTPUT[2].event.getDTEndString() == expected_DTend[2]);

        CHECK(icalEventsOUTPUT[0].rrule.getFrequencyString() == "WEEKLY");
        CHECK(icalEventsOUTPUT[0].rrule.getCountString() == "4");
        CHECK(icalEventsOUTPUT[0].rrule.getIntervalString() == "2");

        CHECK(icalEventsOUTPUT[2].rrule.getFrequencyString() == "DAILY");
        CHECK(icalEventsOUTPUT[2].rrule.getCountString() == "7");
        CHECK(icalEventsOUTPUT[2].rrule.getIntervalString() == "1");

        CHECK(icalEventsOUTPUT[0].alarm.getActionString() == "AUDIO");
        CHECK(icalEventsOUTPUT[0].alarm.getTriggerString() == "-PT5M");
    }

    SECTION("Test importEvents and exportEvents invalid")
    {
        /// Event1
        auto event = Event();

        auto freq  = Frequency::weekly;
        auto rrule = RecurrenceRule(freq, 4, 2);

        auto action   = Action::audio;
        auto duration = Duration(0, 0, 0, 5);
        auto alarm    = Alarm(duration, action);

        auto icalEvent1 = ICalEvent{event, alarm, rrule};

        /// Event2
        auto summary2   = "test2";
        auto DTstart2   = TimePointFromString("2019-09-05 10:00:00");
        auto DTend2     = TimePointFromString("2019-09-05 14:30:00");
        auto UID2       = createUID();
        auto event2     = Event(summary2, DTstart2, DTend2, UID2);
        auto alarm2     = Alarm();
        auto rrule2     = RecurrenceRule();
        auto icalEvent2 = ICalEvent{event2, alarm2, rrule2};

        /// check
        auto IcalEventsINPUT = std::vector<ICalEvent>();
        IcalEventsINPUT.push_back(icalEvent1);
        IcalEventsINPUT.push_back(icalEvent2);

        auto parser = ParserICS();
        parser.importEvents(std::move(IcalEventsINPUT));

        [[maybe_unused]] auto icsFormat = parser.getIcsData();

        std::string expected_uid     = UID2;
        std::string expected_summary = summary2;
        std::string expected_DTstart = "20190905T100000";
        std::string expected_DTend   = "20190905T143000";

        auto icalEventsOUTPUT = parser.exportEvents();

        CHECK(icalEventsOUTPUT[0].event.getUID() == expected_uid);
        CHECK(icalEventsOUTPUT[0].event.getSummary() == expected_summary);
        CHECK(icalEventsOUTPUT[0].event.getDTStartString() == expected_DTstart);
        CHECK(icalEventsOUTPUT[0].event.getDTEndString() == expected_DTend);

        CHECK(icalEventsOUTPUT[0].rrule.getFrequencyString() == "");
        CHECK(icalEventsOUTPUT[0].rrule.getCountString() == "");
        CHECK(icalEventsOUTPUT[0].rrule.getIntervalString() == "");

        CHECK(icalEventsOUTPUT[0].alarm.getActionString() == "");
        CHECK(icalEventsOUTPUT[0].alarm.getTriggerString() == "-PT0M");
    }
}

A test/service-desktop-test/defs.py => test/service-desktop-test/defs.py +27 -0
@@ 0,0 1,27 @@
# Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

endpoint = {
    "deviceInfo": 1,
    "update": 2,
    "backup": 3,
    "restore": 4,
    "factory": 5,
    "contacts": 6,
    "messages": 7,
    "calllog": 8,
    "events": 10
}

method = {
    "get": 1,
    "post": 2,
    "put": 3,
    "del": 4
}

status = {
    "OK": 200,
    "BadRequest": 400,
    "InternalServerError": 500
}

M test/service-desktop-test/main.py => test/service-desktop-test/main.py +2 -1
@@ 9,6 9,7 @@ from tests.deviceinfo import *
from tests.factoryReset import *
from tests.backup import *
from tests.calllog import *
from tests.calendarEvents import *
from termcolor import colored

from harness.harness import Harness


@@ 23,7 24,7 @@ def main():
    serial = test_harness.get_connection()
    final_result = True
    failed_tests = []
    for test_instance in (DeviceInfoTest(serial), UpdateTest(serial), BackupTest(serial), MessageTest(serial),
    for test_instance in (DeviceInfoTest(serial), calendarEventsTest(serial), UpdateTest(serial), BackupTest(serial), MessageTest(serial),
                          MessageTemplateTest(serial), ContactTest(serial), CalllogTest(serial),
                          FactoryResetTest(serial)):
        test_name = type(test_instance).__name__

A test/service-desktop-test/tests/calendarEvents.py => test/service-desktop-test/tests/calendarEvents.py +101 -0
@@ 0,0 1,101 @@
# Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

from harness.interface.defs import endpoint, method, status

from test_api import *
from defs import *


class calendarEventsTest:
    def __init__(self, serial):
        self.serial = serial.get_serial()

    def run(self):
        # add event
        event = "BEGIN:VCALENDAR\nBEGIN:VEVENT\nSUMMARY:Testowy\nDTSTART:20200929T123611\nDTEND:20200929T124611\nBEGIN:VALARM\nTRIGGER:-P5M\nEND:VALARM\nEND:VEVENT\nEND:VCALENDAR\n"

        msg, result_msg = prepare_message(endpoint["events"], method["put"], status["OK"],
                                          {"data": event}, None)
        test = Test(self.serial, msg, result_msg)
        ret, result = test.execute()
        if ret == False:
            return False

        #get all limited events
        msg, result_msg = prepare_message(endpoint["events"], method["get"], status["OK"],
                                          {"offset": 0,
                                           "limit":  1},
                                          {"data":event,
                                           "count": "1"})

        test = Test(self.serial, msg, result_msg)
        ret, result = test.execute()
        print(result)
        uid_index = result["data"].index('UID:')
        summary_index = result["data"].index('SUMMARY:')
        UID = result["data"][uid_index+4:summary_index]
        print(UID)

        if not "SUMMARY:Testowy" in result["data"]:
            return False

        if not "DTSTART:20200929T123611" in result["data"]:
            return False

        if not "DTEND:20200929T124611" in result["data"]:
            return False

        if result["count"] != "1":
            return False

        eventUpdate = "BEGIN:VCALENDAR\nBEGIN:VEVENT\nUID:"+UID+"\n"+"SUMMARY:Update\nDTSTART:20200928T123611\nDTEND:20200928T124611\nEND:VEVENT\nEND:VCALENDAR\n"
        print(eventUpdate)
        # # update event
        msg, result_msg = prepare_message(endpoint["events"], method["post"], status["OK"],
                                          {"data":eventUpdate}, None)
        test = Test(self.serial, msg, result_msg)
        ret, result = test.execute()
        if ret == False:
            return False

        # check updated event
        msg, result_msg = prepare_message(endpoint["events"], method["get"], status["OK"],
                                          {"offset": 0,
                                           "limit":  1},
                                          {"data":eventUpdate,
                                           "count": "1"})
        test = Test(self.serial, msg, result_msg)
        ret, result = test.execute()
        print(result)

        if not "SUMMARY:Update" in result["data"]:
            return False

        if not "DTSTART:20200928T123611" in result["data"]:
            return False

        if not "DTEND:20200928T124611" in result["data"]:
            return False

        if result["count"] != "1":
            return False

        #remove event
        msg, result_msg = prepare_message(endpoint["events"], method["del"], status["OK"],
                                          {"UID": UID}, None)
        test = Test(self.serial, msg, result_msg)
        ret, result = test.execute()
        if ret == False:
            return False

        # check events after remove
        msg, result_msg = prepare_message(endpoint["events"], method["get"], status["OK"],None,
                                          {"data":"",
                                           "count": "1"})
        test = Test(self.serial, msg, result_msg)
        ret, result = test.execute()
        if ret == False:
            return False

        return True