~aleteoryx/muditaos

5b0f2aa44a61bd2124fea48bdf1f9e2fc47c4eb6 — Mateusz Grzegorzek 4 years ago 7717854
[EGD-6262] Refactor Quotes

- Connect QuotesMainWindow to work with agent,
- Use DatabaseModel in Quotes and Categories models,
- Connect add/edit/remove quotes with agent
43 files changed, 792 insertions(+), 803 deletions(-)

M generate_purephone_image.sh
D image/data/applications/settings/quotes.json
M module-apps/application-music-player/widgets/SongItem.cpp
M module-apps/application-settings-new/ApplicationSettings.cpp
M module-apps/application-settings-new/ApplicationSettings.hpp
M module-apps/application-settings-new/CMakeLists.txt
M module-apps/application-settings-new/data/QuoteSwitchData.hpp
M module-apps/application-settings-new/models/CategoriesModel.cpp
M module-apps/application-settings-new/models/CategoriesModel.hpp
M module-apps/application-settings-new/models/QuotesModel.cpp
M module-apps/application-settings-new/models/QuotesModel.hpp
D module-apps/application-settings-new/models/QuotesRepository.cpp
D module-apps/application-settings-new/models/QuotesRepository.hpp
M module-apps/application-settings-new/widgets/CategoryWidget.cpp
M module-apps/application-settings-new/widgets/CategoryWidget.hpp
M module-apps/application-settings-new/widgets/QuoteWidget.cpp
M module-apps/application-settings-new/widgets/QuoteWidget.hpp
M module-apps/application-settings-new/windows/EditQuotesWindow.cpp
M module-apps/application-settings-new/windows/QuoteCategoriesWindow.cpp
M module-apps/application-settings-new/windows/QuoteCategoriesWindow.hpp
M module-apps/application-settings-new/windows/QuotesAddWindow.cpp
M module-apps/application-settings-new/windows/QuotesAddWindow.hpp
M module-apps/application-settings-new/windows/QuotesMainWindow.cpp
M module-apps/application-settings-new/windows/QuotesMainWindow.hpp
M module-apps/application-settings-new/windows/QuotesOptionsWindow.cpp
M module-apps/application-settings-new/windows/QuotesOptionsWindow.hpp
M module-apps/application-settings-new/windows/WallpaperWindow.cpp
M module-db/Interface/BaseInterface.hpp
M module-gui/gui/widgets/ImageBox.cpp
M module-gui/gui/widgets/ImageBox.hpp
M module-services/service-db/ServiceDB.cpp
M module-services/service-db/ServiceDB.hpp
M module-services/service-db/agents/file_indexer/FileIndexerAgent.cpp
M module-services/service-db/agents/file_indexer/FileIndexerAgent.hpp
M module-services/service-db/agents/quotes/QuotesAgent.cpp
M module-services/service-db/agents/quotes/QuotesAgent.hpp
M module-services/service-db/agents/quotes/QuotesQueries.hpp
M module-services/service-db/service-db/FileIndexerMessages.hpp
M module-services/service-db/service-db/PagedData.hpp
M module-services/service-db/service-db/QuotesMessages.hpp
M module-services/service-db/test/test-service-db-quotes.cpp
M module-services/service-db/test/test-service-db-quotes.hpp
M module-services/service-db/test/test-settings/Database.cpp
M generate_purephone_image.sh => generate_purephone_image.sh +0 -1
@@ 98,7 98,6 @@ done
mcopy -s -i "$PART1" .boot.json ::
mcopy -s -i "$PART1" .boot.json.crc32 ::
mmd -i "$PART1" ::/current/sys
mcopy -s -i "$PART1" data ::/current/sys
mmd -i "$PART1" ::/updates

#Littlefs generate image

D image/data/applications/settings/quotes.json => image/data/applications/settings/quotes.json +0 -27
@@ 1,27 0,0 @@
[
  {
    "lang":"english",
    "author": "Buddha",
    "quote": "Do not dwell in the past, do not dream of the future, concentrate the mind on the present moment."
  },
  {
    "lang":"english",
    "author": "Tara Brach",
    "quote": "The only way to live is by accepting each minute as an unrepeatable miracle."
  },
  {
    "lang":"english",
    "author": "Richar Bach",
    "quote": "The simplest things are often the truest"
  },
  {
    "lang":"english",
    "author": "Eckhart Tolle",
    "quote": "Always say yes to the present moment. Say yes to life."
  },
  {
    "lang":"english",
    "author": "Naomi Judd",
    "quote": "Slow down, simplify and be kind"
  }
]

M module-apps/application-music-player/widgets/SongItem.cpp => module-apps/application-music-player/widgets/SongItem.cpp +2 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "module-apps/application-music-player/widgets/SongItem.hpp"


@@ 52,6 52,7 @@ namespace gui
        playedSong = new ImageBox(secondHBox, 0, 0, 0, 0, new Image("messages_error_W_M"));
        playedSong->setMinimumSize(songItem::duration_w, songItem::text_h);
        playedSong->setVisible(false);
        playedSong->setEdges(RectangleEdge::None);

        authorText = new TextFixedSize(secondHBox, 0, 0, 0, 0);
        authorText->setMinimumHeight(songItem::text_h);

M module-apps/application-settings-new/ApplicationSettings.cpp => module-apps/application-settings-new/ApplicationSettings.cpp +12 -14
@@ 36,7 36,6 @@
#include "windows/DateAndTimeMainWindow.hpp"
#include "windows/ChangeTimeZone.hpp"
#include "windows/ChangeDateAndTimeWindow.hpp"
#include <application-settings-new/models/QuotesRepository.hpp>
#include "windows/PhoneModesWindow.hpp"
#include "windows/DoNotDisturbWindow.hpp"
#include "windows/OfflineWindow.hpp"


@@ 82,20 81,12 @@ namespace app
    namespace settings
    {
        constexpr inline auto operators_on = "operators_on";
        const std::string quotesPath =
            purefs::createPath(purefs::dir::getUserDiskPath(), "data/applications/settings/quotes.json");

        auto getQuotesModel(Application *app) -> std::unique_ptr<QuotesModel>
        {
            auto repo = std::make_unique<QuotesJsonRepository>(settings::quotesPath);
            return std::make_unique<QuotesModel>(app, std::move(repo));
        }
    } // namespace settings

    ApplicationSettingsNew::ApplicationSettingsNew(std::string name,
                                                   std::string parent,
                                                   StartInBackground startInBackground)
        : Application(std::move(name), std::move(parent), startInBackground)
        : Application(std::move(name), std::move(parent), startInBackground), AsyncCallbackReceiver{this}
    {
        if ((Store::GSM::SIM::SIM1 == selectedSim || Store::GSM::SIM::SIM2 == selectedSim) &&
            Store::GSM::get()->sim == selectedSim) {


@@ 131,6 122,13 @@ namespace app
            }
        }

        // handle database response
        if (resp != nullptr) {
            if (auto command = callbackStorage->getCallback(resp); command->execute()) {
                refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
            }
        }

        return sys::MessageNone{};
    }



@@ 456,19 454,19 @@ namespace app
            return std::make_unique<gui::DialogRetry>(app, name);
        });
        windowsFactory.attach(gui::window::name::quotes, [](Application *app, const std::string &name) {
            return std::make_unique<gui::QuotesMainWindow>(app, std::move(settings::getQuotesModel(app)));
            return std::make_unique<gui::QuotesMainWindow>(app);
        });
        windowsFactory.attach(gui::window::name::new_quote, [](Application *app, const std::string &name) {
            return std::make_unique<gui::QuoteAddEditWindow>(app, std::move(settings::getQuotesModel(app)));
            return std::make_unique<gui::QuoteAddEditWindow>(app);
        });
        windowsFactory.attach(gui::window::name::options_quote, [](Application *app, const std::string &name) {
            return std::make_unique<gui::QuotesOptionsWindow>(app, std::move(settings::getQuotesModel(app)));
            return std::make_unique<gui::QuotesOptionsWindow>(app);
        });
        windowsFactory.attach(gui::window::name::edit_quotes, [](Application *app, const std::string &name) {
            return std::make_unique<gui::EditQuotesWindow>(app);
        });
        windowsFactory.attach(gui::window::name::quote_categories, [](Application *app, const std::string &name) {
            return std::make_unique<gui::QuoteCategoriesWindow>(app, std::make_unique<Quotes::CategoriesModel>(app));
            return std::make_unique<gui::QuoteCategoriesWindow>(app);
        });

        attachPopups({gui::popup::ID::Volume});

M module-apps/application-settings-new/ApplicationSettings.hpp => module-apps/application-settings-new/ApplicationSettings.hpp +2 -1
@@ 167,7 167,8 @@ namespace app
                                   public settingsInterface::SecuritySettings,
                                   public settingsInterface::DndSettings,
                                   public settingsInterface::OfflineSettings,
                                   public settingsInterface::ConnectionSettings
                                   public settingsInterface::ConnectionSettings,
                                   public AsyncCallbackReceiver
    {
      public:
        ApplicationSettingsNew(std::string name                    = name_settings_new,

M module-apps/application-settings-new/CMakeLists.txt => module-apps/application-settings-new/CMakeLists.txt +0 -1
@@ 21,7 21,6 @@ target_sources( ${PROJECT_NAME}
        models/DateAndTimeModel.cpp
        models/FromTimeToTimeModel.cpp
        models/QuotesModel.cpp
        models/QuotesRepository.cpp
        models/CategoriesModel.cpp
        widgets/ChangePasscodeLockHandler.cpp
        widgets/QuoteWidget.cpp

M module-apps/application-settings-new/data/QuoteSwitchData.hpp => module-apps/application-settings-new/data/QuoteSwitchData.hpp +3 -3
@@ 9,7 9,7 @@
#include <json/json11.hpp>
#include <utility>

namespace app
namespace Quotes
{
    class QuotesModel;
};


@@ 26,7 26,7 @@ namespace gui
    class QuoteSwitchData : public gui::SwitchData
    {
      public:
        QuoteSwitchData(QuoteAction action, app::QuoteRecord quote = {}) : action(action), quote(std::move(quote))
        QuoteSwitchData(QuoteAction action, Quotes::QuoteRecord quote = {}) : action(action), quote(std::move(quote))
        {}

        [[nodiscard]] auto getQuote() const


@@ 40,6 40,6 @@ namespace gui

      private:
        QuoteAction action;
        app::QuoteRecord quote;
        Quotes::QuoteRecord quote;
    };
} // namespace gui

M module-apps/application-settings-new/models/CategoriesModel.cpp => module-apps/application-settings-new/models/CategoriesModel.cpp +51 -71
@@ 2,109 2,89 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "application-settings-new/windows/QuoteCategoriesWindow.hpp"
#include "QuotesRepository.hpp"
#include "CategoriesModel.hpp"
#include <service-db/QuotesMessages.hpp>

namespace style::quotes::list
{
    constexpr auto item_height = 63;
    constexpr auto max_quotes  = 100;
} // namespace style::quotes::list

namespace Quotes
{
    CategoriesModel::CategoriesModel(app::Application *app) : application(app)
    CategoriesModel::CategoriesModel(app::Application *application)
        : DatabaseModel(application), app::AsyncCallbackReceiver{application}, app(application)
    {}

    auto CategoriesModel::requestRecordsCount() -> unsigned int
    {
        return internalData.size();
        return recordsCount;
    }

    auto CategoriesModel::getMinimalItemHeight() const -> unsigned int
    bool CategoriesModel::updateRecords(std::vector<CategoryRecord> records)
    {
        return style::quotes::list::item_height;
        if (DatabaseModel::updateRecords(std::move(records))) {
            list->onProviderDataUpdate();
            return true;
        }
        return false;
    }

    void CategoriesModel::requestRecords(const uint32_t offset, const uint32_t limit)
    auto CategoriesModel::getMinimalItemHeight() const -> unsigned int
    {
        setupModel(offset, limit);
        list->onProviderDataUpdate();
        return style::quotes::list::item_height;
    }

    auto CategoriesModel::getItem(gui::Order order) -> gui::ListItem *
    void CategoriesModel::requestRecords(const uint32_t offset, const uint32_t limit)
    {
        return getRecord(order);
        LOG_DEBUG(
            "Request categories: offset = %u, limit = %u", static_cast<unsigned>(offset), static_cast<unsigned>(limit));
        auto query = std::make_unique<Messages::GetCategoryListRequest>(offset, limit);
        auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->setCallback([this](auto response) { return handleQueryResponse(response); });
        task->execute(app, this);
    }

    void CategoriesModel::createData()
    auto CategoriesModel::handleQueryResponse(db::QueryResult *queryResult) -> bool
    {
        const auto categoryList = getCategoryList();
        if (!categoryList) {
            return;
        }

        for (const auto &category : *categoryList) {
            auto app  = application;
            auto item = new gui::CategoryWidget(
                category,
                [app, category](bool enable) -> bool {
                    LOG_DEBUG(
                        "Sending enable category request: category_id = %d, enable = %d", category.category_id, enable);
                    auto request = std::make_shared<Messages::EnableCategoryByIdRequest>(category.category_id, enable);
                    auto result  = app->bus.sendUnicast(request, service::name::db, DBServiceAPI::DefaultTimeoutInMs);

                    if (result.first != sys::ReturnCodes::Success) {
                        LOG_WARN("Enable category request failed! error code = %d", static_cast<int>(result.first));
                        return false;
                    }

                    auto response      = std::dynamic_pointer_cast<Messages::EnableCategoryByIdResponse>(result.second);
                    const auto success = response && response->success;
                    if (!success)
                        LOG_WARN("Enable category request failed!");
                    return success;
                },
                [app](const UTF8 &text) {
                    app->getCurrentWindow()->bottomBarTemporaryMode(text, gui::BottomBar::Side::CENTER, false);
                },
                [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); });

            item->deleteByList = false;
            internalData.push_back(item);
        auto msgResponse = dynamic_cast<Messages::GetCategoryListResponse *>(queryResult);
        assert(msgResponse != nullptr);

        // If list record count has changed we need to rebuild list.
        if (recordsCount != (msgResponse->getCount())) {
            recordsCount = msgResponse->getCount();
            list->reSendLastRebuildRequest();
            return false;
        }
    }

    void CategoriesModel::rebuild()
    {
        list->clear();
        eraseInternalData();
        createData();
        list->rebuildList();
        LOG_DEBUG("Categories count: %u", static_cast<unsigned>(msgResponse->getCount()));
        auto records = msgResponse->getResults();
        return this->updateRecords(std::move(records));
    }

    auto CategoriesModel::getCategoryList() -> std::optional<std::vector<CategoryRecord>>
    auto CategoriesModel::getItem(gui::Order order) -> gui::ListItem *
    {
        auto categoryList    = std::make_unique<CategoryList>();
        categoryList->limit  = 0;
        categoryList->offset = 0;

        auto request = std::make_shared<Messages::GetCategoryListRequest>(std::move(categoryList));
        auto result =
            application->bus.sendUnicast(std::move(request), service::name::db, DBServiceAPI::DefaultTimeoutInMs);
        if (result.first != sys::ReturnCodes::Success) {
            LOG_WARN("Getting category list failed! error code = %d", static_cast<int>(result.first));
            return std::nullopt;
        }
        auto category = getRecord(order);

        auto response = std::dynamic_pointer_cast<Messages::GetCategoryListResponse>(result.second);
        if (!response) {
            LOG_WARN("Wrong response on category list request!");
            return std::nullopt;
        if (!category) {
            return nullptr;
        }

        LOG_DEBUG("Categories list count: %u", response->getCount());
        return response->getResults();
        auto item = new gui::CategoryWidget(
            *category,
            [this, &category = *category](bool enable) {
                LOG_DEBUG("Sending enable category request: category_id = %u, enable = %d",
                          static_cast<unsigned>(category.category_id),
                          enable);

                auto query = std::make_unique<Messages::EnableCategoryByIdRequest>(category.category_id, enable);
                auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
                task->execute(app, this);
            },
            [app = app](const UTF8 &text) {
                app->getCurrentWindow()->bottomBarTemporaryMode(text, gui::BottomBar::Side::CENTER, false);
            },
            [app = app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); });

        return item;
    }
} // namespace Quotes

M module-apps/application-settings-new/models/CategoriesModel.hpp => module-apps/application-settings-new/models/CategoriesModel.hpp +13 -17
@@ 3,32 3,28 @@

#pragma once

#include "QuotesRepository.hpp"

#include <module-gui/gui/widgets/ListView.hpp>
#include <module-apps/InternalModel.hpp>

namespace gui
{
    class CategoryWidget;
}
#include <Application.hpp>
#include <DatabaseModel.hpp>
#include <ListItemProvider.hpp>
#include <service-db/QuotesMessages.hpp>

namespace Quotes
{
    class CategoriesModel : public app::InternalModel<gui::CategoryWidget *>, public gui::ListItemProvider
    class CategoriesModel : public app::DatabaseModel<CategoryRecord>,
                            public gui::ListItemProvider,
                            public app::AsyncCallbackReceiver
    {
      private:
        app::Application *application = nullptr;

        auto getCategoryList() -> std::optional<std::vector<CategoryRecord>>;

      public:
        explicit CategoriesModel(app::Application *app);
        [[nodiscard]] auto requestRecordsCount() -> unsigned int final;
        [[nodiscard]] auto getMinimalItemHeight() const -> unsigned int final;
        auto getItem(gui::Order order) -> gui::ListItem * final;
        void requestRecords(const uint32_t offset, const uint32_t limit) final;
        void createData();
        void rebuild();
        bool updateRecords(std::vector<CategoryRecord> records);

      private:
        app::Application *app = nullptr;

        auto handleQueryResponse(db::QueryResult *queryResult) -> bool;
    };
} // namespace Quotes

M module-apps/application-settings-new/models/QuotesModel.cpp => module-apps/application-settings-new/models/QuotesModel.cpp +94 -48
@@ 3,7 3,6 @@

#include "application-settings-new/windows/QuotesMainWindow.hpp"
#include "application-settings-new/ApplicationSettings.hpp"
#include "QuotesRepository.hpp"
#include "QuotesModel.hpp"

#include <InputEvent.hpp>


@@ 12,22 11,31 @@
#include <Utils.hpp>
#include <string>
#include <utility>
#include <service-db/QuotesMessages.hpp>

namespace style::quotes::list
{
    constexpr auto item_height = 63;
    constexpr auto max_quotes  = 100;
} // namespace style::quotes::list

namespace app
namespace Quotes
{
    QuotesModel::QuotesModel(app::Application *app, std::unique_ptr<QuotesRepository> repository)
        : application(app), repository(std::move(repository))
    QuotesModel::QuotesModel(app::Application *application)
        : DatabaseModel(application), app::AsyncCallbackReceiver{application}, app(application)
    {}

    auto QuotesModel::requestRecordsCount() -> unsigned int
    {
        return internalData.size();
        return recordsCount;
    }

    bool QuotesModel::updateRecords(std::vector<QuoteRecord> records)
    {
        if (DatabaseModel::updateRecords(std::move(records))) {
            list->onProviderDataUpdate();
            return true;
        }
        return false;
    }

    auto QuotesModel::getMinimalItemHeight() const -> unsigned int


@@ 37,63 45,101 @@ namespace app

    void QuotesModel::requestRecords(const uint32_t offset, const uint32_t limit)
    {
        setupModel(offset, limit);
        list->onProviderDataUpdate();
        LOG_DEBUG(
            "Request quotes: offset = %u, limit = %u", static_cast<unsigned>(offset), static_cast<unsigned>(limit));
        auto query = std::make_unique<Messages::GetQuotesListFromCustomCategoryRequest>(offset, limit);
        auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->setCallback([this](auto response) { return handleQueryResponse(response); });
        task->execute(app, this);
    }

    auto QuotesModel::getItem(gui::Order order) -> gui::ListItem *
    auto QuotesModel::handleQueryResponse(db::QueryResult *queryResult) -> bool
    {
        auto app   = application;
        auto *item = dynamic_cast<gui::QuoteWidget *>(getRecord(order));

        if (item != nullptr) {
            item->inputCallback = [app, item](gui::Item &, const gui::InputEvent &event) {
                if (event.isShortPress() && event.is(gui::KeyCode::KEY_LF)) {
                    app->switchWindow(
                        gui::window::name::options_quote,
                        std::make_unique<gui::QuoteSwitchData>(gui::QuoteAction::None, item->getQuoteData()));
                }
                return false;
            };
        auto msgResponse = dynamic_cast<Messages::GetQuotesListFromCustomCategoryResponse *>(queryResult);
        assert(msgResponse != nullptr);

        // If list record count has changed we need to rebuild list.
        if (recordsCount != (msgResponse->getCount())) {
            recordsCount = msgResponse->getCount();
            list->reSendLastRebuildRequest();
            return false;
        }
        return item;

        LOG_DEBUG("Quotes count: %u", static_cast<unsigned>(msgResponse->getCount()));
        auto records = msgResponse->getResults();
        return this->updateRecords(std::move(records));
    }

    void QuotesModel::rebuild()
    auto QuotesModel::getItem(gui::Order order) -> gui::ListItem *
    {
        list->clear();
        eraseInternalData();
        createData();
        list->rebuildList();
        auto quote = getRecord(order);

        if (!quote) {
            return nullptr;
        }

        auto item = new gui::QuoteWidget(
            *quote,
            [this, &quote = *quote](bool enable) {
                LOG_DEBUG("Sending enable quote request: quote_id = %u, enable = %d",
                          static_cast<unsigned>(quote.quote_id),
                          enable);

                auto query = std::make_unique<Messages::EnableQuoteByIdRequest>(quote.quote_id, enable);
                auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
                task->execute(app, this);
            },
            [app = app](const UTF8 &text) {
                app->getCurrentWindow()->bottomBarTemporaryMode(text, gui::BottomBar::Side::CENTER, false);
            },
            [app = app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); });

        item->inputCallback = [app = app, item](gui::Item &, const gui::InputEvent &event) {
            if (event.isShortPress() && event.is(gui::KeyCode::KEY_LF)) {
                app->switchWindow(gui::window::name::options_quote,
                                  std::make_unique<gui::QuoteSwitchData>(gui::QuoteAction::None, item->getQuoteData()));
            }
            return false;
        };

        return item;
    }

    void QuotesModel::createData()
    void QuotesModel::add(const Quotes::QuoteRecord &record)
    {
        repository->get(0, style::quotes::list::max_quotes, [this](const std::list<QuoteRecord> &quotes, unsigned int) {
            auto app = application;
            for (const auto &quote : quotes) {
                auto item = new gui::QuoteWidget(
                    quote,
                    [app](const UTF8 &text) {
                        app->getCurrentWindow()->bottomBarTemporaryMode(text, gui::BottomBar::Side::CENTER, false);
                    },
                    [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); });

                item->deleteByList = false;
                internalData.push_back(item);
            }
            return true;
        });
        LOG_DEBUG("Adding quote: lang_id = %u, author = %s, quote = %s",
                  static_cast<unsigned>(record.lang_id),
                  record.quote.c_str(),
                  record.author.c_str());

        auto query = std::make_unique<Messages::AddQuoteRequest>(record.lang_id, record.quote, record.author, true);
        auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->execute(app, this);
    }

    void QuotesModel::remove(const app::QuoteRecord &quote)
    void QuotesModel::edit(const Quotes::QuoteRecord &record)
    {
        repository->remove(quote);
        LOG_DEBUG("Saving quote: lang_id = %u, author = %s, quote = %s",
                  static_cast<unsigned>(record.lang_id),
                  record.quote.c_str(),
                  record.author.c_str());

        auto query = std::make_unique<Messages::WriteQuoteRequest>(
            record.quote_id, record.lang_id, record.quote, record.author, record.enabled);
        auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->execute(app, this);
    }

    void QuotesModel::save(const app::QuoteRecord &quote)
    void QuotesModel::remove(const Quotes::QuoteRecord &record)
    {
        repository->save(quote);
        LOG_DEBUG("Removing quote: lang_id = %u, author = %s, quote = %s",
                  static_cast<unsigned>(record.lang_id),
                  record.quote.c_str(),
                  record.author.c_str());

        auto query = std::make_unique<Messages::DeleteQuoteRequest>(record.quote_id);
        auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->execute(app, this);
    }

} // namespace app
} // namespace Quotes

M module-apps/application-settings-new/models/QuotesModel.hpp => module-apps/application-settings-new/models/QuotesModel.hpp +15 -15
@@ 3,40 3,40 @@

#pragma once

#include "QuotesRepository.hpp"

#include <purefs/filesystem_paths.hpp>
#include <module-gui/gui/widgets/ListView.hpp>
#include <module-apps/InternalModel.hpp>
#include <Application.hpp>
#include <DatabaseModel.hpp>
#include <ListItemProvider.hpp>
#include <service-db/QuotesMessages.hpp>

namespace gui
{
    class QuoteWidget;
}

namespace app
namespace Quotes
{
    class QuotesModel : public app::InternalModel<gui::QuoteWidget *>, public gui::ListItemProvider
    class QuotesModel : public app::DatabaseModel<QuoteRecord>,
                        public gui::ListItemProvider,
                        public app::AsyncCallbackReceiver
    {
      public:
        QuotesModel(app::Application *app, std::unique_ptr<QuotesRepository> repository);
        explicit QuotesModel(app::Application *application);

        [[nodiscard]] auto requestRecordsCount() -> unsigned int final;
        [[nodiscard]] auto getMinimalItemHeight() const -> unsigned int final;

        auto getItem(gui::Order order) -> gui::ListItem * final;
        void requestRecords(const uint32_t offset, const uint32_t limit) final;
        bool updateRecords(std::vector<QuoteRecord> records) override;

        void rebuild();
        void add(const QuoteRecord &record);
        void edit(const QuoteRecord &record);
        void remove(const QuoteRecord &record);

        void remove(const app::QuoteRecord &quote);
        void save(const app::QuoteRecord &quote);
        auto handleQueryResponse(db::QueryResult *queryResult) -> bool;

      private:
        void createData();

        app::Application *application                     = nullptr;
        std::unique_ptr<app::QuotesRepository> repository = nullptr;
        app::Application *app = nullptr;
    };

} // namespace app

D module-apps/application-settings-new/models/QuotesRepository.cpp => module-apps/application-settings-new/models/QuotesRepository.cpp +0 -100
@@ 1,100 0,0 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesRepository.hpp"

#include <module-utils/gsl/gsl_util>
#include <algorithm>
#include <utility>

namespace app
{
    QuotesJsonRepository::QuotesJsonRepository(const std::string &path) : repositoryPath{std::move(path)}
    {}

    void QuotesJsonRepository::get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback)
    {
        if (quotes.empty()) {
            readQuotes(repositoryPath);
        }

        if (callback) {
            std::uint32_t size = quotes.size();
            auto start         = std::next(quotes.begin(), std::min(offset, size));
            auto end           = std::next(quotes.begin(), std::min(offset + limit, size));
            std::list<QuoteRecord> result{start, end};
            callback(result, result.size());
        }
    }

    void QuotesJsonRepository::save(const QuoteRecord &quote)
    {
        auto toEdit = std::find_if(quotes.begin(), quotes.end(), [quote](auto &&d) { return d.id == quote.id; });

        if (toEdit != quotes.end()) {
            toEdit->quote  = quote.quote;
            toEdit->author = quote.author;
        }
        else if (quote.id == 0) {
            quotes.push_back(quote);
        }

        writeQuotes(repositoryPath);
    }

    void QuotesJsonRepository::remove(const QuoteRecord &quote)
    {
        quotes.remove_if([quote](auto &&d) { return d.id == quote.id; });
        writeQuotes(repositoryPath);
    }

    void QuotesJsonRepository::writeQuotes(const std::filesystem::path &quotesFilename)
    {
        if (auto file = std::fopen(repositoryPath.c_str(), "w"); file != nullptr) {
            auto _    = gsl::finally([file] { std::fclose(file); });
            auto body = json11::Json{quotes};
            auto text = body.dump();
            std::fwrite(text.c_str(), 1, text.length(), file);
        }
    }

    void QuotesJsonRepository::readQuotes(const std::filesystem::path &quotesFilename)
    {
        std::string err;

        const auto fileContents = readFileToString(quotesFilename);
        auto obj                = json11::Json::parse(fileContents.c_str(), err).array_items();

        if (!err.empty()) {
            LOG_ERROR("Error while parsing quotes from file: %s error: %s ", quotesFilename.c_str(), err.c_str());
            return;
        }

        quotes.clear();

        auto id = 1;
        std::transform(obj.begin(), obj.end(), std::back_inserter(quotes), [&id](auto item) {
            return QuoteRecord{id++, item["quote"].string_value(), item["author"].string_value()};
        });
    }

    auto QuotesJsonRepository::readFileToString(const std::filesystem::path &filename) -> std::string
    {
        constexpr auto tar_buf = 8192 * 4;
        auto file              = std::fopen(filename.c_str(), "r");
        if (file == nullptr) {
            return {};
        }
        auto _            = gsl::finally([file] { std::fclose(file); });
        const auto length = std::filesystem::file_size(filename);

        if (length >= tar_buf) {
            LOG_ERROR("File %s length is too high!", filename.c_str());
            return {};
        }
        LOG_INFO("file length: %u", static_cast<unsigned int>(length));
        auto buffer = std::make_unique<char[]>(length + 1);
        std::fread(buffer.get(), 1, length, file);
        return std::string(buffer.get());
    }
} // namespace app

D module-apps/application-settings-new/models/QuotesRepository.hpp => module-apps/application-settings-new/models/QuotesRepository.hpp +0 -59
@@ 1,59 0,0 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <json/json11.hpp>

#include <list>
#include <functional>
#include <filesystem>
#include <module-apps/Application.hpp>

namespace app
{
    struct QuoteRecord
    {
        int id = 0;
        std::string quote;
        std::string author;

        [[nodiscard]] auto to_json() const -> json11::Json
        {
            return json11::Json::object{{"quote", quote}, {"author", author}};
        }
    };

    class QuotesRepository
    {
      public:
        using OnGetCallback = std::function<bool(const std::list<QuoteRecord> &, unsigned int)>;

        virtual ~QuotesRepository() noexcept = default;

        virtual void get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback) = 0;
        virtual void save(const QuoteRecord &quote)                                                = 0;
        virtual void remove(const QuoteRecord &quote)                                              = 0;
    };

    class QuotesJsonRepository : public QuotesRepository
    {
      public:
        QuotesJsonRepository(const std::string &path);

        void get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback) override;
        void save(const QuoteRecord &quote) override;
        void remove(const QuoteRecord &quote) override;

      private:
        void writeQuotes(const std::filesystem::path &path);
        void readQuotes(const std::filesystem::path &fn);
        std::string readFileToString(const std::filesystem::path &fn);

        std::list<QuoteRecord> quotes;
        std::string repositoryPath;
    };
} // namespace app

M module-apps/application-settings-new/widgets/CategoryWidget.cpp => module-apps/application-settings-new/widgets/CategoryWidget.cpp +8 -9
@@ 34,7 34,7 @@ namespace gui
{

    CategoryWidget::CategoryWidget(const Quotes::CategoryRecord &categoryRecord,
                                   std::function<bool(bool)> enableCategoryCallback,
                                   std::function<void(bool)> enableCategoryCallback,
                                   std::function<void(const UTF8 &)> bottomBarTemporaryMode,
                                   std::function<void()> bottomBarRestoreFromTemporaryMode)
        : category(categoryRecord), enableCategory(std::move(enableCategoryCallback)),


@@ 96,14 96,13 @@ namespace gui
        };

        activatedCallback = [&](gui::Item &item) {
            if (enableCategory(!category.enabled)) {
                category.enabled = !category.enabled;
                tickImage->setVisible(category.enabled);
                auto bottorBarText =
                    category.enabled ? utils::localize.get("common_uncheck") : utils::localize.get("common_check");
                this->bottomBarTemporaryMode(bottorBarText);
                hBox->resizeItems();
            }
            category.enabled = !category.enabled;
            enableCategory(category.enabled);
            tickImage->setVisible(category.enabled);
            auto bottorBarText =
                category.enabled ? utils::localize.get("common_uncheck") : utils::localize.get("common_check");
            this->bottomBarTemporaryMode(bottorBarText);
            hBox->resizeItems();
            return true;
        };


M module-apps/application-settings-new/widgets/CategoryWidget.hpp => module-apps/application-settings-new/widgets/CategoryWidget.hpp +2 -2
@@ 17,7 17,7 @@ namespace gui
    {
      public:
        CategoryWidget(const Quotes::CategoryRecord &categoryRecord,
                       std::function<bool(bool)> enableCategory,
                       std::function<void(bool)> enableCategory,
                       std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr,
                       std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr);



@@ 28,7 28,7 @@ namespace gui
        gui::Image *tickImage        = nullptr;

        Quotes::CategoryRecord category;
        std::function<bool(bool)> enableCategory                     = nullptr;
        std::function<void(bool)> enableCategory                     = nullptr;
        std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr;
        std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr;


M module-apps/application-settings-new/widgets/QuoteWidget.cpp => module-apps/application-settings-new/widgets/QuoteWidget.cpp +19 -29
@@ 19,7 19,7 @@ namespace style::quotes
        inline constexpr uint32_t input_box_h           = h;
        inline constexpr int32_t input_box_right_margin = 20;

        inline constexpr uint32_t description_label_w           = 280;
        inline constexpr uint32_t description_label_w           = style::window::default_body_width;
        inline constexpr uint32_t description_label_h           = 33;
        inline constexpr int32_t description_label_right_margin = 40;



@@ 33,44 33,33 @@ namespace style::quotes
namespace gui
{

    QuoteWidget::QuoteWidget(const app::QuoteRecord &quote,
    QuoteWidget::QuoteWidget(const Quotes::QuoteRecord &quoteRecord,
                             std::function<void(bool)> enableQuoteCallback,
                             std::function<void(const UTF8 &)> bottomBarTemporaryMode,
                             std::function<void()> bottomBarRestoreFromTemporaryMode)
        : bottomBarTemporaryMode(std::move(bottomBarTemporaryMode)),
          bottomBarRestoreFromTemporaryMode(std::move(bottomBarRestoreFromTemporaryMode)), quote(quote)
        : quote(quoteRecord), enableQuote(std::move(enableQuoteCallback)),
          bottomBarTemporaryMode(std::move(bottomBarTemporaryMode)),
          bottomBarRestoreFromTemporaryMode(std::move(bottomBarRestoreFromTemporaryMode))
    {

        setMinimumSize(style::quotes::widget::w, style::quotes::widget::h);

        setMargins(gui::Margins(0, style::margins::big, 0, 0));

        hBox = new gui::HBox(this, 0, 0, this->getWidth(), 80);
        hBox = new gui::HBox(this, 0, 0, 0, 0);
        hBox->setEdges(gui::RectangleEdge::None);
        hBox->setPenFocusWidth(style::window::default_border_focus_w);
        hBox->setPenWidth(style::window::default_border_rect_no_focus);

        inputBoxLabel = new gui::Label(hBox, 0, 0, 0, 0);
        inputBoxLabel->setMinimumSize(style::quotes::widget::input_box_w, style::quotes::widget::input_box_h);

        inputBoxLabel->setMargins(gui::Margins(0, 0, style::quotes::widget::input_box_right_margin, 0));
        inputBoxLabel->setEdges(gui::RectangleEdge::Bottom);
        inputBoxLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        inputBoxLabel->setFont(style::window::font::medium);
        inputBoxLabel->activeItem = false;

        tickImage = new gui::Image(hBox, 0, 0, 0, 0);
        tickImage->setAlignment(Alignment(gui::Alignment::Vertical::Center));
        // Not ideal -> best solution would be to create separate widget with image inside box.
        tickImage->setMargins(gui::Margins(
            style::quotes::widget::tick_image_left_margin, 0, style::quotes::widget::tick_image_right_margin, 0));
        tickImage->set("small_tick_W_M");
        tickImage =
            new ImageBox(hBox, 0, 0, style::widgets::iconsSize, style::widgets::iconsSize, new Image("small_tick_W_M"));
        tickImage->setVisible(true);
        tickImage->activeItem = false;
        tickImage->showImage(quote.enabled);
        tickImage->setEdges(RectangleEdge::Bottom);

        descriptionLabel = new gui::Label(hBox, 0, 0, 0, 0);
        descriptionLabel->setMinimumSize(style::quotes::widget::description_label_w,
        descriptionLabel->setMaximumSize(style::quotes::widget::description_label_w,
                                         style::quotes::widget::description_label_h);
        descriptionLabel->setMargins(gui::Margins(0, 0, style::quotes::widget::description_label_right_margin, 0));
        descriptionLabel->setEdges(gui::RectangleEdge::None);
        descriptionLabel->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
        descriptionLabel->setFont(style::window::font::medium);


@@ 80,14 69,13 @@ namespace gui

        focusChangedCallback = [&](gui::Item &item) {
            if (item.focus) {
                descriptionLabel->setFont(style::footer::font::bold);
                setFocusItem(inputBoxLabel);
                descriptionLabel->setFont(style::window::font::mediumbold);
                auto bottorBarText =
                    tickImage->visible ? utils::localize.get("common_uncheck") : utils::localize.get("common_check");
                    quote.enabled ? utils::localize.get("common_uncheck") : utils::localize.get("common_check");
                this->bottomBarTemporaryMode(bottorBarText);
            }
            else {
                descriptionLabel->setFont(style::footer::font::medium);
                descriptionLabel->setFont(style::window::font::medium);
                setFocusItem(nullptr);
                this->bottomBarRestoreFromTemporaryMode();
            }


@@ 95,9 83,11 @@ namespace gui
        };

        activatedCallback = [&](gui::Item &item) {
            tickImage->setVisible(!tickImage->visible);
            quote.enabled = !quote.enabled;
            enableQuote(quote.enabled);
            tickImage->showImage(quote.enabled);
            auto bottorBarText =
                tickImage->visible ? utils::localize.get("common_uncheck") : utils::localize.get("common_check");
                quote.enabled ? utils::localize.get("common_uncheck") : utils::localize.get("common_check");
            this->bottomBarTemporaryMode(bottorBarText);
            hBox->resizeItems();
            return true;

M module-apps/application-settings-new/widgets/QuoteWidget.hpp => module-apps/application-settings-new/widgets/QuoteWidget.hpp +10 -8
@@ 6,35 6,37 @@
#include "application-settings-new/data/QuoteSwitchData.hpp"

#include <BoxLayout.hpp>
#include <Image.hpp>
#include <ImageBox.hpp>
#include <Label.hpp>
#include <ListItem.hpp>
#include <json/json11.hpp>
#include <service-db/QuotesMessages.hpp>

namespace gui
{
    class QuoteWidget : public ListItem
    {
      public:
        QuoteWidget(const app::QuoteRecord &quote,
        QuoteWidget(const Quotes::QuoteRecord &quoteRecord,
                    std::function<void(bool)> enableQuoteCallback,
                    std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr,
                    std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr);

        [[nodiscard]] auto getQuoteData() const -> app::QuoteRecord
        [[nodiscard]] auto getQuoteData() const -> Quotes::QuoteRecord
        {
            return quote;
        }

      private:
        gui::HBox *hBox              = nullptr;
        gui::Label *inputBoxLabel    = nullptr;
        gui::Label *descriptionLabel = nullptr;
        gui::Image *tickImage        = nullptr;
        HBox *hBox              = nullptr;
        Label *descriptionLabel = nullptr;
        ImageBox *tickImage     = nullptr;

        Quotes::QuoteRecord quote;
        std::function<void(bool)> enableQuote                        = nullptr;
        std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr;
        std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr;

        app::QuoteRecord quote;
    };

} /* namespace gui */

M module-apps/application-settings-new/windows/EditQuotesWindow.cpp => module-apps/application-settings-new/windows/EditQuotesWindow.cpp +1 -1
@@ 88,7 88,7 @@ namespace gui

        optionSwitch = !optionSwitch;

        rebuildOptionList();
        refreshOptionsList();
    }

} // namespace gui

M module-apps/application-settings-new/windows/QuoteCategoriesWindow.cpp => module-apps/application-settings-new/windows/QuoteCategoriesWindow.cpp +8 -4
@@ 8,8 8,9 @@

namespace gui
{
    QuoteCategoriesWindow::QuoteCategoriesWindow(app::Application *app, std::shared_ptr<Quotes::CategoriesModel> model)
        : AppWindow(app, gui::window::name::quote_categories), categoriesModel(std::move(model))
    QuoteCategoriesWindow::QuoteCategoriesWindow(app::Application *app)
        : AppWindow(app, gui::window::name::quote_categories),
          categoriesModel(std::make_shared<Quotes::CategoriesModel>(app))
    {
        buildInterface();
    }


@@ 28,13 29,16 @@ namespace gui
                                 style::quotes::categories::list::Y,
                                 style::quotes::categories::list::Width,
                                 style::quotes::categories::list::Height,
                                 categoriesModel);
                                 categoriesModel,
                                 style::listview::ScrollBarType::Fixed);

        setFocusItem(list);
    }

    void QuoteCategoriesWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        categoriesModel->rebuild();
        if (mode == ShowMode::GUI_SHOW_INIT) {
            list->rebuildList();
        }
    }
} // namespace gui

M module-apps/application-settings-new/windows/QuoteCategoriesWindow.hpp => module-apps/application-settings-new/windows/QuoteCategoriesWindow.hpp +1 -1
@@ 14,7 14,7 @@ namespace gui
    class QuoteCategoriesWindow : public AppWindow
    {
      public:
        QuoteCategoriesWindow(app::Application *app, std::shared_ptr<Quotes::CategoriesModel> model);
        QuoteCategoriesWindow(app::Application *app);

      private:
        void buildInterface() override;

M module-apps/application-settings-new/windows/QuotesAddWindow.cpp => module-apps/application-settings-new/windows/QuotesAddWindow.cpp +9 -4
@@ 6,7 6,6 @@

#include "application-settings-new/ApplicationSettings.hpp"
#include "application-settings-new/data/QuoteSwitchData.hpp"
#include "application-settings-new/models/QuotesRepository.hpp"

#include <i18n/i18n.hpp>
#include <widgets/Text.hpp>


@@ 33,8 32,8 @@ namespace gui
        }
    } // namespace

    QuoteAddEditWindow::QuoteAddEditWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model)
        : AppWindow(app, gui::window::name::quotes), quoteModel(std::move(model))
    QuoteAddEditWindow::QuoteAddEditWindow(app::Application *app)
        : AppWindow(app, gui::window::name::quotes), quoteModel(std::make_shared<Quotes::QuotesModel>(app))
    {
        buildInterface();
    }


@@ 154,7 153,13 @@ namespace gui
            LOG_DEBUG("Save Quote: %s", quoteText->getText().c_str());
            quoteData.quote  = quoteText->getText();
            quoteData.author = authorText->getText();
            quoteModel->save(quoteData);

            if (quoteAction == QuoteAction::Add) {
                quoteModel->add(quoteData);
            }
            else {
                quoteModel->edit(quoteData);
            }

            auto backToOptionWindow = 1;
            auto backToMainWindow   = 2;

M module-apps/application-settings-new/windows/QuotesAddWindow.hpp => module-apps/application-settings-new/windows/QuotesAddWindow.hpp +3 -3
@@ 14,7 14,7 @@ namespace gui
    class QuoteAddEditWindow : public AppWindow
    {
      public:
        QuoteAddEditWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model);
        explicit QuoteAddEditWindow(app::Application *app);
        void buildInterface() override;

      private:


@@ 28,7 28,7 @@ namespace gui
        gui::Label *quoteCharCounter  = nullptr;

        QuoteAction quoteAction;
        app::QuoteRecord quoteData;
        std::shared_ptr<app::QuotesModel> quoteModel;
        Quotes::QuoteRecord quoteData;
        std::shared_ptr<Quotes::QuotesModel> quoteModel;
    };
} // namespace gui

M module-apps/application-settings-new/windows/QuotesMainWindow.cpp => module-apps/application-settings-new/windows/QuotesMainWindow.cpp +6 -6
@@ 1,8 1,7 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesMainWindow.hpp"
#include "application-settings-new/models/QuotesRepository.hpp"
#include "application-settings-new/models/QuotesModel.hpp"
#include "application-settings-new/ApplicationSettings.hpp"
#include "application-settings-new/widgets/SettingsStyle.hpp"


@@ 32,8 31,8 @@ namespace style::quotes

namespace gui
{
    QuotesMainWindow::QuotesMainWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model)
        : AppWindow(app, gui::window::name::quotes), quotesModel(std::move(model))
    QuotesMainWindow::QuotesMainWindow(app::Application *app)
        : AppWindow(app, gui::window::name::quotes), quotesModel(std::make_shared<Quotes::QuotesModel>(app))
    {
        buildInterface();
    }


@@ 56,7 55,8 @@ namespace gui
                                 style::quotes::list::Y,
                                 style::quotes::list::Width,
                                 style::quotes::list::Height,
                                 quotesModel);
                                 quotesModel,
                                 style::listview::ScrollBarType::Fixed);

        setFocusItem(list);
    }


@@ 83,6 83,6 @@ namespace gui

    void QuotesMainWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        quotesModel->rebuild();
        list->rebuildList();
    }
} // namespace gui

M module-apps/application-settings-new/windows/QuotesMainWindow.hpp => module-apps/application-settings-new/windows/QuotesMainWindow.hpp +4 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 17,14 17,14 @@ namespace gui
    class QuotesMainWindow : public AppWindow
    {
      public:
        QuotesMainWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model);
        explicit QuotesMainWindow(app::Application *app);

      private:
        void buildInterface() override;
        void onBeforeShow(ShowMode mode, SwitchData *data);
        auto onInput(const InputEvent &inputEvent) -> bool override;
        void onBeforeShow(ShowMode mode, SwitchData *data) override;

        std::shared_ptr<app::QuotesModel> quotesModel = nullptr;
        std::shared_ptr<Quotes::QuotesModel> quotesModel = nullptr;
        gui::ListView *list                           = nullptr;
    };


M module-apps/application-settings-new/windows/QuotesOptionsWindow.cpp => module-apps/application-settings-new/windows/QuotesOptionsWindow.cpp +2 -4
@@ 3,11 3,9 @@

#include "QuotesOptionsWindow.hpp"
#include "OptionSetting.hpp"
#include "application-settings-new/models/QuotesRepository.hpp"
#include "application-settings-new/ApplicationSettings.hpp"
#include "DialogMetadataMessage.hpp"
#include "QuotesMainWindow.hpp"
#include "application-settings-new/models/QuotesRepository.hpp"
#include "application-settings-new/ApplicationSettings.hpp"
#include "application-settings-new/widgets/QuoteWidget.hpp"



@@ 17,8 15,8 @@
namespace gui
{

    QuotesOptionsWindow::QuotesOptionsWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model)
        : BaseSettingsWindow(app, gui::window::name::quotes), quotesModel(std::move(model))
    QuotesOptionsWindow::QuotesOptionsWindow(app::Application *app)
        : BaseSettingsWindow(app, gui::window::name::quotes), quotesModel(std::make_shared<Quotes::QuotesModel>(app))
    {
        setTitle(utils::localize.get("app_settings_display_wallpaper_quotes_options"));
    }

M module-apps/application-settings-new/windows/QuotesOptionsWindow.hpp => module-apps/application-settings-new/windows/QuotesOptionsWindow.hpp +3 -3
@@ 12,13 12,13 @@ namespace gui
    class QuotesOptionsWindow : public BaseSettingsWindow
    {
      public:
        QuotesOptionsWindow(app::Application *app, std::shared_ptr<app::QuotesModel> model);
        explicit QuotesOptionsWindow(app::Application *app);

      private:
        std::list<Option> buildOptionsList() override;
        void onBeforeShow(ShowMode mode, SwitchData *data) override;

        std::shared_ptr<app::QuotesModel> quotesModel;
        app::QuoteRecord quote;
        std::shared_ptr<Quotes::QuotesModel> quotesModel;
        Quotes::QuoteRecord quote;
    };
} // namespace gui

M module-apps/application-settings-new/windows/WallpaperWindow.cpp => module-apps/application-settings-new/windows/WallpaperWindow.cpp +1 -1
@@ 69,7 69,7 @@ namespace gui
        isWallpaperLogoSwitchOn   = false;

        optionSwitch = !optionSwitch;
        rebuildOptionList();
        refreshOptionsList();
    }

} // namespace gui

M module-db/Interface/BaseInterface.hpp => module-db/Interface/BaseInterface.hpp +4 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 28,6 28,7 @@ namespace db
            CountryCodes,
            Notifications,
            Events,
            Quotes,
        };
    };
}; // namespace db


@@ 55,6 56,8 @@ constexpr const char *c_str(enum db::Interface::Name db)
        return "Notifications";
    case db::Interface::Name::Events:
        return "Events";
    case db::Interface::Name::Quotes:
        return "Quotes";
    };
    return "";
}

M module-gui/gui/widgets/ImageBox.cpp => module-gui/gui/widgets/ImageBox.cpp +6 -13
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ImageBox.hpp"


@@ 7,21 7,14 @@ using namespace gui;

ImageBox::ImageBox(
    Item *parent, const uint32_t &x, const uint32_t &y, const uint32_t &w, const uint32_t &h, Image *image)
    : HBox(parent, x, y, w, h)
    : HBox(parent, x, y, w, h), image(image)
{
    setEdges(RectangleEdge::None);
    setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
    setPenFocusWidth(style::window::default_border_focus_w);
    setPenWidth(style::window::default_border_no_focus_w);
    addWidget(image);
}

    focusChangedCallback = [&](Item &item) {
        if (focus) {
            setEdges(RectangleEdge::Bottom | RectangleEdge::Top);
        }
        else {
            setEdges(RectangleEdge::None);
        }
        return true;
    };
void ImageBox::showImage(bool show)
{
    image->setVisible(show);
}

M module-gui/gui/widgets/ImageBox.hpp => module-gui/gui/widgets/ImageBox.hpp +6 -3
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 10,12 10,15 @@ namespace gui
{
    class ImageBox : public HBox
    {

      public:
        ImageBox(
            Item *parent, const uint32_t &x, const uint32_t &y, const uint32_t &w, const uint32_t &h, Image *image);

        ~ImageBox() override = default;
    };

        void showImage(bool show);

      private:
        Image *image;
    };
}; // namespace gui

M module-services/service-db/ServiceDB.cpp => module-services/service-db/ServiceDB.cpp +11 -2
@@ 14,8 14,8 @@
#include "service-db/DBThreadMessage.hpp"
#include "service-db/QueryMessage.hpp"
#include "service-db/DatabaseAgent.hpp"
#include "agents/quotes/QuotesAgent.cpp"
#include "agents/settings/SettingsAgent.hpp"
#include "agents/quotes/QuotesAgent.hpp"

#include <AlarmsRecord.hpp>
#include <CalllogRecord.hpp>


@@ 65,6 65,7 @@ ServiceDB::~ServiceDB()
    countryCodesDB.reset();
    notificationsDB.reset();
    eventsDB.reset();
    quotesDB.reset();

    Database::deinitialize();
    LOG_INFO("[ServiceDB] Cleaning resources");


@@ 93,6 94,8 @@ db::Interface *ServiceDB::getInterface(db::Interface::Name interface)
        return notificationsRecordInterface.get();
    case db::Interface::Name::Events:
        return eventsRecordInterface.get();
    case db::Interface::Name::Quotes:
        return quotesRecordInterface.get();
    }
    return nullptr;
}


@@ 535,6 538,7 @@ sys::ReturnCodes ServiceDB::InitHandler()
    countryCodesDB  = std::make_unique<CountryCodesDB>("country-codes.db");
    notificationsDB = std::make_unique<NotificationsDB>((purefs::dir::getUserDiskPath() / "notifications.db").c_str());
    eventsDB        = std::make_unique<EventsDB>((purefs::dir::getUserDiskPath() / "events.db").c_str());
    quotesDB        = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "quotes.db").c_str());

    // Create record interfaces
    contactRecordInterface       = std::make_unique<ContactRecordInterface>(contactsDB.get());


@@ 547,10 551,10 @@ sys::ReturnCodes ServiceDB::InitHandler()
    countryCodeRecordInterface   = std::make_unique<CountryCodeRecordInterface>(countryCodesDB.get());
    notificationsRecordInterface = std::make_unique<NotificationsRecordInterface>(notificationsDB.get());
    eventsRecordInterface        = std::make_unique<EventsRecordInterface>(eventsDB.get());
    quotesRecordInterface        = std::make_unique<Quotes::QuotesAgent>(quotesDB.get());

    databaseAgents.emplace(std::make_unique<SettingsAgent>(this));
    databaseAgents.emplace(std::make_unique<FileIndexerAgent>(this));
    databaseAgents.emplace(std::make_unique<Quotes::QuotesAgent>(this));

    for (auto &dbAgent : databaseAgents) {
        dbAgent->initDb();


@@ 614,6 618,11 @@ bool ServiceDB::StoreIntoBackup(const std::filesystem::path &backupPath)
        return false;
    }

    if (quotesDB->storeIntoFile(backupPath / std::filesystem::path(quotesDB->getName()).filename()) == false) {
        LOG_ERROR("quotesDB backup failed");
        return false;
    }

    for (auto &db : databaseAgents) {
        if (db.get() && db.get()->getAgentName() == "settingsAgent") {


M module-services/service-db/ServiceDB.hpp => module-services/service-db/ServiceDB.hpp +6 -0
@@ 51,6 51,10 @@ class SMSTemplateRecordInterface;
class SettingsDB;
class SmsDB;
class ThreadRecordInterface;
namespace Quotes
{
    class QuotesAgent;
}

class ServiceDB : public sys::Service
{


@@ 63,6 67,7 @@ class ServiceDB : public sys::Service
    std::unique_ptr<CountryCodesDB> countryCodesDB;
    std::unique_ptr<NotificationsDB> notificationsDB;
    std::unique_ptr<EventsDB> eventsDB;
    std::unique_ptr<Database> quotesDB;

    std::unique_ptr<SMSRecordInterface> smsRecordInterface;
    std::unique_ptr<ThreadRecordInterface> threadRecordInterface;


@@ 74,6 79,7 @@ class ServiceDB : public sys::Service
    std::unique_ptr<CountryCodeRecordInterface> countryCodeRecordInterface;
    std::unique_ptr<NotificationsRecordInterface> notificationsRecordInterface;
    std::unique_ptr<EventsRecordInterface> eventsRecordInterface;
    std::unique_ptr<Quotes::QuotesAgent> quotesRecordInterface;

  protected:
    db::Interface *getInterface(db::Interface::Name interface);

M module-services/service-db/agents/file_indexer/FileIndexerAgent.cpp => module-services/service-db/agents/file_indexer/FileIndexerAgent.cpp +6 -4
@@ 157,16 157,18 @@ auto FileIndexerAgent::dbGeMetadataCount() -> unsigned int
    return (*retQuery)[0].getUInt32();
}

auto FileIndexerAgent::dbListDir(std::unique_ptr<FileIndexer::ListDir> listDir) -> std::unique_ptr<FileIndexer::ListDir>
auto FileIndexerAgent::dbListDir(unsigned int offset, unsigned int limit, const std::string &directory)
    -> std::unique_ptr<FileIndexer::ListDir>
{
    auto retQuery = database->query(FileIndexer::Statements::getFilesByDir, listDir->directory.c_str());
    return getList<FileIndexer::ListDir, FileIndexer::FileRecord>(std::move(listDir), std::move(retQuery));
    auto retQuery = database->query(FileIndexer::Statements::getFilesByDir, directory.c_str());
    return getList<FileIndexer::ListDir, FileIndexer::FileRecord>(offset, limit, std::move(retQuery));
}

auto FileIndexerAgent::handleListDir(sys::Message *req) -> sys::MessagePointer
{
    if (auto msg = dynamic_cast<FileIndexer::Messages::GetListDirRequest *>(req)) {
        return std::make_shared<FileIndexer::Messages::GetListDirResponse>(dbListDir(std::move(msg->listDir)));
        return std::make_shared<FileIndexer::Messages::GetListDirResponse>(
            dbListDir(msg->offset, msg->limit, msg->directory));
    }
    return std::make_shared<sys::ResponseMessage>();
}

M module-services/service-db/agents/file_indexer/FileIndexerAgent.hpp => module-services/service-db/agents/file_indexer/FileIndexerAgent.hpp +2 -1
@@ 40,7 40,8 @@ class FileIndexerAgent : public DatabaseAgent
    // db operations
    auto dbRegisterFileChange(std::string dir, std::string service) -> bool;
    auto dbUnregisterFileChange(std::string dir, std::string service) -> bool;
    auto dbListDir(std::unique_ptr<FileIndexer::ListDir> listDir) -> std::unique_ptr<FileIndexer::ListDir>;
    auto dbListDir(unsigned int offset, unsigned int limit, const std::string &directory)
        -> std::unique_ptr<FileIndexer::ListDir>;
    auto dbGetProperty(std::unique_ptr<FileIndexer::FileMetadata> metaData) -> FileIndexer::FileMetadata;
    auto dbSetProperty(std::unique_ptr<FileIndexer::FileMetadata> metaData) -> bool;
    auto dbSetProperties(std::unique_ptr<FileIndexer::FileMetadata> metaData) -> bool;

M module-services/service-db/agents/quotes/QuotesAgent.cpp => module-services/service-db/agents/quotes/QuotesAgent.cpp +123 -104
@@ 5,151 5,170 @@
#include "QuotesQueries.hpp"

#include <Application.hpp>
#include <purefs/filesystem_paths.hpp>
#include <Common/Query.hpp>

namespace Quotes
{
    QuotesAgent::QuotesAgent(sys::Service *parentService) : DatabaseAgent(parentService)
    QuotesAgent::QuotesAgent(Database *quotesDB) : database(quotesDB)
    {
        database = std::make_unique<Database>(getDbFilePath().c_str());
    }

    void QuotesAgent::registerMessages()
    auto QuotesAgent::runQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        using std::placeholders::_1;

        parentService->connect(typeid(Messages::GetCategoryListRequest),
                               std::bind(&QuotesAgent::handleCategoryList, this, _1));
        parentService->connect(typeid(Messages::GetQuotesListRequest),
                               std::bind(&QuotesAgent::handleQuotesList, this, _1));
        parentService->connect(typeid(Messages::GetQuotesListByCategoryIdRequest),
                               std::bind(&QuotesAgent::handleQuotesListByCategoryId, this, _1));
        parentService->connect(typeid(Messages::EnableCategoryByIdRequest),
                               std::bind(&QuotesAgent::handleEnableCategoryById, this, _1));
        parentService->connect(typeid(Messages::EnableQuoteByIdRequest),
                               std::bind(&QuotesAgent::handleEnableQuoteById, this, _1));
        parentService->connect(typeid(Messages::GetEnabledQuotesListRequest),
                               std::bind(&QuotesAgent::handleEnabledQuotesList, this, _1));
        parentService->connect(typeid(Messages::AddQuoteRequest), std::bind(&QuotesAgent::handleAddQuote, this, _1));
        parentService->connect(typeid(Messages::ReadQuoteRequest), std::bind(&QuotesAgent::handleReadQuote, this, _1));
        parentService->connect(typeid(Messages::WriteQuoteRequest),
                               std::bind(&QuotesAgent::handleWriteQuote, this, _1));
        parentService->connect(typeid(Messages::DeleteQuoteRequest),
                               std::bind(&QuotesAgent::handleDeleteQuote, this, _1));
        if (typeid(*query) == typeid(Messages::GetCategoryListRequest)) {
            return handleCategoryList(query);
        }
        else if (typeid(*query) == typeid(Messages::EnableCategoryByIdRequest)) {
            return handleEnableCategoryById(query);
        }
        else if (typeid(*query) == typeid(Messages::GetQuotesListRequest)) {
            return handleQuotesList(query);
        }
        else if (typeid(*query) == typeid(Messages::GetQuotesListByCategoryIdRequest)) {
            return handleQuotesListByCategoryId(query);
        }
        else if (typeid(*query) == typeid(Messages::GetQuotesListFromCustomCategoryRequest)) {
            return handleQuotesListFromCustomCategory(query);
        }
        else if (typeid(*query) == typeid(Messages::GetEnabledQuotesListRequest)) {
            return handleEnabledQuotesList(query);
        }
        else if (typeid(*query) == typeid(Messages::EnableQuoteByIdRequest)) {
            return handleEnableQuoteById(query);
        }
        else if (typeid(*query) == typeid(Messages::AddQuoteRequest)) {
            return handleAddQuote(query);
        }
        else if (typeid(*query) == typeid(Messages::ReadQuoteRequest)) {
            return handleReadQuote(query);
        }
        else if (typeid(*query) == typeid(Messages::WriteQuoteRequest)) {
            return handleWriteQuote(query);
        }
        else if (typeid(*query) == typeid(Messages::DeleteQuoteRequest)) {
            return handleDeleteQuote(query);
        }
        return nullptr;
    }

    auto QuotesAgent::getDbFilePath() -> const std::string
    auto QuotesAgent::handleCategoryList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        return (purefs::dir::getUserDiskPath() / "quotes.db").string();
        auto req          = std::dynamic_pointer_cast<Messages::GetCategoryListRequest>(query);
        auto queryResult  = database->query(Queries::getAllCategories);
        auto categoryList = getList<CategoryList, CategoryRecord>(req->offset, req->limit, std::move(queryResult));
        auto response     = std::make_unique<Messages::GetCategoryListResponse>(std::move(categoryList));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::getDbInitString() -> const std::string
    auto QuotesAgent::handleEnableCategoryById(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        return {};
        auto req         = std::dynamic_pointer_cast<Messages::EnableCategoryByIdRequest>(query);
        auto queryResult = database->execute(Queries::enableCategory, req->enable, req->categoryId);
        auto response    = std::make_unique<Messages::EnableCategoryByIdResponse>(std::move(queryResult));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::getAgentName() -> const std::string
    auto QuotesAgent::handleQuotesList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        return std::string("quotesAgent");
        auto req         = std::dynamic_pointer_cast<Messages::GetQuotesListRequest>(query);
        auto queryResult = database->query(Queries::getAllQuotes);
        auto quotesList  = getList<QuotesList, QuoteRecord>(req->offset, req->limit, std::move(queryResult));
        auto response    = std::make_unique<Messages::GetQuotesListResponse>(std::move(quotesList));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleCategoryList(sys::Message *req) -> sys::MessagePointer
    auto QuotesAgent::handleQuotesListByCategoryId(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        if (auto msg = dynamic_cast<Messages::GetCategoryListRequest *>(req)) {
            auto query        = database->query(Queries::getAllCategories);
            auto categoryList = getList<CategoryList, CategoryRecord>(std::move(msg->categoryList), std::move(query));
            return std::make_shared<Messages::GetCategoryListResponse>(std::move(categoryList));
        }
        return sys::msgNotHandled();
        auto req         = std::dynamic_pointer_cast<Messages::GetQuotesListByCategoryIdRequest>(query);
        auto queryResult = database->query(Queries::getQuotesByCategoryId, req->categoryId);
        auto quotesList  = getList<QuotesList, QuoteRecord>(req->offset, req->limit, std::move(queryResult));
        auto response    = std::make_unique<Messages::GetQuotesListByCategoryIdResponse>(std::move(quotesList));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleQuotesList(sys::Message *req) -> sys::MessagePointer
    auto QuotesAgent::handleQuotesListFromCustomCategory(std::shared_ptr<db::Query> query)
        -> std::unique_ptr<db::QueryResult>
    {
        if (auto msg = dynamic_cast<Messages::GetQuotesListRequest *>(req)) {
            auto query      = database->query(Queries::getAllQuotes);
            auto quotesList = getList<QuotesList, QuoteRecord>(std::move(msg->quotesList), std::move(query));
            return std::make_shared<Messages::GetQuotesListResponse>(std::move(quotesList));
        }
        return sys::msgNotHandled();
        auto req         = std::dynamic_pointer_cast<Messages::GetQuotesListFromCustomCategoryRequest>(query);
        auto queryResult = database->query(Queries::getQuotesFromCustomCategory);
        auto quotesList  = getList<QuotesList, QuoteRecord>(req->offset, req->limit, std::move(queryResult));
        auto response    = std::make_unique<Messages::GetQuotesListFromCustomCategoryResponse>(std::move(quotesList));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleQuotesListByCategoryId(sys::Message *req) -> sys::MessagePointer
    auto QuotesAgent::handleEnabledQuotesList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        if (auto msg = dynamic_cast<Messages::GetQuotesListByCategoryIdRequest *>(req)) {
            auto query      = database->query(Queries::getQuotesByCategoryId, msg->categoryId);
            auto quotesList = getList<QuotesList, QuoteRecord>(std::move(msg->quotesList), std::move(query));
            return std::make_shared<Messages::GetQuotesListByCategoryIdResponse>(std::move(quotesList));
        }
        return sys::msgNotHandled();
        auto req         = std::dynamic_pointer_cast<Messages::GetEnabledQuotesListRequest>(query);
        auto queryResult = database->query(Queries::getEnabledQuotes);
        auto quotesList  = getList<QuotesList, QuoteRecord>(req->offset, req->limit, std::move(queryResult));
        auto response    = std::make_unique<Messages::GetEnabledQuotesListResponse>(std::move(quotesList));
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleEnableCategoryById(sys::Message *req) -> sys::MessagePointer
    auto QuotesAgent::handleEnableQuoteById(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        if (auto msg = dynamic_cast<Messages::EnableCategoryByIdRequest *>(req)) {
            auto result = database->execute(Queries::enableCategory, msg->enable, msg->categoryId);
            return std::make_shared<Messages::EnableCategoryByIdResponse>(result);
        }
        return sys::msgNotHandled();
        auto req         = std::dynamic_pointer_cast<Messages::EnableQuoteByIdRequest>(query);
        auto queryResult = database->execute(Queries::enableQuote, req->enable, req->quoteId);
        auto response    = std::make_unique<Messages::EnableQuoteByIdResponse>(queryResult);
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleEnableQuoteById(sys::Message *req) -> sys::MessagePointer
    auto QuotesAgent::handleAddQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        if (auto msg = dynamic_cast<Messages::EnableQuoteByIdRequest *>(req)) {
            auto result = database->execute(Queries::enableQuote, msg->enable, msg->quoteId);
            return std::make_shared<Messages::EnableQuoteByIdResponse>(result);
        }
        return sys::msgNotHandled();
    }
        auto req = std::dynamic_pointer_cast<Messages::AddQuoteRequest>(query);

    auto QuotesAgent::handleEnabledQuotesList(sys::Message *req) -> sys::MessagePointer
    {
        if (auto msg = dynamic_cast<Messages::GetEnabledQuotesListRequest *>(req)) {
            auto query      = database->query(Queries::getEnabledQuotes);
            auto quotesList = getList<QuotesList, QuoteRecord>(std::move(msg->quotesList), std::move(query));
            return std::make_shared<Messages::GetEnabledQuotesListResponse>(std::move(quotesList));
        }
        return sys::msgNotHandled();
    }
        database->execute(
            Queries::addQuoteToQuoteTable, req->langId, req->quote.c_str(), req->author.c_str(), req->enabled);

    auto QuotesAgent::handleAddQuote(sys::Message *req) -> sys::MessagePointer
    {
        if (auto msg = dynamic_cast<Messages::AddQuoteRequest *>(req)) {
            auto result = database->execute(
                Queries::addQuote, msg->langId, msg->quote.c_str(), msg->author.c_str(), msg->enabled);
            auto quoteId = database->getLastInsertRowId();
            return std::make_shared<Messages::AddQuoteResponse>(result, quoteId);
        }
        return sys::msgNotHandled();
        auto quoteId = database->getLastInsertRowId();

        auto queryResult = database->query(Queries::getCustomCategoryId);
        CategoryRecord categoryRecord(queryResult.get());

        auto success = database->execute(Queries::addQuoteToQuoteCategoryMapTable, categoryRecord.category_id, quoteId);

        auto response = std::make_unique<Messages::AddQuoteResponse>(success, quoteId);
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleReadQuote(sys::Message *req) -> sys::MessagePointer
    auto QuotesAgent::handleReadQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        if (auto msg = dynamic_cast<Messages::ReadQuoteRequest *>(req)) {
            auto result = database->query(Queries::readQuote, msg->quoteId);
            QuoteRecord quoteRecord(result.get());
            return std::make_shared<Messages::ReadQuoteResponse>(
                quoteRecord.quote_id, quoteRecord.lang_id, quoteRecord.quote, quoteRecord.author, quoteRecord.enabled);
        }
        return sys::msgNotHandled();
        auto req         = std::dynamic_pointer_cast<Messages::ReadQuoteRequest>(query);
        auto queryResult = database->query(Queries::readQuote, req->quoteId);
        QuoteRecord quoteRecord(queryResult.get());
        auto response = std::make_unique<Messages::ReadQuoteResponse>(
            quoteRecord.quote_id, quoteRecord.lang_id, quoteRecord.quote, quoteRecord.author, quoteRecord.enabled);
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleWriteQuote(sys::Message *req) -> sys::MessagePointer
    auto QuotesAgent::handleWriteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        if (auto msg = dynamic_cast<Messages::WriteQuoteRequest *>(req)) {
            auto result = database->execute(
                Queries::writeQuote, msg->langId, msg->quote.c_str(), msg->author.c_str(), msg->enabled, msg->quoteId);
            return std::make_shared<Messages::WriteQuoteResponse>(result);
        }
        return sys::msgNotHandled();
        auto req         = std::dynamic_pointer_cast<Messages::WriteQuoteRequest>(query);
        auto queryResult = database->execute(
            Queries::writeQuote, req->langId, req->quote.c_str(), req->author.c_str(), req->enabled, req->quoteId);
        auto response = std::make_unique<Messages::WriteQuoteResponse>(queryResult);
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleDeleteQuote(sys::Message *req) -> sys::MessagePointer
    auto QuotesAgent::handleDeleteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        if (auto msg = dynamic_cast<Messages::DeleteQuoteRequest *>(req)) {
            auto result = database->execute(Queries::deleteQuote, msg->quoteId);
            return std::make_shared<Messages::DeleteQuoteResponse>(result);
        }
        return sys::msgNotHandled();
    }
        auto req = std::dynamic_pointer_cast<Messages::DeleteQuoteRequest>(query);
        database->execute(Queries::deleteQuoteFromQuoteCategoryMapTable, req->quoteId);

        auto queryResult = database->execute(Queries::deleteQuoteFromQuoteTable, req->quoteId);

        auto response = std::make_unique<Messages::DeleteQuoteResponse>(queryResult);
        response->setRequestQuery(query);
        return response;
    }
} // namespace Quotes

M module-services/service-db/agents/quotes/QuotesAgent.hpp => module-services/service-db/agents/quotes/QuotesAgent.hpp +20 -21
@@ 3,38 3,37 @@

#pragma once

#include <service-db/DatabaseAgent.hpp>
#include <service-db/QuotesMessages.hpp>
#include <Interface/Record.hpp>

namespace Quotes
{
    class QuotesAgent : public DatabaseAgent
    enum class QuotesRecordField
    {
    };

    class QuotesAgent : public RecordInterface<QuoteRecord, QuotesRecordField>
    {
      public:
        explicit QuotesAgent(sys::Service *parentService);
        explicit QuotesAgent(Database *quotesDB);
        ~QuotesAgent() = default;

        void initDb() override
        {}
        void deinitDb() override
        {}
        void registerMessages() override;
        auto getAgentName() -> const std::string override;
        auto runQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;

      protected:
        auto handleCategoryList(sys::Message *req) -> sys::MessagePointer;
        auto handleQuotesList(sys::Message *req) -> sys::MessagePointer;
        auto handleQuotesListByCategoryId(sys::Message *req) -> sys::MessagePointer;
        auto handleEnableCategoryById(sys::Message *req) -> sys::MessagePointer;
        auto handleEnableQuoteById(sys::Message *req) -> sys::MessagePointer;
        auto handleEnabledQuotesList(sys::Message *req) -> sys::MessagePointer;
        auto handleAddQuote(sys::Message *req) -> sys::MessagePointer;
        auto handleReadQuote(sys::Message *req) -> sys::MessagePointer;
        auto handleWriteQuote(sys::Message *req) -> sys::MessagePointer;
        auto handleDeleteQuote(sys::Message *req) -> sys::MessagePointer;
        auto handleCategoryList(std::shared_ptr<db::Query> msg) -> std::unique_ptr<db::QueryResult>;
        auto handleEnableCategoryById(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleQuotesList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleQuotesListByCategoryId(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleQuotesListFromCustomCategory(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleEnabledQuotesList(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleEnableQuoteById(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleAddQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleReadQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleWriteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleDeleteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;

      private:
        auto getDbInitString() -> const std::string override;
        auto getDbFilePath() -> const std::string override;
        Database *database;
    };
} // namespace Quotes

M module-services/service-db/agents/quotes/QuotesQueries.hpp => module-services/service-db/agents/quotes/QuotesQueries.hpp +35 -3
@@ 28,11 28,33 @@ namespace Quotes::Queries
                            and
                            QCM.category_id = '%lu'
                            and
                            QT.enabled = TRUE
                            CT.enabled = TRUE
                        )sql";

    constexpr auto getQuotesFromCustomCategory = R"sql(
                        SELECT QT.quote_id, QT.lang_id, QT.quote, QT.author, QT.enabled
                        FROM
                            quote_table as QT,
                            quote_category_map as QCM,
                            category_table as CT
                        WHERE
                            QCM.quote_id = QT.quote_id
                            and 
                            QCM.category_id = CT.category_id
                            and
                            CT.category_name = 'Custom'
                            and
                            CT.enabled = TRUE
                        )sql";

    constexpr auto getCustomCategoryId = R"sql(
                        SELECT category_id, category_name, enabled
                        FROM
                            category_table
                        WHERE
                            category_name = 'Custom'
                        )sql";

    constexpr auto enableCategory = R"sql(
                        UPDATE category_table SET enabled = '%d'
                        WHERE category_id = '%lu';


@@ 59,11 81,16 @@ namespace Quotes::Queries
                            CT.enabled = TRUE
                        )sql";

    constexpr auto addQuote = R"sql(
    constexpr auto addQuoteToQuoteTable = R"sql(
                        INSERT INTO quote_table (lang_id, quote, author, enabled)
                        VALUES ('%lu', '%q' , '%q', '%d');
                        )sql";

    constexpr auto addQuoteToQuoteCategoryMapTable = R"sql(
                        INSERT INTO quote_category_map (category_id, quote_id)
                        VALUES ('%lu', '%lu');
                        )sql";

    constexpr auto readQuote = R"sql(
                        SELECT quote_id, lang_id, quote, author, enabled
                        FROM quote_table


@@ 76,7 103,12 @@ namespace Quotes::Queries
                        WHERE quote_id = '%lu';
                        )sql";

    constexpr auto deleteQuote = R"sql(
    constexpr auto deleteQuoteFromQuoteCategoryMapTable = R"sql(
                        DELETE FROM quote_category_map
                        WHERE quote_id = '%lu';
                        )sql";

    constexpr auto deleteQuoteFromQuoteTable = R"sql(
                        DELETE FROM quote_table
                        WHERE quote_id = '%lu';
                        )sql";

M module-services/service-db/service-db/FileIndexerMessages.hpp => module-services/service-db/service-db/FileIndexerMessages.hpp +5 -3
@@ 117,10 117,12 @@ namespace FileIndexer
        {
          public:
            GetListDirRequest() = default;
            explicit GetListDirRequest(std::unique_ptr<ListDir> listDir)
                : FileIndexerMessage(), listDir(std::move(listDir))
            explicit GetListDirRequest(unsigned int offset, unsigned int limit, const std::string &directory)
                : FileIndexerMessage(), offset(offset), limit(limit), directory(directory)
            {}
            std::unique_ptr<ListDir> listDir;
            const unsigned int offset   = 0;
            const unsigned int limit    = 0;
            const std::string directory = std::string();
        };

        class GetListDirResponse : public sys::ResponseMessage

M module-services/service-db/service-db/PagedData.hpp => module-services/service-db/service-db/PagedData.hpp +8 -9
@@ 9,25 9,23 @@
template <typename RecordType> struct PagedData
{
    std::vector<RecordType> data;
    unsigned int limit;
    unsigned int offset;
    unsigned int count;
};

template <typename PagedDataType, typename RecordType>
auto getList(std::unique_ptr<PagedDataType> pagedData, std::unique_ptr<QueryResult> query)
auto getList(unsigned int offset, unsigned int limit, std::shared_ptr<QueryResult> query)
    -> std::unique_ptr<PagedDataType>
{
    auto pagedData = std::make_unique<PagedDataType>();
    if (nullptr == query || DatabaseAgent::ZERO_ROWS_FOUND == query->getRowCount()) {
        pagedData->count = 0;
        pagedData->data.clear();
        return pagedData;
    }
    pagedData->count   = query->getRowCount();
    unsigned int index = 0;

    // returns all records
    if (pagedData->limit == 0) {
    if (limit == 0) {
        do {
            pagedData->data.push_back(RecordType(query.get()));
        } while (query->nextRow());


@@ 35,23 33,24 @@ auto getList(std::unique_ptr<PagedDataType> pagedData, std::unique_ptr<QueryResu
    }

    // validate offset against count
    if (pagedData->offset > pagedData->count) {
    if (offset > pagedData->count) {
        pagedData->count = 0;
        return pagedData;
    }

    unsigned int index = 0;
    // navigate to the proper record specified by offset
    if (pagedData->offset > 0) {
    if (offset > 0) {
        do {
            index++;
        } while (query->nextRow() && (index < pagedData->offset));
        } while (query->nextRow() && (index < offset));
    }

    // add records to the list starting from offset and limited by limit
    do {
        pagedData->data.push_back(RecordType(query.get()));
        index++;
    } while (query->nextRow() && (index < pagedData->offset + pagedData->limit));
    } while (query->nextRow() && (index < offset + limit));

    // returns records in chunks (offset,limit)
    return pagedData;

M module-services/service-db/service-db/QuotesMessages.hpp => module-services/service-db/service-db/QuotesMessages.hpp +194 -57
@@ 3,11 3,11 @@

#pragma once

#include <Common/Query.hpp>
#include <MessageType.hpp>
#include <module-db/Database/QueryResult.hpp>
#include "PagedData.hpp"
#include <Service/Message.hpp>

#include <memory>
#include <vector>



@@ 62,28 62,26 @@ namespace Quotes

    namespace Messages
    {
        class QuotesMessage : public sys::DataMessage
        class GetCategoryListRequest : public db::Query
        {
          public:
            explicit QuotesMessage(MessageType type = MessageType::Quotes) : sys::DataMessage(type){};
            ~QuotesMessage() override = default;
        };

        class GetCategoryListRequest : public QuotesMessage
        {
          public:
            explicit GetCategoryListRequest(std::unique_ptr<CategoryList> categoryList)
                : QuotesMessage(), categoryList(std::move(categoryList))
            explicit GetCategoryListRequest(unsigned int offset, unsigned int limit)
                : Query(Query::Type::Read), offset(offset), limit(limit)
            {}
            std::unique_ptr<CategoryList> categoryList;
            const unsigned int offset;
            const unsigned int limit;

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

        class GetCategoryListResponse : public sys::ResponseMessage
        class GetCategoryListResponse : public db::QueryResult
        {
          public:
            explicit GetCategoryListResponse(std::unique_ptr<CategoryList> categoryList,
                                             sys::ReturnCodes code = sys::ReturnCodes::Success)
                : sys::ResponseMessage(code), categoryList(std::move(categoryList))
            explicit GetCategoryListResponse(std::unique_ptr<CategoryList> categoryList)
                : categoryList(std::move(categoryList))
            {}

            [[nodiscard]] unsigned int getCount() const noexcept


@@ 96,24 94,34 @@ namespace Quotes
                return categoryList->data;
            }

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

          private:
            std::unique_ptr<CategoryList> categoryList;
        };

        class GetQuotesListRequest : public QuotesMessage
        class GetQuotesListRequest : public db::Query
        {
          public:
            explicit GetQuotesListRequest(std::unique_ptr<QuotesList> quotesList) : quotesList(std::move(quotesList))
            explicit GetQuotesListRequest(unsigned int offset, unsigned int limit)
                : Query(Query::Type::Read), offset(offset), limit(limit)
            {}
            std::unique_ptr<QuotesList> quotesList;
            const unsigned int offset;
            const unsigned int limit;

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

        class GetQuotesListResponse : public sys::ResponseMessage
        class GetQuotesListResponse : public db::QueryResult
        {
          public:
            explicit GetQuotesListResponse(std::unique_ptr<QuotesList> quotesList,
                                           sys::ReturnCodes code = sys::ReturnCodes::Success)
                : sys::ResponseMessage(code), quotesList(std::move(quotesList))
            explicit GetQuotesListResponse(std::unique_ptr<QuotesList> quotesList) : quotesList(std::move(quotesList))
            {}

            [[nodiscard]] unsigned int getCount() const noexcept


@@ 126,26 134,36 @@ namespace Quotes
                return quotesList->data;
            }

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

          private:
            std::unique_ptr<QuotesList> quotesList;
        };

        class GetQuotesListByCategoryIdRequest : public QuotesMessage
        class GetQuotesListByCategoryIdRequest : public db::Query
        {
          public:
            explicit GetQuotesListByCategoryIdRequest(std::unique_ptr<QuotesList> quotesList, unsigned int categoryId)
                : quotesList(std::move(quotesList)), categoryId(categoryId)
            explicit GetQuotesListByCategoryIdRequest(unsigned int offset, unsigned int limit, unsigned int categoryId)
                : Query(Query::Type::Read), offset(offset), limit(limit), categoryId(categoryId)
            {}
            std::unique_ptr<QuotesList> quotesList;
            const unsigned int offset;
            const unsigned int limit;
            const unsigned int categoryId;

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

        class GetQuotesListByCategoryIdResponse : public sys::ResponseMessage
        class GetQuotesListByCategoryIdResponse : public db::QueryResult
        {
          public:
            explicit GetQuotesListByCategoryIdResponse(std::unique_ptr<QuotesList> quotesList,
                                                       sys::ReturnCodes code = sys::ReturnCodes::Success)
                : sys::ResponseMessage(code), quotesList(std::move(quotesList))
            explicit GetQuotesListByCategoryIdResponse(std::unique_ptr<QuotesList> quotesList)
                : quotesList(std::move(quotesList))
            {}

            [[nodiscard]] unsigned int getCount() const noexcept


@@ 158,60 176,132 @@ namespace Quotes
                return quotesList->data;
            }

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

          private:
            std::unique_ptr<QuotesList> quotesList;
        };

        class EnableCategoryByIdRequest : public QuotesMessage
        class GetQuotesListFromCustomCategoryRequest : public db::Query
        {
          public:
            explicit GetQuotesListFromCustomCategoryRequest(unsigned int offset, unsigned int limit)
                : Query(Query::Type::Read), offset(offset), limit(limit)
            {}
            const unsigned int offset;
            const unsigned int limit;

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

        class GetQuotesListFromCustomCategoryResponse : public db::QueryResult
        {
          public:
            explicit GetQuotesListFromCustomCategoryResponse(std::unique_ptr<QuotesList> quotesList)
                : quotesList(std::move(quotesList))
            {}

            [[nodiscard]] unsigned int getCount() const noexcept
            {
                return quotesList->count;
            }

            [[nodiscard]] auto getResults() const -> std::vector<QuoteRecord>
            {
                return quotesList->data;
            }

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

          private:
            std::unique_ptr<QuotesList> quotesList;
        };

        class EnableCategoryByIdRequest : public db::Query
        {
          public:
            explicit EnableCategoryByIdRequest(unsigned int categoryId, bool enable)
                : categoryId(categoryId), enable(enable)
                : Query(Query::Type::Read), categoryId(categoryId), enable(enable)
            {}
            const unsigned int categoryId;
            const bool enable;

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

        class EnableCategoryByIdResponse : public QuotesMessage
        class EnableCategoryByIdResponse : public db::QueryResult
        {
          public:
            explicit EnableCategoryByIdResponse(bool success) : success(success)
            {}
            const bool success;

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

        class EnableQuoteByIdRequest : public QuotesMessage
        class EnableQuoteByIdRequest : public db::Query
        {
          public:
            explicit EnableQuoteByIdRequest(unsigned int quoteId, bool enable) : quoteId(quoteId), enable(enable)
            explicit EnableQuoteByIdRequest(unsigned int quoteId, bool enable)
                : Query(Query::Type::Read), quoteId(quoteId), enable(enable)
            {}
            const unsigned int quoteId;
            const bool enable;

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

        class EnableQuoteByIdResponse : public QuotesMessage
        class EnableQuoteByIdResponse : public db::QueryResult
        {
          public:
            explicit EnableQuoteByIdResponse(bool success) : success(success)
            {}
            const bool success;

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

        class GetEnabledQuotesListRequest : public QuotesMessage
        class GetEnabledQuotesListRequest : public db::Query
        {
          public:
            explicit GetEnabledQuotesListRequest(std::unique_ptr<QuotesList> quotesList)
                : quotesList(std::move(quotesList))
            explicit GetEnabledQuotesListRequest(unsigned int offset, unsigned int limit)
                : Query(Query::Type::Read), offset(offset), limit(limit)
            {}
            std::unique_ptr<QuotesList> quotesList;
            const unsigned int offset;
            const unsigned int limit;

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

        class GetEnabledQuotesListResponse : public sys::ResponseMessage
        class GetEnabledQuotesListResponse : public db::QueryResult
        {
          public:
            explicit GetEnabledQuotesListResponse(std::unique_ptr<QuotesList> quotesList,
                                                  sys::ReturnCodes code = sys::ReturnCodes::Success)
                : sys::ResponseMessage(code), quotesList(std::move(quotesList))
            explicit GetEnabledQuotesListResponse(std::unique_ptr<QuotesList> quotesList)
                : quotesList(std::move(quotesList))
            {}

            [[nodiscard]] unsigned int getCount() const noexcept


@@ 224,40 314,61 @@ namespace Quotes
                return quotesList->data;
            }

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

          private:
            std::unique_ptr<QuotesList> quotesList;
        };

        class AddQuoteRequest : public QuotesMessage
        class AddQuoteRequest : public db::Query
        {
          public:
            explicit AddQuoteRequest(unsigned int langId, std::string quote, std::string author, bool enabled)
                : langId(langId), quote(std::move(quote)), author(std::move(author)), enabled(enabled)
                : Query(Query::Type::Create), langId(langId), quote(std::move(quote)), author(std::move(author)),
                  enabled(enabled)
            {}
            const unsigned int langId;
            const std::string quote;
            const std::string author;
            const bool enabled;

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

        class AddQuoteResponse : public QuotesMessage
        class AddQuoteResponse : public db::QueryResult
        {
          public:
            explicit AddQuoteResponse(bool success, unsigned int quoteId) : success(success), quoteId(quoteId)
            {}
            const bool success;
            const unsigned int quoteId;

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

        class ReadQuoteRequest : public QuotesMessage
        class ReadQuoteRequest : public db::Query
        {
          public:
            explicit ReadQuoteRequest(unsigned int quoteId) : quoteId(quoteId)
            explicit ReadQuoteRequest(unsigned int quoteId) : Query(Query::Type::Read), quoteId(quoteId)
            {}
            const unsigned int quoteId;

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

        class ReadQuoteResponse : public QuotesMessage
        class ReadQuoteResponse : public db::QueryResult
        {
          public:
            explicit ReadQuoteResponse(


@@ 269,44 380,70 @@ namespace Quotes
            const std::string quote;
            const std::string author;
            const bool enabled;

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

        class WriteQuoteRequest : public QuotesMessage
        class WriteQuoteRequest : public db::Query
        {
          public:
            explicit WriteQuoteRequest(
                unsigned int quoteId, unsigned int langId, std::string quote, std::string author, bool enabled)
                : quoteId(quoteId), langId(langId), quote(std::move(quote)), author(std::move(author)), enabled(enabled)
                : Query(Query::Type::Update), quoteId(quoteId), langId(langId), quote(std::move(quote)),
                  author(std::move(author)), enabled(enabled)
            {}
            const unsigned int quoteId;
            const unsigned int langId;
            const std::string quote;
            const std::string author;
            const bool enabled;

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

        class WriteQuoteResponse : public QuotesMessage
        class WriteQuoteResponse : public db::QueryResult
        {
          public:
            explicit WriteQuoteResponse(bool success) : success(success)
            {}
            const bool success;

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

        class DeleteQuoteRequest : public QuotesMessage
        class DeleteQuoteRequest : public db::Query
        {
          public:
            explicit DeleteQuoteRequest(unsigned int quoteId) : quoteId(quoteId)
            explicit DeleteQuoteRequest(unsigned int quoteId) : Query(Query::Type::Delete), quoteId(quoteId)
            {}
            const unsigned int quoteId;

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

        class DeleteQuoteResponse : public QuotesMessage
        class DeleteQuoteResponse : public db::QueryResult
        {
          public:
            explicit DeleteQuoteResponse(bool success) : success(success)
            {}
            const bool success;

            auto debugInfo() const -> std::string
            {
                return "DeleteQuoteResponse";
            }
        };
    } // namespace Messages
} // namespace Quotes

M module-services/service-db/test/test-service-db-quotes.cpp => module-services/service-db/test/test-service-db-quotes.cpp +45 -64
@@ 5,71 5,54 @@

#include "test-service-db-quotes.hpp"

#include <purefs/filesystem_paths.hpp>
#include <iostream>

using namespace Quotes;

constexpr auto totalNumOfCategoriesInDb = 6;
constexpr auto totalNumOfQuotesInDb     = 48;
constexpr auto numOfQuotesFromCustomCategory       = 6;
constexpr auto numOfQuotesWithCategoryIdEqualToOne = 9;

TEST_CASE("Quotes")
{
    Database::initialize();

    auto tester = std::make_unique<QuotesAgentTester>(nullptr);
    auto database = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "quotes.db").string().c_str());
    auto tester   = std::make_unique<QuotesAgentTester>(database.get());

    SECTION("Get categories with limit and offset")
    {
        CategoryList categoryList;
        categoryList.limit  = 2;
        categoryList.offset = 1;
        unsigned int offset = 1;
        unsigned int limit  = 2;

        auto record  = std::make_unique<CategoryList>(categoryList);
        auto request = std::make_shared<Messages::GetCategoryListRequest>(std::move(record));
        auto response =
            std::dynamic_pointer_cast<Messages::GetCategoryListResponse>(tester->handleCategoryList(request.get()));
        auto request     = std::make_shared<Messages::GetCategoryListRequest>(offset, limit);
        auto queryResult = tester->handleCategoryList(request);
        auto response    = dynamic_cast<Messages::GetCategoryListResponse *>(queryResult.get());

        REQUIRE(response);
        REQUIRE(response->getCount() == totalNumOfCategoriesInDb);
        REQUIRE(response->getResults().size() == categoryList.limit);
        REQUIRE(response->getResults().size() == limit);
    }

    SECTION("Get all categories")
    {
        CategoryList categoryList;
        categoryList.limit = 0;
        unsigned int offset = 0;
        unsigned int limit  = 0;

        auto record  = std::make_unique<CategoryList>(categoryList);
        auto request = std::make_shared<Messages::GetCategoryListRequest>(std::move(record));
        auto response =
            std::dynamic_pointer_cast<Messages::GetCategoryListResponse>(tester->handleCategoryList(request.get()));
        auto request     = std::make_shared<Messages::GetCategoryListRequest>(offset, limit);
        auto queryResult = tester->handleCategoryList(request);
        auto response    = dynamic_cast<Messages::GetCategoryListResponse *>(queryResult.get());

        REQUIRE(response);
        REQUIRE(response->getCount() == totalNumOfCategoriesInDb);
        REQUIRE(response->getResults().size() == totalNumOfCategoriesInDb);
    }

    SECTION("Get quotes with limit and offset")
    {
        unsigned int limit  = 3;
        unsigned int offset = 4;

        auto response = tester->getAllQuotes(limit, offset);

        REQUIRE(response->getCount() == totalNumOfQuotesInDb);
        REQUIRE(response->getResults().size() == limit);
    }

    SECTION("Get all quotes")
    {
        auto response = tester->getAllQuotes();

        REQUIRE(response->getCount() == totalNumOfQuotesInDb);
        REQUIRE(response->getResults().size() == totalNumOfQuotesInDb);
    }

    SECTION("Get quotes by category id")
    SECTION("Get quotes from custom category")
    {
        const unsigned int categoryId = 1;
        auto quotes                   = tester->getQuotesByCategoryId(categoryId);
        REQUIRE(quotes.size() == numOfQuotesWithCategoryIdEqualToOne);
        auto quotes = tester->getQuotesFromCustomCategory();
        REQUIRE(quotes.size() == numOfQuotesFromCustomCategory);
    }

    SECTION("Enable category by id")


@@ 77,8 60,8 @@ TEST_CASE("Quotes")
        bool enable                   = false;
        const unsigned int categoryId = 1;

        auto response = tester->enableCategory(categoryId, enable);
        REQUIRE(response->success);
        auto success = tester->enableCategory(categoryId, enable);
        REQUIRE(success);

        // Quotes in category one should be disabled
        auto quotes = tester->getQuotesByCategoryId(categoryId);


@@ 86,8 69,8 @@ TEST_CASE("Quotes")

        enable = true;

        response = tester->enableCategory(categoryId, enable);
        REQUIRE(response->success);
        success = tester->enableCategory(categoryId, enable);
        REQUIRE(success);

        // Quotes in category one should be enabled
        quotes = tester->getQuotesByCategoryId(categoryId);


@@ 99,8 82,8 @@ TEST_CASE("Quotes")
        bool enable                = false;
        const unsigned int quoteId = 1;

        auto response = tester->enableQuote(quoteId, enable);
        REQUIRE(response->success);
        auto success = tester->enableQuote(quoteId, enable);
        REQUIRE(success);

        // All quotes except quote with id=1 should be enabled
        auto quotes = tester->getEnabledQuotes();


@@ 108,8 91,8 @@ TEST_CASE("Quotes")

        enable = true;

        response = tester->enableQuote(quoteId, enable);
        REQUIRE(response->success);
        success = tester->enableQuote(quoteId, enable);
        REQUIRE(success);

        // All quotes should be enabled
        quotes = tester->getEnabledQuotes();


@@ 124,45 107,43 @@ TEST_CASE("Quotes")
        bool enabled        = true;

        // Add a new quote
        auto addQuoteResponse = tester->addQuote(langId, quote, author, enabled);
        REQUIRE(addQuoteResponse->success);
        const auto quoteId = addQuoteResponse->quoteId;
        auto quoteId = tester->addQuote(langId, quote, author, enabled);

        // Check if quotes count has increased
        auto getAllQuotesResponse = tester->getAllQuotes();
        auto quotes = tester->getAllQuotes();

        REQUIRE(getAllQuotesResponse->getCount() == totalNumOfQuotesInDb + 1);
        REQUIRE(getAllQuotesResponse->getResults().size() == totalNumOfQuotesInDb + 1);
        REQUIRE(quotes.size() == totalNumOfQuotesInDb + 1);

        // Read added quote
        auto readQuoteResponse = tester->readQuote(quoteId);
        auto queryResult       = tester->readQuote(quoteId);
        auto readQuoteResponse = dynamic_cast<Messages::ReadQuoteResponse *>(queryResult.get());

        REQUIRE(readQuoteResponse->quoteId == quoteId);
        REQUIRE(readQuoteResponse->quote == quote);
        REQUIRE(readQuoteResponse->author == author);

        // Change added quote (overwrite)
        quote                   = "TEST QUOTE CHANGED";
        author                  = "TEST AUTHOR CHANGED";
        auto writeQuoteResponse = tester->writeQuote(quoteId, langId, quote, author, enabled);
        REQUIRE(writeQuoteResponse->success);
        quote        = "TEST QUOTE CHANGED";
        author       = "TEST AUTHOR CHANGED";
        auto success = tester->writeQuote(quoteId, langId, quote, author, enabled);
        REQUIRE(success);

        // Read quote if values have been properly updated
        readQuoteResponse = tester->readQuote(quoteId);
        queryResult       = tester->readQuote(quoteId);
        readQuoteResponse = dynamic_cast<Messages::ReadQuoteResponse *>(queryResult.get());

        REQUIRE(readQuoteResponse->quoteId == quoteId);
        REQUIRE(readQuoteResponse->quote == quote);
        REQUIRE(readQuoteResponse->author == author);

        // Delete added quote
        auto deleteQuoteResponse = tester->deleteQuote(quoteId);
        REQUIRE(deleteQuoteResponse->success);
        success = tester->deleteQuote(quoteId);
        REQUIRE(success);

        // Check if quotes count match count before added quote
        getAllQuotesResponse = tester->getAllQuotes();
        quotes = tester->getAllQuotes();

        REQUIRE(getAllQuotesResponse->getCount() == totalNumOfQuotesInDb);
        REQUIRE(getAllQuotesResponse->getResults().size() == totalNumOfQuotesInDb);
        REQUIRE(quotes.size() == totalNumOfQuotesInDb);
    }

    Database::deinitialize();

M module-services/service-db/test/test-service-db-quotes.hpp => module-services/service-db/test/test-service-db-quotes.hpp +51 -80
@@ 11,132 11,103 @@ namespace Quotes
    class QuotesAgentTester : public QuotesAgent
    {
      public:
        QuotesAgentTester(sys::Service *parentService) : QuotesAgent(parentService){};
        QuotesAgentTester(Database *quotesDB) : QuotesAgent(quotesDB){};
        ~QuotesAgentTester() = default;

        auto handleCategoryList(Messages::GetCategoryListRequest *req) -> sys::MessagePointer
        auto handleCategoryList(std::shared_ptr<Messages::GetCategoryListRequest> query)
            -> std::unique_ptr<db::QueryResult>
        {
            return QuotesAgent::handleCategoryList(req);
            return QuotesAgent::handleCategoryList(query);
        }

        auto handleQuotesList(Messages::GetQuotesListRequest *req) -> sys::MessagePointer
        auto getAllQuotes(unsigned int limit = 0, unsigned int offset = 0) -> std::vector<QuoteRecord>
        {
            return QuotesAgent::handleQuotesList(req);
        }

        auto handleQuotesListByCategoryId(Messages::GetQuotesListByCategoryIdRequest *req) -> sys::MessagePointer
        {
            return QuotesAgent::handleQuotesListByCategoryId(req);
        }

        auto handleEnableCategoryById(Messages::EnableCategoryByIdRequest *req) -> sys::MessagePointer
        {
            return QuotesAgent::handleEnableCategoryById(req);
        }

        auto handleEnableQuoteById(Messages::EnableQuoteByIdRequest *req) -> sys::MessagePointer
        {
            return QuotesAgent::handleEnableQuoteById(req);
        }

        auto handleEnabledQuotesList(Messages::GetEnabledQuotesListRequest *req) -> sys::MessagePointer
        {
            return QuotesAgent::handleEnabledQuotesList(req);
        }

        auto handleAddQuote(Messages::AddQuoteRequest *req) -> sys::MessagePointer
        {
            return QuotesAgent::handleAddQuote(req);
        }

        auto handleReadQuote(Messages::ReadQuoteRequest *req) -> sys::MessagePointer
        {
            return QuotesAgent::handleReadQuote(req);
        }

        auto handleWriteQuote(Messages::WriteQuoteRequest *req) -> sys::MessagePointer
        {
            return QuotesAgent::handleWriteQuote(req);
            auto request     = std::make_shared<Messages::GetQuotesListRequest>(offset, limit);
            auto queryResult = handleQuotesList(request);
            auto response    = dynamic_cast<Messages::GetQuotesListResponse *>(queryResult.get());
            return response->getResults();
        }

        auto getAllQuotes(unsigned int limit = 0, unsigned int offset = 0)
            -> std::shared_ptr<Messages::GetQuotesListResponse>
        auto getQuotesFromCustomCategory() -> std::vector<QuoteRecord>
        {
            QuotesList quotesList;
            quotesList.limit  = limit;
            quotesList.offset = offset;
            unsigned int offset = 0;
            unsigned int limit  = 0;

            auto record  = std::make_unique<QuotesList>(quotesList);
            auto request = std::make_shared<Messages::GetQuotesListRequest>(std::move(record));
            return std::dynamic_pointer_cast<Messages::GetQuotesListResponse>(handleQuotesList(request.get()));
            auto request     = std::make_shared<Messages::GetQuotesListFromCustomCategoryRequest>(offset, limit);
            auto queryResult = handleQuotesListFromCustomCategory(request);
            auto response    = dynamic_cast<Messages::GetQuotesListFromCustomCategoryResponse *>(queryResult.get());
            return response->getResults();
        }

        auto getQuotesByCategoryId(unsigned int categoryId) -> std::vector<QuoteRecord>
        {
            QuotesList quotesList;
            quotesList.limit = 0;

            auto record   = std::make_unique<QuotesList>(quotesList);
            auto request  = std::make_shared<Messages::GetQuotesListByCategoryIdRequest>(std::move(record), categoryId);
            auto response = std::dynamic_pointer_cast<Messages::GetQuotesListByCategoryIdResponse>(
                handleQuotesListByCategoryId(request.get()));
            unsigned int offset = 0;
            unsigned int limit  = 0;

            auto request     = std::make_shared<Messages::GetQuotesListByCategoryIdRequest>(offset, limit, categoryId);
            auto queryResult = handleQuotesListByCategoryId(request);
            auto response    = dynamic_cast<Messages::GetQuotesListByCategoryIdResponse *>(queryResult.get());
            return response->getResults();
        }

        auto getEnabledQuotes() -> std::vector<QuoteRecord>
        {
            QuotesList quotesList;
            quotesList.limit = 0;

            auto record   = std::make_unique<QuotesList>(quotesList);
            auto request  = std::make_shared<Messages::GetEnabledQuotesListRequest>(std::move(record));
            auto response = std::dynamic_pointer_cast<Messages::GetEnabledQuotesListResponse>(
                handleEnabledQuotesList(request.get()));
            unsigned int offset = 0;
            unsigned int limit  = 0;

            auto request     = std::make_shared<Messages::GetEnabledQuotesListRequest>(offset, limit);
            auto queryResult = handleEnabledQuotesList(request);
            auto response    = dynamic_cast<Messages::GetEnabledQuotesListResponse *>(queryResult.get());
            return response->getResults();
        }

        auto enableCategory(unsigned int categoryId, bool enable)
            -> std::shared_ptr<Messages::EnableCategoryByIdResponse>
        auto enableCategory(unsigned int categoryId, bool enable) -> bool
        {
            auto request = std::make_shared<Messages::EnableCategoryByIdRequest>(categoryId, enable);
            return std::dynamic_pointer_cast<Messages::EnableCategoryByIdResponse>(
                handleEnableCategoryById(request.get()));
            auto request     = std::make_shared<Messages::EnableCategoryByIdRequest>(categoryId, enable);
            auto queryResult = handleEnableCategoryById(request);
            auto response    = dynamic_cast<Messages::EnableCategoryByIdResponse *>(queryResult.get());
            return response->success;
        }

        auto enableQuote(unsigned int quoteId, bool enable) -> std::shared_ptr<Messages::EnableQuoteByIdResponse>
        auto enableQuote(unsigned int quoteId, bool enable) -> bool
        {
            auto request = std::make_shared<Messages::EnableQuoteByIdRequest>(quoteId, enable);
            return std::dynamic_pointer_cast<Messages::EnableQuoteByIdResponse>(handleEnableQuoteById(request.get()));
            auto request     = std::make_shared<Messages::EnableQuoteByIdRequest>(quoteId, enable);
            auto queryResult = handleEnableQuoteById(request);
            auto response    = dynamic_cast<Messages::EnableQuoteByIdResponse *>(queryResult.get());
            return response->success;
        }

        auto addQuote(unsigned int langId, std::string quote, std::string author, bool enabled)
            -> std::shared_ptr<Messages::AddQuoteResponse>
        auto addQuote(unsigned int langId, std::string quote, std::string author, bool enabled) -> unsigned int
        {
            auto request =
                std::make_shared<Messages::AddQuoteRequest>(langId, std::move(quote), std::move(author), enabled);
            return std::dynamic_pointer_cast<Messages::AddQuoteResponse>(handleAddQuote(request.get()));
            auto queryResult = handleAddQuote(request);
            auto response    = dynamic_cast<Messages::AddQuoteResponse *>(queryResult.get());
            return response->quoteId;
        }

        auto readQuote(unsigned int quoteId) -> std::shared_ptr<Messages::ReadQuoteResponse>
        auto readQuote(unsigned int quoteId) -> std::unique_ptr<db::QueryResult>
        {
            auto request = std::make_shared<Messages::ReadQuoteRequest>(quoteId);
            return std::dynamic_pointer_cast<Messages::ReadQuoteResponse>(handleReadQuote(request.get()));
            return handleReadQuote(request);
        }

        auto writeQuote(unsigned int quoteId, unsigned int langId, std::string quote, std::string author, bool enabled)
            -> std::shared_ptr<Messages::WriteQuoteResponse>
            -> bool
        {
            auto request = std::make_shared<Messages::WriteQuoteRequest>(
                quoteId, langId, std::move(quote), std::move(author), enabled);
            return std::dynamic_pointer_cast<Messages::WriteQuoteResponse>(handleWriteQuote(request.get()));
            auto queryResult = handleWriteQuote(request);
            auto response    = dynamic_cast<Messages::WriteQuoteResponse *>(queryResult.get());
            return response->success;
        }

        auto deleteQuote(unsigned int quoteId) -> std::shared_ptr<Messages::DeleteQuoteResponse>
        auto deleteQuote(unsigned int quoteId) -> bool
        {
            auto request = std::make_shared<Messages::DeleteQuoteRequest>(quoteId);
            return std::dynamic_pointer_cast<Messages::DeleteQuoteResponse>(handleDeleteQuote(request.get()));
            auto request     = std::make_shared<Messages::DeleteQuoteRequest>(quoteId);
            auto queryResult = handleDeleteQuote(request);
            auto response    = dynamic_cast<Messages::DeleteQuoteResponse *>(queryResult.get());
            return response->success;
        }
    };
} // namespace Quotes

M module-services/service-db/test/test-settings/Database.cpp => module-services/service-db/test/test-settings/Database.cpp +1 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "module-db/Database/Database.hpp"