M module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.cpp => module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.cpp +187 -51
@@ 34,29 34,74 @@ namespace parserFSM
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);
+ const inline auto never = Duration(0, 0, 0, 0xFFFF);
} // namespace duration
} // namespace ical
- namespace json::calendar::events
+ namespace json::calendar
{
- constexpr inline auto UID = "UID";
- constexpr inline auto data = "data";
+ constexpr inline auto events = "calendar_events";
+ namespace event
+ {
+ constexpr inline auto vevent = "VEVENT";
+
+ constexpr inline auto uid = "UID";
+ constexpr inline auto summary = "SUMMARY";
+ constexpr inline auto dtstart = "DTSTART";
+ constexpr inline auto dtend = "DTEND";
+
+ namespace recurrence_rule
+ {
+ constexpr inline auto rrule = "RRULE";
+
+ constexpr inline auto frequency = "FREQ";
+ constexpr inline auto count = "COUNT";
+ constexpr inline auto interval = "INTERVAL";
+ } // namespace recurrence_rule
+
+ namespace alarm
+ {
+ constexpr inline auto valarm = "VALARM";
+
+ constexpr inline auto trigger = "TRIGGER";
+ constexpr inline auto action = "ACTION";
+ } // namespace alarm
+
+ namespace provider
+ {
+ constexpr inline auto provider = "provider";
+
+ constexpr inline auto type = "type";
+ constexpr inline auto id = "id";
+ constexpr inline auto iCalUid = "iCalUid";
+ } // namespace provider
+ } // namespace event
constexpr inline auto offset = "offset";
constexpr inline auto limit = "limit";
constexpr inline auto count = "count";
constexpr inline auto total_count = "total_count";
- constexpr inline auto providers = "providers";
- namespace provider
- {
- constexpr inline auto type = "provider_type";
- constexpr inline auto id = "provider_id";
- constexpr inline auto iCalUid = "provider_iCalUid";
- } // namespace provider
- } // namespace json::calendar::events
+ } // namespace json::calendar
} // namespace parserFSM
using namespace parserFSM;
+auto CalendarEventsHelper::isICalEventValid(ICalEvent icalEvent) const -> bool
+{
+ if (!icalEvent.event.isValid) {
+ LOG_ERROR("Ical event invalid!");
+ return false;
+ }
+ if (!icalEvent.alarm.isValid) {
+ LOG_ERROR("Ical alarm invalid!");
+ return false;
+ }
+ if (!icalEvent.rrule.isValid) {
+ LOG_ERROR("Ical recurrence rule invalid!");
+ return false;
+ }
+ return true;
+}
+
auto CalendarEventsHelper::frequencyFromCustomRepeat(Repeat repeat) const -> Frequency
{
return Frequency(static_cast<uint32_t>(repeat));
@@ 101,43 146,44 @@ auto CalendarEventsHelper::alarmFrom(Reminder reminder) const -> Alarm
{
switch (reminder) {
case Reminder::never: {
- return Alarm();
+ auto beforeEvent = ical::duration::never;
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::five_min_before: {
auto beforeEvent = ical::duration::five_minutes_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::fifteen_min_before: {
auto beforeEvent = ical::duration::fifteen_minutes_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::thirty_min_before: {
auto beforeEvent = ical::duration::thirty_minutes_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::one_hour_before: {
auto beforeEvent = ical::duration::one_hour_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::two_hour_before: {
auto beforeEvent = ical::duration::two_hours_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::one_day_before: {
auto beforeEvent = ical::duration::one_day_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::two_days_before: {
auto beforeEvent = ical::duration::two_days_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::one_week_before: {
auto beforeEvent = ical::duration::one_week_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::event_time: {
auto beforeEvent = ical::duration::event_time;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
}
return Alarm();
@@ 152,11 198,43 @@ auto CalendarEventsHelper::icalEventFrom(const EventsRecord &record) const -> IC
return ICalEvent{event, alarm, rrule};
}
+auto CalendarEventsHelper::eventJsonObjectFrom(EventsRecord record) const -> json11::Json
+{
+ auto icalEvent = icalEventFrom(record);
+ if (!isICalEventValid(icalEvent)) {
+ LOG_ERROR("Bad event record formatting (Event UID: %s)", icalEvent.event.getUID().c_str());
+ }
+
+ auto rruleObj = json11::Json::object{
+ {json::calendar::event::recurrence_rule::frequency, icalEvent.rrule.getFrequencyString().c_str()},
+ {json::calendar::event::recurrence_rule::count, icalEvent.rrule.getCountString().c_str()},
+ {json::calendar::event::recurrence_rule::interval, icalEvent.rrule.getIntervalString().c_str()}};
+
+ auto alarmObj =
+ json11::Json::object{{json::calendar::event::alarm::trigger, icalEvent.alarm.getTriggerString().c_str()},
+ {json::calendar::event::alarm::action, icalEvent.alarm.getActionString().c_str()}};
+
+ auto providerObj =
+ json11::Json::object{{json::calendar::event::provider::type, record.provider_type.c_str()},
+ {json::calendar::event::provider::id, record.provider_id.c_str()},
+ {json::calendar::event::provider::iCalUid, record.provider_iCalUid.c_str()}};
+
+ auto eventObj = json11::Json::object{{json::calendar::event::dtend, icalEvent.event.getDTEndString().c_str()},
+ {json::calendar::event::dtstart, icalEvent.event.getDTStartString().c_str()},
+ {json::calendar::event::summary, icalEvent.event.getSummary().c_str()},
+ {json::calendar::event::uid, icalEvent.event.getUID().c_str()},
+ {json::calendar::event::recurrence_rule::rrule, rruleObj},
+ {json::calendar::event::alarm::valarm, alarmObj},
+ {json::calendar::event::provider::provider, providerObj}};
+
+ return eventObj;
+}
+
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();
+ const auto obj = context.getBody();
+ uint32_t offset = obj[json::calendar::offset].int_value();
+ uint32_t limit = obj[json::calendar::limit].int_value();
auto query = std::make_unique<db::query::events::GetAllLimited>(offset, limit);
auto listener = std::make_unique<db::EndpointListener>(
@@ 166,17 244,22 @@ auto CalendarEventsHelper::requestDataFromDB(Context &context) -> sys::ReturnCod
uint32_t totalCount = EventsResult->getCountResult();
auto parser = std::make_unique<ParserICS>();
std::vector<ICalEvent> icalEvents;
+
+ auto eventsArray = json11::Json::array();
+
for (auto rec : records) {
- icalEvents.push_back(icalEventFrom(rec));
+ auto eventObject = eventJsonObjectFrom(rec);
+
+ eventsArray.emplace_back(eventObject);
}
- parser->importEvents(icalEvents);
- auto jsonObj =
- json11::Json::object({{json::calendar::events::data, parser->getIcsData()},
- {json::calendar::events::count, std::to_string(records.size())},
- {json::calendar::events::total_count, std::to_string(totalCount)}});
+
+ auto jsonObj = json11::Json::object({{json::calendar::events, eventsArray},
+ {json::calendar::count, std::to_string(records.size())},
+ {json::calendar::total_count, std::to_string(totalCount)}});
context.setResponseBody(jsonObj);
MessageHandler::putToSendQueue(context.createSimpleResponse());
+
return true;
}
return false;
@@ 224,8 307,7 @@ auto CalendarEventsHelper::repeatFrom(RecurrenceRule &rrule) const -> Repeat
case Frequency::yearly: {
return Repeat::yearly;
}
- case Frequency::invalid: {
- LOG_ERROR("Frequency invalid");
+ case Frequency::never: {
return Repeat::never;
}
}
@@ 249,26 331,67 @@ auto CalendarEventsHelper::eventsRecordFrom(ICalEvent &icalEvent) const -> Event
return record;
}
+auto CalendarEventsHelper::ICalEventFromJson(json11::Json eventObj) const -> ICalEvent
+{
+ ICalEvent icalEvent;
+ icalEvent.event.setUID(eventObj[json::calendar::event::uid].string_value());
+ icalEvent.event.setSummary(eventObj[json::calendar::event::summary].string_value());
+ icalEvent.event.setDTStart(eventObj[json::calendar::event::dtstart].string_value());
+ icalEvent.event.setDTEnd(eventObj[json::calendar::event::dtend].string_value());
+
+ icalEvent.rrule.setFrequency(
+ eventObj[json::calendar::event::recurrence_rule::rrule][json::calendar::event::recurrence_rule::frequency]
+ .string_value());
+ icalEvent.rrule.setCount(
+ eventObj[json::calendar::event::recurrence_rule::rrule][json::calendar::event::recurrence_rule::count]
+ .string_value());
+ icalEvent.rrule.setInterval(
+ eventObj[json::calendar::event::recurrence_rule::rrule][json::calendar::event::recurrence_rule::interval]
+ .string_value());
+
+ icalEvent.alarm.setTrigger(
+ eventObj[json::calendar::event::alarm::valarm][json::calendar::event::alarm::trigger].string_value());
+ icalEvent.alarm.setAction(
+ eventObj[json::calendar::event::alarm::valarm][json::calendar::event::alarm::action].string_value());
+
+ auto record = eventsRecordFrom(icalEvent);
+
+ return icalEvent;
+}
+
auto CalendarEventsHelper::createDBEntry(Context &context) -> sys::ReturnCodes
{
- auto parser = std::make_shared<ParserICS>();
- parser->loadData(context.getBody()[json::calendar::events::data].string_value());
- auto icalEvents = parser->exportEvents();
+ auto eventsJsonObj = context.getBody();
+ auto eventsJsonArray = eventsJsonObj[json::calendar::events].array_items();
+ bool ret = true;
+ for (auto event : eventsJsonArray) {
- bool ret = true;
- for (auto event : icalEvents) {
- auto UID = event.event.getUID();
- auto record = eventsRecordFrom(event);
- auto query = std::make_unique<db::query::events::Add>(record);
+ auto icalEvent = ICalEventFromJson(event);
+ if (!isICalEventValid(icalEvent)) {
+ context.setResponseStatus(http::Code::BadRequest);
+ MessageHandler::putToSendQueue(context.createSimpleResponse());
+ return sys::ReturnCodes::Failure;
+ }
+
+ auto record = eventsRecordFrom(icalEvent);
+ if (record.UID.empty()) {
+ record.UID = createUID();
+ auto jsonObj = json11::Json::object({{json::calendar::event::uid, record.UID}});
+ context.setResponseBody(jsonObj);
+ }
+ else {
+ LOG_ERROR("UID should not be recieved in put event endpoint. Recieved UID: %s", record.UID.c_str());
+ context.setResponseStatus(http::Code::BadRequest);
+ MessageHandler::putToSendQueue(context.createSimpleResponse());
+ return sys::ReturnCodes::Failure;
+ }
+
+ auto query = std::make_unique<db::query::events::Add>(record);
auto listener = std::make_unique<db::EndpointListener>(
- [=](db::QueryResult *result, Context context) {
+ [&](db::QueryResult *result, Context context) {
if (auto EventResult = dynamic_cast<db::query::events::AddResult *>(result)) {
- auto jsonObj = json11::Json::object(
- {{json::calendar::events::data, parser->getIcsData()}, {json::calendar::events::UID, UID}});
-
- context.setResponseBody(jsonObj);
context.setResponseStatus(EventResult->getResult() ? http::Code::OK
: http::Code::InternalServerError);
@@ 295,14 418,19 @@ auto CalendarEventsHelper::createDBEntry(Context &context) -> sys::ReturnCodes
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();
+ auto eventsJsonObj = context.getBody();
bool ret = true;
- for (auto event : icalEvents) {
+ for (auto event : eventsJsonObj[json::calendar::events].array_items()) {
- auto record = eventsRecordFrom(event);
+ auto icalEvent = ICalEventFromJson(event);
+ if (!isICalEventValid(icalEvent) || icalEvent.event.getUID().empty()) {
+ context.setResponseStatus(http::Code::BadRequest);
+ MessageHandler::putToSendQueue(context.createSimpleResponse());
+ return sys::ReturnCodes::Failure;
+ }
+
+ auto record = eventsRecordFrom(icalEvent);
auto query = std::make_unique<db::query::events::EditICS>(record);
auto listener = std::make_unique<db::EndpointListener>(
[](db::QueryResult *result, Context context) {
@@ 331,7 459,15 @@ auto CalendarEventsHelper::updateDBEntry(Context &context) -> sys::ReturnCodes
auto CalendarEventsHelper::deleteDBEntry(Context &context) -> sys::ReturnCodes
{
- auto UID = context.getBody()[json::calendar::events::UID].string_value();
+ auto UID = context.getBody()[json::calendar::event::uid].string_value();
+ auto checkUID = Event();
+ checkUID.setUID(UID);
+ if (!checkUID.isValid) {
+ LOG_ERROR("Wrong UID format. Provided UID: %s", UID.c_str());
+ context.setResponseStatus(http::Code::BadRequest);
+ MessageHandler::putToSendQueue(context.createSimpleResponse());
+ return sys::ReturnCodes::Failure;
+ }
auto query = std::make_unique<db::query::events::RemoveICS>(UID);
auto listener = std::make_unique<db::EndpointListener>(
[=](db::QueryResult *result, Context context) {
M module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.hpp => module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.hpp +4 -0
@@ 29,6 29,10 @@ namespace parserFSM
[[nodiscard]] auto repeatFrom(RecurrenceRule &rrule) const -> Repeat;
[[nodiscard]] auto eventsRecordFrom(ICalEvent &icalEvent) const -> EventsRecord;
+ [[nodiscard]] auto eventJsonObjectFrom(EventsRecord record) const -> json11::Json;
+ [[nodiscard]] auto ICalEventFromJson(json11::Json eventObj) const -> ICalEvent;
+ [[nodiscard]] auto isICalEventValid(ICalEvent event) const -> bool;
+
public:
CalendarEventsHelper(sys::Service *_ownerServicePtr) : DBHelper(_ownerServicePtr)
{}
M module-utils/ical/ParserICS.cpp => module-utils/ical/ParserICS.cpp +211 -38
@@ 2,7 2,8 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "ParserICS.hpp"
-//#include <exception>
+#include <cmath>
+#include <module-utils/time/time_date_validation.hpp>
namespace ical
{
@@ 72,6 73,8 @@ namespace ical
} // namespace frequency
constexpr inline auto count = "COUNT=";
constexpr inline auto interval = "INTERVAL=";
+
+ constexpr inline auto max_interval = 2;
} // namespace rrule
namespace duration
@@ 79,22 82,44 @@ namespace ical
constexpr inline auto minutesInHour = 60;
constexpr inline auto minutesInDay = 24 * minutesInHour;
constexpr inline auto minutesInWeek = 7 * minutesInDay;
+
+ namespace minutes
+ {
+ constexpr inline auto never_happens_value = 0xFFFF;
+ }
} // namespace duration
+ constexpr inline auto date_char_length = yearDigitsNumb + monthDigitsNumb + dayDigitsNumb;
+ constexpr inline auto time_char_length = HourDigitsNumb + MinDigitsNumb + SecDigitsNumb;
+ constexpr inline auto dt_char_length = date_char_length + 1 + time_char_length;
} // namespace ical
Duration::Duration(uint32_t week, uint32_t day, uint32_t hour, uint32_t minute)
{
+ if (minute == ical::duration::minutes::never_happens_value) {
+ this->minute = ical::duration::minutes::never_happens_value;
+ return;
+ }
this->week = week;
this->day = day;
this->hour = hour;
this->minute = minute;
+
+ if (!utils::time::validateTime(hour, minute, false)) {
+ isValid = false;
+ }
}
Duration::Duration(const std::string &property)
{
+ if (property.empty()) {
+ LOG_DEBUG("Duration is empty. Event with no Alarm!");
+ this->minute = ical::duration::minutes::never_happens_value;
+ return;
+ }
uint32_t i = 0;
- if (property.empty() || (property[0] != 'P' && property[1] != 'P')) {
+ if ((property[0] != 'P' && property[1] != 'P')) {
LOG_ERROR("Duration constructor: Invalid format provided: %s", property.c_str());
+ isValid = false;
return;
}
while (property[i] != '\0' && i < property.length()) {
@@ 118,14 143,24 @@ Duration::Duration(const std::string &property)
case 'M':
this->minute = stoi(value);
break;
+ default:
+ LOG_ERROR("Wrong duration unit value format");
+ isValid = false;
+ break;
}
}
catch (std::exception &e) {
LOG_DEBUG("Duration conversion from string to int failed with exception:%s", e.what());
+ isValid = false;
}
}
++i;
}
+
+ if (!utils::time::validateTime(hour, minute, false)) {
+ LOG_ERROR("Duration time value is invalid");
+ isValid = false;
+ }
}
auto Duration::getDurationInMinutes() const -> uint32_t
@@ 140,20 175,23 @@ auto Duration::getDurationInMinutes() const -> uint32_t
auto Duration::getDurationString() const -> std::string
{
std::string durationString;
+ if (minute == ical::duration::minutes::never_happens_value) {
+ return durationString;
+ }
if (week) {
- durationString += std::to_string(week) + 'W';
+ durationString += utils::to_string(week) + 'W';
}
if (day) {
- durationString += std::to_string(day) + 'D';
+ durationString += utils::to_string(day) + 'D';
}
if (hour || minute) {
durationString += 'T';
}
if (hour) {
- durationString += std::to_string(hour) + 'H';
+ durationString += utils::to_string(hour) + 'H';
}
if (minute) {
- durationString += std::to_string(minute) + 'M';
+ durationString += utils::to_string(minute) + 'M';
}
if (week == 0 && day == 0 && hour == 0 && minute == 0) {
durationString += "T0M";
@@ 161,13 199,14 @@ auto Duration::getDurationString() const -> std::string
return durationString;
}
-Alarm::Alarm(Duration &beforeEvent, Action action)
+Alarm::Alarm(Duration &timeBeforeEvent, Action action)
{
- if (beforeEvent.getDurationString().empty()) {
- LOG_ERROR("Got empty duration value");
+ if (!timeBeforeEvent.isValid) {
+ LOG_ERROR("Duration format is invalid");
+ isValid = false;
return;
}
- this->trigger = beforeEvent;
+ this->trigger = timeBeforeEvent;
this->action = action;
}
@@ 182,14 221,25 @@ void Alarm::setAction(const std::string &action)
else if (action == ical::alarm::value::action::audio) {
this->action = Action::audio;
}
+ else if (action.empty()) {
+ LOG_DEBUG("Alarm with no action");
+ this->action = Action::none;
+ }
else {
- this->action = Action::invalid;
+ LOG_ERROR("Alarm action invalid");
+ isValid = false;
}
}
void Alarm::setTrigger(const std::string &duration)
{
- this->trigger = Duration(duration);
+ auto timeBeforeEvent = Duration(duration);
+ if (!timeBeforeEvent.isValid) {
+ LOG_ERROR("Duration format is invalid");
+ isValid = false;
+ return;
+ }
+ this->trigger = timeBeforeEvent;
}
auto Alarm::getTriggerValue() const -> Duration
@@ 204,7 254,11 @@ auto Alarm::getActionValue() const -> Action
auto Alarm::getTriggerString() const -> std::string
{
- return ical::alarm::value::before + trigger.getDurationString();
+ auto durationString = trigger.getDurationString();
+ if (durationString.empty()) {
+ return durationString;
+ }
+ return ical::alarm::value::before + durationString;
}
auto Alarm::getActionString() const -> std::string
@@ 220,8 274,8 @@ auto Alarm::getActionString() const -> std::string
case Action::procedure: {
return ical::alarm::value::action::procedure;
}
- case Action::invalid: {
- LOG_ERROR("Alarm with no action");
+ case Action::none: {
+ LOG_DEBUG("Alarm with no action");
return "";
}
}
@@ 230,11 284,15 @@ auto Alarm::getActionString() const -> std::string
RecurrenceRule::RecurrenceRule(Frequency freq, uint32_t count, uint32_t interval)
{
- if (count == 0 || interval == 0) {
- LOG_ERROR("Invalid count or interval value!");
+ if ((count == 0 || interval == 0) && freq != Frequency::never) {
+ LOG_ERROR("Invalid rrule format!");
+ isValid = false;
return;
}
-
+ if (interval > ical::rrule::max_interval) {
+ LOG_ERROR("Invalid interval value!");
+ isValid = false;
+ }
this->frequency = freq;
this->count = count;
this->interval = interval;
@@ 254,9 312,12 @@ void RecurrenceRule::setFrequency(const std::string &property)
else if (property == ical::rrule::frequency::yearly) {
this->frequency = Frequency::yearly;
}
+ else if (property.empty()) {
+ this->frequency = Frequency::never;
+ }
else {
LOG_ERROR("Invalid frequency!");
- this->frequency = Frequency::invalid;
+ isValid = false;
}
}
void RecurrenceRule::setCount(const std::string &property)
@@ 265,7 326,8 @@ void RecurrenceRule::setCount(const std::string &property)
this->count = stoi(property);
}
catch (...) {
- LOG_ERROR("Count value not conversionable to int!");
+ LOG_ERROR("Count value is not an integer!");
+ isValid = false;
}
}
@@ 276,6 338,7 @@ void RecurrenceRule::setInterval(const std::string &property)
}
catch (...) {
LOG_ERROR("Interval value not conversionable to int!");
+ isValid = false;
}
}
@@ 314,8 377,8 @@ auto RecurrenceRule::getFrequencyString() const -> std::string
frequencyStr = ical::rrule::frequency::yearly;
break;
}
- case Frequency::invalid: {
- LOG_ERROR("Frequency invalid");
+ case Frequency::never: {
+ LOG_DEBUG("Frequency never");
return "";
}
}
@@ 324,21 387,12 @@ auto RecurrenceRule::getFrequencyString() const -> std::string
auto RecurrenceRule::getCountString() const -> std::string
{
- if (this->frequency == Frequency::invalid) {
- LOG_ERROR("Frequency value is invalid!");
- return "";
- }
- return std::to_string(this->count);
+ return utils::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);
+ return utils::to_string(this->interval);
}
auto Event::getDateFromIcalFormat(const std::string &icalDateTime) const -> std::string
@@ 346,21 400,50 @@ auto Event::getDateFromIcalFormat(const std::string &icalDateTime) const -> std:
return icalDateTime.substr(0, icalDateTime.find_first_of('T'));
}
+auto Event::getYearFromIcalDate(const std::string &icalDate) const -> std::string
+{
+ return icalDate.substr(0, yearDigitsNumb);
+}
+
+auto Event::getMonthFromIcalDate(const std::string &icalDate) const -> std::string
+{
+ return icalDate.substr(yearDigitsNumb, monthDigitsNumb);
+}
+
+auto Event::getDayFromIcalDate(const std::string &icalDate) const -> std::string
+{
+ return icalDate.substr(yearDigitsNumb + monthDigitsNumb, dayDigitsNumb);
+}
+
auto Event::getTimeFromIcalFormat(const std::string &icalDateTime) const -> std::string
{
return icalDateTime.substr(icalDateTime.find_first_of('T') + 1);
}
+auto Event::getHourFromIcalTime(const std::string &icalTime) const -> std::string
+{
+ return icalTime.substr(0, HourDigitsNumb);
+}
+
+auto Event::getMinutesFromIcalTime(const std::string &icalTime) const -> std::string
+{
+ return icalTime.substr(HourDigitsNumb, MinDigitsNumb);
+}
+
+auto Event::getSecondsFromIcalTime(const std::string &icalTime) const -> std::string
+{
+ return icalTime.substr(HourDigitsNumb + MinDigitsNumb, SecDigitsNumb);
+}
+
auto Event::dateStringFrom(const std::string &icalDate) const -> std::string
{
- return icalDate.substr(0, yearDigitsNumb) + "-" + icalDate.substr(yearDigitsNumb, monthDigitsNumb) + "-" +
- icalDate.substr(yearDigitsNumb + monthDigitsNumb, dayDigitsNumb);
+ return getYearFromIcalDate(icalDate) + "-" + getMonthFromIcalDate(icalDate) + "-" + getDayFromIcalDate(icalDate);
}
auto Event::timeStringFrom(const std::string &icalTime) const -> std::string
{
- return icalTime.substr(0, HourDigitsNumb) + ":" + icalTime.substr(HourDigitsNumb, MinDigitsNumb) + ":" +
- icalTime.substr(HourDigitsNumb + MinDigitsNumb, SecDigitsNumb);
+ return getHourFromIcalTime(icalTime) + ":" + getMinutesFromIcalTime(icalTime) + ":" +
+ getSecondsFromIcalTime(icalTime);
}
auto Event::TimePointFromIcalDate(const std::string &icalDateTime) const -> TimePoint
@@ 387,28 470,118 @@ auto Event::TimePointToIcalDate(const TimePoint &tp) const -> std::string
return IcalDate;
}
+auto Event::isDate(const std::string &date) -> bool
+{
+ return utils::time::validateDate(getDayFromIcalDate(date), getMonthFromIcalDate(date), getYearFromIcalDate(date));
+}
+
+auto Event::isTime(const std::string &time) -> bool
+{
+ [[maybe_unused]] uint32_t seconds;
+ try {
+ seconds = stoi(getSecondsFromIcalTime(time));
+ }
+ catch (...) {
+ LOG_ERROR("Seconds value is not an integer!");
+ return false;
+ }
+ return utils::time::validateTime(getHourFromIcalTime(time), getMinutesFromIcalTime(time), false);
+}
+
+auto Event::validateDT(const std::string &dt) -> bool
+{
+ uint32_t dateTimeSize;
+ if (dt.find_first_of('Z') == ical::date_char_length + ical::time_char_length) {
+ dateTimeSize = ical::dt_char_length + 1;
+ }
+ else {
+ dateTimeSize = ical::dt_char_length;
+ }
+ if (dt.size() != dateTimeSize) {
+ LOG_ERROR("Date time length is invalid");
+ return false;
+ }
+
+ auto separatorIndex = dt.find_first_of('T');
+ LOG_DEBUG("Separator index = %d", (int)separatorIndex);
+ if (separatorIndex != (yearDigitsNumb + monthDigitsNumb + dayDigitsNumb)) {
+ LOG_ERROR("Date time separator is invalid");
+ return false;
+ }
+
+ auto date = getDateFromIcalFormat(dt);
+ auto time = getTimeFromIcalFormat(dt);
+
+ if (!isDate(date)) {
+ LOG_ERROR("Date is invalid");
+ return false;
+ }
+
+ if (!isTime(time)) {
+ LOG_ERROR("Time is invalid");
+ return false;
+ }
+
+ return true;
+}
+
+auto Event::validateUID(const std::string &UID) -> bool
+{
+ auto DTimestamp = UID.substr(0, UID.find_first_of('-'));
+ auto id = UID.substr(UID.find_first_of('-') + 1);
+
+ try {
+ stoi(id);
+ }
+ catch (...) {
+ LOG_ERROR("UID value is not an integer!");
+ return false;
+ }
+
+ return validateDT(DTimestamp);
+}
+
Event::Event(const std::string &summary, const TimePoint from, TimePoint till, const std::string &uid)
{
+ if (summary.empty()) {
+ isValid = false;
+ }
+ if (!validateUID(uid) && !uid.empty()) {
+ isValid = false;
+ }
+ this->uid = uid;
this->summary = summary;
this->dtstart = from;
this->dtend = till;
- this->uid = uid;
}
void Event::setUID(const std::string &property)
{
+ if (!validateUID(property) && !property.empty()) {
+ LOG_ERROR("UID invalid format");
+ isValid = false;
+ }
this->uid = property;
}
void Event::setSummary(const std::string &property)
{
+ if (property.empty()) {
+ isValid = false;
+ }
this->summary = property;
}
void Event::setDTStart(const std::string &property)
{
+ if (!validateDT(property)) {
+ isValid = false;
+ }
this->dtstart = TimePointFromIcalDate(property);
}
void Event::setDTEnd(const std::string &property)
{
+ if (!validateDT(property)) {
+ isValid = false;
+ }
this->dtend = TimePointFromIcalDate(property);
}
M module-utils/ical/ParserICS.hpp => module-utils/ical/ParserICS.hpp +24 -7
@@ 17,6 17,7 @@ class Duration
uint32_t week = 0, day = 0, hour = 0, minute = 0;
public:
+ bool isValid = true;
Duration(uint32_t week = 0, uint32_t day = 0, uint32_t hour = 0, uint32_t minute = 0);
explicit Duration(const std::string &property);
@@ 26,7 27,7 @@ class Duration
enum class Action
{
- invalid,
+ none,
audio,
display,
procedure
@@ 35,11 36,12 @@ enum class Action
class Alarm
{
Duration trigger;
- Action action;
+ Action action = Action::none;
public:
- Alarm() = default;
- explicit Alarm(Duration &beforeEvent, Action action = Action::invalid);
+ bool isValid = true;
+ Alarm() = default;
+ explicit Alarm(Duration &beforeEvent, Action action = Action::none);
void setAction(const std::string &action);
void setTrigger(const std::string &duration);
@@ 53,7 55,7 @@ class Alarm
enum class Frequency
{
- invalid,
+ never,
daily,
weekly,
monthly,
@@ 63,11 65,12 @@ enum class Frequency
class RecurrenceRule
{
- Frequency frequency = Frequency::invalid;
+ Frequency frequency = Frequency::never;
uint32_t count = 0;
uint32_t interval = 0;
public:
+ bool isValid = true;
RecurrenceRule() = default;
RecurrenceRule(Frequency freq, uint32_t count, uint32_t interval);
@@ 91,8 94,21 @@ class Event
TimePoint dtstart;
TimePoint dtend;
+ auto isDate(const std::string &dt) -> bool;
+ auto isTime(const std::string &dt) -> bool;
+ auto validateDT(const std::string &dt) -> bool;
+ auto validateUID(const std::string &uid) -> bool;
+
[[nodiscard]] auto getDateFromIcalFormat(const std::string &icalDateTime) const -> std::string;
+ [[nodiscard]] auto getYearFromIcalDate(const std::string &icalDate) const -> std::string;
+ [[nodiscard]] auto getMonthFromIcalDate(const std::string &icalDate) const -> std::string;
+ [[nodiscard]] auto getDayFromIcalDate(const std::string &icalDate) const -> std::string;
+
[[nodiscard]] auto getTimeFromIcalFormat(const std::string &icalDateTime) const -> std::string;
+ [[nodiscard]] auto getHourFromIcalTime(const std::string &icalTime) const -> std::string;
+ [[nodiscard]] auto getMinutesFromIcalTime(const std::string &icalTime) const -> std::string;
+ [[nodiscard]] auto getSecondsFromIcalTime(const std::string &icalTime) const -> std::string;
+
[[nodiscard]] auto dateStringFrom(const std::string &icalDate) const -> std::string;
[[nodiscard]] auto timeStringFrom(const std::string &icalTime) const -> std::string;
@@ 100,7 116,8 @@ class Event
[[nodiscard]] auto TimePointToIcalDate(const TimePoint &tp) const -> std::string;
public:
- Event() = default;
+ bool isValid = true;
+ Event() = default;
Event(const std::string &summary, TimePoint from, TimePoint till, const std::string &uid);
void setUID(const std::string &property);
M test/pytest/service-desktop/test_calendar.py => test/pytest/service-desktop/test_calendar.py +243 -31
@@ 2,41 2,103 @@
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
import pytest
from harness.interface.defs import status
+import copy
-
-@pytest.mark.skip("ICS format bug")
@pytest.mark.service_desktop_test
def test_calendar(harness):
- # add event
- event = "BEGIN:VCALENDAR\nBEGIN:VEVENT\nSUMMARY:Testowy\nDTSTART:20200929T123611\nDTEND:20200929T124611\nBEGIN" \
- ":VALARM\nTRIGGER:-P5M\nEND:VALARM\nEND:VEVENT\nEND:VCALENDAR\n "
+ # add events
+ add_body = {
+ "calendar_events":[
+ {
+ "UID":"",
+ "SUMMARY":"Testowy",
+ "DTSTART":"20200129T123600",
+ "DTEND":"20200129T124600",
+ "RRULE":{
+ "COUNT":"0",
+ "FREQ":"",
+ "INTERVAL":"0"
+ },
+ "VALARM":{
+ "ACTION":"",
+ "TRIGGER":""
+ },
+ "provider":{
+ "iCalUid":"",
+ "id":"",
+ "type":""
+ }
+ }
+ ]
+ }
+
+ ret = harness.endpoint_request("events", "put", add_body)
+ assert ret["status"] == status["OK"]
- body = {"data": event}
- ret = harness.endpoint_request("events", "put", body)
+ #add second event
+ second_event = copy.deepcopy(add_body)
+ second_event["calendar_events"][0]["SUMMARY"] = "Testowy2"
+ ret = harness.endpoint_request("events", "put", second_event)
assert ret["status"] == status["OK"]
# get all limited events
- body = {"offset": 0, "limit": 1}
- ret = harness.endpoint_request("events", "get", body)
+ get_body = {"offset": 0, "limit": 1}
+ ret = harness.endpoint_request("events", "get", get_body)
+
assert ret["status"] == status["OK"]
- assert ret["body"]["data"] == event
- assert ret["body"]["count"] == 1
+ assert ret["body"]["count"] == '1'
+ assert ret["body"]["total_count"] == '2'
+
- uid_index = ret["body"]["data"].index('UID:')
- summary_index = ret["body"]["data"].index('SUMMARY:')
- UID = ret["body"]["data"][uid_index + 4:summary_index]
+ for event in ret["body"]["calendar_events"]:
+ assert event["SUMMARY"] == add_body["calendar_events"][0]["SUMMARY"]
+ assert event["RRULE"]["COUNT"] == add_body["calendar_events"][0]["RRULE"]["COUNT"]
+ assert event["RRULE"]["FREQ"] == add_body["calendar_events"][0]["RRULE"]["FREQ"]
+ assert event["RRULE"]["INTERVAL"] == add_body["calendar_events"][0]["RRULE"]["INTERVAL"]
+ assert event["VALARM"]["ACTION"] == add_body["calendar_events"][0]["VALARM"]["ACTION"]
+ assert event["VALARM"]["TRIGGER"] == add_body["calendar_events"][0]["VALARM"]["TRIGGER"]
+ assert event["provider"]["iCalUid"] == add_body["calendar_events"][0]["provider"]["iCalUid"]
+ assert event["provider"]["id"] == add_body["calendar_events"][0]["provider"]["id"]
+ assert event["provider"]["type"] == add_body["calendar_events"][0]["provider"]["type"]
- assert "SUMMARY:Testowy" in ret["body"]["data"]
- assert "DTSTART:20200929T123611" in ret["body"]["data"]
- assert "DTEND:20200929T124611" in ret["body"]["data"]
+ # remove event
+ del_body = {"UID": ret["body"]["calendar_events"][0]["UID"]}
+ ret = harness.endpoint_request("events", "del", del_body)
+ assert ret["status"] == status["OK"]
+
+ # check events after remove
+ body = {"offset": 0, "limit": 1}
+ ret = harness.endpoint_request("events", "get", body)
+ assert ret["status"] == status["OK"]
assert ret["body"]["count"] == "1"
# update event
- event_update = "BEGIN:VCALENDAR\nBEGIN:VEVENT\nUID:" + UID + "\n" + "SUMMARY:Update\nDTSTART:20200928T123611" \
- "\nDTEND:20200928T124611\nEND:VEVENT\nEND" \
- ":VCALENDAR\n "
- body = {"data": event_update}
- ret = harness.endpoint_request("events", "post", body)
+ update_body = {
+ "calendar_events":[
+ {
+ "UID":ret["body"]["calendar_events"][0]["UID"],
+ "SUMMARY":"TestowyUpdate",
+ "DTSTART":"20200929T123600",
+ "DTEND":"20200929T124600",
+ "RRULE":{
+ "COUNT":"0",
+ "FREQ":"",
+ "INTERVAL":"0"
+ },
+ "VALARM":{
+ "ACTION":"",
+ "TRIGGER":"-PT5M"
+ },
+ "provider":{
+ "iCalUid":"",
+ "id":"",
+ "type":""
+ }
+ }
+ ]
+ }
+
+ ret = harness.endpoint_request("events", "post", update_body)
assert ret["status"] == status["OK"]
# get updated event
@@ 44,20 106,170 @@ def test_calendar(harness):
ret = harness.endpoint_request("events", "get", body)
assert ret["status"] == status["OK"]
- assert ret["body"]["data"] == event_update
- assert ret["body"]["count"] == 1
+ for event in ret["body"]["calendar_events"]:
+ assert event["SUMMARY"] == update_body["calendar_events"][0]["SUMMARY"]
+ assert event["RRULE"]["COUNT"] == update_body["calendar_events"][0]["RRULE"]["COUNT"]
+ assert event["RRULE"]["FREQ"] == update_body["calendar_events"][0]["RRULE"]["FREQ"]
+ assert event["RRULE"]["INTERVAL"] == update_body["calendar_events"][0]["RRULE"]["INTERVAL"]
+ assert event["VALARM"]["ACTION"] == update_body["calendar_events"][0]["VALARM"]["ACTION"]
+ assert event["VALARM"]["TRIGGER"] == update_body["calendar_events"][0]["VALARM"]["TRIGGER"]
+ assert event["provider"]["iCalUid"] == update_body["calendar_events"][0]["provider"]["iCalUid"]
+ assert event["provider"]["id"] == update_body["calendar_events"][0]["provider"]["id"]
+ assert event["provider"]["type"] == update_body["calendar_events"][0]["provider"]["type"]
- assert "SUMMARY:Update" in ret["body"]["data"]
- assert "DTSTART:20200928T123611" in ret["body"]["data"]
- assert "DTEND:20200928T124611" in ret["body"]["data"]
assert ret["body"]["count"] == "1"
- # remove event
- body = {"UID": UID}
- ret = harness.endpoint_request("events", "del", body)
+
+
+ # remove second event
+ body = {"offset": 0, "limit": 1}
+ ret = harness.endpoint_request("events", "get", body)
+ assert ret["status"] == status["OK"]
+
+ del_body = {"UID": ret["body"]["calendar_events"][0]["UID"]}
+ ret = harness.endpoint_request("events", "del", del_body)
assert ret["status"] == status["OK"]
# check events after remove
- body = {"data": "", "count": "1"}
+ body = {"offset": 0, "limit": 1}
ret = harness.endpoint_request("events", "get", body)
assert ret["status"] == status["OK"]
+ assert ret["body"]["count"] == "0"
+
+
+
+ # INVALID FORMAT
+ add_body_invalid = copy.deepcopy(add_body)
+ update_body_invalid = copy.deepcopy(update_body)
+
+ # add events invalid date and time formats
+ # wrong month
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201329T123600"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong day
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201132T123600"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong year
+ add_body_invalid["calendar_events"][0]["DTEND"] = "50201120T123600"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong hour
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201120T253600"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong minute
+ add_body_invalid["calendar_events"][0]["DTEND"] = "20201122T126300"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong second
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201122T1200OO"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ #wrong separator
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201122R120059"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong length
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201122T12550000"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ add_body_invalid["calendar_events"][0]["DTEND"] = "201122T125500"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # Summary invalid
+ add_body_invalid["calendar_events"][0]["SUMMARY"] = ""
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+
+ # Alarm invalid
+ #trigger
+ add_body_invalid["calendar_events"][0]["VALARM"]["TRIGGER"] = "-PTM"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ add_body_invalid["calendar_events"][0]["VALARM"]["TRIGGER"] = "-PT4T"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ add_body_invalid["calendar_events"][0]["VALARM"]["TRIGGER"] = "-PT63M"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # Recurrency Rule invalid
+ # Freq
+ add_body_invalid["calendar_events"][0]["RRULE"]["FREQ"] = "DAIL"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # Count
+ add_body_invalid["calendar_events"][0]["RRULE"]["COUNT"] = ""
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # interval
+ add_body_invalid["calendar_events"][0]["RRULE"]["COUNT"] = ""
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # UID invalid (UID should be empty in add event request)
+ add_body_invalid["calendar_events"][0]["UID"] = "20201122T125500-63"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+
+ # UPDATE INVALID
+ # UID invalid
+ update_body_invalid["calendar_events"][0]["UID"] = ""
+ ret = harness.endpoint_request("events", "post", update_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ update_body_invalid = copy.deepcopy(update_body_invalid)
+
+ update_body_invalid["calendar_events"][0]["UID"] = "43526gsdgsa322"
+ ret = harness.endpoint_request("events", "post", update_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ update_body_invalid = copy.deepcopy(update_body_invalid)
+
+ # REMOVE INVALID
+ # It is a valid UID, but not exist in any events in DB
+ # (handle it in db)
+ # del_body = {"UID": "20151122T125500-63"}
+ #
+ # ret = harness.endpoint_request("events", "post", del_body)
+ # assert ret["status"] == status["InternalServerError"]
+
+ # UID invalid
+ del_body = {"UID": "20201122T125500-dadsadsada"}
+ ret = harness.endpoint_request("events", "del", del_body)
+ assert ret["status"] == status["BadRequest"]
+
+ del_body = {"UID": "201122T125500-63"}
+ ret = harness.endpoint_request("events", "del", del_body)
+ assert ret["status"] == status["BadRequest"]
+
M test/pytest/service-desktop/test_templates.py => test/pytest/service-desktop/test_templates.py +1 -1
@@ 59,4 59,4 @@ def test_messages(harness):
body = {"template": True, "count": True}
ret = harness.endpoint_request("messages", "get", body)
assert ret["status"] == status["OK"]
- assert ret["body"]["count"] == count>
\ No newline at end of file
+ assert ret["body"]["count"] == count