~aleteoryx/muditaos

898de0540cf40c516edc2bac6456616953035989 — Mateusz Grzegorzek 5 years ago 20fe1b8
[EGD-5932] Create Quotes agent

- add Quotes agent,
- implement get all quotes
  and get all categories queries,
- extract PagedData from ListDirData
  and reuse it in QuotesMessages,
- unify naming in Quotes and FileIndexer,
- add Quotes agent unit tests
M enabled_unittests => enabled_unittests +1 -0
@@ 211,6 211,7 @@ TESTS_LIST["catch2-PowerManager"]="
TESTS_LIST["catch2-service-db"]="
    DB_API;
    Settings Messages;
    Quotes;
"
#---------
TESTS_LIST["catch2-service-db-settings"]="

M image/user/db/quotes_002.sql => image/user/db/quotes_002.sql +9 -9
@@ 2,11 2,11 @@
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

INSERT OR REPLACE INTO quote_languages (lang_id, lang_name) VALUES
    (1, "Polish"),
    (2, "English"),
    (3, "Spanish"),
    (4, "French"),
    (5, "German");
    (1, 'Polish'),
    (2, 'English'),
    (3, 'Spanish'),
    (4, 'French'),
    (5, 'German');


INSERT OR REPLACE INTO category_table (category_id, category_name) VALUES


@@ 59,10 59,10 @@ INSERT OR REPLACE INTO quote_table (quote_id, lang_id, quote, author) VALUES
    (38, 3, 'La única manera de vivir es aceptar cada minuto como un milagro irrepetible.', 'Tara Brach'),
    (39, 3, 'Las cosas más simples son a menudo las más reales.', 'Richard Bach'),
    (40, 3, 'Di siempre "sí" al momento presente. Di "sí" a la vida.', 'Eckhart Tolle'),
    (41, 4, "Ne t'attarde pas sur le passé, ne rêve pas de l'avenir, concentre-toi sur le moment présent.", 'Buddha'),
    (42, 4, "La seule façon de vivre est d'accepter chaque minute comme un miracle non répétable.", 'Tara Brach'),
    (43, 4, "Les choses les plus simples sont souvent les plus vraies.", 'Richard Bach'),
    (44, 4, "Dites toujours “oui” au moment présent. Dites “oui” à la vie.", 'Eckhart Tolle'),
    (41, 4, 'Ne t''attarde pas sur le passé, ne rêve pas de l''avenir, concentre-toi sur le moment présent.', 'Buddha'),
    (42, 4, 'La seule façon de vivre est d''accepter chaque minute comme un miracle non répétable.', 'Tara Brach'),
    (43, 4, 'Les choses les plus simples sont souvent les plus vraies.', 'Richard Bach'),
    (44, 4, 'Dites toujours “oui” au moment présent. Dites “oui” à la vie.', 'Eckhart Tolle'),
    (45, 5, 'Bleibe nicht in der Vergangenheit stecken und träume nicht von der Zukunft. Konzentriere dich auf das Jetzt.', 'Buddha'),
    (46, 5, 'Die einzige Art, zu leben, besteht darin, jede Minute als unwiederbringliches Wunder anzusehen.', 'Tara Brach'),
    (47, 5, 'Die einfachsten Dinge sind manchmal die echtesten', 'Richard Bach'),

M module-services/service-db/CMakeLists.txt => module-services/service-db/CMakeLists.txt +1 -0
@@ 21,6 21,7 @@ set(SOURCES
    agents/settings/Settings.cpp
    agents/settings/SettingsCache.cpp
    agents/file_indexer/FileIndexerAgent.cpp
    agents/quotes/QuotesAgent.cpp
)

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

M module-services/service-db/ServiceDB.cpp => module-services/service-db/ServiceDB.cpp +2 -0
@@ 15,6 15,7 @@
#include "service-db/QueryMessage.hpp"
#include "service-db/DatabaseAgent.hpp"
#include "agents/settings/SettingsAgent.hpp"
#include "agents/quotes/QuotesAgent.hpp"

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


@@ 549,6 550,7 @@ sys::ReturnCodes ServiceDB::InitHandler()

    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();

M module-services/service-db/agents/file_indexer/FileIndexerAgent.cpp => module-services/service-db/agents/file_indexer/FileIndexerAgent.cpp +19 -54
@@ 17,7 17,7 @@ FileIndexerAgent::FileIndexerAgent(sys::Service *parentService) : DatabaseAgent(
void FileIndexerAgent::initDb()
{
    auto notifications = database->query(FileIndexer::Statements::getAllNotifications);
    if (nullptr == notifications || FileIndexer::ONE_ROW_FOUND == notifications->getRowCount()) {
    if (nullptr == notifications || DatabaseAgent::ONE_ROW_FOUND == notifications->getRowCount()) {
        return;
    }
    if (notifications->getFieldCount() == FileIndexer::NOTIFICATION_RECORD_COLUMN_COUNT) {


@@ 41,7 41,7 @@ void FileIndexerAgent::registerMessages()
    using std::placeholders::_1;

    // all API asynchronic
    parentService->connect(FileIndexer::Messages::GetListDirMessage(),
    parentService->connect(FileIndexer::Messages::GetListDirRequest(),
                           std::bind(&FileIndexerAgent::handleListDir, this, _1));

    parentService->connect(FileIndexer::Messages::GetRecordMessage(),


@@ 87,7 87,7 @@ auto FileIndexerAgent::getAgentName() -> const std::string
auto FileIndexerAgent::dbRegisterFileChange(std::string dir, std::string service) -> bool
{
    auto retQuery = database->query(FileIndexer::Statements::getNotification, dir.c_str(), service.c_str());
    if (nullptr == retQuery || FileIndexer::ZERO_ROWS_FOUND == retQuery->getRowCount()) {
    if (nullptr == retQuery || DatabaseAgent::ZERO_ROWS_FOUND == retQuery->getRowCount()) {
        // notification does not exist in db, insert a new one
        return database->execute(FileIndexer::Statements::setNotification, dir.c_str(), service.c_str());
    }


@@ 142,7 142,7 @@ auto FileIndexerAgent::handleUnregisterOnFileChange(sys::Message *req) -> sys::M
auto FileIndexerAgent::dbGetFilesCount() -> unsigned int
{
    auto retQuery = database->query(FileIndexer::Statements::getFilesCount);
    if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
    if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
        return 0;
    }
    return (*retQuery)[0].getUInt32();


@@ 151,57 151,22 @@ auto FileIndexerAgent::dbGetFilesCount() -> unsigned int
auto FileIndexerAgent::dbGeMetadataCount() -> unsigned int
{
    auto retQuery = database->query(FileIndexer::Statements::getMetadataCount);
    if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
    if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
        return 0;
    }
    return (*retQuery)[0].getUInt32();
}

auto FileIndexerAgent::dbListDir(std::unique_ptr<FileIndexer::ListDirData> listDir)
    -> std::unique_ptr<FileIndexer::ListDirData>
auto FileIndexerAgent::dbListDir(std::unique_ptr<FileIndexer::ListDir> listDir) -> std::unique_ptr<FileIndexer::ListDir>
{
    auto retQuery = database->query(FileIndexer::Statements::getFilesByDir, listDir->directory.c_str());
    if (nullptr == retQuery || FileIndexer::ZERO_ROWS_FOUND == retQuery->getRowCount()) {
        listDir->count = 0;
        listDir->fileList.clear();
        return listDir;
    }
    listDir->count     = retQuery->getRowCount();
    unsigned int index = 0;

    // returns all files in directory
    if (listDir->list_limit == 0) {
        do {
            listDir->fileList.push_back(FileIndexer::FileRecord(retQuery.get()));
        } while (retQuery->nextRow());
        return listDir;
    }

    // returns files from  directory in chunks (offset,limit)
    // validate offset against count
    if (listDir->list_offset > listDir->count) {
        listDir->count = 0;
        return listDir;
    }
    // Navigate to the proper row specified by offset
    if (listDir->list_offset > 0) {
        do {
            index++;
        } while (retQuery->nextRow() && (index < listDir->list_offset));
    }
    // Add records to the file list starting form list_offset and limited by list_limit
    do {
        listDir->fileList.push_back(FileIndexer::FileRecord(retQuery.get()));
        index++;
    } while (retQuery->nextRow() && (index < listDir->list_offset + listDir->list_limit));

    return listDir;
    return getList<FileIndexer::ListDir, FileIndexer::FileRecord>(std::move(listDir), std::move(retQuery));
}

auto FileIndexerAgent::handleListDir(sys::Message *req) -> sys::MessagePointer
{
    if (auto msg = dynamic_cast<FileIndexer::Messages::GetListDirMessage *>(req)) {
        return std::make_shared<FileIndexer::Messages::GetListDirResponseMessage>(dbListDir(std::move(msg->listDir)));
    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<sys::ResponseMessage>();
}


@@ 212,13 177,13 @@ auto FileIndexerAgent::dbGetRecord(std::unique_ptr<FileIndexer::FileRecord> reco

    if (false == record->path.empty()) {
        retQuery = database->query(FileIndexer::Statements::getFileInfoByPath, record->path.c_str());
        if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
        if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
            return FileIndexer::FileRecord{};
        }
    }
    else if (record->file_id != FileIndexer::FILE_ID_NOT_EXISTS) {
        retQuery = database->query(FileIndexer::Statements::getFileInfoById, record->file_id);
        if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
        if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
            return FileIndexer::FileRecord{};
        }
    }


@@ 233,7 198,7 @@ auto FileIndexerAgent::dbSetRecord(std::unique_ptr<FileIndexer::FileRecord> reco
    auto retQuery = database->query(FileIndexer::Statements::getFileInfoByPath, record->path.c_str());
    FileIndexer::FileRecord retRecord(retQuery.get());

    if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
    if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
        // file do not exist in db, insert a new file record
        return database->execute(FileIndexer::Statements::insertFileInfo,
                                 // retRecord.file_id,


@@ 260,7 225,7 @@ auto FileIndexerAgent::dbDeleteFile(std::unique_ptr<FileIndexer::FileRecord> rec
    unsigned int file_id = FileIndexer::FILE_ID_NOT_EXISTS;
    if (false == record->path.empty()) {
        auto retQuery = database->query(FileIndexer::Statements::getFileIdByPath, record->path.c_str());
        if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
        if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
            return false;
        }
        else {


@@ 278,7 243,7 @@ auto FileIndexerAgent::dbDeleteAllFilesInDir(std::unique_ptr<FileIndexer::FileRe
    bool retStatus = false;
    if (false == record->directory.empty()) {
        auto retQuery = database->query(FileIndexer::Statements::getFilesIdByDir, record->directory.c_str());
        if (nullptr == retQuery || FileIndexer::ZERO_ROWS_FOUND == retQuery->getRowCount()) {
        if (nullptr == retQuery || DatabaseAgent::ZERO_ROWS_FOUND == retQuery->getRowCount()) {
            return false;
        }
        else {


@@ 392,7 357,7 @@ auto FileIndexerAgent::dbGetProperty(std::unique_ptr<FileIndexer::FileMetadata> 
    std::unique_ptr<QueryResult> retQuery = nullptr;

    retQuery = database->query(FileIndexer::Statements::getPropertyValue, property.c_str(), metaData->path.c_str());
    if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
    if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
        return *metaData;
    }
    FileIndexer::FileMetadata retMetaData;


@@ 429,14 394,14 @@ auto FileIndexerAgent::dbSetProperty(std::unique_ptr<FileIndexer::FileMetadata> 

    if (false == metaData->path.empty()) {
        retQuery = database->query(FileIndexer::Statements::getFileInfoByPath, metaData->path.c_str());
        if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
        if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
            return false;
        }
        fileId = (*retQuery)[0].getUInt32();
    }
    else if (metaData->file_id != FileIndexer::FILE_ID_NOT_EXISTS) {
        retQuery = database->query(FileIndexer::Statements::getFileInfoById, metaData->file_id);
        if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
        if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
            return false;
        }
        fileId = metaData->file_id;


@@ 460,14 425,14 @@ auto FileIndexerAgent::dbSetProperties(std::unique_ptr<FileIndexer::FileMetadata

    if (false == metaData->path.empty()) {
        retQuery = database->query(FileIndexer::Statements::getFileInfoByPath, metaData->path.c_str());
        if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
        if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
            return false;
        }
        fileId = (*retQuery)[0].getUInt32();
    }
    else if (metaData->file_id != FileIndexer::FILE_ID_NOT_EXISTS) {
        retQuery = database->query(FileIndexer::Statements::getFileInfoById, metaData->file_id);
        if (nullptr == retQuery || FileIndexer::ONE_ROW_FOUND != retQuery->getRowCount()) {
        if (nullptr == retQuery || DatabaseAgent::ONE_ROW_FOUND != retQuery->getRowCount()) {
            return false;
        }
        fileId = metaData->file_id;

M module-services/service-db/agents/file_indexer/FileIndexerAgent.hpp => module-services/service-db/agents/file_indexer/FileIndexerAgent.hpp +2 -2
@@ 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


@@ 40,7 40,7 @@ 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::ListDirData> listDir) -> std::unique_ptr<FileIndexer::ListDirData>;
    auto dbListDir(std::unique_ptr<FileIndexer::ListDir> listDir) -> 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;

A module-services/service-db/agents/quotes/QuotesAgent.cpp => module-services/service-db/agents/quotes/QuotesAgent.cpp +61 -0
@@ 0,0 1,61 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesAgent.hpp"
#include "QuotesQueries.hpp"

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

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

    void QuotesAgent::registerMessages()
    {
        using std::placeholders::_1;

        parentService->connect(Messages::GetCategoryListRequest(),
                               std::bind(&QuotesAgent::handleCategoryList, this, _1));
        parentService->connect(Messages::GetQuotesListRequest(), std::bind(&QuotesAgent::handleQuotesList, this, _1));
    }

    auto QuotesAgent::getDbFilePath() -> const std::string
    {
        return (purefs::dir::getUserDiskPath() / "quotes.db").string();
    }

    auto QuotesAgent::getDbInitString() -> const std::string
    {
        return {};
    }

    auto QuotesAgent::getAgentName() -> const std::string
    {
        return std::string("quotesAgent");
    }

    auto QuotesAgent::handleCategoryList(sys::Message *req) -> sys::MessagePointer
    {
        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 app::msgNotHandled();
    }

    auto QuotesAgent::handleQuotesList(sys::Message *req) -> sys::MessagePointer
    {
        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 app::msgNotHandled();
    }

} // namespace Quotes

A module-services/service-db/agents/quotes/QuotesAgent.hpp => module-services/service-db/agents/quotes/QuotesAgent.hpp +32 -0
@@ 0,0 1,32 @@
// 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 <service-db/DatabaseAgent.hpp>
#include <service-db/QuotesMessages.hpp>

namespace Quotes
{
    class QuotesAgent : public DatabaseAgent
    {
      public:
        QuotesAgent(sys::Service *parentService);
        ~QuotesAgent() = default;

        void initDb() override
        {}
        void deinitDb() override
        {}
        void registerMessages() override;
        auto getAgentName() -> const std::string override;

      protected:
        auto handleCategoryList(sys::Message *req) -> sys::MessagePointer;
        auto handleQuotesList(sys::Message *req) -> sys::MessagePointer;

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

A module-services/service-db/agents/quotes/QuotesQueries.hpp => module-services/service-db/agents/quotes/QuotesQueries.hpp +17 -0
@@ 0,0 1,17 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

namespace Quotes::Queries
{
    constexpr auto getAllCategories = R"sql(
                        SELECT category_id, category_name, enabled
                        FROM category_table;
                        )sql";

    constexpr auto getAllQuotes = R"sql(
                        SELECT quote_id, lang_id, quote, author, enabled
                        FROM quote_table;
                        )sql";
} // namespace Quotes::Queries

M module-services/service-db/service-db/DatabaseAgent.hpp => module-services/service-db/service-db/DatabaseAgent.hpp +3 -0
@@ 34,6 34,9 @@ class DatabaseAgent
    }
    [[nodiscard]] virtual auto getDbFilePath() -> const std::string = 0;

    static constexpr auto ZERO_ROWS_FOUND = 0;
    static constexpr auto ONE_ROW_FOUND   = 1;

  protected:
    [[nodiscard]] virtual auto getDbInitString() -> const std::string = 0;


M module-services/service-db/service-db/FileIndexerMessages.hpp => module-services/service-db/service-db/FileIndexerMessages.hpp +14 -20
@@ 1,10 1,11 @@
// 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

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

#include <map>
#include <memory>


@@ 16,8 17,6 @@ namespace FileIndexer
    constexpr unsigned int FILE_ID_NOT_EXISTS       = 0;
    constexpr unsigned int FILE_RECORD_COLUMN_COUNT = 7;
    constexpr unsigned int NOTIFICATION_RECORD_COLUMN_COUNT = 3;
    constexpr unsigned int ONE_ROW_FOUND            = 1;
    constexpr unsigned int ZERO_ROWS_FOUND          = 0;

    // refers to file_tab
    struct FileRecord


@@ 74,13 73,8 @@ namespace FileIndexer
        std::map<std::string, std::string> properties; // propertyName, value
    };

    struct ListDirData : FileRecord
    {
        std::vector<FileRecord> fileList;
        unsigned int list_limit;
        unsigned int list_offset;
        unsigned int count;
    };
    struct ListDir : FileRecord, PagedData<FileRecord>
    {};

    namespace Messages
    {


@@ 119,31 113,31 @@ namespace FileIndexer
            std::unique_ptr<std::string> directory;
        };

        class GetListDirMessage : public FileIndexerMessage
        class GetListDirRequest : public FileIndexerMessage
        {
          public:
            GetListDirMessage() = default;
            explicit GetListDirMessage(std::unique_ptr<ListDirData> listDir)
            GetListDirRequest() = default;
            explicit GetListDirRequest(std::unique_ptr<ListDir> listDir)
                : FileIndexerMessage(), listDir(std::move(listDir))
            {}
            std::unique_ptr<ListDirData> listDir;
            std::unique_ptr<ListDir> listDir;
        };

        class GetListDirResponseMessage : public sys::ResponseMessage
        class GetListDirResponse : public sys::ResponseMessage
        {
          public:
            explicit GetListDirResponseMessage(std::unique_ptr<ListDirData> listDir,
                                               sys::ReturnCodes code = sys::ReturnCodes::Success)
            explicit GetListDirResponse(std::unique_ptr<ListDir> listDir,
                                        sys::ReturnCodes code = sys::ReturnCodes::Success)
                : sys::ResponseMessage(code), listDir(std::move(listDir))
            {}
            std::unique_ptr<ListDirData> listDir;
            std::unique_ptr<ListDir> listDir;
            unsigned int getCount()
            {
                return listDir->count;
            }
            auto getResults() -> std::vector<FileRecord>
            {
                return listDir->fileList;
                return listDir->data;
            }
        };


A module-services/service-db/service-db/PagedData.hpp => module-services/service-db/service-db/PagedData.hpp +58 -0
@@ 0,0 1,58 @@
// 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 <service-db/DatabaseAgent.hpp>
#include <vector>

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)
    -> std::unique_ptr<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) {
        do {
            pagedData->data.push_back(RecordType(query.get()));
        } while (query->nextRow());
        return pagedData;
    }

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

    // navigate to the proper record specified by offset
    if (pagedData->offset > 0) {
        do {
            index++;
        } while (query->nextRow() && (index < pagedData->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));

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

A module-services/service-db/service-db/QuotesMessages.hpp => module-services/service-db/service-db/QuotesMessages.hpp +137 -0
@@ 0,0 1,137 @@
// 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 <MessageType.hpp>
#include <module-db/Database/QueryResult.hpp>
#include "PagedData.hpp"
#include <Service/Message.hpp>

#include <memory>
#include <vector>

namespace Quotes
{
    struct CategoryRecord
    {
        unsigned int category_id;
        std::string category_name;
        bool enabled;

        [[nodiscard]] auto RecordEqual(CategoryRecord cr) const noexcept -> bool
        {
            return cr.category_name == category_name;
        }

        CategoryRecord() = default;

        explicit CategoryRecord(QueryResult *query)
        {
            category_id   = (*query)[0].getInt32();
            category_name = (*query)[1].getString();
            enabled       = (*query)[2].getBool();
        }
    };

    struct CategoryList : PagedData<CategoryRecord>
    {};

    struct QuoteRecord
    {
        unsigned int quote_id;
        unsigned int lang_id;
        std::string quote;
        std::string author;
        bool enabled;

        [[nodiscard]] auto RecordEqual(QuoteRecord qr) const noexcept -> bool
        {
            return qr.lang_id == lang_id && qr.quote == quote;
        }

        QuoteRecord() = default;

        explicit QuoteRecord(QueryResult *query)
        {
            quote_id = (*query)[0].getInt32();
            lang_id  = (*query)[1].getInt32();
            quote    = (*query)[2].getString();
            author   = (*query)[3].getString();
            enabled  = (*query)[4].getBool();
        }
    };

    struct QuotesList : PagedData<QuoteRecord>
    {};

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

        class GetCategoryListRequest : public QuotesMessage
        {
          public:
            GetCategoryListRequest() = default;
            explicit GetCategoryListRequest(std::unique_ptr<CategoryList> categoryList)
                : QuotesMessage(), categoryList(std::move(categoryList))
            {}
            std::unique_ptr<CategoryList> categoryList;
        };

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

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

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

        class GetQuotesListRequest : public QuotesMessage
        {
          public:
            GetQuotesListRequest() = default;
            explicit GetQuotesListRequest(std::unique_ptr<QuotesList> quotesList)
                : QuotesMessage(), quotesList(std::move(quotesList))
            {}
            std::unique_ptr<QuotesList> quotesList;
        };

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

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

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

M module-services/service-db/test/CMakeLists.txt => module-services/service-db/test/CMakeLists.txt +1 -0
@@ 7,6 7,7 @@ add_catch2_executable(
            test-service-db-api.cpp
            test-service-db-settings-messages.cpp
            #test-service-db-file_indexer.cpp
            test-service-db-quotes.cpp
        LIBS
            service-audio
            module-vfs

M module-services/service-db/test/test-service-db-file_indexer.cpp => module-services/service-db/test/test-service-db-file_indexer.cpp +26 -26
@@ 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 <catch2/catch.hpp>


@@ 140,23 140,23 @@ TEST_CASE("FileIndexer")

    SECTION("List dir - support for listview")
    {
        FileIndexer::ListDirData inputData{};
        FileIndexer::ListDir inputData{};
        std::cout << "List dir - support for listview" << std::endl << std::flush;

        // GetListDir
        inputData.directory   = "mp3";
        inputData.list_offset = 1;
        inputData.list_limit  = 2;
        inputData.offset      = 1;
        inputData.limit       = 2;

        auto recordPtr = std::make_unique<FileIndexer::ListDirData>(inputData);
        auto getMsg    = std::make_shared<FileIndexer::Messages::GetListDirMessage>(std::move(recordPtr));
        auto recordPtr = std::make_unique<FileIndexer::ListDir>(inputData);
        auto getMsg    = std::make_shared<FileIndexer::Messages::GetListDirRequest>(std::move(recordPtr));
        std::unique_ptr<FileIndexerTest> fiAgentTest{new FileIndexerTest(&file_indexer_client_service)};
        sys::MessagePointer recMsg = fiAgentTest->handleListDirTest(getMsg.get());
        auto msg                   = dynamic_cast<FileIndexer::Messages::GetListDirResponseMessage *>(recMsg.get());
        auto msg                   = dynamic_cast<FileIndexer::Messages::GetListDirResponse *>(recMsg.get());
        auto fileCount             = msg->getCount();
        auto fileList              = msg->getResults();
        LOG_INFO("Total number of files in %s = %d", inputData.directory.c_str(), fileCount);
        LOG_INFO("Limit= %d Offset= %d", inputData.list_limit, inputData.list_offset);
        LOG_INFO("Limit= %d Offset= %d", inputData.limit, inputData.offset);
        for (auto fileRec : fileList) {
            LOG_INFO("file id= %d file path= %s  size = %d  mime= %d  mtime = %d directory= %s ftype= %d",
                     fileRec.file_id,


@@ 167,7 167,7 @@ TEST_CASE("FileIndexer")
                     fileRec.directory.c_str(),
                     fileRec.file_type);
        }
        REQUIRE(fileList.size() == inputData.list_limit);
        REQUIRE(fileList.size() == inputData.limit);
        REQUIRE(fileList[0].directory == inputData.directory);
    }



@@ 227,7 227,7 @@ TEST_CASE("FileIndexer")

    SECTION("DeleteFile /DeleteAllFilesInDir")
    {
        FileIndexer::ListDirData inputData{};
        FileIndexer::ListDir inputData{};
        FileIndexer::FileRecord record{};
        std::cout << "DeleteFile /DeleteAllFilesInDir" << std::endl << std::flush;



@@ 256,16 256,16 @@ TEST_CASE("FileIndexer")

        std::cout << "Get listDir" << std::endl << std::flush;
        inputData.directory   = "wav";
        inputData.list_offset = 0;
        inputData.list_limit  = 10;
        auto inputDataPtr     = std::make_unique<FileIndexer::ListDirData>(inputData);
        auto getListDirMsg    = std::make_shared<FileIndexer::Messages::GetListDirMessage>(std::move(inputDataPtr));
        inputData.offset      = 0;
        inputData.limit       = 10;
        auto inputDataPtr     = std::make_unique<FileIndexer::ListDir>(inputData);
        auto getListDirMsg    = std::make_shared<FileIndexer::Messages::GetListDirRequest>(std::move(inputDataPtr));
        recMsg                = fiAgentTest->handleListDirTest(getListDirMsg.get());
        auto msg              = dynamic_cast<FileIndexer::Messages::GetListDirResponseMessage *>(recMsg.get());
        auto msg              = dynamic_cast<FileIndexer::Messages::GetListDirResponse *>(recMsg.get());
        auto fileCount        = msg->getCount();
        auto fileList         = msg->getResults();
        LOG_INFO("Total number of files in %s = %d", inputData.directory.c_str(), fileCount);
        LOG_INFO("Limit= %d Offset= %d", inputData.list_limit, inputData.list_offset);
        LOG_INFO("Limit= %d Offset= %d", inputData.limit, inputData.offset);
        for (auto fileRec : fileList) {
            LOG_INFO("file id= %d file path= %s  size = %d  mime= %d  mtime = %d directory= %s ftype= %d",
                     fileRec.file_id,


@@ 286,12 286,12 @@ TEST_CASE("FileIndexer")
        auto delRecMsg   = fiAgentTest->handleDeleteFileTest(delMsg.get());

        inputData.directory    = "wav";
        inputData.list_offset  = 0;
        inputData.list_limit   = 10;
        inputDataPtr           = std::make_unique<FileIndexer::ListDirData>(inputData);
        getListDirMsg          = std::make_shared<FileIndexer::Messages::GetListDirMessage>(std::move(inputDataPtr));
        inputData.offset       = 0;
        inputData.limit        = 10;
        inputDataPtr           = std::make_unique<FileIndexer::ListDir>(inputData);
        getListDirMsg          = std::make_shared<FileIndexer::Messages::GetListDirRequest>(std::move(inputDataPtr));
        recMsg                 = fiAgentTest->handleListDirTest(getListDirMsg.get());
        msg                    = dynamic_cast<FileIndexer::Messages::GetListDirResponseMessage *>(recMsg.get());
        msg                    = dynamic_cast<FileIndexer::Messages::GetListDirResponse *>(recMsg.get());
        auto fileCountAfterDel = msg->getCount();
        REQUIRE(fileCountAfterDel == fileCount - 1);



@@ 303,12 303,12 @@ TEST_CASE("FileIndexer")
        delRecMsg         = fiAgentTest->handleDeleteAllFilesInDirTest(delAllMsg.get());

        inputData.directory   = "wav";
        inputData.list_offset = 0;
        inputData.list_limit  = 10;
        inputDataPtr          = std::make_unique<FileIndexer::ListDirData>(inputData);
        getListDirMsg         = std::make_shared<FileIndexer::Messages::GetListDirMessage>(std::move(inputDataPtr));
        inputData.offset      = 0;
        inputData.limit       = 10;
        inputDataPtr          = std::make_unique<FileIndexer::ListDir>(inputData);
        getListDirMsg         = std::make_shared<FileIndexer::Messages::GetListDirRequest>(std::move(inputDataPtr));
        recMsg                = fiAgentTest->handleListDirTest(getListDirMsg.get());
        msg                   = dynamic_cast<FileIndexer::Messages::GetListDirResponseMessage *>(recMsg.get());
        msg                   = dynamic_cast<FileIndexer::Messages::GetListDirResponse *>(recMsg.get());
        fileCountAfterDel     = msg->getCount();
        REQUIRE(fileCountAfterDel == 0);
        std::cout << "DeleteFile /DeleteAllFilesInDir  finished" << std::endl << std::flush;

M module-services/service-db/test/test-service-db-file_indexer.hpp => module-services/service-db/test/test-service-db-file_indexer.hpp +2 -2
@@ 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


@@ 23,7 23,7 @@ class FileIndexerTest : public FileIndexerAgent
        return FileIndexerAgent::handleSetRecord(req);
    }

    auto handleListDirTest(FileIndexer::Messages::GetListDirMessage *req) -> sys::MessagePointer
    auto handleListDirTest(FileIndexer::Messages::GetListDirRequest *req) -> sys::MessagePointer
    {
        return FileIndexerAgent::handleListDir(req);
    }

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

#include <catch2/catch.hpp>

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

using namespace Quotes;

constexpr auto totalNumOfCategoriesInDb = 6;
constexpr auto totalNumOfQuotesInDb     = 48;

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

    auto tester = std::make_unique<QuotesAgentTester>(nullptr);

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

        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()));

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

    SECTION("Get all categories")
    {
        CategoryList categoryList;
        categoryList.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()));

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

    SECTION("Get quotes with limit and offset")
    {
        QuotesList quotesList;
        quotesList.limit  = 3;
        quotesList.offset = 4;

        auto record  = std::make_unique<QuotesList>(quotesList);
        auto request = std::make_shared<Messages::GetQuotesListRequest>(std::move(record));
        auto response =
            std::dynamic_pointer_cast<Messages::GetQuotesListResponse>(tester->handleQuotesList(request.get()));

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

    SECTION("Get all quotes")
    {
        QuotesList quotesList;
        quotesList.limit = 0;

        auto record  = std::make_unique<QuotesList>(quotesList);
        auto request = std::make_shared<Messages::GetQuotesListRequest>(std::move(record));
        auto response =
            std::dynamic_pointer_cast<Messages::GetQuotesListResponse>(tester->handleQuotesList(request.get()));

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

    Database::deinitialize();
}

A module-services/service-db/test/test-service-db-quotes.hpp => module-services/service-db/test/test-service-db-quotes.hpp +27 -0
@@ 0,0 1,27 @@
// 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 <service-db/QuotesMessages.hpp>
#include <service-db/agents/quotes/QuotesAgent.hpp>

namespace Quotes
{
    class QuotesAgentTester : public QuotesAgent
    {
      public:
        QuotesAgentTester(sys::Service *parentService) : QuotesAgent(parentService){};
        ~QuotesAgentTester() = default;

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

        auto handleQuotesList(Messages::GetQuotesListRequest *req) -> sys::MessagePointer
        {
            return QuotesAgent::handleQuotesList(req);
        }
    };
} // namespace Quotes

M source/MessageType.hpp => source/MessageType.hpp +3 -0
@@ 259,6 259,9 @@ enum class MessageType

    // Vibra messages
    VibraPulseMessage,

    // Quotes messages
    Quotes,
};

#endif /* SOURCE_MESSAGETYPE_HPP_ */