~aleteoryx/muditaos

0a9846e4a8eed667bc8e906af8becc0523e5de6c — Mateusz Piesta 3 years ago 1723f12
[MOS-694] Move DB initialization from OS to build scripts

* Switched to DB initialization at compile time
* Organized and cleaned up db files directories(not finished completely)
* Fixed DB related unit tests
* Minor improvements to CMake
* Small fixes for GCC12 build
148 files changed, 1879 insertions(+), 1975 deletions(-)

M CMakeLists.txt
A cmake/modules/AddDatabases.cmake
M cmake/modules/Assets.cmake
M cmake/modules/ProjectConfig.cmake
M config/apply_update.sh
D image/user/db/settings_001.sql
M module-apps/apps-common/CMakeLists.txt
M module-cellular/at/src/UrcQind.cpp
M module-db/CMakeLists.txt
A module-db/Common/Types.hpp
M module-db/Database/Database.cpp
M module-db/Database/Database.hpp
D module-db/Database/DatabaseInitializer.cpp
D module-db/Database/DatabaseInitializer.hpp
D module-db/Databases/CountryCodesDB.cpp
D module-db/Databases/CountryCodesDB.hpp
M module-db/Interface/AlarmEventRecord.cpp
M module-db/Interface/AlarmEventRecord.hpp
M module-db/Interface/BaseInterface.hpp
M module-db/Interface/CalllogRecord.hpp
M module-db/Interface/ContactRecord.cpp
M module-db/Interface/ContactRecord.hpp
D module-db/Interface/CountryCodeRecord.hpp
M module-db/Interface/MultimediaFilesRecord.cpp
M module-db/Interface/NotesRecord.hpp
M module-db/Interface/NotificationsRecord.cpp
M module-db/Interface/SMSRecord.hpp
M module-db/Interface/SMSTemplateRecord.hpp
M module-db/Interface/SettingsRecord.hpp
M module-db/Interface/ThreadRecord.hpp
M module-db/Tables/AlarmEventsTable.cpp
M module-db/Tables/CalllogTable.cpp
M module-db/Tables/ContactsAddressTable.cpp
M module-db/Tables/ContactsGroups.cpp
M module-db/Tables/ContactsNameTable.cpp
M module-db/Tables/ContactsNumberTable.cpp
M module-db/Tables/ContactsRingtonesTable.cpp
M module-db/Tables/ContactsTable.cpp
D module-db/Tables/CountryCodesTable.cpp
D module-db/Tables/CountryCodesTable.hpp
M module-db/Tables/MultimediaFilesTable.cpp
M module-db/Tables/NotesTable.cpp
M module-db/Tables/NotificationsTable.cpp
M module-db/Tables/SMSTable.cpp
M module-db/Tables/ThreadsTable.cpp
R module-db/{Databases => databases}/CalllogDB.cpp
R module-db/{Databases => databases}/CalllogDB.hpp
R module-db/{Databases => databases}/ContactsDB.cpp
R module-db/{Databases => databases}/ContactsDB.hpp
R module-db/{Databases => databases}/EventsDB.cpp
R module-db/{Databases => databases}/EventsDB.hpp
R module-db/{Databases => databases}/MultimediaFilesDB.cpp
R module-db/{Databases => databases}/MultimediaFilesDB.hpp
R module-db/{Databases => databases}/NotesDB.cpp
R module-db/{Databases => databases}/NotesDB.hpp
R module-db/{Databases => databases}/NotificationsDB.cpp
R module-db/{Databases => databases}/NotificationsDB.hpp
R module-db/{Databases => databases}/SmsDB.cpp
R module-db/{Databases => databases}/SmsDB.hpp
R {image/user/db => module-db/databases/scripts}/events_001.sql
R {image/user/db => module-db/databases/scripts}/multimedia_001.sql
M module-db/tests/AlarmEventRecord_tests.cpp
M module-db/tests/CMakeLists.txt
M module-db/tests/CalllogRecord_tests.cpp
M module-db/tests/CalllogTable_tests.cpp
M module-db/tests/ContactGroups_tests.cpp
M module-db/tests/ContactsAddressTable_tests.cpp
M module-db/tests/ContactsNameTable_tests.cpp
M module-db/tests/ContactsNumberTable_tests.cpp
M module-db/tests/ContactsRecord_tests.cpp
M module-db/tests/ContactsRingtonesTable_tests.cpp
M module-db/tests/ContactsTable_tests.cpp
D module-db/tests/DbInitializer.cpp
A module-db/tests/Helpers.cpp
A module-db/tests/Helpers.hpp
M module-db/tests/MultimediaFilesTable_tests.cpp
M module-db/tests/NotesRecord_tests.cpp
M module-db/tests/NotesTable_tests.cpp
M module-db/tests/NotificationsRecord_tests.cpp
M module-db/tests/NotificationsTable_tests.cpp
M module-db/tests/QueryInterface.cpp
M module-db/tests/SMSRecord_tests.cpp
M module-db/tests/SMSTable_tests.cpp
M module-db/tests/SMSTemplateRecord_tests.cpp
M module-db/tests/SMSTemplateTable_tests.cpp
M module-db/tests/ThreadRecord_tests.cpp
M module-db/tests/ThreadsTable_tests.cpp
D module-db/tests/common.cpp
D module-db/tests/common.hpp
D module-db/tests/test-initializer/CMakeLists.txt
D module-db/tests/test-initializer/unittest.cpp
M module-services/service-db/DBServiceAPI.cpp
M module-services/service-db/agents/settings/SettingsAgent.cpp
M module-services/service-db/test/CMakeLists.txt
A module-services/service-db/test/lang/English.json
M module-services/service-db/test/test-factory-settings.cpp
M module-services/service-db/test/test-service-db-quotes.cpp
M module-services/service-fileindexer/ServiceFileIndexer.cpp
M module-services/service-fileindexer/include/service-fileindexer/ServiceFileIndexer.hpp
M module-vfs/paths/filesystem_paths.cpp
M module-vfs/paths/include/purefs/filesystem_paths.hpp
M products/BellHybrid/BellHybridMain.cpp
M products/BellHybrid/CMakeLists.txt
M products/BellHybrid/apps/application-bell-bedtime/CMakeLists.txt
M products/BellHybrid/apps/application-bell-main/CMakeLists.txt
M products/BellHybrid/apps/application-bell-meditation-timer/data/MeditationCommon.hpp
M products/BellHybrid/apps/application-bell-settings/CMakeLists.txt
M products/BellHybrid/apps/common/include/common/data/BatteryUtils.hpp
M products/BellHybrid/services/db/ServiceDB.cpp
M products/BellHybrid/services/db/agents/MeditationStatsAgent.cpp
M products/BellHybrid/services/db/databases/MeditationStatisticsTable.cpp
R {image/user/db => products/BellHybrid/services/db/databases/scripts}/meditation_stats_001.sql
R {image/user/db => products/BellHybrid/services/db/databases/scripts}/settings_bell_001.sql
R {image/user/db => products/BellHybrid/services/db/databases/scripts}/settings_bell_002.sql
M products/BellHybrid/services/db/tests/CMakeLists.txt
M products/BellHybrid/services/db/tests/MeditationStatisticsTable_tests.cpp
M products/PurePhone/CMakeLists.txt
M products/PurePhone/PurePhoneMain.cpp
M products/PurePhone/services/db/ServiceDB.cpp
R {image/user/db => products/PurePhone/services/db/databases/scripts}/alarms_001.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/alarms_002.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/calllog_001.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/calllog_002-devel.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/contacts_001.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/contacts_002.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/contacts_003-devel.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/custom_quotes_001.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/custom_quotes_002-devel.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/notes_001.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/notes_002-devel.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/notifications_001.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/notifications_002.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/predefined_quotes_001.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/predefined_quotes_002-devel.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/predefined_quotes_002.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/settings_v2_001.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/settings_v2_002-devel.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/settings_v2_002.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/sms_001.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/sms_002.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/sms_003.sql
R {image/user/db => products/PurePhone/services/db/databases/scripts}/sms_004-devel.sql
M products/PurePhone/services/db/include/db/ServiceDB.hpp
M products/PurePhone/test/test-settings/Database.cpp
M products/PurePhone/test/test-settings/test-service-db-settings-api.cpp
M tools/generate_image.sh
M tools/generate_update_image.sh
A tools/init_databases.py
M CMakeLists.txt => CMakeLists.txt +20 -1
@@ 28,6 28,7 @@ include(DiskImage)
include(AddPackage)
include(AutoModuleOption)
include(AddDirectories)
include(AddDatabases)

message("Selected product: ${PRODUCT}")
message("Selected board:   ${BOARD}")


@@ 168,12 169,30 @@ add_custom_target(
        "Generating version info"
    )

# Create common user directories
add_directories(
        TARGET user_directories_common
        PREFIX ${CMAKE_BINARY_DIR}/sysroot/sys/user
        DEPENDS assets
        DIRECTORIES logs crash_dumps backup tmp storage audio
        DIRECTORIES logs crash_dumps backup tmp storage audio db
)

# Create and initialize common databases
add_databases_target(
        TARGET create_databases_common
        SOURCE_DIR ${CMAKE_SOURCE_DIR}/module-db/databases/scripts
        DEST_DIR ${CMAKE_BINARY_DIR}/sysroot/sys/user/db
        DEVEL ${WITH_DEVELOPMENT_FEATURES}
)
# Create and initialize product-specific databases
add_databases_target(
        TARGET create_product_databases
        SOURCE_DIR ${CMAKE_SOURCE_DIR}/products/${PRODUCT}/services/db/databases/scripts
        DEST_DIR ${CMAKE_BINARY_DIR}/sysroot/sys/user/db
        DEVEL ${WITH_DEVELOPMENT_FEATURES}
        DEPENDS create_databases_common
)

add_library(version-header INTERFACE)
target_include_directories(version-header INTERFACE $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/source/include>)
add_dependencies(version-header version)

A cmake/modules/AddDatabases.cmake => cmake/modules/AddDatabases.cmake +32 -0
@@ 0,0 1,32 @@
#[[
Example of use:
add_databases_target(
        TARGET <target_name>
        SOURCE_DIR <source_dir>
        DEST_DIR <destination_dir>
        DEVEL <true/false>
        DEPENDS <dependencies list>
)
]]#
function(add_databases_target)
    cmake_parse_arguments(
            _ARG
            ""
            "TARGET;SOURCE_DIR;DEST_DIR;DEVEL;"
            "DEPENDS"
            ${ARGN}
    )

    if (${_ARG_DEVEL} STREQUAL "ON")
        set (DEVEL --development True)
    endif()

    add_custom_target(
            ${_ARG_TARGET}
            DEPENDS ${_ARG_DEPENDS}

            COMMAND python3 ${PROJECT_SOURCE_DIR}/tools/init_databases.py --input_path ${_ARG_SOURCE_DIR} --output_path ${_ARG_DEST_DIR} ${DEVEL}
            COMMENT
            "Creating databases using schemas from: ${_ARG_SOURCE_DIR} and storing them under: ${_ARG_DEST_DIR}"
    )
endfunction()
\ No newline at end of file

M cmake/modules/Assets.cmake => cmake/modules/Assets.cmake +1 -3
@@ 23,13 23,11 @@ function(add_assets_target)
            ${_ASSETS_DEST_DIR}
        COMMAND rsync -qravu
            ${_ASSETS_SOURCE_DIR}/assets
            ${_ASSETS_SOURCE_DIR}/country-codes.db
            ${_ASSETS_DEST_DIR}/current
        COMMAND rsync -qravu ${EXCLUDED}
            ${_ASSETS_SOURCE_DIR}/user
            ${_ASSETS_DEST_DIR}
        COMMAND find ${_ASSETS_DEST_DIR} -name "*-devel*" | sed "\"s,\\(.*\\)-devel\\(.*\\),& \\1\\2,\"" | xargs --no-run-if-empty -L1 mv
        COMMENT
            "Copying assets.. add_assets_target (${_ASSETS_TARGET}) <- ${_ASSETS_DEPENDS}"
            "Copying assets, development features: ${_ASSETS_DEVEL}"
    )
endfunction()

M cmake/modules/ProjectConfig.cmake => cmake/modules/ProjectConfig.cmake +0 -3
@@ 87,6 87,3 @@ set(PROJECT_CONFIG_DEFINITIONS
        PROF_ON=${PROF_ON}
        CACHE INTERNAL ""
        )

message(STATUS "BlueKitchen selected")
set(BT_STACK "BlueKitchen")

M config/apply_update.sh => config/apply_update.sh +0 -1
@@ 99,7 99,6 @@ fi
echo "Copyind data"
cp $TMPDIR/boot.bin $PHONE_MOUNT/current/
cp $TMPDIR/Luts.bin $PHONE_MOUNT/current/
cp $TMPDIR/country-codes.db $PHONE_MOUNT/current/
cp $TMPDIR/version.json $PHONE_MOUNT/current/
cp -r $TMPDIR/assets $PHONE_MOUNT/current/


D image/user/db/settings_001.sql => image/user/db/settings_001.sql +0 -11
@@ 1,11 0,0 @@
-- Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

INSERT OR REPLACE INTO settings_tab (path, value) VALUES
    ('timeFormat', '0'),
    ('dateFormat', '1'),
    ('activeSim', '1'),
    ('lockPassHash', '0'),
    ('lockTime', '30000'),
    ('displayLanguage', 'En'),
    ('inputLanguag', 'En');

M module-apps/apps-common/CMakeLists.txt => module-apps/apps-common/CMakeLists.txt +0 -15
@@ 143,18 143,3 @@ target_compile_options(apps-common
        $<$<COMPILE_LANGUAGE:CXX>:-Wno-literal-suffix>

)

# Bell Hybrid related libs
add_library(bellgui)
target_sources( bellgui
   PRIVATE
)
target_include_directories( bellgui
   PUBLIC
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/options/type>
)

target_link_libraries( bellgui
   PRIVATE
      module-gui
)

M module-cellular/at/src/UrcQind.cpp => module-cellular/at/src/UrcQind.cpp +1 -1
@@ 40,7 40,7 @@ auto Qind::getFotaStage() const noexcept -> std::optional<FotaStage>

auto Qind::getFotaParameter() const noexcept -> std::string
{
    if (isFotaValid() && tokens.size() >= fotaMinTokenSize) {
    if (isFotaValid() && tokens.size() > fotaMinTokenSize) {
        return tokens[Param];
    }
    return std::string();

M module-db/CMakeLists.txt => module-db/CMakeLists.txt +9 -11
@@ 13,18 13,17 @@ set(SOURCES
        Database/Field.cpp
        Database/QueryResult.cpp
        Database/Database.cpp
        Database/DatabaseInitializer.cpp
        Database/sqlite3vfs.cpp
        ${SQLITE3_SOURCE}

        Databases/CalllogDB.cpp
        Databases/ContactsDB.cpp
        Databases/CountryCodesDB.cpp
        Databases/EventsDB.cpp
        Databases/MultimediaFilesDB.cpp
        Databases/NotesDB.cpp
        Databases/NotificationsDB.cpp
        Databases/SmsDB.cpp
        databases/EventsDB.cpp
        databases/MultimediaFilesDB.cpp

        databases/CalllogDB.cpp
        databases/ContactsDB.cpp
        databases/NotesDB.cpp
        databases/NotificationsDB.cpp
        databases/SmsDB.cpp

        Tables/AlarmEventsTable.cpp
        Tables/Table.cpp


@@ 38,7 37,6 @@ set(SOURCES
        Tables/ContactsGroups.cpp
        Tables/NotesTable.cpp
        Tables/CalllogTable.cpp
        Tables/CountryCodesTable.cpp
        Tables/SMSTemplateTable.cpp
        Tables/NotificationsTable.cpp
        Tables/MultimediaFilesTable.cpp


@@ 153,7 151,7 @@ target_include_directories(${PROJECT_NAME}
        ${CMAKE_CURRENT_SOURCE_DIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/Interface
        ${CMAKE_CURRENT_SOURCE_DIR}/Tables
        ${CMAKE_CURRENT_SOURCE_DIR}/Databases
        ${CMAKE_CURRENT_SOURCE_DIR}/databases
        ${CMAKE_CURRENT_SOURCE_DIR}/Database
)


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

#pragma once

/// 32-bit unsigned integer
#define u32_ "%" PRIu32
/// 32-bit unsigned integer with comma, for instance: 323,
#define u32_c "%" PRIu32 ","
/// Zero terminated string with single-quotes on both ends, for instance: 'my string'
#define str_ "%Q"
/// The same as above with additional comma at the end, for instance: 'my string',
#define str_c "%Q,"
\ No newline at end of file

M module-db/Database/Database.cpp => module-db/Database/Database.cpp +4 -30
@@ 2,16 2,10 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "Database.hpp"
#include "DatabaseInitializer.hpp"

#include <log/log.hpp>

#include <purefs/filesystem_paths.hpp>
#include <gsl/util>

#include <cassert>
#include <cstring>
#include <memory>

/* Declarations *********************/
extern sqlite3_vfs *sqlite3_ecophonevfs(void);


@@ 75,8 69,7 @@ constexpr auto dbApplicationId = 0x65727550; // ASCII for "Pure"
constexpr auto enabled         = 1;

Database::Database(const char *name, bool readOnly)
    : dbConnection(nullptr), dbName(name), queryStatementBuffer{nullptr}, isInitialized_(false),
      initializer(std::make_unique<DatabaseInitializer>(this))
    : dbConnection(nullptr), dbName(name), queryStatementBuffer{nullptr}, isInitialized_(false)
{
    const int flags = (readOnly) ? (SQLITE_OPEN_READONLY) : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
    LOG_INFO("Opening database: %s", dbName.c_str());


@@ 89,30 82,11 @@ Database::Database(const char *name, bool readOnly)
    pragmaQuery("PRAGMA integrity_check;");
    pragmaQuery("PRAGMA locking_mode=EXCLUSIVE");

    if (pragmaQueryForValue("PRAGMA application_id;", dbApplicationId)) {
        LOG_DEBUG("Database %s initialized", dbName.c_str());
        isInitialized_ = true;
        return;
    }

    /** Workaround for [MOS-351].
     * Updater app loads databases to wrong directory (1/current/user/db) rather than
     * (3/user/db). Therefore, we need to check both directories to support both flashing image and updating through
     * updater.
     * !!! This workaround should be removed with minor patch of updater, however it cannot be released in one package
     * with database changes.
     **/
    auto filePath = (purefs::dir::getCurrentOSPath() / "user/db");
    if (!std::filesystem::exists(filePath)) {
        filePath = (purefs::dir::getUserDiskPath() / "db");
    }

    LOG_INFO("Running scripts: %s", filePath.c_str());
    isInitialized_ = initializer->run(filePath.c_str(), InitScriptExtension);

    if (isInitialized_) {
    if (isInitialized_ = pragmaQueryForValue("PRAGMA application_id;", dbApplicationId); not isInitialized_) {
        populateDbAppId();
        isInitialized_ = true;
    }
    LOG_DEBUG("Database %s initialized", dbName.c_str());
}

void Database::populateDbAppId()

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

#pragma once


@@ 10,8 10,6 @@
#include <stdexcept>
#include <filesystem>

class DatabaseInitializer;

class DatabaseInitialisationError : public std::runtime_error
{
  public:


@@ 52,7 50,6 @@ class Database
    }

  private:
    static constexpr auto InitScriptExtension  = "sql";
    static constexpr std::uint32_t maxQueryLen = (8 * 1024);

    void initQueryStatementBuffer();


@@ 75,5 72,4 @@ class Database
    std::string dbName;
    char *queryStatementBuffer;
    bool isInitialized_;
    std::unique_ptr<DatabaseInitializer> initializer;
};

D module-db/Database/DatabaseInitializer.cpp => module-db/Database/DatabaseInitializer.cpp +0 -142
@@ 1,142 0,0 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "DatabaseInitializer.hpp"

#include <log/log.hpp>
#include <Utils.hpp>

#include <algorithm>
#include <cstdio>
#include <memory>
#include <array>
#include <set>
#include <string>
#include <sstream>
#include <array>

DatabaseInitializer::DatabaseInitializer(Database *db) : db(db)
{}

bool DatabaseInitializer::run(std::filesystem::path path, std::string ext)
{
    // Database name is database file path, need to strip off all filesystem related stuff(path, extension)
    std::filesystem::path dbpath = db->getName();
    std::string dbname           = dbpath.filename().replace_extension();

    for (int i = 1;; i++) {
        auto fname = std::make_unique<std::stringstream>();
        (*fname) << dbname << "_" << std::setfill('0') << std::setw(3) << i << '.' << ext;
        auto file = path / fname->str();
        LOG_DEBUG("Running db script: %s", file.c_str());
        auto commands = readCommands(file);
        if (commands.empty())
            break;
        if (!executeOnDb(commands)) {
            LOG_ERROR("Can't initialize database [%s] with [%s]", db->getName().c_str(), file.c_str());
            return false;
        }
    }
    return true;
}

std::string DatabaseInitializer::readContent(const char *filename) const noexcept
{
    std::unique_ptr<char[]> fcontent;
    long fsize = 0;

    auto fp = std::fopen(filename, "r");
    if (fp) {
        std::fseek(fp, 0, SEEK_END);
        fsize = std::ftell(fp);
        std::rewind(fp);

        fcontent = std::make_unique<char[]>(fsize + 1);

        std::fread(fcontent.get(), 1, fsize, fp);

        std::fclose(fp);
    }
    else
        return {};

    return std::string(fcontent.get());
}

std::vector<std::string> DatabaseInitializer::readCommands(std::filesystem::path filePath)
{
    auto fileContent = readContent(filePath.c_str());
    if (fileContent.empty())
        return {};

    std::string currentStatement{};
    std::vector<std::string> statements{};

    std::string line{};
    for (auto &c : fileContent) {
        if (c != '\n') {
            line += c;
        }
        else {
            if (line.empty() || utils::startsWith(line, "--")) {
                line.clear();
                continue;
            }
            if (utils::endsWith(line, ";")) {
                statements.push_back(currentStatement + line);
                currentStatement.clear();
                line.clear();
                continue;
            }
            currentStatement += line;

            line.clear();
        }
    }
    return statements;
}

std::array<std::string, 3> DatabaseInitializer::splitFilename(std::string filename)
{
    auto name    = filename.substr(0, filename.find("."));
    auto prefix  = name.substr(0, name.find_last_of("_"));
    auto postfix = name.substr(name.find_last_of("_") + 1, std::string::npos);

    return {name, prefix, postfix};
}

std::vector<std::filesystem::path> DatabaseInitializer::listFiles(std::filesystem::path path,
                                                                  std::string prefix,
                                                                  std::string ext)
{
    std::set<std::pair<int, std::filesystem::path>> orderedFiles;
    for (const auto &entry : std::filesystem::directory_iterator(path)) {
        if (!entry.is_directory() && entry.path().has_filename()) {
            try {
                auto parts      = splitFilename(entry.path().filename().string());
                auto filePrefix = parts[1];
                if (filePrefix == prefix) {
                    auto num = std::stoi(parts[2]);
                    orderedFiles.insert({num, entry.path()});
                }
            }
            catch (std::invalid_argument &e) {
                LOG_INFO("Ignoring file: %s", entry.path().c_str());
            }
        }
    }

    std::vector<std::filesystem::path> files;
    std::for_each(orderedFiles.begin(), orderedFiles.end(), [&](auto item) { files.push_back(item.second); });
    return files;
}

bool DatabaseInitializer::executeOnDb(const std::vector<std::string> statements)
{
    for (auto st : statements) {
        if (!db->execute(st.c_str())) {
            return false;
        }
    }
    return true;
}

D module-db/Database/DatabaseInitializer.hpp => module-db/Database/DatabaseInitializer.hpp +0 -58
@@ 1,58 0,0 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "Database.hpp"
#include <fstream>
#include <filesystem>

class DatabaseInitializer
{
    class ScopedFile
    {
      public:
        ScopedFile(std::string path, std::string mode)
        {
            file = std::fopen(path.c_str(), mode.c_str());
        }

        ~ScopedFile()
        {
            std::fclose(file);
        }

        [[nodiscard]] std::FILE *get() const
        {
            return file;
        }

      private:
        std::FILE *file = nullptr;
    };

  public:
    DatabaseInitializer(Database *db);
    ~DatabaseInitializer() = default;

    auto run(std::filesystem::path path, std::string ext = "sql") -> bool;

    auto readCommands(std::filesystem::path filePath) -> std::vector<std::string>;

    auto listFiles(std::filesystem::path path, std::string prefix, std::string ext)
        -> std::vector<std::filesystem::path>;

    auto executeOnDb(const std::vector<std::string> statements) -> bool;

  private:
    /*
     * Splits filename in format <prefix>_<num>.ext into array
     *  [0] - filename
     *  [1] - prefix
     *  [2] - num
     */
    auto splitFilename(std::string filename) -> std::array<std::string, 3>;
    std::string readContent(const char *filename) const noexcept;

    Database *db = nullptr;
};

D module-db/Databases/CountryCodesDB.cpp => module-db/Databases/CountryCodesDB.cpp +0 -14
@@ 1,14 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 "CountryCodesDB.hpp"

CountryCodesDB::CountryCodesDB(const char *name) : Database(name, true), countryCodes(this)
{
    if (countryCodes.create() == false)
        return;
    isInitialized_ = true;
}

CountryCodesDB::~CountryCodesDB()
{}

D module-db/Databases/CountryCodesDB.hpp => module-db/Databases/CountryCodesDB.hpp +0 -15
@@ 1,15 0,0 @@
// 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 "Database/Database.hpp"
#include "Tables/CountryCodesTable.hpp"

class CountryCodesDB : public Database
{
  public:
    CountryCodesDB(const char *name);
    ~CountryCodesDB();

    CountryCodesTable countryCodes;
};

M module-db/Interface/AlarmEventRecord.cpp => module-db/Interface/AlarmEventRecord.cpp +1 -1
@@ 3,7 3,7 @@

#include "AlarmEventRecord.hpp"

#include <Databases/EventsDB.hpp>
#include <databases/EventsDB.hpp>
#include <queries/alarm_events/QueryAlarmEventsAdd.hpp>
#include <queries/alarm_events/QueryAlarmEventsEdit.hpp>
#include <queries/alarm_events/QueryAlarmEventsGet.hpp>

M module-db/Interface/AlarmEventRecord.hpp => module-db/Interface/AlarmEventRecord.hpp +1 -1
@@ 4,7 4,7 @@
#pragma once

#include "EventRecord.hpp"
#include <Databases/EventsDB.hpp>
#include <databases/EventsDB.hpp>

#include <cstdint>


M module-db/Interface/BaseInterface.hpp => module-db/Interface/BaseInterface.hpp +0 -3
@@ 25,7 25,6 @@ namespace db
            Contact,
            Notes,
            Calllog,
            CountryCodes,
            Notifications,
            Quotes,
            MultimediaFiles


@@ 50,8 49,6 @@ constexpr const char *c_str(enum db::Interface::Name db)
        return "Notes";
    case db::Interface::Name::Calllog:
        return "Callog";
    case db::Interface::Name::CountryCodes:
        return "CountryCodes";
    case db::Interface::Name::Notifications:
        return "Notifications";
    case db::Interface::Name::Quotes:

M module-db/Interface/CalllogRecord.hpp => module-db/Interface/CalllogRecord.hpp +1 -1
@@ 5,7 5,7 @@

#include "Common/Common.hpp"
#include "ContactRecord.hpp"
#include "Databases/CalllogDB.hpp"
#include "module-db/databases/CalllogDB.hpp"
#include "Record.hpp"
#include "queries/calllog/QueryCalllogSetAllRead.hpp"
#include <PhoneNumber.hpp>

M module-db/Interface/ContactRecord.cpp => module-db/Interface/ContactRecord.cpp +1 -1
@@ 1214,7 1214,7 @@ auto ContactRecordInterface::addTemporaryContactForNumber(const ContactRecord::N
{
    ContactRecord tmp;
    tmp.numbers = std::vector<ContactRecord::Number>{number};
    tmp.addToGroup(ContactsDB::temporaryGroupId());
    tmp.addToGroup(contactDB->groups.temporaryId());
    if (!Add(tmp)) {
        error_db_data("Cannot add contact record");
        return std::nullopt;

M module-db/Interface/ContactRecord.hpp => module-db/Interface/ContactRecord.hpp +1 -1
@@ 5,7 5,7 @@

#include <Common/Query.hpp>
#include <Common/Logging.hpp>
#include <Databases/ContactsDB.hpp>
#include "module-db/databases/ContactsDB.hpp"
#include <Tables/ContactsGroups.hpp>

#include <i18n/i18n.hpp>

D module-db/Interface/CountryCodeRecord.hpp => module-db/Interface/CountryCodeRecord.hpp +0 -51
@@ 1,51 0,0 @@
// 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 "../Common/Common.hpp"
#include "../Databases/CountryCodesDB.hpp"
#include "Record.hpp"
#include "utf8/UTF8.hpp"

#include <vector>

struct CountryCodeRecord
{
    uint32_t id;
    uint32_t mcc;
    uint32_t mnc;
    UTF8 iso;
    UTF8 country;
    uint32_t country_code;
    UTF8 network;
};

enum class CountryCodeRecordField
{
    MCC,
    MNC,
    ISO,
    Country
};

class CountryCodeRecordInterface : public RecordInterface<CountryCodeRecord, CountryCodeRecordField>
{

  public:
    CountryCodeRecordInterface(CountryCodesDB *db) : codesDB(db)
    {}
    ~CountryCodeRecordInterface()
    {}
    CodesTableRow GetByMCC(uint32_t mcc)
    {
        if (codesDB) {
            return (codesDB->countryCodes.GetByMCC(mcc));
        }

        return (CodesTableRow());
    }

  private:
    CountryCodesDB *codesDB;
};

M module-db/Interface/MultimediaFilesRecord.cpp => module-db/Interface/MultimediaFilesRecord.cpp +1 -1
@@ 4,7 4,7 @@
#include "MultimediaFilesRecord.hpp"
#include "BaseInterface.hpp"

#include <Databases/MultimediaFilesDB.hpp>
#include <databases/MultimediaFilesDB.hpp>
#include <queries/multimedia_files/QueryMultimediaFilesAdd.hpp>
#include <queries/multimedia_files/QueryMultimediaFilesEdit.hpp>
#include <queries/multimedia_files/QueryMultimediaFilesGet.hpp>

M module-db/Interface/NotesRecord.hpp => module-db/Interface/NotesRecord.hpp +1 -1
@@ 10,7 10,7 @@

#include <utf8/UTF8.hpp>

#include "module-db/Databases/NotesDB.hpp"
#include "module-db/databases/NotesDB.hpp"
#include "../Common/Common.hpp"

struct NotesRecord : public Record

M module-db/Interface/NotificationsRecord.cpp => module-db/Interface/NotificationsRecord.cpp +1 -1
@@ 8,7 8,7 @@
#include "module-db/queries/notifications/QueryNotificationsMultipleIncrement.hpp"
#include "module-db/queries/notifications/QueryNotificationsClear.hpp"
#include "module-db/queries/notifications/QueryNotificationsGetAll.hpp"
#include "Databases/NotificationsDB.hpp"
#include "module-db/databases/NotificationsDB.hpp"

#include <log/log.hpp>
#include <Utils.hpp>

M module-db/Interface/SMSRecord.hpp => module-db/Interface/SMSRecord.hpp +2 -2
@@ 7,8 7,8 @@

#include "Record.hpp"
#include "ThreadRecord.hpp"
#include "module-db/Databases/SmsDB.hpp"
#include "module-db/Databases/ContactsDB.hpp"
#include "module-db/databases/SmsDB.hpp"
#include "module-db/databases/ContactsDB.hpp"
#include "module-db/Common/Common.hpp"

#include <utf8/UTF8.hpp>

M module-db/Interface/SMSTemplateRecord.hpp => module-db/Interface/SMSTemplateRecord.hpp +1 -1
@@ 4,7 4,7 @@
#pragma once

#include "Record.hpp"
#include "Databases/SmsDB.hpp"
#include "module-db/databases/SmsDB.hpp"
#include "Common/Common.hpp"

#include <utf8/UTF8.hpp>

M module-db/Interface/SettingsRecord.hpp => module-db/Interface/SettingsRecord.hpp +1 -1
@@ 6,7 6,7 @@
#include "Record.hpp"
#include "utf8/UTF8.hpp"
#include "../Common/Common.hpp"
#include "../Databases/SettingsDB.hpp"
#include "../databases/SettingsDB.hpp"
#include <i18n/i18n.hpp>

struct SettingsRecord

M module-db/Interface/ThreadRecord.hpp => module-db/Interface/ThreadRecord.hpp +2 -2
@@ 4,8 4,8 @@
#pragma once

#include "Record.hpp"
#include "module-db/Databases/SmsDB.hpp"
#include "module-db/Databases/ContactsDB.hpp"
#include "module-db/databases/SmsDB.hpp"
#include "module-db/databases/ContactsDB.hpp"
#include "module-db/Common/Common.hpp"
#include "module-db/queries/messages/threads/QueryThreadMarkAsRead.hpp"
#include <PhoneNumber.hpp>

M module-db/Tables/AlarmEventsTable.cpp => module-db/Tables/AlarmEventsTable.cpp +17 -31
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "AlarmEventsTable.hpp"
#include "Common/Types.hpp"

#include <Interface/AlarmEventRecord.hpp>



@@ 50,7 51,7 @@ bool AlarmEventsTable::create()
bool AlarmEventsTable::add(AlarmEventsTableRow entry)
{
    return db->execute("INSERT or ignore INTO alarms ( hour, minute, music_tone, enabled, snooze_duration, rrule)"
                       "VALUES ( %lu, %lu, '%q', %d, %lu, '%q');",
                       "VALUES (" u32_c u32_c str_c u32_c u32_c str_ ");",
                       entry.hourOfDay,
                       entry.minuteOfHour,
                       entry.musicTone.c_str(),


@@ 61,16 62,13 @@ bool AlarmEventsTable::add(AlarmEventsTableRow entry)

bool AlarmEventsTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM alarms "
                       "WHERE alarms._id = %lu;",
                       id);
    return db->execute("DELETE FROM alarms WHERE alarms._id=" u32_ ";", id);
}

bool AlarmEventsTable::update(AlarmEventsTableRow entry)
{
    return db->execute("UPDATE alarms SET hour = '%lu', minute = '%lu', music_tone = '%q', enabled = '%d', "
                       "snooze_duration = '%lu', rrule = '%q' "
                       "WHERE _id=%lu;",
    return db->execute("UPDATE alarms SET hour=" u32_c "minute=" u32_c "music_tone=" str_c "enabled=" u32_c
                       "snooze_duration=" u32_c "rrule=" str_ " WHERE _id=" u32_ ";",
                       entry.hourOfDay,
                       entry.minuteOfHour,
                       entry.musicTone.c_str(),


@@ 82,10 80,7 @@ bool AlarmEventsTable::update(AlarmEventsTableRow entry)

AlarmEventsTableRow AlarmEventsTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * "
                              "FROM alarms "
                              "WHERE _id = %lu;",
                              id);
    auto retQuery = db->query("SELECT * FROM alarms WHERE _id=" u32_ ";", id);

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


@@ 96,19 91,15 @@ AlarmEventsTableRow AlarmEventsTable::getById(uint32_t id)

std::vector<AlarmEventsTableRow> AlarmEventsTable::getLimitOffset(uint32_t offset, uint32_t limit)
{
    auto retQuery = db->query("SELECT * FROM alarms "
                              "ORDER BY hour, minute "
                              "LIMIT %lu OFFSET %lu;",
                              limit,
                              offset);
    auto retQuery =
        db->query("SELECT * FROM alarms ORDER BY hour, minute LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);

    return retQueryUnpack(std::move(retQuery));
}

std::vector<AlarmEventsTableRow> AlarmEventsTable::getEnabled()
{
    auto retQuery = db->query("SELECT * FROM alarms "
                              "WHERE enabled = 1;");
    auto retQuery = db->query("SELECT * FROM alarms WHERE enabled = 1;");

    return retQueryUnpack(std::move(retQuery));
}


@@ 125,21 116,19 @@ std::vector<AlarmEventsTableRow> AlarmEventsTable::getLimitOffsetByField(uint32_
        return {};
    }

    retQuery = db->query("SELECT * FROM alarms e "
                         "WHERE %q = '%q' "
                         "ORDER BY hour, minute "
                         "LIMIT %lu OFFSET %lu;",
                         fieldName.c_str(),
                         str,
                         limit,
                         offset);
    retQuery =
        db->query("SELECT * FROM alarms e WHERE %q=" str_ "ORDER BY hour, minute LIMIT " u32_ " OFFSET " u32_c ";",
                  fieldName.c_str(),
                  str,
                  limit,
                  offset);

    return retQueryUnpack(std::move(retQuery));
}

auto AlarmEventsTable::toggleAll(bool toggle) -> bool
{
    auto ret = db->execute("UPDATE alarms SET enabled = '%d';", static_cast<int>(toggle));
    auto ret = db->execute("UPDATE alarms SET enabled=" u32_ ";", static_cast<int>(toggle));

    return ret;
}


@@ 156,10 145,7 @@ uint32_t AlarmEventsTable::count()

uint32_t AlarmEventsTable::countByFieldId(const char *field, uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM alarms "
                              "WHERE %q=%lu;",
                              field,
                              id);
    auto queryRet = db->query("SELECT COUNT(*) FROM alarms WHERE %q=" u32_ ";", field, id);
    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;
    }

M module-db/Tables/CalllogTable.cpp => module-db/Tables/CalllogTable.cpp +25 -23
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CalllogTable.hpp"
#include "Common/Types.hpp"
#include <log/log.hpp>
#include <Utils.hpp>



@@ 18,23 19,22 @@ bool CalllogTable::create()

bool CalllogTable::add(CalllogTableRow entry)
{
    return db->execute(
        "INSERT or ignore INTO calls (number, e164number, presentation, date, duration, type, name, contactId, "
        "isRead) VALUES ('%q', '%q', %lu, %q, %q, %lu, '%q', '%lu', %d);",
        entry.number.c_str(),
        entry.e164number.c_str(),
        static_cast<uint32_t>(entry.presentation),
        utils::to_string(entry.date).c_str(),
        utils::to_string(entry.duration).c_str(),
        static_cast<uint32_t>(entry.type),
        entry.name.c_str(),
        entry.contactId,
        entry.isRead);
    return db->execute("INSERT or ignore INTO calls (number, e164number, presentation, date, duration, type, name, "
                       "contactId,isRead) VALUES (" str_c str_c u32_c str_c str_c u32_c str_c u32_c u32_ ");",
                       entry.number.c_str(),
                       entry.e164number.c_str(),
                       static_cast<uint32_t>(entry.presentation),
                       utils::to_string(entry.date).c_str(),
                       utils::to_string(entry.duration).c_str(),
                       static_cast<uint32_t>(entry.type),
                       entry.name.c_str(),
                       entry.contactId,
                       entry.isRead);
}

bool CalllogTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM calls where _id = %lu;", id);
    return db->execute("DELETE FROM calls where _id=" u32_ ";", id);
}

bool CalllogTable::removeByField(CalllogTableFields field, const char *str)


@@ 44,10 44,9 @@ bool CalllogTable::removeByField(CalllogTableFields field, const char *str)

bool CalllogTable::update(CalllogTableRow entry)
{
    return db->execute("UPDATE calls SET number = '%q', e164number = '%q', presentation = %lu, date = %lu, duration = "
                       "%lu, type = %lu, "
                       "name = '%q', contactId = '%u', isRead = "
                       "%d WHERE _id = %lu;",
    return db->execute("UPDATE calls SET number=" str_c "e164number=" str_c "presentation=" u32_c "date=" u32_c
                       "duration=" u32_c "type=" u32_c "name=" str_c "contactId=" u32_c "isRead=" u32_
                       " WHERE _id=" u32_ ";",
                       entry.number.c_str(),
                       entry.e164number.c_str(),
                       static_cast<uint32_t>(entry.presentation),


@@ 62,7 61,7 @@ bool CalllogTable::update(CalllogTableRow entry)

CalllogTableRow CalllogTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM calls WHERE _id= %u;", id);
    auto retQuery = db->query("SELECT * FROM calls WHERE _id=" u32_ ";", id);

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


@@ 84,7 83,7 @@ CalllogTableRow CalllogTable::getById(uint32_t id)

std::vector<CalllogTableRow> CalllogTable::getByContactId(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM calls WHERE contactId= %lu;", id);
    auto retQuery = db->query("SELECT * FROM calls WHERE contactId=" u32_ ";", id);

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


@@ 112,7 111,7 @@ std::vector<CalllogTableRow> CalllogTable::getByContactId(uint32_t id)

std::vector<CalllogTableRow> CalllogTable::getLimitOffset(uint32_t offset, uint32_t limit)
{
    auto retQuery = db->query("SELECT * from calls ORDER BY date DESC LIMIT %lu OFFSET %lu;", limit, offset);
    auto retQuery = db->query("SELECT * from calls ORDER BY date DESC LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);

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


@@ 156,8 155,11 @@ std::vector<CalllogTableRow> CalllogTable::getLimitOffsetByField(uint32_t offset
        return std::vector<CalllogTableRow>();
    }

    auto retQuery = db->query(
        "SELECT * from calls WHERE %q='%q' ORDER BY date LIMIT %lu OFFSET %lu;", fieldName.c_str(), str, limit, offset);
    auto retQuery = db->query("SELECT * from calls WHERE %q=" u32_ " ORDER BY date LIMIT " u32_ " OFFSET " u32_ ";",
                              fieldName.c_str(),
                              str,
                              limit,
                              offset);

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


@@ 213,7 215,7 @@ uint32_t CalllogTable::count()

uint32_t CalllogTable::countByFieldId(const char *field, uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM calls WHERE %q=%lu;", field, id);
    auto queryRet = db->query("SELECT COUNT(*) FROM calls WHERE %q=" u32_ ";", field, id);

    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;

M module-db/Tables/ContactsAddressTable.cpp => module-db/Tables/ContactsAddressTable.cpp +15 -12
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ContactsAddressTable.hpp"
#include "Common/Types.hpp"

ContactsAddressTable::ContactsAddressTable(Database *db) : Table(db)
{}


@@ 17,7 18,7 @@ bool ContactsAddressTable::create()
bool ContactsAddressTable::add(ContactsAddressTableRow entry)
{
    return db->execute("insert or ignore into contact_address (contact_id, address, note, mail) "
                       "VALUES (%lu, '%q', '%q', '%q');",
                       "VALUES (" u32_c str_c str_c str_ ");",
                       entry.contactID,
                       entry.address.c_str(),
                       entry.note.c_str(),


@@ 26,13 27,13 @@ bool ContactsAddressTable::add(ContactsAddressTableRow entry)

bool ContactsAddressTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM contact_address where _id = %u;", id);
    return db->execute("DELETE FROM contact_address where _id=" u32_ ";", id);
}

bool ContactsAddressTable::update(ContactsAddressTableRow entry)
{
    return db->execute("UPDATE contact_address SET contact_id = %lu, address = '%q', note = '%q', mail = '%q' "
                       "WHERE _id=%lu;",
    return db->execute("UPDATE contact_address SET contact_id=" u32_c "address=" str_c "note=" str_c "mail=" str_
                       "WHERE _id=" u32_ ";",
                       entry.contactID,
                       entry.address.c_str(),
                       entry.note.c_str(),


@@ 42,7 43,7 @@ bool ContactsAddressTable::update(ContactsAddressTableRow entry)

ContactsAddressTableRow ContactsAddressTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM contact_address WHERE _id= %lu;", id);
    auto retQuery = db->query("SELECT * FROM contact_address WHERE _id=" u32_ ";", id);

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


@@ 59,7 60,8 @@ ContactsAddressTableRow ContactsAddressTable::getById(uint32_t id)

std::vector<ContactsAddressTableRow> ContactsAddressTable::getLimitOffset(uint32_t offset, uint32_t limit)
{
    auto retQuery = db->query("SELECT * from contact_address ORDER BY contact_id LIMIT %lu OFFSET %lu;", limit, offset);
    auto retQuery =
        db->query("SELECT * from contact_address ORDER BY contact_id LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);

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


@@ 95,11 97,12 @@ std::vector<ContactsAddressTableRow> ContactsAddressTable::getLimitOffsetByField
        return std::vector<ContactsAddressTableRow>();
    }

    auto retQuery = db->query("SELECT * from contact_address WHERE %q='%q' ORDER BY contact_id LIMIT %lu OFFSET %lu;",
                              fieldName.c_str(),
                              str,
                              limit,
                              offset);
    auto retQuery =
        db->query("SELECT * from contact_address WHERE %q=" str_ " ORDER BY contact_id LIMIT " u32_ " OFFSET " u32_ ";",
                  fieldName.c_str(),
                  str,
                  limit,
                  offset);

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


@@ 133,7 136,7 @@ uint32_t ContactsAddressTable::count()

uint32_t ContactsAddressTable::countByFieldId(const char *field, uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM contact_address WHERE %q=%lu;", field, id);
    auto queryRet = db->query("SELECT COUNT(*) FROM contact_address WHERE %q=" u32_ ";", field, id);

    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;

M module-db/Tables/ContactsGroups.cpp => module-db/Tables/ContactsGroups.cpp +15 -18
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ContactsGroups.hpp"
#include "Common/Types.hpp"

namespace statements
{


@@ 12,8 13,8 @@ namespace statements
    const char *blockedId    = "SELECT _id FROM contact_groups WHERE name = 'Blocked';";
    const char *temporaryId  = "SELECT _id FROM contact_groups WHERE name = 'Temporary';";

    const char *getId   = "SELECT _id FROM contact_groups WHERE name = '%q';";
    const char *getById = "SELECT _id, name FROM contact_groups WHERE _id = %u;";
    const char *getId   = "SELECT _id FROM contact_groups WHERE name=" str_ ";";
    const char *getById = "SELECT _id, name FROM contact_groups WHERE _id=" u32_ ";";

    /**
     * delete a group only if it is not a "special group"


@@ 22,24 23,21 @@ namespace statements
     * delete cg from contact_groups cg left join contact_group_protected cgp on cg._id=cgp.group_id where cgp._id is
     * null; so we have the following statement
     */
    const char *deleteById = "DELETE FROM contact_groups WHERE _id = %u "
                             "AND NOT EXISTS (SELECT * FROM contact_group_protected WHERE group_id=contact_groups._id)";
    const char *deleteById =
        "DELETE FROM contact_groups WHERE _id=" u32_
        " AND NOT EXISTS (SELECT * FROM contact_group_protected WHERE group_id=contact_groups._id)";

    const char *getAllLimtOfset = "SELECT _id, name FROM contact_groups ORDER BY _id LIMIT %lu OFFSET %lu;";
    const char *getAllLimtOfset = "SELECT _id, name FROM contact_groups ORDER BY _id LIMIT " u32_ " OFFSET " u32_ ";";

    const char *addGrup           = "INSERT INTO contact_groups (name) VALUES ('%q');";
    const char *addProtectedGroup = "INSERT INTO conctact_group_protected (group_id) VALUES ('%u');";
    const char *updateGroupName   = "UPDATE contact_groups SET name = '%q' WHERE _id = '%u';";
    const char *deleteGroup       = "DELETE FROM table_name WHERE _id = :id;";
    const char *addGrup         = "INSERT INTO contact_groups (name) VALUES(" str_ ");";
    const char *updateGroupName = "UPDATE contact_groups SET name=" str_ " WHERE _id=" u32_ ";";

    const char *addContactToGroup = "INSERT INTO contact_match_groups (contact_id, group_id)"
                                    "  VALUES(%u, %u)";
    const char *addContactToGroup = "INSERT INTO contact_match_groups (contact_id, group_id) VALUES(" u32_c u32_ ");";

    const char *delContactFromGroup = "DELETE FROM contact_match_groups "
                                      "   WHERE "
                                      "      contact_id = %u "
                                      "      AND "
                                      "      group_id = %u;";
                                      "      contact_id=" u32_ " AND "
                                      "      group_id=" u32_ ";";

    const char *getGroupsForContact = "SELECT groups._id, groups.name "
                                      " FROM contact_groups as groups,"


@@ 47,11 45,10 @@ namespace statements
                                      "     contacts "
                                      " WHERE contacts._id = cmg.contact_id "
                                      "      AND groups._id = cmg.group_id "
                                      "      AND contacts._id = %u "
                                      " ORDER BY groups._id ASC;";
                                      "      AND contacts._id=" u32_ " ORDER BY groups._id ASC;";

    const char *getContactsForGroup = "SELECT cmg.contact_id FROM contact_match_groups as cmg "
                                      " WHERE cmg.group_id = %u;";
    const char *getContactsForGroup =
        "SELECT cmg.contact_id FROM contact_match_groups as cmg WHERE cmg.group_id=" u32_ ";";
} // namespace statements

ContactsGroupsTable::ContactsGroupsTable(Database *db) : Table(db)

M module-db/Tables/ContactsNameTable.cpp => module-db/Tables/ContactsNameTable.cpp +20 -18
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ContactsNameTable.hpp"
#include "Common/Types.hpp"
#include <Utils.hpp>

ContactsNameTable::ContactsNameTable(Database *db) : Table(db)


@@ 18,7 19,7 @@ bool ContactsNameTable::create()
bool ContactsNameTable::add(ContactsNameTableRow entry)
{
    return db->execute("insert or ignore into contact_name (contact_id, name_primary, name_alternative) "
                       "VALUES (%lu, '%q', '%q');",
                       "VALUES (" u32_c str_c str_ ");",
                       entry.contactID,
                       entry.namePrimary.c_str(),
                       entry.nameAlternative.c_str());


@@ 26,13 27,13 @@ bool ContactsNameTable::add(ContactsNameTableRow entry)

bool ContactsNameTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM contact_name where _id = %lu;", id);
    return db->execute("DELETE FROM contact_name where _id=" u32_ ";", id);
}

bool ContactsNameTable::update(ContactsNameTableRow entry)
{
    return db->execute("UPDATE contact_name SET contact_id = %lu, name_primary = '%q', name_alternative = '%q' "
                       "WHERE _id = %lu;",
    return db->execute("UPDATE contact_name SET contact_id=" u32_c "name_primary=" str_c "name_alternative=" str_
                       "WHERE _id=" u32_ ";",
                       entry.contactID,
                       entry.namePrimary.c_str(),
                       entry.nameAlternative.c_str(),


@@ 41,7 42,7 @@ bool ContactsNameTable::update(ContactsNameTableRow entry)

ContactsNameTableRow ContactsNameTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM contact_name WHERE _id= %lu;", id);
    auto retQuery = db->query("SELECT * FROM contact_name WHERE _id=" u32_ ";", id);

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


@@ 57,8 58,8 @@ ContactsNameTableRow ContactsNameTable::getById(uint32_t id)

std::vector<ContactsNameTableRow> ContactsNameTable::getLimitOffset(uint32_t offset, uint32_t limit)
{
    auto retQuery =
        db->query("SELECT * from contact_name ORDER BY name_alternative ASC LIMIT %lu OFFSET %lu;", limit, offset);
    auto retQuery = db->query(
        "SELECT * from contact_name ORDER BY name_alternative ASC LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);

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


@@ 97,12 98,12 @@ std::vector<ContactsNameTableRow> ContactsNameTable::getLimitOffsetByField(uint3
        return std::vector<ContactsNameTableRow>();
    }

    auto retQuery =
        db->query("SELECT * from contact_name WHERE %q='%q' ORDER BY name_alternative LIMIT %lu OFFSET %lu;",
                  fieldName.c_str(),
                  str,
                  limit,
                  offset);
    auto retQuery = db->query("SELECT * from contact_name WHERE %q=" str_ " ORDER BY name_alternative LIMIT " u32_
                              " OFFSET " u32_ ";",
                              fieldName.c_str(),
                              str,
                              limit,
                              offset);

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


@@ 135,7 136,7 @@ uint32_t ContactsNameTable::count()

uint32_t ContactsNameTable::countByFieldId(const char *field, uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM contact_name WHERE %q=%lu;", field, id);
    auto queryRet = db->query("SELECT COUNT(*) FROM contact_name WHERE %q=" u32_ ";", field, id);

    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;


@@ 147,10 148,11 @@ uint32_t ContactsNameTable::countByFieldId(const char *field, uint32_t id)
std::vector<ContactsNameTableRow> ContactsNameTable::GetByName(const char *primaryName, const char *alternativeName)
{

    auto retQuery = db->query("SELECT * from contact_name WHERE name_primary='%q' AND name_alternative='%q' ORDER BY "
                              "name_alternative LIMIT 1;",
                              primaryName,
                              alternativeName);
    auto retQuery =
        db->query("SELECT * from contact_name WHERE name_primary=" str_ " AND name_alternative=" str_ " ORDER BY "
                  "name_alternative LIMIT 1;",
                  primaryName,
                  alternativeName);

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

M module-db/Tables/ContactsNumberTable.cpp => module-db/Tables/ContactsNumberTable.cpp +31 -27
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ContactsNumberTable.hpp"
#include "Common/Types.hpp"

ContactsNumberTable::ContactsNumberTable(Database *db) : Table(db)
{}


@@ 16,33 17,34 @@ bool ContactsNumberTable::create()

bool ContactsNumberTable::add(ContactsNumberTableRow entry)
{
    return db->execute("insert or ignore into contact_number (contact_id, number_user, number_e164, type) VALUES (%lu, "
                       "'%q', '%q', %lu);",
                       entry.contactID,
                       entry.numberUser.c_str(),
                       entry.numbere164.c_str(),
                       entry.type);
    return db->execute(
        "insert or ignore into contact_number (contact_id, number_user, number_e164, type) VALUES (" u32_c str_c str_c
            u32_ ");",
        entry.contactID,
        entry.numberUser.c_str(),
        entry.numbere164.c_str(),
        entry.type);
}

bool ContactsNumberTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM contact_number where _id = %u;", id);
    return db->execute("DELETE FROM contact_number where _id=" u32_ ";", id);
}

bool ContactsNumberTable::update(ContactsNumberTableRow entry)
{
    return db->execute(
        "UPDATE contact_number SET contact_id = %lu, number_user = '%q', number_e164 = '%q', type = %lu WHERE _id=%lu;",
        entry.contactID,
        entry.numberUser.c_str(),
        entry.numbere164.c_str(),
        entry.type,
        entry.ID);
    return db->execute("UPDATE contact_number SET contact_id=" u32_c "number_user=" str_c "number_e164=" str_c
                       "type=" u32_ " WHERE _id=" u32_ ";",
                       entry.contactID,
                       entry.numberUser.c_str(),
                       entry.numbere164.c_str(),
                       entry.type,
                       entry.ID);
}

ContactsNumberTableRow ContactsNumberTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM contact_number WHERE _id= %lu;", id);
    auto retQuery = db->query("SELECT * FROM contact_number WHERE _id=" u32_ ";", id);

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


@@ 59,7 61,7 @@ ContactsNumberTableRow ContactsNumberTable::getById(uint32_t id)

std::vector<ContactsNumberTableRow> ContactsNumberTable::getByContactId(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM contact_number WHERE contact_id = %lu;", id);
    auto retQuery = db->query("SELECT * FROM contact_number WHERE contact_id=" u32_ ";", id);
    if (!retQuery || (retQuery->getRowCount() == 0U)) {
        return {};
    }


@@ 81,7 83,7 @@ std::vector<ContactsNumberTableRow> ContactsNumberTable::getByContactId(uint32_t

std::vector<ContactsNumberTableRow> ContactsNumberTable::getLimitOffset(uint32_t offset, uint32_t limit)
{
    auto retQuery = db->query("SELECT * from contact_number LIMIT %lu OFFSET %lu;", limit, offset);
    auto retQuery = db->query("SELECT * from contact_number LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);

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


@@ 107,10 109,11 @@ std::vector<ContactsNumberTableRow> ContactsNumberTable::getLimitOffset(const st
                                                                        uint32_t limit)
{
    const char lastCharacter = number.back();
    auto retQuery = db->query("SELECT * from contact_number WHERE number_user like '%%%c' LIMIT %lu OFFSET %lu;",
                              lastCharacter,
                              limit,
                              offset);
    auto retQuery =
        db->query("SELECT * from contact_number WHERE number_user like '%%%c' LIMIT " u32_ " OFFSET " u32_ ";",
                  lastCharacter,
                  limit,
                  offset);
    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
        return std::vector<ContactsNumberTableRow>();
    }


@@ 145,11 148,12 @@ std::vector<ContactsNumberTableRow> ContactsNumberTable::getLimitOffsetByField(u
        return std::vector<ContactsNumberTableRow>();
    }

    auto retQuery = db->query("SELECT * from contact_number WHERE %q='%q' ORDER BY number_user LIMIT %lu OFFSET %lu;",
                              fieldName.c_str(),
                              str,
                              limit,
                              offset);
    auto retQuery =
        db->query("SELECT * from contact_number WHERE %q=" str_ " ORDER BY number_user LIMIT " u32_ " OFFSET " u32_ ";",
                  fieldName.c_str(),
                  str,
                  limit,
                  offset);

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


@@ 183,7 187,7 @@ uint32_t ContactsNumberTable::count()

uint32_t ContactsNumberTable::countByFieldId(const char *field, uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM contact_number WHERE %q=%lu;", field, id);
    auto queryRet = db->query("SELECT COUNT(*) FROM contact_number WHERE %q=" u32_ ";", field, id);

    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;

M module-db/Tables/ContactsRingtonesTable.cpp => module-db/Tables/ContactsRingtonesTable.cpp +9 -7
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ContactsRingtonesTable.hpp"
#include "Common/Types.hpp"

ContactsRingtonesTable::ContactsRingtonesTable(Database *db) : Table(db)
{}


@@ 16,19 17,19 @@ bool ContactsRingtonesTable::create()

bool ContactsRingtonesTable::add(ContactsRingtonesTableRow entry)
{
    return db->execute("insert or ignore into contact_ringtones (contact_id, asset_path ) VALUES (%lu, '%q');",
    return db->execute("insert or ignore into contact_ringtones (contact_id, asset_path ) VALUES (" u32_c str_ ");",
                       entry.contactID,
                       entry.assetPath.c_str());
}

bool ContactsRingtonesTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM contact_ringtones where _id = %u;", id);
    return db->execute("DELETE FROM contact_ringtones where _id=" u32_ ";", id);
}

bool ContactsRingtonesTable::update(ContactsRingtonesTableRow entry)
{
    return db->execute("UPDATE contact_ringtones SET contact_id = %lu, asset_path = '%q' WHERE _id=%lu;",
    return db->execute("UPDATE contact_ringtones SET contact_id=" u32_c "asset_path=" str_ " WHERE _id=" u32_ ";",
                       entry.contactID,
                       entry.assetPath.c_str(),
                       entry.ID);


@@ 36,7 37,7 @@ bool ContactsRingtonesTable::update(ContactsRingtonesTableRow entry)

ContactsRingtonesTableRow ContactsRingtonesTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM contact_ringtones WHERE _id= %lu;", id);
    auto retQuery = db->query("SELECT * FROM contact_ringtones WHERE _id=" u32_ ";", id);

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


@@ 52,7 53,7 @@ ContactsRingtonesTableRow ContactsRingtonesTable::getById(uint32_t id)
std::vector<ContactsRingtonesTableRow> ContactsRingtonesTable::getLimitOffset(uint32_t offset, uint32_t limit)
{
    auto retQuery =
        db->query("SELECT * from contact_ringtones ORDER BY contact_id LIMIT %lu OFFSET %lu;", limit, offset);
        db->query("SELECT * from contact_ringtones ORDER BY contact_id LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);

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


@@ 85,7 86,8 @@ std::vector<ContactsRingtonesTableRow> ContactsRingtonesTable::getLimitOffsetByF
        return std::vector<ContactsRingtonesTableRow>();
    }

    auto retQuery = db->query("SELECT * from contact_ringtones WHERE %q='%q' ORDER BY contact_id LIMIT %lu OFFSET %lu;",
    auto retQuery = db->query("SELECT * from contact_ringtones WHERE %q=" str_ " ORDER BY contact_id LIMIT " u32_
                              " OFFSET " u32_ ";",
                              fieldName.c_str(),
                              str,
                              limit,


@@ 121,7 123,7 @@ uint32_t ContactsRingtonesTable::count()

uint32_t ContactsRingtonesTable::countByFieldId(const char *field, uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM contact_ringtones WHERE %q=%lu;", field, id);
    auto queryRet = db->query("SELECT COUNT(*) FROM contact_ringtones WHERE %q=" u32_ ";", field, id);

    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;

M module-db/Tables/ContactsTable.cpp => module-db/Tables/ContactsTable.cpp +16 -15
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ContactsTable.hpp"
#include "Common/Types.hpp"
#include <log/log.hpp>
#include <Utils.hpp>



@@ 17,15 18,14 @@ namespace ColumnName

namespace statements
{
    const auto selectWithoutTemp = "SELECT * FROM contacts WHERE _id= %lu"
                                   " AND "
    const auto selectWithoutTemp = "SELECT * FROM contacts WHERE _id=" u32_ " AND "
                                   " contacts._id NOT IN ( "
                                   "   SELECT cmg.contact_id "
                                   "   FROM contact_match_groups cmg, contact_groups cg "
                                   "   WHERE cmg.group_id = cg._id "
                                   "       AND cg.name = 'Temporary' "
                                   "   ) ";
    const auto selectWithTemp = "SELECT * FROM contacts WHERE _id= %lu";
    const auto selectWithTemp = "SELECT * FROM contacts WHERE _id=" u32_ ";";
} // namespace statements

ContactsTable::ContactsTable(Database *db) : Table(db)


@@ 42,7 42,7 @@ bool ContactsTable::create()
bool ContactsTable::add(ContactsTableRow entry)
{
    return db->execute("insert or ignore into contacts (name_id, numbers_id, ring_id, address_id, speeddial) "
                       " VALUES (%lu, '%q', %lu, %lu, '%q');",
                       " VALUES (" u32_c str_c u32_c u32_c str_ ");",
                       entry.nameID,
                       entry.numbersID.c_str(),
                       entry.ringID,


@@ 52,18 52,18 @@ bool ContactsTable::add(ContactsTableRow entry)

bool ContactsTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM contacts where _id = %u;", id);
    return db->execute("DELETE FROM contacts where _id=" u32_ ";", id);
}

bool ContactsTable::BlockByID(uint32_t id, bool shouldBeBlocked)
{
    return db->execute("UPDATE contacts SET blacklist=%lu WHERE _id=%lu", shouldBeBlocked ? 1 : 0, id);
    return db->execute("UPDATE contacts SET blacklist=" u32_ " WHERE _id=" u32_ ";", shouldBeBlocked ? 1 : 0, id);
}

bool ContactsTable::update(ContactsTableRow entry)
{
    return db->execute("UPDATE contacts SET name_id = %lu, numbers_id = '%q', ring_id = %lu, address_id = %lu, "
                       " speeddial = '%q' WHERE _id=%lu;",
    return db->execute("UPDATE contacts SET name_id=" u32_c "numbers_id=" str_c "ring_id=" u32_c "address_id=" u32_c
                       "speeddial=" str_ " WHERE _id=" u32_ ";",
                       entry.nameID,
                       entry.numbersID.c_str(),
                       entry.ringID,


@@ 362,7 362,7 @@ std::vector<ContactsTableRow> ContactsTable::getLimitOffset(uint32_t offset, uin
                              "    WHERE cmg.group_id = cg._id "
                              "        AND cg.name = 'Temporary' "
                              " ) "
                              "ORDER BY name_id LIMIT %lu OFFSET %lu;",
                              "ORDER BY name_id LIMIT " u32_ " OFFSET " u32_ ";",
                              limit,
                              offset);



@@ 401,11 401,12 @@ std::vector<ContactsTableRow> ContactsTable::getLimitOffsetByField(uint32_t offs
        return std::vector<ContactsTableRow>();
    }

    auto retQuery = db->query("SELECT * from contacts WHERE %q='%q' ORDER BY name_id LIMIT %lu OFFSET %lu;",
                              fieldName.c_str(),
                              str,
                              limit,
                              offset);
    auto retQuery =
        db->query("SELECT * from contacts WHERE %q=" str_ " ORDER BY name_id LIMIT " u32_ " OFFSET " u32_ ";",
                  fieldName.c_str(),
                  str,
                  limit,
                  offset);

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


@@ 446,7 447,7 @@ uint32_t ContactsTable::count()

uint32_t ContactsTable::countByFieldId(const char *field, uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM contacts WHERE %q=%lu;", field, id);
    auto queryRet = db->query("SELECT COUNT(*) FROM contacts WHERE %q=" u32_ ";", field, id);

    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;

D module-db/Tables/CountryCodesTable.cpp => module-db/Tables/CountryCodesTable.cpp +0 -45
@@ 1,45 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 "CountryCodesTable.hpp"

CountryCodesTable::CountryCodesTable(Database *db) : Table(db)
{}

CountryCodesTable::~CountryCodesTable()
{}

bool CountryCodesTable::create()
{
    return db->execute(createTableQuery);
}

CodesTableRow CountryCodesTable::GetByMCC(uint32_t mcc)
{
    auto retQuery = db->query("SELECT * FROM codes WHERE mcc= %lu LIMIT 1;", mcc);

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

    return CodesTableRow{
        (*retQuery)[0].getUInt32(), /* _id */
        (*retQuery)[1].getUInt32(), /* mcc */
        (*retQuery)[2].getUInt32(), /* mnc */
        (*retQuery)[3].getString(), /* iso */
        (*retQuery)[4].getString(), /* country name */
        (*retQuery)[5].getUInt32(), /* country code */
        (*retQuery)[5].getString()  /* network name */
    };
}

uint32_t CountryCodesTable::count()
{
    auto queryRet = db->query("SELECT COUNT(*) FROM codes;");

    if (!queryRet || queryRet->getRowCount() == 0) {
        return 0;
    }

    return uint32_t{(*queryRet)[0].getUInt32()};
}

D module-db/Tables/CountryCodesTable.hpp => module-db/Tables/CountryCodesTable.hpp +0 -75
@@ 1,75 0,0 @@
// 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 "Common/Common.hpp"
#include "Database/Database.hpp"
#include "Record.hpp"
#include "Table.hpp"
#include "utf8/UTF8.hpp"
#include <string>

struct CodesTableRow : public Record
{
    uint32_t mcc;
    uint32_t mnc;
    std::string iso;
    std::string country;
    uint32_t country_code;
    std::string network;
};

enum CodesTableFields
{
};

class CountryCodesTable : public Table<CodesTableRow, CodesTableFields>
{
  public:
    CountryCodesTable(Database *db);
    virtual ~CountryCodesTable();
    bool create() override final;
    uint32_t count() override final;
    bool add(CodesTableRow entry) override final
    {
        return (true);
    }
    bool removeById(uint32_t id) override final
    {
        return (true);
    }
    bool update(CodesTableRow entry) override final
    {
        return (true);
    }
    CodesTableRow getById(uint32_t id) override final
    {
        return CodesTableRow();
    }
    std::vector<CodesTableRow> getLimitOffset(uint32_t offset, uint32_t limit) override final
    {
        return std::vector<CodesTableRow>();
    }
    std::vector<CodesTableRow> getLimitOffsetByField(uint32_t offset,
                                                     uint32_t limit,
                                                     CodesTableFields field,
                                                     const char *str) override final
    {
        return std::vector<CodesTableRow>();
    }
    uint32_t countByFieldId(const char *field, uint32_t id) override final
    {
        return count();
    }
    CodesTableRow GetByMCC(uint32_t mcc);

  private:
    const char *createTableQuery = "CREATE TABLE IF NOT EXISTS codes("
                                   "_id INTEGER PRIMARY KEY,"
                                   "mcc INTEGER,"
                                   "mnc INTEGER,"
                                   "iso TEXT NOT NULL,"
                                   "country TEXT NOT NULL,"
                                   "country_code INTEGER,"
                                   "network TEXT NOT NULL);";
};

M module-db/Tables/MultimediaFilesTable.cpp => module-db/Tables/MultimediaFilesTable.cpp +3 -8
@@ 2,17 2,12 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "MultimediaFilesTable.hpp"

#include "Common/Types.hpp"
#include <Database/QueryResult.hpp>
#include <Utils.hpp>
#include <magic_enum.hpp>
#include <inttypes.h>

#define u32_  "%" PRIu32
#define u32_c "%" PRIu32 ","
#define str_  "%Q"
#define str_c "%Q,"

namespace db::multimedia_files
{
    TableRow CreateTableRow(const QueryResult &result)


@@ 105,7 100,7 @@ namespace db::multimedia_files

    bool MultimediaFilesTable::removeById(uint32_t id)
    {
        return db->execute("DELETE FROM files WHERE _id = %lu;", id);
        return db->execute("DELETE FROM files WHERE _id=" u32_ ";", id);
    }

    bool MultimediaFilesTable::removeByField(TableFields field, const char *str)


@@ 116,7 111,7 @@ namespace db::multimedia_files
            return false;
        }

        return db->execute("DELETE FROM files WHERE %q = %Q;", fieldName.c_str(), str);
        return db->execute("DELETE FROM files WHERE %q=" str_ ";", fieldName.c_str(), str);
    }

    bool MultimediaFilesTable::removeAll()

M module-db/Tables/NotesTable.cpp => module-db/Tables/NotesTable.cpp +15 -14
@@ 2,7 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotesTable.hpp"
#include <log/log.hpp>
#include "Common/Types.hpp"
#include <string>

NotesTable::NotesTable(Database *db) : Table(db)


@@ 16,7 16,7 @@ bool NotesTable::create()
bool NotesTable::add(NotesTableRow entry)
{
    return db->execute(
        "INSERT or ignore INTO notes ( date, snippet ) VALUES ( %lu, '%q' );", entry.date, entry.snippet.c_str());
        "INSERT or ignore INTO notes ( date, snippet ) VALUES (" u32_c str_ ");", entry.date, entry.snippet.c_str());
}

bool NotesTable::removeAll()


@@ 26,7 26,7 @@ bool NotesTable::removeAll()

bool NotesTable::removeById(std::uint32_t id)
{
    return db->execute("DELETE FROM notes where _id = %lu;", id);
    return db->execute("DELETE FROM notes where _id=" u32_ ";", id);
}

bool NotesTable::removeByField(NotesTableFields field, const char *str)


@@ 42,18 42,18 @@ bool NotesTable::removeByField(NotesTableFields field, const char *str)
    default:
        return false;
    }
    return db->execute("DELETE FROM note where %q = '%q';", fieldName.c_str(), str);
    return db->execute("DELETE FROM note where %q=" str_ ";", fieldName.c_str(), str);
}

bool NotesTable::update(NotesTableRow entry)
{
    return db->execute(
        "UPDATE notes SET date = %lu, snippet = '%q' WHERE _id = %lu;", entry.date, entry.snippet.c_str(), entry.ID);
        "UPDATE notes SET date=" u32_c "snippet=" str_ " WHERE _id=" u32_, entry.date, entry.snippet.c_str(), entry.ID);
}

NotesTableRow NotesTable::getById(std::uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM notes WHERE _id = %lu;", id);
    auto retQuery = db->query("SELECT * FROM notes WHERE _id=" u32_ ";", id);
    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
        return NotesTableRow();
    }


@@ 66,7 66,7 @@ NotesTableRow NotesTable::getById(std::uint32_t id)

std::vector<NotesTableRow> NotesTable::getLimitOffset(std::uint32_t offset, std::uint32_t limit)
{
    auto retQuery = db->query("SELECT * FROM notes ORDER BY date DESC LIMIT %lu OFFSET %lu;", limit, offset);
    auto retQuery = db->query("SELECT * FROM notes ORDER BY date DESC LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);
    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
        return std::vector<NotesTableRow>();
    }


@@ 99,11 99,12 @@ std::vector<NotesTableRow> NotesTable::getLimitOffsetByField(std::uint32_t offse
        return std::vector<NotesTableRow>();
    }

    auto retQuery = db->query("SELECT * FROM notes WHERE %q='%q' ORDER BY date DESC LIMIT %lu OFFSET %lu;",
                              fieldName.c_str(),
                              str,
                              limit,
                              offset);
    auto retQuery =
        db->query("SELECT * FROM notes WHERE %q=" str_ " ORDER BY date DESC LIMIT " u32_ " OFFSET " u32_ ";",
                  fieldName.c_str(),
                  str,
                  limit,
                  offset);
    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
        return std::vector<NotesTableRow>();
    }


@@ 125,7 126,7 @@ std::pair<std::vector<NotesTableRow>, int> NotesTable::getByText(const std::stri
{

    int count     = 0;
    auto queryRet = db->query("SELECT COUNT(*), INSTR(snippet,'%q') pos FROM notes WHERE pos > 0;", text.c_str());
    auto queryRet = db->query("SELECT COUNT(*), INSTR(snippet," str_ ") pos FROM notes WHERE pos > 0;", text.c_str());
    if (queryRet && queryRet->getRowCount() != 0) {
        count = (*queryRet)[0].getUInt32();
    }


@@ 162,7 163,7 @@ std::uint32_t NotesTable::count()

std::uint32_t NotesTable::countByFieldId(const char *field, std::uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM notes WHERE %q = %lu;", field, id);
    auto queryRet = db->query("SELECT COUNT(*) FROM notes WHERE %q=" u32_ ";", field, id);
    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;
    }

M module-db/Tables/NotificationsTable.cpp => module-db/Tables/NotificationsTable.cpp +8 -8
@@ 2,10 2,10 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotificationsTable.hpp"
#include "Common/Types.hpp"
#include "Database/Database.hpp"

#include <log/log.hpp>
#include <Utils.hpp>
#include <cassert>

NotificationsTable::NotificationsTable(Database *db) : Table(db)


@@ 18,7 18,7 @@ bool NotificationsTable::create()

bool NotificationsTable::add(NotificationsTableRow entry)
{
    return db->execute("INSERT or IGNORE INTO notifications (key, value, contact_id) VALUES (%lu, %lu, %lu);",
    return db->execute("INSERT or IGNORE INTO notifications (key, value, contact_id) VALUES (" u32_c u32_c u32_ ");",
                       entry.key,
                       entry.value,
                       entry.contactID);


@@ 26,7 26,7 @@ bool NotificationsTable::add(NotificationsTableRow entry)

bool NotificationsTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM notifications where _id = %lu;", id);
    return db->execute("DELETE FROM notifications where _id=" u32_ ";", id);
}

bool NotificationsTable::removeByField(NotificationsTableFields field, const char *str)


@@ 39,12 39,12 @@ bool NotificationsTable::removeByField(NotificationsTableFields field, const cha
        break;
    }

    return db->execute("DELETE FROM notifications where %q = '%q';", fieldName.c_str(), str);
    return db->execute("DELETE FROM notifications where %q=" str_ ";", fieldName.c_str(), str);
}

bool NotificationsTable::update(NotificationsTableRow entry)
{
    return db->execute("UPDATE notifications SET key = %lu, value = %lu, contact_id = %lu WHERE _id = %lu;",
    return db->execute("UPDATE notifications SET key=" u32_c "value=" u32_c "contact_id=" u32_ " WHERE _id=" u32_ ";",
                       entry.key,
                       entry.value,
                       entry.contactID,


@@ 53,7 53,7 @@ bool NotificationsTable::update(NotificationsTableRow entry)

NotificationsTableRow NotificationsTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM notifications WHERE _id= %u;", id);
    auto retQuery = db->query("SELECT * FROM notifications WHERE _id=" u32_ ";", id);

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


@@ 71,7 71,7 @@ NotificationsTableRow NotificationsTable::getById(uint32_t id)

NotificationsTableRow NotificationsTable::getByKey(uint32_t key)
{
    auto retQuery = db->query("SELECT * FROM notifications WHERE key= %u;", key);
    auto retQuery = db->query("SELECT * FROM notifications WHERE key=" u32_ ";", key);

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


@@ 89,7 89,7 @@ NotificationsTableRow NotificationsTable::getByKey(uint32_t key)

std::vector<NotificationsTableRow> NotificationsTable::getLimitOffset(uint32_t offset, uint32_t limit)
{
    auto retQuery = db->query("SELECT * from notifications LIMIT %lu OFFSET %lu;", limit, offset);
    auto retQuery = db->query("SELECT * from notifications LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);

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

M module-db/Tables/SMSTable.cpp => module-db/Tables/SMSTable.cpp +39 -30
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "SMSTable.hpp"
#include "Common/Types.hpp"
#include <log/log.hpp>

SMSTable::SMSTable(Database *db) : Table(db)


@@ 15,7 16,7 @@ bool SMSTable::create()
bool SMSTable::add(SMSTableRow entry)
{
    return db->execute("INSERT or ignore INTO sms ( thread_id,contact_id, date, error_code, body, "
                       "type ) VALUES (%lu,%lu,%lu,0,'%q',%d);",
                       "type ) VALUES (" u32_c u32_c u32_c "0," str_c u32_ ");",
                       entry.threadID,
                       entry.contactID,
                       entry.date,


@@ 25,7 26,7 @@ bool SMSTable::add(SMSTableRow entry)

bool SMSTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM sms where _id = %u;", id);
    return db->execute("DELETE FROM sms where _id=" u32_ ";", id);
}

bool SMSTable::removeByField(SMSTableFields field, const char *str)


@@ 48,13 49,13 @@ bool SMSTable::removeByField(SMSTableFields field, const char *str)
        return false;
    }

    return db->execute("DELETE FROM sms where %q = '%q';", fieldName.c_str(), str);
    return db->execute("DELETE FROM sms where %q=" u32_ ";", fieldName.c_str(), str);
}

bool SMSTable::update(SMSTableRow entry)
{
    return db->execute("UPDATE sms SET thread_id = %lu, contact_id = %lu ,date = %lu, error_code = 0, "
                       "body = '%q', type =%d WHERE _id=%lu;",
    return db->execute("UPDATE sms SET thread_id=" u32_c "contact_id=" u32_c "date=" u32_c "error_code=0,"
                       "body=" str_c "type=" u32_ " WHERE _id=" u32_ ";",
                       entry.threadID,
                       entry.contactID,
                       entry.date,


@@ 65,7 66,7 @@ bool SMSTable::update(SMSTableRow entry)

SMSTableRow SMSTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM sms WHERE _id= %u;", id);
    auto retQuery = db->query("SELECT * FROM sms WHERE _id=" u32_ ";", id);

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


@@ 84,7 85,7 @@ SMSTableRow SMSTable::getById(uint32_t id)

std::vector<SMSTableRow> SMSTable::getByContactId(uint32_t contactId)
{
    auto retQuery = db->query("SELECT * FROM sms WHERE contact_id= %u;", contactId);
    auto retQuery = db->query("SELECT * FROM sms WHERE contact_id=" u32_ ";", contactId);

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


@@ 108,10 109,11 @@ std::vector<SMSTableRow> SMSTable::getByContactId(uint32_t contactId)
}
std::vector<SMSTableRow> SMSTable::getByThreadId(uint32_t threadId, uint32_t offset, uint32_t limit)
{
    auto retQuery = db->query("SELECT * FROM sms WHERE thread_id= %u", threadId);
    auto retQuery = db->query("SELECT * FROM sms WHERE thread_id=" u32_ ";", threadId);

    if (limit != 0) {
        retQuery = db->query("SELECT * FROM sms WHERE thread_id= %u LIMIT %u OFFSET %u", threadId, limit, offset);
        retQuery = db->query(
            "SELECT * FROM sms WHERE thread_id=" u32_ " LIMIT " u32_ " OFFSET " u32_ ";", threadId, limit, offset);
    }

    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {


@@ 139,14 141,15 @@ std::vector<SMSTableRow> SMSTable::getByThreadIdWithoutDraftWithEmptyInput(uint3
                                                                           uint32_t offset,
                                                                           uint32_t limit)
{
    auto retQuery = db->query("SELECT * FROM sms WHERE thread_id= %u AND type != %u UNION ALL SELECT 0 as _id, 0 as "
                              "thread_id, 0 as contact_id, 0 as "
                              "date, 0 as error_code, 0 as body, %u as type LIMIT %u OFFSET %u",
                              threadId,
                              SMSType::DRAFT,
                              SMSType::INPUT,
                              limit,
                              offset);
    auto retQuery =
        db->query("SELECT * FROM sms WHERE thread_id=" u32_ " AND type!=" u32_ " UNION ALL SELECT 0 as _id, 0 as "
                  "thread_id, 0 as contact_id, 0 as "
                  "date, 0 as error_code, 0 as body, " u32_ " as type LIMIT " u32_ " OFFSET " u32_,
                  threadId,
                  SMSType::DRAFT,
                  SMSType::INPUT,
                  limit,
                  offset);

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


@@ 171,7 174,8 @@ std::vector<SMSTableRow> SMSTable::getByThreadIdWithoutDraftWithEmptyInput(uint3

uint32_t SMSTable::countWithoutDraftsByThreadId(uint32_t threadId)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM sms WHERE thread_id= %u AND type != %u;", threadId, SMSType::DRAFT);
    auto queryRet =
        db->query("SELECT COUNT(*) FROM sms WHERE thread_id=" u32_ " AND type!=" u32_ ";", threadId, SMSType::DRAFT);

    if (queryRet == nullptr || queryRet->getRowCount() == 0) {
        return 0;


@@ 182,8 186,10 @@ uint32_t SMSTable::countWithoutDraftsByThreadId(uint32_t threadId)

SMSTableRow SMSTable::getDraftByThreadId(uint32_t threadId)
{
    auto retQuery = db->query(
        "SELECT * FROM sms WHERE thread_id= %u AND type = %u ORDER BY date DESC LIMIT 1;", threadId, SMSType::DRAFT);
    auto retQuery =
        db->query("SELECT * FROM sms WHERE thread_id=" u32_ " AND type=" u32_ " ORDER BY date DESC LIMIT 1;",
                  threadId,
                  SMSType::DRAFT);

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


@@ 203,7 209,7 @@ SMSTableRow SMSTable::getDraftByThreadId(uint32_t threadId)
std::vector<SMSTableRow> SMSTable::getByText(std::string text)
{

    auto retQuery = db->query("SELECT *, INSTR(body,'%q') pos FROM sms WHERE pos > 0;", text.c_str());
    auto retQuery = db->query("SELECT *, INSTR(body," str_ ") pos FROM sms WHERE pos > 0;", text.c_str());

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


@@ 228,8 234,8 @@ std::vector<SMSTableRow> SMSTable::getByText(std::string text)

std::vector<SMSTableRow> SMSTable::getByText(std::string text, uint32_t threadId)
{
    auto retQuery =
        db->query("SELECT *, INSTR(body,'%q') pos FROM sms WHERE pos > 0 AND thread_id=%u;", text.c_str(), threadId);
    auto retQuery = db->query(
        "SELECT *, INSTR(body," str_ ") pos FROM sms WHERE pos > 0 AND thread_id=" u32_ ";", text.c_str(), threadId);
    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
        return {};
    }


@@ 251,7 257,7 @@ std::vector<SMSTableRow> SMSTable::getByText(std::string text, uint32_t threadId

std::vector<SMSTableRow> SMSTable::getLimitOffset(uint32_t offset, uint32_t limit)
{
    auto retQuery = db->query("SELECT * from sms ORDER BY date DESC LIMIT %lu OFFSET %lu;", limit, offset);
    auto retQuery = db->query("SELECT * from sms ORDER BY date DESC LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);

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


@@ 295,7 301,7 @@ std::vector<SMSTableRow> SMSTable::getLimitOffsetByField(uint32_t offset,
        return std::vector<SMSTableRow>();
    }

    auto retQuery = db->query("SELECT * from sms WHERE %q='%q' ORDER BY date DESC LIMIT %lu OFFSET %lu;",
    auto retQuery = db->query("SELECT * from sms WHERE %q=" str_ " ORDER BY date DESC LIMIT " u32_ " OFFSET " u32_ ";",
                              fieldName.c_str(),
                              str,
                              limit,


@@ 335,7 341,7 @@ uint32_t SMSTable::count()

uint32_t SMSTable::countByFieldId(const char *field, uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM sms WHERE %q=%lu;", field, id);
    auto queryRet = db->query("SELECT COUNT(*) FROM sms WHERE %q=" u32_ ";", field, id);

    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;


@@ 347,12 353,15 @@ uint32_t SMSTable::countByFieldId(const char *field, uint32_t id)
std::pair<uint32_t, std::vector<SMSTableRow>> SMSTable::getManyByType(SMSType type, uint32_t offset, uint32_t limit)
{
    auto ret   = std::pair<uint32_t, std::vector<SMSTableRow>>{0, {}};
    auto count = db->query("SELECT COUNT (*) from sms WHERE type='%lu';", type);
    auto count = db->query("SELECT COUNT (*) from sms WHERE type=" u32_ ";", type);
    ret.first  = count == nullptr ? 0 : (*count)[0].getUInt32();
    if (ret.first != 0) {
        limit         = limit == 0 ? ret.first : limit; // no limit intended
        auto retQuery = db->query(
            "SELECT * from sms WHERE type='%lu' ORDER BY date ASC LIMIT %lu OFFSET %lu;", type, limit, offset);
        limit = limit == 0 ? ret.first : limit; // no limit intended
        auto retQuery =
            db->query("SELECT * from sms WHERE type=" u32_ " ORDER BY date ASC LIMIT " u32_ " OFFSET " u32_ ";",
                      type,
                      limit,
                      offset);

        if (retQuery == nullptr || retQuery->getRowCount() == 0) {
            ret.second = {};

M module-db/Tables/ThreadsTable.cpp => module-db/Tables/ThreadsTable.cpp +28 -27
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ThreadsTable.hpp"
#include "Common/Types.hpp"
#include <log/log.hpp>

ThreadsTable::ThreadsTable(Database *db) : Table(db)


@@ 17,7 18,7 @@ bool ThreadsTable::add(ThreadsTableRow entry)

    return db->execute(
        "INSERT or ignore INTO threads ( date, msg_count, read, contact_id, number_id, snippet, last_dir ) VALUES "
        "( %lu, %lu, %lu, %lu, %lu, '%q', %lu );",
        "(" u32_c u32_c u32_c u32_c u32_c str_c u32_ ");",
        entry.date,
        entry.msgCount,
        entry.unreadMsgCount,


@@ 29,14 30,13 @@ bool ThreadsTable::add(ThreadsTableRow entry)

bool ThreadsTable::removeById(uint32_t id)
{
    return db->execute("DELETE FROM threads where _id = %u;", id);
    return db->execute("DELETE FROM threads where _id= " u32_ ";", id);
}

bool ThreadsTable::update(ThreadsTableRow entry)
{
    return db->execute("UPDATE threads SET date = %lu, msg_count = %lu ,read = %lu, contact_id = %lu, number_id = %lu, "
                       "snippet = '%q', "
                       "last_dir = %lu WHERE _id=%lu;",
    return db->execute("UPDATE threads SET date=" u32_c "msg_count=" u32_c "read=" u32_c "contact_id=" u32_c
                       "number_id=" u32_c "snippet=" str_c "last_dir=" u32_ " WHERE _id=" u32_ ";",
                       entry.date,
                       entry.msgCount,
                       entry.unreadMsgCount,


@@ 49,7 49,7 @@ bool ThreadsTable::update(ThreadsTableRow entry)

ThreadsTableRow ThreadsTable::getById(uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM threads WHERE _id= %u;", id);
    auto retQuery = db->query("SELECT * FROM threads WHERE _id=" u32_ ";", id);

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


@@ 86,7 86,8 @@ void fillRetQuery(std::vector<ThreadsTableRow> &ret, const std::unique_ptr<Query
std::vector<ThreadsTableRow> ThreadsTable::getLimitOffset(uint32_t offset, uint32_t limit)
{

    auto retQuery = db->query("SELECT * from threads ORDER BY date DESC LIMIT %lu OFFSET %lu;", limit, offset);
    auto retQuery =
        db->query("SELECT * from threads ORDER BY date DESC LIMIT " u32_ " OFFSET " u32_ ";", limit, offset);

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


@@ 171,7 172,7 @@ uint32_t ThreadsTable::count(EntryState state)

uint32_t ThreadsTable::countByFieldId(const char *field, uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM threads WHERE %q=%u;", field, id);
    auto queryRet = db->query("SELECT COUNT(*) FROM threads WHERE %q=" u32_ ";", field, id);

    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;


@@ 184,26 185,26 @@ std::pair<uint32_t, std::vector<ThreadsTableRow>> ThreadsTable::getBySMSQuery(st
                                                                              uint32_t offset,
                                                                              uint32_t limit)
{
    auto ret       = std::pair<uint32_t, std::vector<ThreadsTableRow>>{0, {}};
    auto count_ret = db->query("SELECT COUNT (*) from sms WHERE sms.body like \'%%%q%%\'", text.c_str());
    ret.first      = count_ret == nullptr ? 0 : (*count_ret)[0].getUInt32();

    if (ret.first != 0) {
        auto retQuery =
            db->query("SELECT * from sms WHERE sms.body like \'%%%q%%\' ORDER BY date DESC LIMIT %lu OFFSET %lu;",
                      text.c_str(),
                      limit,
                      offset);

        if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
            ret.second = {};
        }
        else {
            fillRetQuery(ret.second, retQuery);
        }
    const auto totalCountQuery =
        db->query("SELECT COUNT(*) from sms INNER JOIN threads ON sms.thread_id=threads._id where sms.body "
                  "like \'%%%q%%\'",
                  text.c_str());

    if ((totalCountQuery == nullptr) || (totalCountQuery->getRowCount() == 0)) {
        return {};
    }
    else {
        ret.second = {};

    const auto retQuery = db->query("SELECT * from sms INNER JOIN threads ON sms.thread_id=threads._id where sms.body "
                                    "like \'%%%q%%\' ORDER BY date DESC LIMIT " u32_ " OFFSET " u32_ ";",
                                    text.c_str(),
                                    limit,
                                    offset);

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

    auto ret = std::pair<uint32_t, std::vector<ThreadsTableRow>>{(*totalCountQuery)[0].getUInt32(), {}};
    fillRetQuery(ret.second, retQuery);
    return ret;
}

R module-db/Databases/CalllogDB.cpp => module-db/databases/CalllogDB.cpp +0 -0
R module-db/Databases/CalllogDB.hpp => module-db/databases/CalllogDB.hpp +1 -1
@@ 4,7 4,7 @@
#pragma once

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

class CalllogDB : public Database
{

R module-db/Databases/ContactsDB.cpp => module-db/databases/ContactsDB.cpp +0 -0
R module-db/Databases/ContactsDB.hpp => module-db/databases/ContactsDB.hpp +7 -7
@@ 3,13 3,13 @@

#pragma once

#include <Database/Database.hpp>
#include <Tables/ContactsTable.hpp>
#include <Tables/ContactsNameTable.hpp>
#include <Tables/ContactsNumberTable.hpp>
#include <Tables/ContactsRingtonesTable.hpp>
#include <Tables/ContactsAddressTable.hpp>
#include <Tables/ContactsGroups.hpp>
#include "module-db/Database/Database.hpp"
#include "module-db/Tables/ContactsTable.hpp"
#include "module-db/Tables/ContactsNameTable.hpp"
#include "module-db/Tables/ContactsNumberTable.hpp"
#include "module-db/Tables/ContactsRingtonesTable.hpp"
#include "module-db/Tables/ContactsAddressTable.hpp"
#include "module-db/Tables/ContactsGroups.hpp"

class ContactsDB : public Database
{

R module-db/Databases/EventsDB.cpp => module-db/databases/EventsDB.cpp +0 -0
R module-db/Databases/EventsDB.hpp => module-db/databases/EventsDB.hpp +0 -0
R module-db/Databases/MultimediaFilesDB.cpp => module-db/databases/MultimediaFilesDB.cpp +0 -0
R module-db/Databases/MultimediaFilesDB.hpp => module-db/databases/MultimediaFilesDB.hpp +0 -0
R module-db/Databases/NotesDB.cpp => module-db/databases/NotesDB.cpp +0 -0
R module-db/Databases/NotesDB.hpp => module-db/databases/NotesDB.hpp +1 -1
@@ 4,7 4,7 @@
#pragma once

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

class NotesDB : public Database
{

R module-db/Databases/NotificationsDB.cpp => module-db/databases/NotificationsDB.cpp +0 -0
R module-db/Databases/NotificationsDB.hpp => module-db/databases/NotificationsDB.hpp +0 -0
R module-db/Databases/SmsDB.cpp => module-db/databases/SmsDB.cpp +0 -0
R module-db/Databases/SmsDB.hpp => module-db/databases/SmsDB.hpp +3 -3
@@ 4,9 4,9 @@
#pragma once

#include "Database/Database.hpp"
#include "../Tables/SMSTable.hpp"
#include "../Tables/ThreadsTable.hpp"
#include "../Tables/SMSTemplateTable.hpp"
#include "module-db/Tables/SMSTable.hpp"
#include "module-db/Tables/ThreadsTable.hpp"
#include "module-db/Tables/SMSTemplateTable.hpp"

class SmsDB : public Database
{

R image/user/db/events_001.sql => module-db/databases/scripts/events_001.sql +0 -0
R image/user/db/multimedia_001.sql => module-db/databases/scripts/multimedia_001.sql +0 -0
M module-db/tests/AlarmEventRecord_tests.cpp => module-db/tests/AlarmEventRecord_tests.cpp +4 -15
@@ 1,10 1,9 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common.hpp"

#include <Helpers.hpp>
#include <Database/Database.hpp>
#include <Databases/EventsDB.hpp>
#include <databases/EventsDB.hpp>
#include <Interface/AlarmEventRecord.hpp>
#include <queries/alarm_events/QueryAlarmEventsAdd.hpp>
#include <queries/alarm_events/QueryAlarmEventsEdit.hpp>


@@ 17,7 16,6 @@

#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <filesystem>



@@ 34,15 32,8 @@ TEST_CASE("AlarmEventRecord queries tests")
    constexpr auto testEnabled        = true;
    constexpr auto testSnoozeDuration = 15;

    Database::initialize();

    const auto eventsPath = (std::filesystem::path{"sys/user"} / "events.db");
    RemoveDbFiles(eventsPath.stem());

    auto eventsDB = EventsDB(eventsPath.c_str());
    REQUIRE(eventsDB.isInitialized());

    AlarmEventRecordInterface alarmEventRecordInterface(&eventsDB);
    db::tests::DatabaseUnderTest<EventsDB> db{"events.db", db::tests::getScriptsPath()};
    AlarmEventRecordInterface alarmEventRecordInterface(&db.get());

    auto getQuery = [&](uint32_t id,
                        std::chrono::hours hour,


@@ 260,8 251,6 @@ TEST_CASE("AlarmEventRecord queries tests")
        REQUIRE(retAlarmEvents[1].enabled);
        REQUIRE(retAlarmEvents[2].enabled);
    }

    Database::deinitialize();
}

TEST_CASE("AlarmEventRecord recurrence generation tests")

M module-db/tests/CMakeLists.txt => module-db/tests/CMakeLists.txt +7 -5
@@ 1,3 1,9 @@
add_library(module-db-test_helpers Helpers.cpp)
add_library(module-db::test::helpers ALIAS module-db-test_helpers)
target_link_libraries(module-db-test_helpers PRIVATE module-db)
target_include_directories(module-db-test_helpers PUBLIC . )


add_catch2_executable(
    NAME
        db


@@ 12,7 18,6 @@ add_catch2_executable(
        ContactsRecord_tests.cpp
        ContactsRingtonesTable_tests.cpp
        ContactsTable_tests.cpp
        DbInitializer.cpp
        MultimediaFilesTable_tests.cpp
        NotesRecord_tests.cpp
        NotesTable_tests.cpp


@@ 25,12 30,9 @@ add_catch2_executable(
        SMSTemplateTable_tests.cpp
        ThreadRecord_tests.cpp
        ThreadsTable_tests.cpp
        common.cpp
        
    LIBS
        module-sys
        module-db::test::helpers
        module-db
        json::json
    USE_FS
)
add_subdirectory(test-initializer)

M module-db/tests/CalllogRecord_tests.cpp => module-db/tests/CalllogRecord_tests.cpp +5 -22
@@ 3,34 3,19 @@

#include <catch2/catch.hpp>

#include "Helpers.hpp"
#include "Interface/CalllogRecord.hpp"
#include "Database/Database.hpp"
#include "Databases/CalllogDB.hpp"
#include "module-db/databases/CalllogDB.hpp"

#include <cstdint>
#include <cstring>
#include <filesystem>
#include <algorithm>
#include <iostream>

TEST_CASE("Calllog Record tests")
{
    Database::initialize();

    const auto calllogPath  = (std::filesystem::path{"sys/user"} / "calllog.db");
    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    if (std::filesystem::exists(calllogPath)) {
        REQUIRE(std::filesystem::remove(calllogPath));
    }
    if (std::filesystem::exists(contactsPath)) {
        REQUIRE(std::filesystem::remove(contactsPath));
    }

    CalllogDB calllogDb(calllogPath.c_str());
    ContactsDB contactsDb(contactsPath.c_str());

    REQUIRE(calllogDb.isInitialized());
    REQUIRE(contactsDb.isInitialized());
    db::tests::DatabaseUnderTest<CalllogDB> calllogDb{"calllog.db", db::tests::getPurePhoneScriptsPath()};
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    SECTION("Default Constructor")
    {


@@ 44,7 29,7 @@ TEST_CASE("Calllog Record tests")
        REQUIRE(testRec.isRead == true);
    }

    CalllogRecordInterface calllogRecordInterface(&calllogDb, &contactsDb);
    CalllogRecordInterface calllogRecordInterface(&calllogDb.get(), &contactsDb.get());
    CalllogRecord testRec;
    testRec.presentation = PresentationType::PR_ALLOWED;
    testRec.date         = 100;


@@ 181,6 166,4 @@ TEST_CASE("Calllog Record tests")
        REQUIRE(calllogRecordInterface.GetCount(EntryState::UNREAD) == 0);
        REQUIRE(calllogRecordInterface.GetCount(EntryState::READ) == 4);
    }

    Database::deinitialize();
}

M module-db/tests/CalllogTable_tests.cpp => module-db/tests/CalllogTable_tests.cpp +4 -16
@@ 2,30 2,20 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include "Database/Database.hpp"
#include "Databases/CalllogDB.hpp"
#include "Helpers.hpp"
#include "module-db/databases/CalllogDB.hpp"

#include "Tables/CalllogTable.hpp"

#include <cstdint>
#include <string>
#include <algorithm>
#include <filesystem>

TEST_CASE("Calllog Table tests")
{
    Database::initialize();

    const auto calllogPath = (std::filesystem::path{"sys/user"} / "calllog.db");
    if (std::filesystem::exists(calllogPath)) {
        REQUIRE(std::filesystem::remove(calllogPath));
    }
    db::tests::DatabaseUnderTest<CalllogDB> calllogDb{"calllog.db", db::tests::getPurePhoneScriptsPath()};

    CalllogDB calllogDb{calllogPath.c_str()};
    REQUIRE(calllogDb.isInitialized());

    auto &callsTbl = calllogDb.calls;
    auto &callsTbl = calllogDb.get().calls;

    SECTION("Default Constructor")
    {


@@ 164,6 154,4 @@ TEST_CASE("Calllog Table tests")
        REQUIRE(callsTbl.count(EntryState::UNREAD) == 0);
        REQUIRE(callsTbl.count(EntryState::READ) == 4);
    }

    Database::deinitialize();
}

M module-db/tests/ContactGroups_tests.cpp => module-db/tests/ContactGroups_tests.cpp +14 -29
@@ 1,18 1,13 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common.hpp"
#include <catch2/catch.hpp>
#include "Helpers.hpp"

#include <Databases/ContactsDB.hpp>
#include "module-db/databases/ContactsDB.hpp"
#include <Tables/ContactsTable.hpp>
#include <Tables/ContactsGroups.hpp>

#include <filesystem>

#include <iomanip>
#include <sstream>

namespace consts
{
    uint32_t favouritesId = 1;


@@ 25,16 20,8 @@ void addSomeContacts(ContactsDB &contactsDb);

TEST_CASE("Contact Groups tests")
{
    INFO("sqlite Init");
    Database::initialize();
    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(contactsPath.stem());

    ContactsDB contactDb{contactsPath.c_str()};
    INFO("contactDB init");
    REQUIRE(contactDb.isInitialized());
    ContactsGroupsTable contactGroupsTable = ContactsGroupsTable(&contactDb);
    INFO("Create groups table");
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};
    ContactsGroupsTable contactGroupsTable = ContactsGroupsTable(&contactsDb.get());
    REQUIRE(contactGroupsTable.create());

    SECTION("Adding checking standard groups")


@@ 108,16 95,16 @@ TEST_CASE("Contact Groups tests")
        REQUIRE(someGroups[9].ID == 17);

        INFO("Adding some contacts");
        addSomeContacts(contactDb);
        addSomeContacts(contactsDb.get());
    }

    SECTION("Update Groups")
    {
        addSomeContacts(contactDb);
        addSomeContacts(contactsDb.get());

        ContactsGroupsTableRow favouritesGroup(contactGroupsTable.getById(contactDb.favouritesGroupId()));
        ContactsGroupsTableRow iceGroup(contactGroupsTable.getById(contactDb.iceGroupId()));
        ContactsGroupsTableRow blockedGroup(contactGroupsTable.getById(contactDb.blockedGroupId()));
        ContactsGroupsTableRow favouritesGroup(contactGroupsTable.getById(contactGroupsTable.favouritesId()));
        ContactsGroupsTableRow iceGroup(contactGroupsTable.getById(contactGroupsTable.iceId()));
        ContactsGroupsTableRow blockedGroup(contactGroupsTable.getById(contactGroupsTable.blockedId()));
        ContactsGroupsTableRow familyGroup("Family");
        ContactsGroupsTableRow friendsGroup("Friends");
        ContactsGroupsTableRow workGroup("Work");


@@ 131,13 118,13 @@ TEST_CASE("Contact Groups tests")
        workGroup.ID = contactGroupsTable.getId(workGroup.name);

        // adding contact to some groups
        contactGroupsTable.addContactToGroup(1, contactDb.favouritesGroupId());
        contactGroupsTable.addContactToGroup(1, contactDb.iceGroupId());
        contactGroupsTable.addContactToGroup(1, contactDb.blockedGroupId());
        contactGroupsTable.addContactToGroup(1, contactGroupsTable.favouritesId());
        contactGroupsTable.addContactToGroup(1, contactGroupsTable.iceId());
        contactGroupsTable.addContactToGroup(1, contactGroupsTable.blockedId());
        contactGroupsTable.addContactToGroup(1, familyGroup.ID);
        contactGroupsTable.addContactToGroup(1, workGroup.ID);

        // checking if adding was sucessfull
        // checking if adding was successful
        REQUIRE(contactGroupsTable.getGroupsForContact(1).size() == 5);

        // creating new groups set


@@ 154,7 141,7 @@ TEST_CASE("Contact Groups tests")
        auto checkGroupsSet = contactGroupsTable.getGroupsForContact(1);
        auto checkEnd       = checkGroupsSet.end();

        // checking if update was successfull
        // checking if update was successful
        REQUIRE(checkGroupsSet.size() == 4);
        REQUIRE(checkGroupsSet.find(blockedGroup) != checkEnd);
        REQUIRE(checkGroupsSet.find(familyGroup) != checkEnd);


@@ 164,8 151,6 @@ TEST_CASE("Contact Groups tests")
        REQUIRE(checkGroupsSet.find(favouritesGroup) == checkEnd);
        REQUIRE(checkGroupsSet.find(iceGroup) == checkEnd);
    }

    Database::deinitialize();
}

void addSomeContacts(ContactsDB &contactsDb)

M module-db/tests/ContactsAddressTable_tests.cpp => module-db/tests/ContactsAddressTable_tests.cpp +29 -39
@@ 2,21 2,12 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include "Databases/ContactsDB.hpp"
#include <filesystem>
#include "Helpers.hpp"
#include "module-db/databases/ContactsDB.hpp"

TEST_CASE("Contacts address Table tests")
{
    Database::initialize();

    const auto callogPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    if (std::filesystem::exists(callogPath)) {
        REQUIRE(std::filesystem::remove(callogPath));
    }

    ContactsDB contactsdb{callogPath.c_str()};
    REQUIRE(contactsdb.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    ContactsAddressTableRow testRow1 = {Record(DB_ID_NONE),
                                        .contactID = 0,


@@ 24,71 15,70 @@ TEST_CASE("Contacts address Table tests")
                                        .note      = "Test note",
                                        .mail      = "test@mudita.com"};

    const auto contactsCount = contactsdb.address.count() + 1;
    const auto contactsCount = contactsDb.get().address.count() + 1;
    // clear contacts table
    for (std::uint32_t id = 1; id <= contactsCount; id++) {
        REQUIRE(contactsdb.address.removeById(id));
        REQUIRE(contactsDb.get().address.removeById(id));
    }
    // add 4 elements into table
    REQUIRE(contactsdb.address.add(testRow1));
    REQUIRE(contactsdb.address.add(testRow1));
    REQUIRE(contactsdb.address.add(testRow1));
    REQUIRE(contactsdb.address.add(testRow1));
    REQUIRE(contactsDb.get().address.add(testRow1));
    REQUIRE(contactsDb.get().address.add(testRow1));
    REQUIRE(contactsDb.get().address.add(testRow1));
    REQUIRE(contactsDb.get().address.add(testRow1));

    // Table should have 4 elements
    REQUIRE(contactsdb.address.count() == 4);
    REQUIRE(contactsDb.get().address.count() == 4);

    // update existing element in table
    testRow1.ID   = 4;
    testRow1.note = "Modified test note";
    REQUIRE(contactsdb.address.update(testRow1));
    REQUIRE(contactsDb.get().address.update(testRow1));

    // Get table row using valid ID & check if it was updated
    auto sms = contactsdb.address.getById(4);
    auto sms = contactsDb.get().address.getById(4);
    REQUIRE(sms.note == testRow1.note);

    // Get table row using invalid ID(should return empty contactsdb.addressRow)
    auto smsFailed = contactsdb.address.getById(100);
    // Get table row using invalid ID(should return empty contactsDb.get().addressRow)
    auto smsFailed = contactsDb.get().address.getById(100);
    REQUIRE(smsFailed.note == "");

    // Get table rows using valid offset/limit parameters
    auto retOffsetLimit = contactsdb.address.getLimitOffset(0, 4);
    auto retOffsetLimit = contactsDb.get().address.getLimitOffset(0, 4);
    REQUIRE(retOffsetLimit.size() == 4);

    // Get table rows using valid offset/limit parameters and specific field's ID
    REQUIRE(contactsdb.address.getLimitOffsetByField(0, 4, ContactAddressTableFields::Mail, "test@mudita.com").size() ==
            4);
    REQUIRE(contactsDb.get()
                .address.getLimitOffsetByField(0, 4, ContactAddressTableFields::Mail, "test@mudita.com")
                .size() == 4);

    // Get table rows using invalid limit parameters(should return 4 elements instead of 100)
    auto retOffsetLimitBigger = contactsdb.address.getLimitOffset(0, 100);
    auto retOffsetLimitBigger = contactsDb.get().address.getLimitOffset(0, 100);
    REQUIRE(retOffsetLimitBigger.size() == 4);

    // Get table rows using invalid offset/limit parameters(should return empty object)
    auto retOffsetLimitFailed = contactsdb.address.getLimitOffset(5, 4);
    auto retOffsetLimitFailed = contactsDb.get().address.getLimitOffset(5, 4);
    REQUIRE(retOffsetLimitFailed.size() == 0);

    // Get count of elements by field's ID
    REQUIRE(contactsdb.address.countByFieldId("contact_id", 0) == 4);
    REQUIRE(contactsDb.get().address.countByFieldId("contact_id", 0) == 4);

    // Get count of elements by invalid field's ID
    REQUIRE(contactsdb.address.countByFieldId("invalid_field", 0) == 0);
    REQUIRE(contactsDb.get().address.countByFieldId("invalid_field", 0) == 0);

    // Remove existing element
    REQUIRE(contactsdb.address.removeById(2));
    REQUIRE(contactsDb.get().address.removeById(2));

    // Table should have now 3 elements
    REQUIRE(contactsdb.address.count() == 3);
    REQUIRE(contactsDb.get().address.count() == 3);

    // Remove non existing element
    REQUIRE(contactsdb.address.removeById(100));
    REQUIRE(contactsDb.get().address.removeById(100));

    // Remove all elements from table
    REQUIRE(contactsdb.address.removeById(1));
    REQUIRE(contactsdb.address.removeById(3));
    REQUIRE(contactsdb.address.removeById(4));
    REQUIRE(contactsDb.get().address.removeById(1));
    REQUIRE(contactsDb.get().address.removeById(3));
    REQUIRE(contactsDb.get().address.removeById(4));

    // Table should be empty now
    REQUIRE(contactsdb.address.count() == 0);

    Database::deinitialize();
    REQUIRE(contactsDb.get().address.count() == 0);
}

M module-db/tests/ContactsNameTable_tests.cpp => module-db/tests/ContactsNameTable_tests.cpp +28 -39
@@ 2,95 2,84 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include "Helpers.hpp"

#include "Database/Database.hpp"
#include "Databases/ContactsDB.hpp"
#include "module-db/databases/ContactsDB.hpp"

#include <algorithm>

#include <cstdint>
#include <cstdio>
#include <cstring>
#include <filesystem>

TEST_CASE("Contacts Name Table tests")
{
    Database::initialize();

    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    if (std::filesystem::exists(contactsPath)) {
        REQUIRE(std::filesystem::remove(contactsPath));
    }

    ContactsDB contactsdb(contactsPath.c_str());
    REQUIRE(contactsdb.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    ContactsNameTableRow testRow1 = {
        Record(DB_ID_NONE), .contactID = DB_ID_NONE, .namePrimary = "Mateusz", .nameAlternative = "Pati"};

    const auto contactsCount = contactsdb.name.count() + 1;
    const auto contactsCount = contactsDb.get().name.count() + 1;
    // clear contacts table
    for (std::uint32_t id = 1; id <= contactsCount; id++) {
        REQUIRE(contactsdb.name.removeById(id));
        REQUIRE(contactsDb.get().name.removeById(id));
    }
    // add 4 elements into table
    REQUIRE(contactsdb.name.add(testRow1));
    REQUIRE(contactsdb.name.add(testRow1));
    REQUIRE(contactsdb.name.add(testRow1));
    REQUIRE(contactsdb.name.add(testRow1));
    REQUIRE(contactsDb.get().name.add(testRow1));
    REQUIRE(contactsDb.get().name.add(testRow1));
    REQUIRE(contactsDb.get().name.add(testRow1));
    REQUIRE(contactsDb.get().name.add(testRow1));

    // Table should have 4 elements
    REQUIRE(contactsdb.name.count() == 4);
    REQUIRE(contactsDb.get().name.count() == 4);

    // update existing element in table
    testRow1.ID              = 4;
    testRow1.nameAlternative = "Pateusz";
    REQUIRE(contactsdb.name.update(testRow1));
    REQUIRE(contactsDb.get().name.update(testRow1));

    // Get table row using valid ID & check if it was updated
    auto sms = contactsdb.name.getById(4);
    auto sms = contactsDb.get().name.getById(4);
    REQUIRE(sms.nameAlternative == testRow1.nameAlternative);

    // Get table row using invalid ID(should return empty contactsdb.nameRow)
    auto smsFailed = contactsdb.name.getById(100);
    // Get table row using invalid ID(should return empty contactsDb.get().nameRow)
    auto smsFailed = contactsDb.get().name.getById(100);
    REQUIRE(smsFailed.nameAlternative == "");

    // Get table rows using valid offset/limit parameters
    auto retOffsetLimit = contactsdb.name.getLimitOffset(0, 4);
    auto retOffsetLimit = contactsDb.get().name.getLimitOffset(0, 4);
    REQUIRE(retOffsetLimit.size() == 4);

    // Get table rows using valid offset/limit parameters and specific field's ID
    REQUIRE(contactsdb.name.getLimitOffsetByField(0, 4, ContactNameTableFields::NamePrimary, "Mateusz").size() == 4);
    REQUIRE(contactsDb.get().name.getLimitOffsetByField(0, 4, ContactNameTableFields::NamePrimary, "Mateusz").size() ==
            4);

    // Get table rows using invalid limit parameters(should return 4 elements instead of 100)
    auto retOffsetLimitBigger = contactsdb.name.getLimitOffset(0, 100);
    auto retOffsetLimitBigger = contactsDb.get().name.getLimitOffset(0, 100);
    REQUIRE(retOffsetLimitBigger.size() == 4);

    // Get table rows using invalid offset/limit parameters(should return empty object)
    auto retOffsetLimitFailed = contactsdb.name.getLimitOffset(5, 4);
    auto retOffsetLimitFailed = contactsDb.get().name.getLimitOffset(5, 4);
    REQUIRE(retOffsetLimitFailed.size() == 0);

    // Get count of elements by field's ID
    REQUIRE(contactsdb.name.countByFieldId("contact_id", 0) == 4);
    REQUIRE(contactsDb.get().name.countByFieldId("contact_id", 0) == 4);

    // Get count of elements by invalid field's ID
    REQUIRE(contactsdb.name.countByFieldId("invalid_field", 0) == 0);
    REQUIRE(contactsDb.get().name.countByFieldId("invalid_field", 0) == 0);

    REQUIRE(contactsdb.name.removeById(2));
    REQUIRE(contactsDb.get().name.removeById(2));

    // Table should have now 3 elements
    REQUIRE(contactsdb.name.count() == 3);
    REQUIRE(contactsDb.get().name.count() == 3);

    // Remove non existing element
    REQUIRE(contactsdb.name.removeById(100));
    REQUIRE(contactsDb.get().name.removeById(100));

    // Remove all elements from table
    REQUIRE(contactsdb.name.removeById(1));
    REQUIRE(contactsdb.name.removeById(3));
    REQUIRE(contactsdb.name.removeById(4));
    REQUIRE(contactsDb.get().name.removeById(1));
    REQUIRE(contactsDb.get().name.removeById(3));
    REQUIRE(contactsDb.get().name.removeById(4));

    // Table should be empty now
    REQUIRE(contactsdb.name.count() == 0);

    Database::deinitialize();
    REQUIRE(contactsDb.get().name.count() == 0);
}

M module-db/tests/ContactsNumberTable_tests.cpp => module-db/tests/ContactsNumberTable_tests.cpp +29 -40
@@ 2,96 2,85 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include <filesystem>
#include "Helpers.hpp"

#include "Database/Database.hpp"
#include "Databases/ContactsDB.hpp"
#include "module-db/databases/ContactsDB.hpp"

#include <algorithm>

#include <cstdint>
#include <cstdio>
#include <cstring>

TEST_CASE("Contacts Number Table tests")
{
    Database::initialize();

    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    if (std::filesystem::exists(contactsPath)) {
        REQUIRE(std::filesystem::remove(contactsPath));
    }

    ContactsDB contactsdb{contactsPath.c_str()};
    REQUIRE(contactsdb.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    ContactsNumberTableRow testRow1 = {
        Record(DB_ID_NONE), .contactID = DB_ID_NONE, .numberUser = "111222333", .numbere164 = "333222111"};

    const auto contactsCount = contactsdb.number.count() + 1;
    const auto contactsCount = contactsDb.get().number.count() + 1;
    // clear contacts table
    for (std::uint32_t id = 1; id <= contactsCount; id++) {
        REQUIRE(contactsdb.number.removeById(id));
        REQUIRE(contactsDb.get().number.removeById(id));
    }
    // add 4 elements into table
    REQUIRE(contactsdb.number.add(testRow1));
    REQUIRE(contactsdb.number.add(testRow1));
    REQUIRE(contactsdb.number.add(testRow1));
    REQUIRE(contactsdb.number.add(testRow1));
    REQUIRE(contactsDb.get().number.add(testRow1));
    REQUIRE(contactsDb.get().number.add(testRow1));
    REQUIRE(contactsDb.get().number.add(testRow1));
    REQUIRE(contactsDb.get().number.add(testRow1));

    // Table should have 4 elements
    REQUIRE(contactsdb.number.count() == 4);
    REQUIRE(contactsDb.get().number.count() == 4);

    // update existing element in table
    testRow1.ID         = 4;
    testRow1.numberUser = "999888777";
    REQUIRE(contactsdb.number.update(testRow1));
    REQUIRE(contactsDb.get().number.update(testRow1));

    // Get table row using valid ID & check if it was updated
    auto sms = contactsdb.number.getById(4);
    auto sms = contactsDb.get().number.getById(4);
    REQUIRE(sms.numberUser == testRow1.numberUser);

    // Get table row using invalid ID(should return empty contactsdb.numberRow)
    auto smsFailed = contactsdb.number.getById(100);
    // Get table row using invalid ID(should return empty contactsDb.get().numberRow)
    auto smsFailed = contactsDb.get().number.getById(100);
    REQUIRE(smsFailed.numberUser == "");

    // Get table rows using valid offset/limit parameters
    auto retOffsetLimit = contactsdb.number.getLimitOffset(0, 4);
    auto retOffsetLimit = contactsDb.get().number.getLimitOffset(0, 4);
    REQUIRE(retOffsetLimit.size() == 4);

    // Get table rows using valid offset/limit parameters and specific field's ID
    REQUIRE(contactsdb.number.getLimitOffsetByField(0, 4, ContactNumberTableFields::NumberE164, "333222111").size() ==
            4);
    REQUIRE(
        contactsDb.get().number.getLimitOffsetByField(0, 4, ContactNumberTableFields::NumberE164, "333222111").size() ==
        4);

    // Get table rows using invalid limit parameters(should return 4 elements instead of 100)
    auto retOffsetLimitBigger = contactsdb.number.getLimitOffset(0, 100);
    auto retOffsetLimitBigger = contactsDb.get().number.getLimitOffset(0, 100);
    REQUIRE(retOffsetLimitBigger.size() == 4);

    // Get table rows using invalid offset/limit parameters(should return empty object)
    auto retOffsetLimitFailed = contactsdb.number.getLimitOffset(5, 4);
    auto retOffsetLimitFailed = contactsDb.get().number.getLimitOffset(5, 4);
    REQUIRE(retOffsetLimitFailed.size() == 0);

    // Get count of elements by field's ID
    REQUIRE(contactsdb.number.countByFieldId("contact_id", DB_ID_NONE) == 4);
    REQUIRE(contactsDb.get().number.countByFieldId("contact_id", DB_ID_NONE) == 4);

    // Get count of elements by invalid field's ID
    REQUIRE(contactsdb.number.countByFieldId("invalid_field", DB_ID_NONE) == 0);
    REQUIRE(contactsDb.get().number.countByFieldId("invalid_field", DB_ID_NONE) == 0);

    REQUIRE(contactsdb.number.removeById(2));
    REQUIRE(contactsDb.get().number.removeById(2));

    // Table should have now 3 elements
    REQUIRE(contactsdb.number.count() == 3);
    REQUIRE(contactsDb.get().number.count() == 3);

    // Remove non existing element
    REQUIRE(contactsdb.number.removeById(100));
    REQUIRE(contactsDb.get().number.removeById(100));

    // Remove all elements from table
    REQUIRE(contactsdb.number.removeById(1));
    REQUIRE(contactsdb.number.removeById(3));
    REQUIRE(contactsdb.number.removeById(4));
    REQUIRE(contactsDb.get().number.removeById(1));
    REQUIRE(contactsDb.get().number.removeById(3));
    REQUIRE(contactsDb.get().number.removeById(4));

    // Table should be empty now
    REQUIRE(contactsdb.number.count() == 0);

    Database::deinitialize();
    REQUIRE(contactsDb.get().number.count() == 0);
}

M module-db/tests/ContactsRecord_tests.cpp => module-db/tests/ContactsRecord_tests.cpp +22 -68
@@ 2,21 2,15 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common.hpp"
#include "Helpers.hpp"
#include <catch2/catch.hpp>

#include "Interface/ContactRecord.hpp"
#include <filesystem>
#include <i18n/i18n.hpp>

TEST_CASE("Contact Record db tests")
{
    Database::initialize();

    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(contactsPath.stem());

    ContactsDB contactDB(contactsPath.c_str());
    REQUIRE(contactDB.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    const char *primaryNameTest                   = "PrimaryNameTest";
    const char *alternativeNameTest               = "AlternativeNameTest";


@@ 29,7 23,7 @@ TEST_CASE("Contact Record db tests")
    const char *speeddialTest                     = "100";
    const ContactNumberType contactNumberTypeTest = ContactNumberType::PAGER;

    ContactRecordInterface contRecInterface(&contactDB);
    ContactRecordInterface contRecInterface(&contactsDb.get());

    ContactRecord recordIN;



@@ 134,8 128,6 @@ TEST_CASE("Contact Record db tests")
            REQUIRE(w.speeddial == speeddialTest);
        }
    }

    Database::deinitialize();
}

TEST_CASE("Test contact name formatting")


@@ 254,14 246,9 @@ TEST_CASE("Test converting contact data to string")

TEST_CASE("Contact record numbers update")
{
    Database::initialize();
    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(contactsPath.stem());

    ContactsDB contactDB(contactsPath.c_str());
    REQUIRE(contactDB.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    auto records = ContactRecordInterface(&contactDB);
    auto records = ContactRecordInterface(&contactsDb.get());

    ContactRecord testRecord, otherRecord;
    std::array<std::string, 4> numbers   = {{{"600100100"}, {"600100200"}, {"600100300"}, {"600100400"}}};


@@ 288,7 275,7 @@ TEST_CASE("Contact record numbers update")
        auto newRecord = records.GetByID(1);
        REQUIRE(newRecord.numbers.size() == 2);
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 4);
        REQUIRE(contactsDb.get().number.count() == 4);

        auto validatationRecord = records.GetByID(1);
        REQUIRE(validatationRecord.numbers.size() == 2);


@@ 309,7 296,7 @@ TEST_CASE("Contact record numbers update")
        newRecord.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(numbers[1], std::string(""))});
        REQUIRE(records.Update(newRecord));

        REQUIRE(contactDB.number.count() == 4);
        REQUIRE(contactsDb.get().number.count() == 4);

        auto validatationRecord = records.GetByID(1);
        REQUIRE(validatationRecord.numbers.size() == 1);


@@ 323,7 310,7 @@ TEST_CASE("Contact record numbers update")
        newRecord.numbers = std::vector<ContactRecord::Number>(
            {ContactRecord::Number(numbers[0], std::string("")), ContactRecord::Number(numbers[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 4);
        REQUIRE(contactsDb.get().number.count() == 4);

        validatationRecord = records.GetByID(1);
        REQUIRE(validatationRecord.numbers.size() == 2);


@@ 348,7 335,7 @@ TEST_CASE("Contact record numbers update")
        REQUIRE(records.Update(newRecord));

        auto validatationRecord = records.GetByID(1);
        REQUIRE(contactDB.number.count() == 4);
        REQUIRE(contactsDb.get().number.count() == 4);
        REQUIRE(validatationRecord.numbers.size() == 2);
        REQUIRE(validatationRecord.numbers[0].number.getEntered() == numbers[1]);
        REQUIRE(validatationRecord.numbers[1].number.getEntered() == numbers[0]);


@@ 362,7 349,7 @@ TEST_CASE("Contact record numbers update")
        newRecord.numbers = std::vector<ContactRecord::Number>(
            {ContactRecord::Number(numbers[2], std::string("")), ContactRecord::Number(numbers[1], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 4);
        REQUIRE(contactsDb.get().number.count() == 4);

        auto validatationRecord = records.GetByID(1);
        REQUIRE(validatationRecord.numbers.size() == 2);


@@ 481,7 468,7 @@ TEST_CASE("Contact record numbers update")
        newRecord.numbers = std::vector<ContactRecord::Number>(
            {ContactRecord::Number(numbers[2], std::string("")), ContactRecord::Number(numbers[3], std::string(""))});
        REQUIRE(records.Update(newRecord));
        REQUIRE(contactDB.number.count() == 4);
        REQUIRE(contactsDb.get().number.count() == 4);

        auto validationRecord = records.GetByIdWithTemporary(1);
        REQUIRE(validationRecord.ID != DB_ID_NONE);


@@ 509,15 496,10 @@ TEST_CASE("Contact record numbers update")

TEST_CASE("Contacts list merge")
{
    Database::initialize();
    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(contactsPath.stem());

    ContactsDB contactDB(contactsPath.c_str());
    REQUIRE(contactDB.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    // Preparation of DB initial state
    auto records                                                          = ContactRecordInterface(&contactDB);
    auto records                                                          = ContactRecordInterface(&contactsDb.get());
    std::array<std::pair<std::string, std::string>, 3> rawContactsInitial = {
        {{"600100100", "test1"}, {"600100200", "test2"}, {"600100300", "test3"}}};
    for (auto &rawContact : rawContactsInitial) {


@@ 603,21 585,14 @@ TEST_CASE("Contacts list merge")
        REQUIRE(validatationRecord.numbers[0].number.getEntered() == rawContactsOverlapping[1].first);
        REQUIRE(validatationRecord.primaryName == rawContactsOverlapping[1].second);
    }

    Database::deinitialize();
}

TEST_CASE("Contacts list merge - advanced cases")
{
    Database::initialize();
    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(contactsPath.stem());

    ContactsDB contactDB(contactsPath.c_str());
    REQUIRE(contactDB.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    // Preparation of DB initial state
    auto records = ContactRecordInterface(&contactDB);
    auto records = ContactRecordInterface(&contactsDb.get());
    // 3 numbers in single contact
    std::array<std::string, 3> numbers = {"600100100", "600100200", "600100300"};
    ContactRecord record;


@@ 650,15 625,10 @@ TEST_CASE("Contacts list merge - advanced cases")

TEST_CASE("Contacts list duplicates search")
{
    Database::initialize();
    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(contactsPath.stem());

    ContactsDB contactDB(contactsPath.c_str());
    REQUIRE(contactDB.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    // Preparation of DB initial state
    auto records                                                          = ContactRecordInterface(&contactDB);
    auto records                                                          = ContactRecordInterface(&contactsDb.get());
    std::array<std::pair<std::string, std::string>, 3> rawContactsInitial = {
        {{"600100100", "test1"}, {"600100200", "test2"}, {"600100300", "test3"}}};
    for (auto &rawContact : rawContactsInitial) {


@@ 698,21 668,14 @@ TEST_CASE("Contacts list duplicates search")

    REQUIRE(duplicates[1].numbers[0].number.getEntered() == rawContactsToCheck[2].first);
    REQUIRE(duplicates[1].primaryName == rawContactsToCheck[2].second);

    Database::deinitialize();
}

TEST_CASE("Check if new contact record can be recognised as a duplicate in DB")
{
    Database::initialize();
    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(contactsPath.stem());

    ContactsDB contactDB(contactsPath.c_str());
    REQUIRE(contactDB.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    // Preparation of DB initial state
    auto records = ContactRecordInterface(&contactDB);
    auto records = ContactRecordInterface(&contactsDb.get());
    ContactRecord testContactRecord;

    testContactRecord.primaryName     = "PrimaryNameTest";


@@ 747,21 710,14 @@ TEST_CASE("Check if new contact record can be recognised as a duplicate in DB")

        REQUIRE(records.verifyDuplicate(duplicateContactRecord) == true);
    }

    Database::deinitialize();
}

TEST_CASE("Check if new contact record exists in DB as a temporary contact")
{
    Database::initialize();
    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(contactsPath.stem());

    ContactsDB contactDB(contactsPath.c_str());
    REQUIRE(contactDB.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    // Preparation of DB initial state
    auto records = ContactRecordInterface(&contactDB);
    auto records = ContactRecordInterface(&contactsDb.get());
    ContactRecord testContactRecord;

    testContactRecord.primaryName     = "PrimaryNameTest";


@@ 790,7 746,7 @@ TEST_CASE("Check if new contact record exists in DB as a temporary contact")
        temporaryContactRecord.numbers = std::vector<ContactRecord::Number>({
            ContactRecord::Number("600123452", "+48600123452", ContactNumberType::HOME),
        });
        temporaryContactRecord.addToGroup(ContactsDB::temporaryGroupId());
        temporaryContactRecord.addToGroup(contactsDb.get().groups.temporaryId());

        REQUIRE(records.Add(temporaryContactRecord));



@@ 803,6 759,4 @@ TEST_CASE("Check if new contact record exists in DB as a temporary contact")

        REQUIRE(records.verifyTemporary(noTemporaryContactRecord) == true);
    }

    Database::deinitialize();
}

M module-db/tests/ContactsRingtonesTable_tests.cpp => module-db/tests/ContactsRingtonesTable_tests.cpp +28 -41
@@ 2,96 2,83 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include "Helpers.hpp"

#include "Database/Database.hpp"
#include "Databases/ContactsDB.hpp"
#include "module-db/databases/ContactsDB.hpp"

#include <algorithm>
#include <filesystem>

#include <cstdint>
#include <cstdio>
#include <cstring>

TEST_CASE("Contacts Ringtones Table tests")
{
    Database::initialize();

    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    if (std::filesystem::exists(contactsPath)) {
        REQUIRE(std::filesystem::remove(contactsPath));
    }

    ContactsDB contactsdb{contactsPath.c_str()};
    REQUIRE(contactsdb.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    ContactsRingtonesTableRow testRow1(DB_ID_NONE, DB_ID_NONE, "/test/assets/path/ringtone.wr");

    const auto contactsCount = contactsdb.ringtones.count() + 1;
    const auto contactsCount = contactsDb.get().ringtones.count() + 1;
    // clear contacts table
    for (std::uint32_t id = 1; id <= contactsCount; id++) {
        REQUIRE(contactsdb.ringtones.removeById(id));
        REQUIRE(contactsDb.get().ringtones.removeById(id));
    }
    // add 4 elements into table
    REQUIRE(contactsdb.ringtones.add(testRow1));
    REQUIRE(contactsdb.ringtones.add(testRow1));
    REQUIRE(contactsdb.ringtones.add(testRow1));
    REQUIRE(contactsdb.ringtones.add(testRow1));
    REQUIRE(contactsDb.get().ringtones.add(testRow1));
    REQUIRE(contactsDb.get().ringtones.add(testRow1));
    REQUIRE(contactsDb.get().ringtones.add(testRow1));
    REQUIRE(contactsDb.get().ringtones.add(testRow1));

    // Table should have 4 elements
    REQUIRE(contactsdb.ringtones.count() == 4);
    REQUIRE(contactsDb.get().ringtones.count() == 4);

    // update existing element in table
    testRow1.ID        = 4;
    testRow1.assetPath = "/testnew/assets/path/ringtone2.wr";
    REQUIRE(contactsdb.ringtones.update(testRow1));
    REQUIRE(contactsDb.get().ringtones.update(testRow1));

    // Get table row using valid ID & check if it was updated
    auto sms = contactsdb.ringtones.getById(4);
    auto sms = contactsDb.get().ringtones.getById(4);
    REQUIRE(sms.assetPath == testRow1.assetPath);

    // Get table row using invalid ID(should return empty contactsdb.ringtonesRow)
    auto smsFailed = contactsdb.ringtones.getById(100);
    // Get table row using invalid ID(should return empty contactsDb.get().ringtonesRow)
    auto smsFailed = contactsDb.get().ringtones.getById(100);
    REQUIRE(smsFailed.assetPath == "");

    // Get table rows using valid offset/limit parameters
    auto retOffsetLimit = contactsdb.ringtones.getLimitOffset(0, 4);
    auto retOffsetLimit = contactsDb.get().ringtones.getLimitOffset(0, 4);
    REQUIRE(retOffsetLimit.size() == 4);

    // Get table rows using valid offset/limit parameters and specific field's ID
    REQUIRE(contactsdb.ringtones
    REQUIRE(contactsDb.get()
                .ringtones
                .getLimitOffsetByField(0, 4, ContactRingtonesTableFields::AssetPath, "/test/assets/path/ringtone.wr")
                .size() == 3);

    // Get table rows using invalid limit parameters(should return 4 elements instead of 100)
    auto retOffsetLimitBigger = contactsdb.ringtones.getLimitOffset(0, 100);
    auto retOffsetLimitBigger = contactsDb.get().ringtones.getLimitOffset(0, 100);
    REQUIRE(retOffsetLimitBigger.size() == 4);

    // Get table rows using invalid offset/limit parameters(should return empty object)
    auto retOffsetLimitFailed = contactsdb.ringtones.getLimitOffset(5, 4);
    auto retOffsetLimitFailed = contactsDb.get().ringtones.getLimitOffset(5, 4);
    REQUIRE(retOffsetLimitFailed.size() == 0);

    // Get count of elements by field's ID
    REQUIRE(contactsdb.ringtones.countByFieldId("contact_id", 0) == 4);
    REQUIRE(contactsDb.get().ringtones.countByFieldId("contact_id", 0) == 4);

    // Get count of elements by invalid field's ID
    REQUIRE(contactsdb.ringtones.countByFieldId("invalid_field", 0) == 0);
    REQUIRE(contactsDb.get().ringtones.countByFieldId("invalid_field", 0) == 0);

    REQUIRE(contactsdb.ringtones.removeById(2));
    REQUIRE(contactsDb.get().ringtones.removeById(2));

    // Table should have now 3 elements
    REQUIRE(contactsdb.ringtones.count() == 3);
    REQUIRE(contactsDb.get().ringtones.count() == 3);

    // Remove non existing element
    REQUIRE(contactsdb.ringtones.removeById(100));
    REQUIRE(contactsDb.get().ringtones.removeById(100));

    // Remove all elements from table
    REQUIRE(contactsdb.ringtones.removeById(1));
    REQUIRE(contactsdb.ringtones.removeById(3));
    REQUIRE(contactsdb.ringtones.removeById(4));
    REQUIRE(contactsDb.get().ringtones.removeById(1));
    REQUIRE(contactsDb.get().ringtones.removeById(3));
    REQUIRE(contactsDb.get().ringtones.removeById(4));

    // Table should be empty now
    REQUIRE(contactsdb.ringtones.count() == 0);

    Database::deinitialize();
    REQUIRE(contactsDb.get().ringtones.count() == 0);
}

M module-db/tests/ContactsTable_tests.cpp => module-db/tests/ContactsTable_tests.cpp +46 -51
@@ 2,22 2,15 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include "Helpers.hpp"
#include <filesystem>

#include "Databases/ContactsDB.hpp"
#include "module-db/databases/ContactsDB.hpp"
#include "Tables/ContactsTable.hpp"

TEST_CASE("Contacts Table tests")
{
    Database::initialize();

    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    if (std::filesystem::exists(contactsPath)) {
        REQUIRE(std::filesystem::remove(contactsPath));
    }

    ContactsDB contactsdb{contactsPath.c_str()};
    REQUIRE(contactsdb.isInitialized());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    ContactsTableRow testRow1 = {Record(DB_ID_NONE),
                                 .nameID    = DB_ID_NONE,


@@ 27,93 20,95 @@ TEST_CASE("Contacts Table tests")
                                 .speedDial = "666"

    };
    REQUIRE(contactsdb.execute("INSERT OR REPLACE INTO  contact_name (_id,contact_id,name_primary,name_alternative) "
                               "VALUES (1,1,'Alek','Wyczesany');"));
    REQUIRE(contactsdb.execute("INSERT OR REPLACE INTO  contact_name (_id,contact_id,name_primary,name_alternative) "
                               "VALUES (2,2,'Zofia','Wyczesany');"));
    REQUIRE(contactsdb.execute("INSERT OR REPLACE INTO  contact_name (_id,contact_id,name_primary,name_alternative) "
                               "VALUES (3,3,'Cezary','Wyczesany');"));
    REQUIRE(contactsdb.execute("INSERT OR REPLACE INTO  contact_name (_id,contact_id,name_primary,name_alternative) "
                               "VALUES (4,4,'Alek','Arbuz');"));
    REQUIRE(
        contactsdb.execute("INSERT OR REPLACE INTO  contact_match_groups (_id,group_id,contact_id) VALUES (1,1,1);"));
        contactsDb.get().execute("INSERT OR REPLACE INTO  contact_name (_id,contact_id,name_primary,name_alternative) "
                                 "VALUES (1,1,'Alek','Wyczesany');"));
    REQUIRE(
        contactsdb.execute("INSERT OR REPLACE INTO  contact_match_groups (_id,group_id,contact_id) VALUES (2,2,2);"));
        contactsDb.get().execute("INSERT OR REPLACE INTO  contact_name (_id,contact_id,name_primary,name_alternative) "
                                 "VALUES (2,2,'Zofia','Wyczesany');"));
    REQUIRE(
        contactsdb.execute("INSERT OR REPLACE INTO  contact_match_groups (_id,group_id,contact_id) VALUES (3,1,3);"));
        contactsDb.get().execute("INSERT OR REPLACE INTO  contact_name (_id,contact_id,name_primary,name_alternative) "
                                 "VALUES (3,3,'Cezary','Wyczesany');"));
    REQUIRE(
        contactsdb.execute("INSERT OR REPLACE INTO  contact_match_groups (_id,group_id,contact_id) VALUES (4,1,4);"));

    const auto contactsCount = contactsdb.contacts.count() + 1;
        contactsDb.get().execute("INSERT OR REPLACE INTO  contact_name (_id,contact_id,name_primary,name_alternative) "
                                 "VALUES (4,4,'Alek','Arbuz');"));
    REQUIRE(contactsDb.get().execute(
        "INSERT OR REPLACE INTO  contact_match_groups (_id,group_id,contact_id) VALUES (1,1,1);"));
    REQUIRE(contactsDb.get().execute(
        "INSERT OR REPLACE INTO  contact_match_groups (_id,group_id,contact_id) VALUES (2,2,2);"));
    REQUIRE(contactsDb.get().execute(
        "INSERT OR REPLACE INTO  contact_match_groups (_id,group_id,contact_id) VALUES (3,1,3);"));
    REQUIRE(contactsDb.get().execute(
        "INSERT OR REPLACE INTO  contact_match_groups (_id,group_id,contact_id) VALUES (4,1,4);"));

    const auto contactsCount = contactsDb.get().contacts.count() + 1;
    // clear contacts table
    for (std::uint32_t id = 1; id <= contactsCount; id++) {
        REQUIRE(contactsdb.contacts.removeById(id));
        REQUIRE(contactsDb.get().contacts.removeById(id));
    }
    // add 4 elements into table
    REQUIRE(contactsdb.contacts.add(testRow1));
    REQUIRE(contactsdb.contacts.add(testRow1));
    REQUIRE(contactsdb.contacts.add(testRow1));
    REQUIRE(contactsdb.contacts.add(testRow1));
    REQUIRE(contactsDb.get().contacts.add(testRow1));
    REQUIRE(contactsDb.get().contacts.add(testRow1));
    REQUIRE(contactsDb.get().contacts.add(testRow1));
    REQUIRE(contactsDb.get().contacts.add(testRow1));

    // Table should have 4 elements
    REQUIRE(contactsdb.contacts.count() == 4);
    REQUIRE(contactsDb.get().contacts.count() == 4);

    // update existing element in table
    testRow1.ID        = 4;
    testRow1.speedDial = "777";
    REQUIRE(contactsdb.contacts.update(testRow1));
    REQUIRE(contactsDb.get().contacts.update(testRow1));

    // Get table row using valid ID & check if it was updated
    auto sms = contactsdb.contacts.getById(4);
    auto sms = contactsDb.get().contacts.getById(4);
    REQUIRE(sms.speedDial == testRow1.speedDial);

    // Get table row using invalid ID(should return empty contactsdb.contactsRow)
    auto smsFailed = contactsdb.contacts.getById(100);
    // Get table row using invalid ID(should return empty contactsDb.get().contactsRow)
    auto smsFailed = contactsDb.get().contacts.getById(100);
    REQUIRE(smsFailed.speedDial == "");

    // Get table rows using valid offset/limit parameters
    auto retOffsetLimit = contactsdb.contacts.getLimitOffset(0, 4);
    auto retOffsetLimit = contactsDb.get().contacts.getLimitOffset(0, 4);
    REQUIRE(retOffsetLimit.size() == 4);

    // Get table rows using valid offset/limit parameters and specific field's ID
    REQUIRE(contactsdb.contacts.getLimitOffsetByField(0, 4, ContactTableFields::SpeedDial, "666").size() == 3);
    REQUIRE(contactsDb.get().contacts.getLimitOffsetByField(0, 4, ContactTableFields::SpeedDial, "666").size() == 3);

    // Get table rows using invalid limit parameters(should return 4 elements instead of 100)
    auto retOffsetLimitBigger = contactsdb.contacts.getLimitOffset(0, 100);
    auto retOffsetLimitBigger = contactsDb.get().contacts.getLimitOffset(0, 100);
    REQUIRE(retOffsetLimitBigger.size() == 4);

    auto sortedRetOffsetLimitBigger =
        contactsdb.contacts.GetIDsSortedByField(ContactsTable::MatchType::Name, "", 1, 4, 0);
        contactsDb.get().contacts.GetIDsSortedByField(ContactsTable::MatchType::Name, "", 1, 4, 0);
    REQUIRE(sortedRetOffsetLimitBigger.size() == 4);

    sortedRetOffsetLimitBigger = contactsdb.contacts.GetIDsSortedByName(1, 4);
    sortedRetOffsetLimitBigger = contactsDb.get().contacts.GetIDsSortedByName(1, 4);
    REQUIRE(sortedRetOffsetLimitBigger.size() == 1);

    // Get table rows using invalid offset/limit parameters(should return empty object)
    auto retOffsetLimitFailed = contactsdb.contacts.getLimitOffset(5, 4);
    auto retOffsetLimitFailed = contactsDb.get().contacts.getLimitOffset(5, 4);
    REQUIRE(retOffsetLimitFailed.size() == 0);

    // Get count of elements by field's ID
    REQUIRE(contactsdb.contacts.countByFieldId("ring_id", 0) == 4);
    REQUIRE(contactsDb.get().contacts.countByFieldId("ring_id", 0) == 4);

    // Get count of elements by invalid field's ID
    REQUIRE(contactsdb.contacts.countByFieldId("invalid_field", 0) == 0);
    REQUIRE(contactsDb.get().contacts.countByFieldId("invalid_field", 0) == 0);

    REQUIRE(contactsdb.contacts.removeById(2));
    REQUIRE(contactsDb.get().contacts.removeById(2));

    // Table should have now 3 elements
    REQUIRE(contactsdb.contacts.count() == 3);
    REQUIRE(contactsDb.get().contacts.count() == 3);

    // Remove non existing element
    REQUIRE(contactsdb.contacts.removeById(100));
    REQUIRE(contactsDb.get().contacts.removeById(100));

    // Remove all elements from table
    REQUIRE(contactsdb.contacts.removeById(1));
    REQUIRE(contactsdb.contacts.removeById(3));
    REQUIRE(contactsdb.contacts.removeById(4));
    REQUIRE(contactsDb.get().contacts.removeById(1));
    REQUIRE(contactsDb.get().contacts.removeById(3));
    REQUIRE(contactsDb.get().contacts.removeById(4));

    // Table should be empty now
    REQUIRE(contactsdb.contacts.count() == 0);

    Database::deinitialize();
    REQUIRE(contactsDb.get().contacts.count() == 0);
}

D module-db/tests/DbInitializer.cpp => module-db/tests/DbInitializer.cpp +0 -130
@@ 1,130 0,0 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

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

#include <log/log.hpp>
#include <Utils.hpp>

#include <algorithm>
#include <set>
#include <array>

DatabaseInitializer::DatabaseInitializer(Database *db) : db(db)
{}

bool DatabaseInitializer::run(std::filesystem::path path, std::string ext)
{
    // Database name is database file path, need to strip off all filesystem related stuff(path, extension)
    std::filesystem::path dbpath = db->getName();
    std::string dbname           = dbpath.filename().replace_extension();

    auto files = listFiles(path, dbname, ext);
    for (auto file : files) {
        LOG_DEBUG("Running db script: %s", file.c_str());
        auto commands = readCommands(file);
        if (!executeOnDb(commands)) {
            LOG_ERROR("Can't initialize database [%s] with [%s]", db->getName().c_str(), file.c_str());
            return false;
        }
    }
    return true;
}

std::string DatabaseInitializer::readContent(const char *filename) const noexcept
{
    std::unique_ptr<char[]> fcontent;
    long fsize = 0;

    auto fp = std::fopen(filename, "r");
    if (fp) {
        std::fseek(fp, 0, SEEK_END);
        fsize = std::ftell(fp);
        std::rewind(fp);

        fcontent = std::make_unique<char[]>(fsize + 1);

        std::fread(fcontent.get(), 1, fsize, fp);

        std::fclose(fp);
    }

    return std::string(fcontent.get());
}

std::vector<std::string> DatabaseInitializer::readCommands(std::filesystem::path filePath)
{
    auto fileContent = readContent(filePath.c_str());
    std::string currentStatement{};
    std::vector<std::string> statements{};

    std::string line{};
    for (auto &c : fileContent) {
        if (c != '\n') {
            line += c;
        }
        else {
            if (line.empty() || utils::startsWith(line, "--")) {
                line.clear();
                continue;
            }
            if (utils::endsWith(line, ";")) {
                statements.push_back(currentStatement + line);
                currentStatement.clear();
                line.clear();
                continue;
            }
            currentStatement += line;

            line.clear();
        }
    }
    return statements;
}

std::array<std::string, 3> DatabaseInitializer::splitFilename(std::string filename)
{
    auto name    = filename.substr(0, filename.find("."));
    auto prefix  = name.substr(0, name.find_last_of("_"));
    auto postfix = name.substr(name.find_last_of("_") + 1, std::string::npos);

    return {name, prefix, postfix};
}

std::vector<std::filesystem::path> DatabaseInitializer::listFiles(std::filesystem::path path,
                                                                  std::string prefix,
                                                                  std::string ext)
{
    std::set<std::pair<int, std::filesystem::path>> orderedFiles;
    for (const auto &entry : std::filesystem::directory_iterator(path)) {
        if (!entry.is_directory() && entry.path().has_filename()) {
            try {
                auto parts      = splitFilename(entry.path().filename().string());
                auto filePrefix = parts[1];
                if (filePrefix == prefix) {
                    auto num = std::stoi(parts[2]);
                    if ((num == 1) || (num == 2 && (prefix == "contacts" || prefix == "notifications"))) {
                        orderedFiles.insert({num, entry.path()});
                    }
                }
            }
            catch (std::invalid_argument &e) {
                LOG_INFO("Ignoring file: %s", entry.path().c_str());
            }
        }
    }

    std::vector<std::filesystem::path> files;
    std::for_each(orderedFiles.begin(), orderedFiles.end(), [&](auto item) { files.push_back(item.second); });
    return files;
}

bool DatabaseInitializer::executeOnDb(const std::vector<std::string> statements)
{
    for (auto st : statements) {
        if (!db->execute(st.c_str())) {
            return false;
        }
    }
    return true;
}

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

#include "Helpers.hpp"

#include <Utils.hpp>
#include <set>
#include <algorithm>
#include <utility>

namespace
{
    std::string readContent(const std::string &filename) noexcept
    {
        auto getFileSize = [](FILE *fd) -> std::size_t {
            std::fseek(fd, 0, SEEK_END);
            const auto size = std::ftell(fd);
            std::rewind(fd);
            return size;
        };

        std::vector<char> fileContent;
        if (const auto fp = std::fopen(filename.c_str(), "r")) {
            const auto fileSize = getFileSize(fp);

            fileContent.reserve(fileSize + 1);
            std::fread(fileContent.data(), 1, fileSize, fp);
            std::fclose(fp);
            fileContent[fileSize] = '\0';
            return fileContent.data();
        }
        return {};
    }

    std::vector<std::string> readCommands(const std::filesystem::path &filePath)
    {
        const auto fileContent = readContent(filePath.c_str());
        std::string currentStatement{};
        std::vector<std::string> statements{};

        std::string line{};
        for (const auto &c : fileContent) {
            if (c != '\n') {
                line += c;
            }
            else {
                if (line.empty() || utils::startsWith(line, "--")) {
                    line.clear();
                    continue;
                }
                if (utils::endsWith(line, ";")) {
                    statements.push_back(currentStatement + line);
                    currentStatement.clear();
                    line.clear();
                    continue;
                }
                currentStatement += line;

                line.clear();
            }
        }
        return statements;
    }

    std::array<std::string, 3> splitFilename(const std::string &filename)
    {
        const auto name    = filename.substr(0, filename.find("."));
        const auto prefix  = name.substr(0, name.find_last_of("_"));
        const auto postfix = name.substr(name.find_last_of("_") + 1, std::string::npos);

        return {name, prefix, postfix};
    }

    bool isOnExcludedList(const std::string &name, const std::vector<std::string> &excludedList)
    {
        return std::any_of(excludedList.begin(), excludedList.end(), [&name](const auto &entry) {
            return name.find(entry) != std::string::npos;
        });
    }

    std::vector<std::filesystem::path> listFiles(const std::filesystem::path &path,
                                                 const std::string &prefix,
                                                 const std::vector<std::string> &excludedList)
    {
        std::set<std::pair<int, std::filesystem::path>> orderedFiles;
        for (const auto &entry : std::filesystem::directory_iterator(path)) {
            if (!entry.is_directory() && entry.path().has_filename()) {
                try {
                    const auto parts       = splitFilename(entry.path().filename().string());
                    const auto &filePrefix = parts[1];
                    if (filePrefix == prefix and not isOnExcludedList(parts[0], excludedList)) {
                        orderedFiles.insert({std::stoi(parts[2]), entry.path()});
                    }
                }
                catch (std::invalid_argument &e) {
                    printf("Ignoring file: %s", entry.path().c_str());
                }
            }
        }

        std::vector<std::filesystem::path> files;
        std::for_each(orderedFiles.begin(), orderedFiles.end(), [&](auto item) { files.push_back(item.second); });
        return files;
    }

    bool executeOnDb(Database &db, const std::vector<std::string> &statements)
    {
        return std::all_of(statements.begin(), statements.end(), [&db](const auto &statement) {
            return db.execute(statement.c_str());
        });
    }
} // namespace

namespace db::tests
{
    std::filesystem::path currentFileLocation()
    {
        const std::string path = __FILE__;
        return std::filesystem::path{path.substr(0, path.rfind('/'))};
    }

    std::filesystem::path getScriptsPath()
    {
        return currentFileLocation() / "../databases/scripts";
    }

    /// TODO:
    /// This function is obviously wrong and should not exist in the first place. Unfortunately, due to the
    /// current directory structure, product specific modules and some unit tests are placed in common directories.
    /// After proper module-db and service-db split it wont' be needed as every product-specific db unit test will be
    /// placed in the correct directory.
    std::filesystem::path getPurePhoneScriptsPath()
    {
        return currentFileLocation() / "../../products/PurePhone/services/db/databases/scripts";
    }

    bool DatabaseScripts::operator()(Database &db)
    {
        const std::filesystem::path dbpath = db.getName();
        const std::string dbname           = dbpath.filename().replace_extension();

        const auto scripts = listFiles(directory, dbname, exclude);
        return std::all_of(scripts.begin(), scripts.end(), [&db](const auto &script) {
            return executeOnDb(db, readCommands(script));
        });
    }
    DatabaseScripts::DatabaseScripts(std::filesystem::path directory, std::vector<std::string> &&exclude)
        : directory{std::move(directory)}, extension{".sql"}, exclude{std::move(exclude)}
    {}
} // namespace db::tests
\ No newline at end of file

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

#pragma once

#include <Database/Database.hpp>
#include <string>
#include <filesystem>

namespace db::tests
{
    std::filesystem::path getScriptsPath();
    std::filesystem::path getPurePhoneScriptsPath();

    class DatabaseScripts
    {
      public:
        DatabaseScripts(std::filesystem::path directory, std::vector<std::string> &&exclude);
        bool operator()(Database &db);

      private:
        std::filesystem::path directory;
        std::string extension;
        std::vector<std::string> exclude;
    };

    template <typename Db>
    class DatabaseUnderTest
    {
      public:
        DatabaseUnderTest(std::filesystem::path directory,
                          std::filesystem::path scriptsPath,
                          bool withDevelopment = false)
        {
            Database::initialize();

            std::filesystem::remove(directory);

            std::vector<std::string> excludeList;
            if (not withDevelopment) {
                excludeList.emplace_back("-devel");
            }

            /// Some databases initialize internal fields in the constructors. To avoid fetching wrong data from
            /// uninitialized database fields spawn temporary database, execute any available scripts and finally
            /// recreate it. This way we are sure database will load correct data.
            {
                auto tempDb = std::make_unique<Db>(directory.c_str());

                if (not tempDb->isInitialized()) {
                    throw std::runtime_error("Could not initialize database");
                }

                if (not scriptsPath.empty() and not DatabaseScripts{scriptsPath, std::move(excludeList)}(*tempDb)) {
                    throw std::runtime_error("Failed to execute database scripts");
                }
            }

            db = std::make_unique<Db>(directory.c_str());
        }

        explicit DatabaseUnderTest(std::filesystem::path directory) : DatabaseUnderTest(directory, {})
        {}

        ~DatabaseUnderTest()
        {
            Database::deinitialize();
        }

        Db &get()
        {
            return *db;
        }

      private:
        std::unique_ptr<Db> db;
    };
} // namespace db::tests

M module-db/tests/MultimediaFilesTable_tests.cpp => module-db/tests/MultimediaFilesTable_tests.cpp +3 -5
@@ 3,8 3,8 @@

#include <catch2/catch.hpp>

#include "common.hpp"
#include <Databases/MultimediaFilesDB.hpp>
#include <Helpers.hpp>
#include <databases/MultimediaFilesDB.hpp>
#include <Interface/MultimediaFilesRecord.hpp>
#include <queries/multimedia_files/QueryMultimediaFilesAdd.hpp>
#include <queries/multimedia_files/QueryMultimediaFilesEdit.hpp>


@@ 79,7 79,7 @@ const std::vector<TableRow> records = {

TEST_CASE("Multimedia DB tests")
{
    DatabaseUnderTest<MultimediaFilesDB> db{"multimedia.db"};
    db::tests::DatabaseUnderTest<MultimediaFilesDB> db{"multimedia.db", db::tests::getScriptsPath()};
    MultimediaFilesRecordInterface multimediaFilesRecordInterface(&db.get());

    constexpr auto PageSize = 8;


@@ 666,6 666,4 @@ TEST_CASE("Multimedia DB tests")
            }
        }
    }

    REQUIRE(Database::deinitialize());
}

M module-db/tests/NotesRecord_tests.cpp => module-db/tests/NotesRecord_tests.cpp +4 -11
@@ 2,8 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include <filesystem>
#include "Helpers.hpp"
#include <Interface/NotesRecord.hpp>
#include <queries/notes/QueryNotesGet.hpp>
#include <queries/notes/QueryNotesGetByText.hpp>


@@ 11,17 10,13 @@
#include <queries/notes/QueryNoteStore.hpp>

#include "Database/Database.hpp"
#include "Databases/NotesDB.hpp"
#include "module-db/databases/NotesDB.hpp"

TEST_CASE("Notes Record tests")
{
    Database::initialize();

    const auto notesDbPath = std::filesystem::path{"sys/user"} / "notes.db";
    NotesDB notesDb{notesDbPath.c_str()};
    REQUIRE(notesDb.isInitialized());
    db::tests::DatabaseUnderTest<NotesDB> notesDb{"notes.db", db::tests::getPurePhoneScriptsPath()};

    NotesRecordInterface notesRecordInterface{&notesDb};
    NotesRecordInterface notesRecordInterface{&notesDb.get()};
    notesRecordInterface.RemoveAll(); // Empty the notes database.

    constexpr auto testSnippet = "TEST SNIPPET";


@@ 94,6 89,4 @@ TEST_CASE("Notes Record tests")
        REQUIRE(removeResult->succeed());
        REQUIRE(notesRecordInterface.GetCount() == 0);
    }

    Database::deinitialize();
};

M module-db/tests/NotesTable_tests.cpp => module-db/tests/NotesTable_tests.cpp +4 -10
@@ 2,21 2,17 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include "Helpers.hpp"

#include <filesystem>
#include <Tables/NotesTable.hpp>
#include "Database/Database.hpp"
#include "Databases/NotesDB.hpp"
#include "module-db/databases/NotesDB.hpp"

TEST_CASE("Notes Table tests")
{
    Database::initialize();
    db::tests::DatabaseUnderTest<NotesDB> notesDb{"notes.db", db::tests::getPurePhoneScriptsPath()};

    const auto notesDbPath = std::filesystem::path{"sys/user"} / "notes.db";
    NotesDB notesDb{notesDbPath.c_str()};
    REQUIRE(notesDb.isInitialized());

    NotesTable table{&notesDb};
    NotesTable table{&notesDb.get()};
    table.removeAll();
    REQUIRE(table.count() == 0);



@@ 70,6 66,4 @@ TEST_CASE("Notes Table tests")
        table.removeById(1);
        REQUIRE(table.count() == 0);
    }

    Database::deinitialize();
}

M module-db/tests/NotificationsRecord_tests.cpp => module-db/tests/NotificationsRecord_tests.cpp +12 -24
@@ 1,22 1,19 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common.hpp"
#include <catch2/catch.hpp>

#include "Helpers.hpp"
#include <Interface/NotificationsRecord.hpp>
#include <Interface/ContactRecord.hpp>
#include <Database/Database.hpp>
#include <Databases/NotificationsDB.hpp>
#include <Databases/ContactsDB.hpp>
#include "module-db/databases/NotificationsDB.hpp"
#include "module-db/databases/ContactsDB.hpp"
#include <queries/notifications/QueryNotificationsGet.hpp>
#include <queries/notifications/QueryNotificationsIncrement.hpp>
#include <queries/notifications/QueryNotificationsDecrement.hpp>
#include <queries/notifications/QueryNotificationsClear.hpp>
#include <queries/notifications/QueryNotificationsGetAll.hpp>

#include <filesystem>

#include <stdint.h>
#include <stdio.h>
#include <string.h>


@@ 48,37 45,30 @@ TEST_CASE("Notifications Record tests")
        REQUIRE_FALSE(testRec.contactRecord.has_value());
    }

    Database::initialize();
    const auto notificationsPath = (std::filesystem::path{"sys/user"} / "notifications.db");
    const auto contactsPath      = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(notificationsPath.stem());
    RemoveDbFiles(contactsPath.stem());
    db::tests::DatabaseUnderTest<NotificationsDB> notificationsDb{"notifications.db",
                                                                  db::tests::getPurePhoneScriptsPath()};
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    NotificationsDB notificationsDb{notificationsPath.c_str()};
    ContactsDB contactsDb{contactsPath.c_str()};
    REQUIRE(notificationsDb.isInitialized());
    REQUIRE(contactsDb.isInitialized());

    const auto notificationsCount = notificationsDb.notifications.count() + 1;
    const auto notificationsCount = notificationsDb.get().notifications.count() + 1;
    // clear notifications table
    for (std::size_t id = 1; id <= notificationsCount; id++) {
        REQUIRE(notificationsDb.notifications.removeById(id));
        REQUIRE(notificationsDb.get().notifications.removeById(id));
    }

    ContactRecordInterface contactRecordInterface(&contactsDb);
    NotificationsRecordInterface notificationsRecordInterface(&notificationsDb, &contactRecordInterface);
    ContactRecordInterface contactRecordInterface(&contactsDb.get());
    NotificationsRecordInterface notificationsRecordInterface(&notificationsDb.get(), &contactRecordInterface);
    REQUIRE(contactRecordInterface.GetCount() == 0);
    REQUIRE(notificationsRecordInterface.GetCount() == 0);

    NotificationsTableRow callsRow{
        Record(DB_ID_NONE), .key = static_cast<uint32_t>(NotificationsRecord::Key::Calls), .value = 0};

    REQUIRE(notificationsDb.notifications.add(callsRow));
    REQUIRE(notificationsDb.get().notifications.add(callsRow));

    NotificationsTableRow smsRow{
        Record(DB_ID_NONE), .key = static_cast<uint32_t>(NotificationsRecord::Key::Sms), .value = 0};

    REQUIRE(notificationsDb.notifications.add(smsRow));
    REQUIRE(notificationsDb.get().notifications.add(smsRow));
    NotificationsRecord testRec;
    auto numberOfNotifcations = notificationsRecordInterface.GetCount();
    REQUIRE(numberOfNotifcations == 2); // calls and sms notifications


@@ 404,6 394,4 @@ TEST_CASE("Notifications Record tests")
            getByKey(NotificationsRecord::Key::Sms, noNotificationExpected, contactNotExpected);
        }
    }

    Database::deinitialize();
}

M module-db/tests/NotificationsTable_tests.cpp => module-db/tests/NotificationsTable_tests.cpp +5 -10
@@ 1,11 1,11 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common.hpp"
#include <catch2/catch.hpp>
#include "Helpers.hpp"

#include "Database/Database.hpp"
#include "Databases/NotificationsDB.hpp"
#include "module-db/databases/NotificationsDB.hpp"
#include "Interface/NotificationsRecord.hpp"
#include "Tables/NotificationsTable.hpp"



@@ 17,13 17,10 @@

TEST_CASE("Notifications Table tests")
{
    Database::initialize();
    const auto notificationsPath = (std::filesystem::path{"sys/user"} / "notifications.db");
    RemoveDbFiles(notificationsPath.stem());
    NotificationsDB notificationsDb{notificationsPath.c_str()};
    REQUIRE(notificationsDb.isInitialized());
    db::tests::DatabaseUnderTest<NotificationsDB> notificationDb{"notifications.db",
                                                                 db::tests::getPurePhoneScriptsPath()};

    auto &notificationsTbl        = notificationsDb.notifications;
    auto &notificationsTbl        = notificationDb.get().notifications;
    const auto notificationsCount = notificationsTbl.count() + 1;
    // clear notifications table
    for (std::size_t id = 1; id <= notificationsCount; id++) {


@@ 170,6 167,4 @@ TEST_CASE("Notifications Table tests")
        REQUIRE(entry.value == 8);
        REQUIRE(entry.contactID == DB_ID_NONE);
    }

    Database::deinitialize();
}

M module-db/tests/QueryInterface.cpp => module-db/tests/QueryInterface.cpp +7 -9
@@ 3,10 3,11 @@

#include <catch2/catch.hpp>

#include "Helpers.hpp"
#include "Common/Query.hpp"
#include "Databases/ContactsDB.hpp"
#include "module-db/databases/ContactsDB.hpp"
#include "Database/Database.hpp"
#include "Databases/SmsDB.hpp"
#include "module-db/databases/SmsDB.hpp"
#include "SMSRecord.hpp"
#include "ThreadRecord.hpp"
#include "queries/messages/threads/QueryThreadsSearchForList.hpp"


@@ 37,15 38,12 @@ namespace db

TEST_CASE("Query interface")
{
    Database::initialize();
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};
    db::tests::DatabaseUnderTest<SmsDB> smsDb{"sms.db", db::tests::getPurePhoneScriptsPath()};

    auto contactsDB      = std::make_unique<ContactsDB>((std::filesystem::path{"sys/user"} / "contacts.db").c_str());
    auto smsDB           = std::make_unique<SmsDB>((std::filesystem::path{"sys/user"} / "sms.db").c_str());
    auto smsInterface    = std::make_unique<SMSRecordInterface>(smsDB.get(), contactsDB.get());
    auto threadInterface = std::make_unique<ThreadRecordInterface>(smsDB.get(), contactsDB.get());
    auto smsInterface    = std::make_unique<SMSRecordInterface>(&smsDb.get(), &contactsDb.get());
    auto threadInterface = std::make_unique<ThreadRecordInterface>(&smsDb.get(), &contactsDb.get());

    REQUIRE(contactsDB);
    REQUIRE(smsDB);
    REQUIRE(smsInterface);

    SECTION("unknown query -> no results")

M module-db/tests/SMSRecord_tests.cpp => module-db/tests/SMSRecord_tests.cpp +9 -22
@@ 1,21 1,18 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common.hpp"
#include "Helpers.hpp"
#include <Database/Database.hpp>
#include <Databases/ContactsDB.hpp>
#include <Databases/SmsDB.hpp>
#include "module-db/databases/ContactsDB.hpp"
#include "module-db/databases/SmsDB.hpp"
#include <Interface/ContactRecord.hpp>
#include <Interface/SMSRecord.hpp>
#include <Interface/ThreadRecord.hpp>

#include <country.hpp>
#include <PhoneNumber.hpp>

#include <catch2/catch.hpp>

#include <algorithm>
#include <filesystem>
#include <cstdint>
#include <cstdio>
#include <cstring>


@@ 29,15 26,8 @@ struct test

TEST_CASE("SMS Record tests")
{
    Database::initialize();

    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    const auto smsPath      = (std::filesystem::path{"sys/user"} / "sms.db");
    RemoveDbFiles(contactsPath.stem());
    RemoveDbFiles(smsPath.stem());

    ContactsDB contactsDB(contactsPath.c_str());
    SmsDB smsDB(smsPath.c_str());
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};
    db::tests::DatabaseUnderTest<SmsDB> smsDb{"sms.db", db::tests::getPurePhoneScriptsPath()};

    const uint32_t dateTest      = 123456789;
    const uint32_t errorCodeTest = 555;


@@ 47,7 37,7 @@ TEST_CASE("SMS Record tests")
    const char *bodyTest2        = "Test SMS Body2";
    const SMSType typeTest       = SMSType::DRAFT;

    SMSRecordInterface smsRecInterface(&smsDB, &contactsDB);
    SMSRecordInterface smsRecInterface(&smsDb.get(), &contactsDb.get());

    SMSRecord recordIN;
    recordIN.date      = dateTest;


@@ 136,7 126,7 @@ TEST_CASE("SMS Record tests")
        }

        // Remove sms records in order to check automatic management of threads and contact databases
        ThreadRecordInterface threadRecordInterface(&smsDB, &contactsDB);
        ThreadRecordInterface threadRecordInterface(&smsDb.get(), &contactsDb.get());
        REQUIRE(smsRecInterface.RemoveByID(1));
        REQUIRE(smsRecInterface.RemoveByID(2));



@@ 145,7 135,7 @@ TEST_CASE("SMS Record tests")

        // Test removing a message which belongs to non-existent thread
        REQUIRE(smsRecInterface.Add(recordIN));
        REQUIRE(smsDB.threads.removeById(1)); // stealthy thread remove
        REQUIRE(smsDb.get().threads.removeById(1)); // stealthy thread remove
        REQUIRE(smsRecInterface.RemoveByID(1));

        // Test handling of missmatch in sms vs. thread tables


@@ 166,7 156,7 @@ TEST_CASE("SMS Record tests")
                                  .snippet        = threadRec.snippet,
                                  .type           = threadRec.type};
        threadRaw.msgCount = trueCount + 1; // break the DB
        REQUIRE(smsDB.threads.update(threadRaw));
        REQUIRE(smsDb.get().threads.update(threadRaw));

        REQUIRE(static_cast<int>(
                    smsRecInterface.GetLimitOffsetByField(0, 100, SMSRecordField::ThreadID, "1")->size()) == trueCount);


@@ 203,7 193,6 @@ TEST_CASE("SMS Record tests")
        REQUIRE(smsRecInterface.Add(recordIN));
        REQUIRE(smsRecInterface.Add(recordIN));
        REQUIRE(smsRecInterface.Add(recordIN));
        Database::deinitialize();
    }

    SECTION("SMS Record Draft and Input test")


@@ 225,6 214,4 @@ TEST_CASE("SMS Record tests")
        auto result = dynamic_cast<db::query::SMSGetForListResult *>(ret.get());
        REQUIRE(result != nullptr);
    }

    Database::deinitialize();
}

M module-db/tests/SMSTable_tests.cpp => module-db/tests/SMSTable_tests.cpp +35 -45
@@ 2,9 2,10 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include "Helpers.hpp"

#include "Database/Database.hpp"
#include "Databases/SmsDB.hpp"
#include "module-db/databases/SmsDB.hpp"

#include <algorithm>
#include <filesystem>


@@ 13,16 14,7 @@

TEST_CASE("SMS Table tests")
{
    Database::initialize();

    const auto smsPath = (std::filesystem::path{"sys/user"} / "sms.db");

    if (std::filesystem::exists(smsPath)) {
        REQUIRE(std::filesystem::remove(smsPath));
    }

    SmsDB smsdb(smsPath.c_str());
    REQUIRE(smsdb.isInitialized());
    db::tests::DatabaseUnderTest<SmsDB> smsDb{"sms.db", db::tests::getPurePhoneScriptsPath()};

    SMSTableRow testRow1 = {Record(0),
                            .threadID  = 0,


@@ 44,88 36,86 @@ TEST_CASE("SMS Table tests")

    };

    const auto smsCount = smsdb.sms.count() + 1;
    const auto smsCount = smsDb.get().sms.count() + 1;
    // clear sms table
    for (std::uint32_t id = 1; id <= smsCount; id++) {
        REQUIRE(smsdb.sms.removeById(id));
        REQUIRE(smsDb.get().sms.removeById(id));
    }

    REQUIRE(smsdb.sms.count() == 0);
    REQUIRE(smsDb.get().sms.count() == 0);
    // add 4 elements into table
    REQUIRE(smsdb.sms.add(testRow1));
    REQUIRE(smsdb.sms.add(testRow1));
    REQUIRE(smsdb.sms.add(testRow1));
    REQUIRE(smsdb.sms.add(testRow1));
    REQUIRE(smsDb.get().sms.add(testRow1));
    REQUIRE(smsDb.get().sms.add(testRow1));
    REQUIRE(smsDb.get().sms.add(testRow1));
    REQUIRE(smsDb.get().sms.add(testRow1));

    // Table should have 4 elements
    REQUIRE(smsdb.sms.count() == 4);
    REQUIRE(smsDb.get().sms.count() == 4);

    // update existing element in table
    testRow1.ID   = 4;
    testRow1.body = "updated Test SMS message ";
    REQUIRE(smsdb.sms.update(testRow1));
    REQUIRE(smsDb.get().sms.update(testRow1));

    // Get table row using valid ID & check if it was updated
    auto sms = smsdb.sms.getById(4);
    auto sms = smsDb.get().sms.getById(4);
    REQUIRE(sms.body == testRow1.body);

    // Get table row using invalid ID(should return empty smsdb.smsRow)
    auto smsFailed = smsdb.sms.getById(100);
    // Get table row using invalid ID(should return empty smsDb.get().smsRow)
    auto smsFailed = smsDb.get().sms.getById(100);
    REQUIRE(smsFailed.body == "");

    // Get table rows using valid offset/limit parameters
    auto retOffsetLimit = smsdb.sms.getLimitOffset(0, 4);
    auto retOffsetLimit = smsDb.get().sms.getLimitOffset(0, 4);
    REQUIRE(retOffsetLimit.size() == 4);

    // Get table rows using valid offset/limit parameters and specific field's ID
    REQUIRE(smsdb.sms.getLimitOffsetByField(0, 4, SMSTableFields::Date, "0").size() == 4);
    REQUIRE(smsDb.get().sms.getLimitOffsetByField(0, 4, SMSTableFields::Date, "0").size() == 4);

    // Get table rows using invalid limit parameters(should return 4 elements instead of 100)
    auto retOffsetLimitBigger = smsdb.sms.getLimitOffset(0, 100);
    auto retOffsetLimitBigger = smsDb.get().sms.getLimitOffset(0, 100);
    REQUIRE(retOffsetLimitBigger.size() == 4);

    // Get table rows using invalid offset/limit parameters(should return empty object)
    auto retOffsetLimitFailed = smsdb.sms.getLimitOffset(5, 4);
    auto retOffsetLimitFailed = smsDb.get().sms.getLimitOffset(5, 4);
    REQUIRE(retOffsetLimitFailed.size() == 0);

    // Get count of elements by field's ID
    REQUIRE(smsdb.sms.countByFieldId("thread_id", 0) == 4);
    REQUIRE(smsDb.get().sms.countByFieldId("thread_id", 0) == 4);

    // Get count of elements by invalid field's ID
    REQUIRE(smsdb.sms.countByFieldId("invalid_field", 0) == 0);
    REQUIRE(smsDb.get().sms.countByFieldId("invalid_field", 0) == 0);

    REQUIRE(smsdb.sms.removeById(2));
    REQUIRE(smsDb.get().sms.removeById(2));

    // Table should have now 3 elements
    REQUIRE(smsdb.sms.count() == 3);
    REQUIRE(smsDb.get().sms.count() == 3);

    // Remove non existing element
    REQUIRE(smsdb.sms.removeById(100));
    REQUIRE(smsDb.get().sms.removeById(100));

    // Remove all elements from table
    REQUIRE(smsdb.sms.removeById(1));
    REQUIRE(smsdb.sms.removeById(3));
    REQUIRE(smsdb.sms.removeById(4));
    REQUIRE(smsDb.get().sms.removeById(1));
    REQUIRE(smsDb.get().sms.removeById(3));
    REQUIRE(smsDb.get().sms.removeById(4));

    // Table should be empty now
    REQUIRE(smsdb.sms.count() == 0);
    REQUIRE(smsDb.get().sms.count() == 0);

    SECTION("SMS Draft and Input Table test")
    {
        REQUIRE(smsdb.sms.add(testRow1));
        REQUIRE(smsdb.sms.add(testRow1));
        REQUIRE(smsdb.sms.add(testRow2));
        REQUIRE(smsDb.get().sms.add(testRow1));
        REQUIRE(smsDb.get().sms.add(testRow1));
        REQUIRE(smsDb.get().sms.add(testRow2));

        REQUIRE(smsdb.sms.countWithoutDraftsByThreadId(0) == 2);
        REQUIRE(smsdb.sms.count() == 3);
        REQUIRE(smsDb.get().sms.countWithoutDraftsByThreadId(0) == 2);
        REQUIRE(smsDb.get().sms.count() == 3);

        REQUIRE(smsdb.sms.getDraftByThreadId(0).body == "Test Draft SMS");
        REQUIRE(smsDb.get().sms.getDraftByThreadId(0).body == "Test Draft SMS");

        auto results = smsdb.sms.getByThreadIdWithoutDraftWithEmptyInput(0, 0, 10);
        auto results = smsDb.get().sms.getByThreadIdWithoutDraftWithEmptyInput(0, 0, 10);

        REQUIRE(results.size() == 3);
        REQUIRE(results.back().type == SMSType::INPUT);
    }

    Database::deinitialize();
}

M module-db/tests/SMSTemplateRecord_tests.cpp => module-db/tests/SMSTemplateRecord_tests.cpp +4 -19
@@ 2,31 2,18 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include "Helpers.hpp"
#include "Interface/SMSTemplateRecord.hpp"
#include "Database/Database.hpp"
#include "Databases/SmsDB.hpp"
#include "module-db/databases/SmsDB.hpp"

#include <algorithm>
#include <filesystem>

#include <cstdint>
#include <cstdio>
#include <cstring>

TEST_CASE("SMS templates Record tests")
{
    Database::initialize();

    const auto smsPath = (std::filesystem::path{"sys/user"} / "sms.db");
    if (std::filesystem::exists(smsPath)) {
        REQUIRE(std::filesystem::remove(smsPath));
    }

    SmsDB smsDB(smsPath.c_str());
    REQUIRE(smsDB.isInitialized());
    db::tests::DatabaseUnderTest<SmsDB> smsDb{"sms.db", db::tests::getPurePhoneScriptsPath()};

    SMSTemplateRecordInterface SMSTemplateRecordInterface(&smsDB);
    SMSTemplateRecordInterface SMSTemplateRecordInterface(&smsDb.get());
    SMSTemplateRecord testRec;
    testRec.text               = "Test text";
    testRec.lastUsageTimestamp = 100;


@@ 105,6 92,4 @@ TEST_CASE("SMS templates Record tests")
        // Table should be empty now
        REQUIRE(SMSTemplateRecordInterface.GetCount() == 0);
    }

    Database::deinitialize();
}

M module-db/tests/SMSTemplateTable_tests.cpp => module-db/tests/SMSTemplateTable_tests.cpp +4 -13
@@ 2,9 2,10 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include "Helpers.hpp"

#include "Database/Database.hpp"
#include "Databases/SmsDB.hpp"
#include "module-db/databases/SmsDB.hpp"

#include "Tables/SMSTemplateTable.hpp"



@@ 17,17 18,9 @@

TEST_CASE("SMS Templates Table tests")
{
    Database::initialize();
    db::tests::DatabaseUnderTest<SmsDB> smsDb{"sms.db", db::tests::getPurePhoneScriptsPath()};

    const auto smsPath = (std::filesystem::path{"sys/user"} / "sms.db");
    if (std::filesystem::exists(smsPath)) {
        REQUIRE(std::filesystem::remove(smsPath));
    }

    SmsDB smsDb{smsPath.c_str()};
    REQUIRE(smsDb.isInitialized());

    auto &templatesTbl = smsDb.templates;
    auto &templatesTbl = smsDb.get().templates;

    SMSTemplateTableRow testRow = {Record(0), .text = "Test text", .lastUsageTimestamp = 100};



@@ 121,6 114,4 @@ TEST_CASE("SMS Templates Table tests")
        // Table should be empty now
        REQUIRE(templatesTbl.count() == 0);
    }

    Database::deinitialize();
}

M module-db/tests/ThreadRecord_tests.cpp => module-db/tests/ThreadRecord_tests.cpp +8 -19
@@ 1,11 1,11 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common.hpp"
#include "Helpers.hpp"

#include <Database/Database.hpp>
#include <Databases/ContactsDB.hpp>
#include <Databases/SmsDB.hpp>
#include "module-db/databases/ContactsDB.hpp"
#include "module-db/databases/SmsDB.hpp"
#include <Interface/ContactRecord.hpp>
#include <Interface/SMSRecord.hpp>
#include <Interface/ThreadRecord.hpp>


@@ 27,24 27,15 @@

TEST_CASE("Thread Record tests")
{
    Database::initialize();

    const auto contactsPath = (std::filesystem::path{"sys/user"} / "contacts.db");
    const auto smsPath      = (std::filesystem::path{"sys/user"} / "sms.db");
    RemoveDbFiles(contactsPath.stem());
    RemoveDbFiles(smsPath.stem());

    SmsDB smsDB(smsPath.c_str());
    REQUIRE(smsDB.isInitialized());
    ContactsDB contactsDB(contactsPath.c_str());
    REQUIRE(contactsDB.isInitialized());
    db::tests::DatabaseUnderTest<SmsDB> smsDb{"sms.db", db::tests::getPurePhoneScriptsPath()};
    db::tests::DatabaseUnderTest<ContactsDB> contactsDb{"contacts.db", db::tests::getPurePhoneScriptsPath()};

    const uint32_t dateTest  = 123456789;
    const char *snippetTest  = "Test snippet";
    const char *snippetTest2 = "Test snippet2";
    const SMSType typeTest   = SMSType::UNKNOWN;

    ThreadRecordInterface threadRecordInterface1(&smsDB, &contactsDB);
    ThreadRecordInterface threadRecordInterface1(&smsDb.get(), &contactsDb.get());

    ThreadRecord recordIN;
    recordIN.date    = dateTest;


@@ 187,7 178,7 @@ TEST_CASE("Thread Record tests")
        const utils::PhoneNumber phoneNumber("+48600123456", utils::country::Id::UNKNOWN);
        const std::string lastSmsBody = "Ola";

        SMSRecordInterface smsRecInterface(&smsDB, &contactsDB);
        SMSRecordInterface smsRecInterface(&smsDb.get(), &contactsDb.get());
        SMSRecord recordIN;
        recordIN.date      = 123456789;
        recordIN.errorCode = 0;


@@ 242,7 233,7 @@ TEST_CASE("Thread Record tests")

        const utils::PhoneNumber phoneNumber("+48600123456", utils::country::Id::UNKNOWN);

        SMSRecordInterface smsRecInterface(&smsDB, &contactsDB);
        SMSRecordInterface smsRecInterface(&smsDb.get(), &contactsDb.get());
        SMSRecord recordIN;
        recordIN.date      = 123456789;
        recordIN.errorCode = 0;


@@ 279,6 270,4 @@ TEST_CASE("Thread Record tests")
            REQUIRE(smsRec->body == smsBody);
        }
    }

    Database::deinitialize();
}

M module-db/tests/ThreadsTable_tests.cpp => module-db/tests/ThreadsTable_tests.cpp +29 -38
@@ 2,9 2,10 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include "Helpers.hpp"

#include "Database/Database.hpp"
#include "Databases/SmsDB.hpp"
#include "module-db/databases/SmsDB.hpp"
#include "Tables/ThreadsTable.hpp"

#include <algorithm>


@@ 16,15 17,7 @@

TEST_CASE("Threads Table tests")
{
    Database::initialize();

    const auto smsPath = (std::filesystem::path{"sys/user"} / "sms.db");
    if (std::filesystem::exists(smsPath)) {
        REQUIRE(std::filesystem::remove(smsPath));
    }

    SmsDB smsdb{smsPath.c_str()};
    REQUIRE(smsdb.isInitialized());
    db::tests::DatabaseUnderTest<SmsDB> smsDb{"sms.db", db::tests::getPurePhoneScriptsPath()};

    ThreadsTableRow testRow1 = {Record(0),
                                .date           = 0,


@@ 36,74 29,72 @@ TEST_CASE("Threads Table tests")

    };

    const auto smsThreadsCount = smsdb.threads.count() + 1;
    const auto smsThreadsCount = smsDb.get().threads.count() + 1;
    // clear threads table
    for (std::size_t id = 1; id <= smsThreadsCount; id++) {
        REQUIRE(smsdb.threads.removeById(id));
        REQUIRE(smsDb.get().threads.removeById(id));
    }

    // add 4 elements into table
    REQUIRE(smsdb.threads.add(testRow1));
    REQUIRE(smsdb.threads.add(testRow1));
    REQUIRE(smsdb.threads.add(testRow1));
    REQUIRE(smsDb.get().threads.add(testRow1));
    REQUIRE(smsDb.get().threads.add(testRow1));
    REQUIRE(smsDb.get().threads.add(testRow1));
    testRow1.unreadMsgCount = 10;
    REQUIRE(smsdb.threads.add(testRow1));
    REQUIRE(smsDb.get().threads.add(testRow1));

    // Table should have 4 elements
    REQUIRE(smsdb.threads.count() == 4);
    REQUIRE(smsdb.threads.count(EntryState::ALL) == 4);
    REQUIRE(smsdb.threads.count(EntryState::READ) == 3);
    REQUIRE(smsdb.threads.count(EntryState::UNREAD) == 1);
    REQUIRE(smsDb.get().threads.count() == 4);
    REQUIRE(smsDb.get().threads.count(EntryState::ALL) == 4);
    REQUIRE(smsDb.get().threads.count(EntryState::READ) == 3);
    REQUIRE(smsDb.get().threads.count(EntryState::UNREAD) == 1);

    // update existing element in table
    testRow1.ID      = 4;
    testRow1.snippet = "updated Test snippet";
    REQUIRE(smsdb.threads.update(testRow1));
    REQUIRE(smsDb.get().threads.update(testRow1));

    // Get table row using valid ID & check if it was updated
    auto thread = smsdb.threads.getById(4);
    auto thread = smsDb.get().threads.getById(4);
    REQUIRE(thread.snippet == testRow1.snippet);

    // Get table row using invalid ID(should return empty SMSTableRow)
    auto threadFailed = smsdb.threads.getById(100);
    auto threadFailed = smsDb.get().threads.getById(100);
    REQUIRE(threadFailed.snippet == "");

    // Get table rows using valid offset/limit parameters
    auto retOffsetLimit = smsdb.threads.getLimitOffset(0, 4);
    auto retOffsetLimit = smsDb.get().threads.getLimitOffset(0, 4);
    REQUIRE(retOffsetLimit.size() == 4);

    // Get table rows using invalid limit parameters(should return 4 elements instead of 100)
    auto retOffsetLimitBigger = smsdb.threads.getLimitOffset(0, 100);
    auto retOffsetLimitBigger = smsDb.get().threads.getLimitOffset(0, 100);
    REQUIRE(retOffsetLimitBigger.size() == 4);

    // Get table rows using invalid offset/limit parameters(should return empty object)
    auto retOffsetLimitFailed = smsdb.threads.getLimitOffset(5, 4);
    auto retOffsetLimitFailed = smsDb.get().threads.getLimitOffset(5, 4);
    REQUIRE(retOffsetLimitFailed.size() == 0);

    // Get table rows using valid offset/limit parameters and specific field's ID
    REQUIRE(smsdb.threads.getLimitOffsetByField(0, 4, ThreadsTableFields::MsgCount, "0").size() == 4);
    REQUIRE(smsDb.get().threads.getLimitOffsetByField(0, 4, ThreadsTableFields::MsgCount, "0").size() == 4);

    // Get count of elements by field's ID
    REQUIRE(smsdb.threads.countByFieldId("contact_id", 0) == 4);
    REQUIRE(smsDb.get().threads.countByFieldId("contact_id", 0) == 4);

    // Get count of elements by invalid field's ID
    REQUIRE(smsdb.threads.countByFieldId("invalid_field", 0) == 0);
    REQUIRE(smsDb.get().threads.countByFieldId("invalid_field", 0) == 0);

    REQUIRE(smsdb.threads.removeById(2));
    REQUIRE(smsDb.get().threads.removeById(2));

    // Table should have now 3 elements
    REQUIRE(smsdb.threads.count() == 3);
    REQUIRE(smsDb.get().threads.count() == 3);

    // Remove non existing element
    REQUIRE(smsdb.threads.removeById(100));
    REQUIRE(smsDb.get().threads.removeById(100));

    // Remove all elements from table
    REQUIRE(smsdb.threads.removeById(1));
    REQUIRE(smsdb.threads.removeById(3));
    REQUIRE(smsdb.threads.removeById(4));
    REQUIRE(smsDb.get().threads.removeById(1));
    REQUIRE(smsDb.get().threads.removeById(3));
    REQUIRE(smsDb.get().threads.removeById(4));

    // Table should be empty now
    REQUIRE(smsdb.threads.count() == 0);

    Database::deinitialize();
    REQUIRE(smsDb.get().threads.count() == 0);
}

D module-db/tests/common.cpp => module-db/tests/common.cpp +0 -16
@@ 1,16 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 "common.hpp"
#include <vector>
#include <filesystem>

void RemoveDbFiles(const std::string &dbName)
{
    std::vector<std::string> dbFileExt = {".db", ".db-journal", ".db-wal"};
    for (const auto &ext : dbFileExt) {
        const auto dbPath = (std::filesystem::path{"sys/user"} / std::filesystem::path{dbName + ext});
        if (std::filesystem::exists(dbPath)) {
            std::filesystem::remove(dbPath.c_str());
        }
    }
}

D module-db/tests/common.hpp => module-db/tests/common.hpp +0 -45
@@ 1,45 0,0 @@
// 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 "Database/Database.hpp"
#include <catch2/catch.hpp>
#include <string>
#include <filesystem>

void RemoveDbFiles(const std::string &dbName);

template <typename Db>
class DatabaseUnderTest
{
  public:
    explicit DatabaseUnderTest(std::filesystem::path name)
    {
        Database::initialize();

        if (std::filesystem::exists(name)) {
            std::filesystem::remove(name);
        }

        db = std::make_unique<Db>(name.c_str());

        if (not db->isInitialized()) {
            throw std::runtime_error("Could not initialize database");
        }
    }

    ~DatabaseUnderTest()
    {
        Database::deinitialize();
    }

    Db &get()
    {
        return *db;
    }

  private:
    std::filesystem::path name;
    std::unique_ptr<Db> db;
};

D module-db/tests/test-initializer/CMakeLists.txt => module-db/tests/test-initializer/CMakeLists.txt +0 -12
@@ 1,12 0,0 @@
add_catch2_executable(
    NAME
        db-initializer
    SRCS
        "${CMAKE_CURRENT_SOURCE_DIR}/unittest.cpp"
        
    LIBS
        module-sys
        module-db
    
    USE_FS
)

D module-db/tests/test-initializer/unittest.cpp => module-db/tests/test-initializer/unittest.cpp +0 -292
@@ 1,292 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 <catch2/catch.hpp>

#include "Database/Database.hpp"
#include "Database/DatabaseInitializer.hpp"

#include "Tables/SMSTable.hpp"

#include <algorithm>
#include <fstream>
#include <iostream>

#include <cstdint>
#include <cstdio>
#include <cstring>

TEST_CASE("Create and destroy simple database")
{
    Database::initialize();

    SECTION("Create database")
    {

        Database testDB("test.db");

        const char *media_album_table = "CREATE TABLE IF NOT EXISTS albums("
                                        "_id	        		INTEGER PRIMARY KEY,"
                                        "artist_id	            INTEGER,"
                                        "name	        		TEXT UNIQUE,"
                                        "FOREIGN KEY(artist_id) REFERENCES artists(_id)"
                                        ");";

        const char *media_artist_table = "CREATE TABLE IF NOT EXISTS artists("
                                         "_id	                INTEGER PRIMARY KEY,"
                                         "name   	            TEXT UNIQUE"
                                         ");";

        const char *media_songs_table = "CREATE TABLE IF NOT EXISTS tracks("
                                        "_id	                INTEGER PRIMARY KEY,"
                                        "filename	            TEXT UNIQUE,"
                                        "name	            	TEXT,"
                                        "duration	            INTEGER,"
                                        "artist_id	            INTEGER,"
                                        "album_id	            INTEGER,"
                                        "cover  	            INTEGER,"
                                        "FOREIGN KEY(artist_id) REFERENCES artists(_id),"
                                        "FOREIGN KEY(album_id) 	REFERENCES albums(_id)"
                                        ");";

        const char *testdb_queries[] = {media_artist_table, media_album_table, media_songs_table};

        // execute all commands from the array
        for (uint32_t i = 0; i < sizeof(testdb_queries) / sizeof(char *); i++) {
            REQUIRE(testDB.execute(testdb_queries[i]) == true);
        }
    }

    SECTION("Add records to database")
    {
        Database testDB("test.db");

        REQUIRE(testDB.execute("insert or ignore into artists ( name ) VALUES ( '%q');", "Mati Patus") == true);
        REQUIRE(testDB.execute("insert or ignore into artists ( name ) VALUES ( '%q');", "Mati Patus2") == true);
        REQUIRE(testDB.execute("insert or ignore into artists ( name ) VALUES ( '%q');", "Mati Patus3") == true);
        REQUIRE(testDB.execute("insert or ignore into artists ( name ) VALUES ( '%q');", "Mati Patus4") == true);
        REQUIRE(testDB.execute("insert or ignore into artists ( name ) VALUES ( '%q');", "Mati Patus5") == true);
        REQUIRE(testDB.execute("insert or ignore into artists ( name ) VALUES ( '%q');", "Mati Patus6") == true);
    }

    SECTION("query database")
    {
        Database testDB("test.db");

        auto queryRes = testDB.query("SELECT * from artists;");

        REQUIRE(queryRes->getFieldCount() == 2);
        REQUIRE(queryRes->getRowCount() == 6);
    }

    SECTION("Store database into backup file")
    {
        std::string backupPathDB = "testbackup.db";
        std::remove(backupPathDB.c_str());
        Database testDB("test.db");
        REQUIRE(testDB.storeIntoFile(backupPathDB) == true);
        std::ifstream f(backupPathDB);
        REQUIRE(f.good() == true);
    }

    Database::deinitialize();
}

class ScopedDir
{
  public:
    explicit ScopedDir(const std::string &p) : path(p)
    {
        if (!(std::filesystem::exists(path.c_str()))) {
            REQUIRE(std::filesystem::create_directory(path.c_str()));
        }
    }

    ~ScopedDir()
    {
        if (std::filesystem::exists(path.c_str())) {
            REQUIRE(std::filesystem::remove_all(path.c_str()) > 0);
        }
    }

    auto operator()(const std::string &file = "") -> std::filesystem::path
    {
        return path / file;
    }

  private:
    std::filesystem::path path;
};

TEST_CASE("Database initialization scripts")
{
    Database::initialize();

    const std::string script_create = "CREATE TABLE IF NOT EXISTS tracks("
                                      "_id	                INTEGER PRIMARY KEY,"
                                      "filename	            TEXT UNIQUE,"
                                      "name	            	TEXT,"
                                      "duration	            INTEGER,"
                                      "artist_id	            INTEGER,"
                                      "album_id	            INTEGER,"
                                      "cover  	            INTEGER,"
                                      "FOREIGN KEY(artist_id) REFERENCES artists(_id),"
                                      "FOREIGN KEY(album_id) 	REFERENCES albums(_id)"
                                      ");\n";

    const std::string script_insert = "insert or ignore into tracks(name) VALUES('ala ma kota');\n";

    const std::string script_comment = "--insert or ignore into tracks(name) VALUES('ula');\n"
                                       "insert or ignore into tracks(name) VALUES('ala ma kota');\n"
                                       "--insert or ignore into tracks(name) VALUES('basia');\n"
                                       "insert or ignore into tracks(name) VALUES('ala ma kota');\n";

    const std::string script_invalid = "inserts error(name) VALUES('super');\n";

    SECTION("list files")
    {
        ScopedDir dir("scripts");

        auto file = std::fopen(dir("test_1.sql").c_str(), "w");
        std::fclose(file);

        file = std::fopen(dir("test_021.sql").c_str(), "w");
        std::fclose(file);

        file = std::fopen(dir("test_011.sql").c_str(), "w");
        std::fclose(file);

        file = std::fopen(dir("test_013.sql").c_str(), "w");
        std::fclose(file);

        file = std::fopen(dir("noprefix_003.sql").c_str(), "w");
        std::fclose(file);

        Database db(dir("test.db").c_str());
        DatabaseInitializer initializer(&db);
        auto files = initializer.listFiles("scripts", "test", "sql");

        REQUIRE(files.size() == 4);
        REQUIRE(files[0] == (std::filesystem::path{"scripts"} / "test_1.sql"));
        REQUIRE(files[1] == (std::filesystem::path{"scripts"} / "test_011.sql"));
        REQUIRE(files[2] == (std::filesystem::path{"scripts"} / "test_013.sql"));
        REQUIRE(files[3] == (std::filesystem::path{"scripts"} / "test_021.sql"));
    }

    SECTION("execute single valid script")
    {
        ScopedDir dir("execute_valid_script");
        std::string test_file("test_001.sql");

        auto file = std::fopen(dir(test_file).c_str(), "w");
        std::fwrite(script_create.data(), sizeof(char), script_create.size(), file);
        std::fclose(file);

        Database db(dir("test.db").c_str());
        DatabaseInitializer initializer(&db);
        auto commands = initializer.readCommands(dir(test_file));
        REQUIRE(commands.size() == 1);

        bool r = initializer.run(dir());
        REQUIRE(r == true);
    }

    SECTION("execute 2 valid script files")
    {
        ScopedDir dir("scripts");
        std::string test_file("test_001.sql");

        auto file = std::fopen(dir(test_file).c_str(), "w");
        std::fwrite(script_create.data(), sizeof(char), script_create.size(), file);
        std::fwrite(script_insert.data(), sizeof(char), script_insert.size(), file);
        std::fclose(file);

        Database db(dir("test.db").c_str());
        DatabaseInitializer initializer(&db);
        auto commands = initializer.readCommands(dir(test_file));
        REQUIRE(commands.size() == 2);

        bool result = initializer.run(dir());
        REQUIRE(result == true);
    }

    SECTION("execute multiple valid script files")
    {
        ScopedDir dir("scripts");
        std::string test_file("test_001.sql");

        auto file = std::fopen(dir(test_file).c_str(), "w");
        std::fwrite(script_create.data(), sizeof(char), script_create.size(), file);
        std::fwrite(script_insert.data(), sizeof(char), script_insert.size(), file);
        std::fwrite(script_insert.data(), sizeof(char), script_insert.size(), file);
        std::fwrite(script_insert.data(), sizeof(char), script_insert.size(), file);
        std::fwrite(script_insert.data(), sizeof(char), script_insert.size(), file);
        std::fwrite(script_insert.data(), sizeof(char), script_insert.size(), file);
        std::fclose(file);

        Database db(dir("test.db").c_str());
        DatabaseInitializer initializer(&db);
        auto commands = initializer.readCommands(dir(test_file));
        REQUIRE(commands.size() == 6);

        bool result = initializer.run(dir());
        REQUIRE(result == true);
    }

    SECTION("execute empty script files")
    {
        ScopedDir dir("scripts");
        std::string test_file("test_001.sql");

        auto file = std::fopen(dir(test_file).c_str(), "w");
        std::fclose(file);

        Database db(dir("test.db").c_str());
        DatabaseInitializer initializer(&db);
        auto commands = initializer.readCommands(dir(test_file));
        REQUIRE(commands.empty());

        bool result = initializer.run(dir());
        REQUIRE(result == true);
    }

    SECTION("execute script file with comment")
    {
        ScopedDir dir("read_script_file_with_comment");
        std::string test_file("test_001.sql");

        auto file = std::fopen(dir(test_file).c_str(), "w");
        std::fwrite(script_create.data(), sizeof(char), script_create.size(), file);
        std::fwrite(script_comment.data(), sizeof(char), script_comment.size(), file);
        std::fclose(file);

        Database db(dir("test.db").c_str());
        DatabaseInitializer initializer(&db);
        auto commands = initializer.readCommands(dir(test_file));
        REQUIRE(commands.size() == 3);

        bool result = initializer.run(dir());
        REQUIRE(result == true);
    }

    SECTION("execute invalid script")
    {
        ScopedDir dir("execute_invalid_script");
        std::string test_file("test_001.sql");

        auto file = std::fopen(dir(test_file).c_str(), "w");
        std::fwrite(script_create.data(), sizeof(char), script_create.size(), file);
        std::fwrite(script_invalid.data(), sizeof(char), script_invalid.size(), file);
        std::fclose(file);

        Database db(dir("test.db").c_str());
        DatabaseInitializer initializer(&db);
        auto commands = initializer.readCommands(dir(test_file));
        REQUIRE(commands.size() == 2);

        bool result = initializer.run(dir());
        REQUIRE(result == false);
    }

    REQUIRE(Database::deinitialize() == true);
}

M module-services/service-db/DBServiceAPI.cpp => module-services/service-db/DBServiceAPI.cpp +0 -1
@@ 16,7 16,6 @@
#include <SMSTemplateRecord.hpp>
#include <system/Common.hpp>
#include <Service/Service.hpp>
#include <Tables/CountryCodesTable.hpp>
#include <ThreadRecord.hpp>
#include <Utils.hpp>
#include <log/log.hpp>

M module-services/service-db/agents/settings/SettingsAgent.cpp => module-services/service-db/agents/settings/SettingsAgent.cpp +1 -1
@@ 70,7 70,7 @@ void SettingsAgent::unRegisterMessages()

auto SettingsAgent::getDbFilePath() -> const std::string
{
    return (purefs::dir::getUserDiskPath() / dbName).string();
    return (purefs::dir::getDatabasesPath() / dbName).string();
}
auto SettingsAgent::getAgentName() -> const std::string
{

M module-services/service-db/test/CMakeLists.txt => module-services/service-db/test/CMakeLists.txt +1 -2
@@ 8,7 8,7 @@ add_catch2_executable(
            test-service-db-quotes.cpp
            test-factory-settings.cpp
        LIBS
            iosyscalls
            module-db::test::helpers
            module-audio
            module-cellular
            module-sys


@@ 16,7 16,6 @@ add_catch2_executable(
            module-vfs
            service-audio
            service-cellular
        USE_FS
)

add_catch2_executable(

A module-services/service-db/test/lang/English.json => module-services/service-db/test/lang/English.json +733 -0
@@ 0,0 1,733 @@
{
  "metadata": {
    "display_name": "English"
  },
  "common_add": "ADD",
  "common_open": "OPEN",
  "common_call": "CALL",
  "common_save": "SAVE",
  "common_edit": "EDIT",
  "common_import": "IMPORT",
  "common_send": "SEND",
  "common_reply": "REPLY",
  "common_confirm": "CONFIRM",
  "common_select": "SELECT",
  "common_use": "USE",
  "common_ok": "OK",
  "common_back": "BACK",
  "common_skip": "SKIP",
  "common_contacts": "CONTACTS",
  "common_set": "SET",
  "common_show": "SHOW",
  "common_yes": "Yes",
  "common_no": "No",
  "common_switch": "SWITCH",
  "common_options": "OPTIONS",
  "common_options_title": "Options",
  "common_information": "Information",
  "common_check": "CHECK",
  "common_uncheck": "UNCHECK",
  "common_emoji": "EMOJI",
  "common_special_characters": "SPECIAL",
  "common_start": "START",
  "common_stop": "STOP",
  "common_resume": "RESUME",
  "common_pause": "PAUSE",
  "common_play": "PLAY",
  "common_retry": "TRY AGAIN",
  "common_replace": "REPLACE",
  "common_abort": "ABORT",
  "common_connect": "CONNECT",
  "common_disconnect": "DISCONNECT",
  "common_forget": "FORGET",
  "common_adjust": "ADJUST",
  "common_mo": "MO",
  "common_tu": "TU",
  "common_we": "WE",
  "common_th": "TH",
  "common_fr": "FR",
  "common_sa": "SA",
  "common_su": "SU",
  "common_mon": "Mon",
  "common_tue": "Tue",
  "common_wed": "Wed",
  "common_thu": "Thu",
  "common_fri": "Fri",
  "common_sat": "Sat",
  "common_sun": "Sun",
  "common_monday": "Monday",
  "common_tuesday": "Tuesday",
  "common_wednesday": "Wednesday",
  "common_thursday": "Thursday",
  "common_friday": "Friday",
  "common_saturday": "Saturday",
  "common_sunday": "Sunday",
  "common_january": "January",
  "common_february": "February",
  "common_march": "March",
  "common_april": "April",
  "common_may": "May",
  "common_june": "June",
  "common_july": "July",
  "common_august": "August",
  "common_september": "September",
  "common_october": "October",
  "common_november": "November",
  "common_december": "December",
  "common_yesterday": "Yesterday",
  "common_today": "Today",
  "common_results_prefix": "Results: ",
  "common_search": "SEARCH",
  "common_accept": "ACCEPT",
  "common_minute_lower": "minute",
  "common_minutes_lower": "minutes",
  "common_minutes_lower_genitive": "minutes",
  "common_minute_short": "min",
  "common_second_lower": "second",
  "common_seconds_lower": "seconds",
  "common_seconds_lower_genitive": "seconds",
  "common_second_short": "sec",
  "common_paused": "Paused",
  "common_text_copy": "Copy text",
  "common_text_paste": "Paste text",
  "locale_12hour_min": "%I:%M %p",
  "locale_12hour_min_short": "%I:%M",
  "locale_24hour_min": "%H:%M",
  "locale_date_DD_MM_YYYY": "%d.%m.%Y",
  "locale_date_MM_DD_YYYY": "%m.%d.%Y",
  "locale_date_DD_MM": "%d.%m",
  "locale_date_MM_DD": "%m.%d",
  "locale_date_Day_DD_Mon": "%A, %d %b",
  "locale_date_Day_Mon_DD": "%A, %b %d",
  "common_AM": "AM",
  "common_PM": "PM",
  "duration_min_0sec": "%M:%0S",
  "duration_0min_0sec": "%0M:%0S",
  "duration_0hmin_0sec": "%0N:%0S",
  "duration_hour_0min_0sec": "%H:%0M:%0S",
  "brightness_text": "BRIGHTNESS",
  "phone_needs_rebooting": "Your phone needs rebooting. Press any key to confirm and please remove the battery for 10 seconds to perform a full reboot",
  "home_modes_connected": "CONNECTED",
  "home_modes_notdisturb": "DO NOT DISTURB",
  "home_modes_offline": "OFFLINE",
  "home_modes_message_only": "Message only",
  "statusbar_battery_charging": "Charg",
  "statusbar_battery_plugged": "Plug",
  "app_alarm_clock_title_main": "Alarm clock",
  "app_alarm_clock_repeat_never": "Never",
  "app_alarm_clock_repeat_everyday": "Everyday",
  "app_alarm_clock_repeat_week_days": "Weekdays",
  "app_alarm_clock_repeat_custom": "Custom",
  "app_alarm_clock_no_alarms_information": "<text align='center' color='4'>No alarms yet.<p>Press <b>left arrow</b> to add new.</p></text>",
  "app_alarm_clock_options_edit": "Edit",
  "app_alarm_clock_options_delete": "Delete",
  "app_alarm_clock_options_turn_off_all_alarms": "Turn off all alarms",
  "app_alarm_clock_delete_confirmation": "Delete this alarm?",
  "app_alarm_clock_new_alarm_title": "New alarm",
  "app_alarm_clock_edit_alarm_title": "Edit alarm",
  "app_alarm_clock_sound": "Sound",
  "app_alarm_clock_snooze": "Snooze",
  "app_alarm_clock_repeat": "Repeat",
  "app_alarm_clock_no_snooze": "None",
  "app_alarm_clock_snooze_5_min": "5 min",
  "app_alarm_clock_snooze_10_min": "10 min",
  "app_alarm_clock_snooze_15_min": "15 min",
  "app_alarm_clock_snooze_30_min": "30 min",
  "app_alarm_clock_custom_repeat_title": "Custom repeat",
  "app_calendar_title_main": "Calendar",
  "app_calendar_all_day": "All day",
  "app_calculator_title_main": "Calculator",
  "app_calculator_equals": "EQUALS",
  "app_calculator_decimal_separator": ".",
  "app_calculator_error": "Error",
  "app_options_invalid_option": " <Invalid Option> ",
  "app_options_contact_details": "Contact details",
  "app_options_contact_add": "Add to contacts",
  "app_options_contact_edit": "Edit Contact",
  "app_notes_title_main": "Notes",
  "app_notes_edit_new_note": "Edit/New Note",
  "app_notes_edit": "EDIT",
  "app_notes_edited": "Edited",
  "app_notes_delete_note": "Delete",
  "app_notes_note_delete_confirmation": "Do you really want to delete this note?",
  "app_notes_no_notes": "<text align='center' color='4'>No notes yet.<p>Press <b>left arrow</b> to add new.</p></text>",
  "app_notes_search_no_results": "No notes found.",
  "app_calllog_title_main": "Calls",
  "app_calllog_no_calls": "<text align='center' color='4'>No calls yet.</text>",
  "app_calllog_type": "Call",
  "app_calllog_duration": "Duration",
  "app_calllog_incoming_call": "Incoming call",
  "app_calllog_outgoing_call": "Outgoing call",
  "app_calllog_missed_call": "Missed call",
  "app_calllog_rejected_call": "Rejected call",
  "app_calllog_empty_incoming": "<text color='5'>Incoming</text>",
  "app_calllog_empty_outgoing": "<text color='5'>Outgoing</text>",
  "app_calllog_empty_missed": "<text color='5'>Missed</text>",
  "app_calllog_date": "Date",
  "app_calllog_options_delete_call": "Delete call",
  "app_calllog_delete_call_confirmation": "Delete this call from the list?",
  "app_calllog_delete_all_calls": "Delete all calls",
  "app_calllog_delete_all_calls_confirmation": "Delete all calls from the list?",
  "app_desktop_unlock": "UNLOCK",
  "app_desktop_menu": "MENU",
  "app_desktop_emergency": "SOS",
  "app_desktop_info": "Info",
  "app_desktop_info_mmi_none_specified_failed": "Operation failed",
  "app_desktop_info_mmi_none_specified_success": "Operation successful",
  "app_desktop_info_mmi_common_failed": "failed",
  "app_desktop_info_mmi_common_no_message": "not",
  "app_desktop_info_mmi_common_mmi_not_supported": "not supported",
  "app_desktop_info_mmi_common_enabled": "enabled",
  "app_desktop_info_mmi_common_disabled": "disabled",
  "app_desktop_info_mmi_common_voice": "voice",
  "app_desktop_info_mmi_common_data": "data",
  "app_desktop_info_mmi_common_fax": "FAX",
  "app_desktop_info_mmi_common_sync": "sync",
  "app_desktop_info_mmi_common_async": "async",
  "app_desktop_info_mmi_common_all_disabled": "all bearer services disabled",
  "app_desktop_info_mmi_common_deactivated": "service was deactivated",
  "app_desktop_info_mmi_common_activated": "service was activated",
  "app_desktop_info_mmi_common_query": "query",
  "app_desktop_info_mmi_clir_according_to_subscription": "CLIR according to subscription",
  "app_desktop_info_mmi_clir_enabled": "CLIR enabled",
  "app_desktop_info_mmi_clir_disabled": "CLIR diasbled",
  "app_desktop_info_mmi_clir_not_provisioned": "CLIR not provisioned",
  "app_desktop_info_mmi_clir_permanent_provisioned": "CLIR permanently provisioned",
  "app_desktop_info_mmi_clir_unknown": "CLIR unknown",
  "app_desktop_info_mmi_clir_temporary_restricted": "CLIR temporarily restricted",
  "app_desktop_info_mmi_clir_temporary_allowed": "CLIR temporarily allowed",
  "app_desktop_info_mmi_registration_failed": "Registration failed",
  "app_desktop_info_mmi_registration_success": "Registration was successful",
  "app_desktop_info_mmi_erasure_failed": "Erasure failed",
  "app_desktop_info_mmi_erasure_success": "Erasure was successful",
  "app_desktop_info_mmi_disabling_failed": "Service disabling failed",
  "app_desktop_info_mmi_disabling_success": "Service has been disabled",
  "app_desktop_info_mmi_enabling_failed": "Service enabling failed",
  "app_desktop_info_mmi_enabling_success": "Service has been enabled",
  "app_desktop_info_mmi_call_forwarding_disabled": "call forwarding disabled",
  "app_desktop_info_mmi_call_barring_activated": "Call barring was activated",
  "app_desktop_info_mmi_call_barring_deactivated": "Call barring was deactivated",
  "app_desktop_info_mmi_clip_activated": "CLIP activated",
  "app_desktop_info_mmi_clip_deactivated": "CLIP deactivated",
  "app_desktop_info_mmi_clip_not_provisioned": "CLIP not provisioned",
  "app_desktop_info_mmi_clip_provisioned": "CLIP provisioned",
  "app_desktop_info_mmi_clip_unknown": "CLIP unknown",
  "app_desktop_info_mmi_call_waiting_activated": "Call waiting was activated",
  "app_desktop_info_mmi_call_waiting_deactivated": "Call waiting was deactivated",
  "app_desktop_info_mmi_call_forwarding": "Call forwarding",
  "app_desktop_info_mmi_call_barring": "Call barring",
  "app_desktop_info_mmi_call_waiting": "Call waiting",
  "app_desktop_info_mmi_clip": "Caller ID displayed (CLIP)",
  "app_desktop_info_mmi_clir": "Caller ID suppressed (CLIR)",
  "app_desktop_info_mmi_imei": "IMEI (MEID)",
  "app_desktop_info_mmi_result_success": "Success",
  "app_desktop_info_mmi_result_failed": "Failed",
  "sim_header_setup": "<text><token>$SIM</token> setup</text>",
  "sim_enter_pin_unlock": "<text>Enter the PIN code to set up <br></br> the <token>$PINTYPE</token> card:</text>",
  "sim_enter_enter_current": "<text>Type current Pin Code:</text>",
  "sim_change_pin": "Change PIN code",
  "sim_enter_new_pin": "Enter new PIN code:",
  "sim_confirm_new_pin": "Confirm new PIN code:",
  "sim_setup_wrong_pin": "<text>Wrong PIN code. You have<br></br><token>$ATTEMPTS</token> attempts left.</text>",
  "sim_setup_wrong_pin_last_attempt": "<text>Wrong PIN code. You have<br></br>1 attempt left.</text>",
  "sim_wrong_pin_confirmation": "Wrong PIN code.",
  "sim_pin_changed_successfully": "PIN code changed successfully",
  "sim_cme_error": "<text>SIM card<br></br>CME error:<token>$CMECODE</token></text>",
  "sim_puk_blocked": "<text>The SIM card is blocked.<br></br>Please, contact the operator.</text>",
  "sim_setup_enter_puk": "<text>The SIM card is blocked.<br></br>To unblock it, type the PUK code:</text>",
  "sim_setup_wrong_puk": "<text>Wrong PUK code.<br></br>You have <token>$ATTEMPTS</token> attempts left</text>",
  "sim_setup_wrong_puk_last_attempt": "<text>Wrong PUK code.<br></br>You have 1 attempt left</text>",
  "sim_setup_wrong_puk_last_attempt_warning": "<text>If the code is wrong this time, the<br></br>SIM card will be blocked and you'll<br></br>have to contact the operator.</text>",
  "sim_card_pin_disabled": "SIM card pin disabled",
  "sim_card_pin_enabled": "SIM card pin enabled",
  "sim_card_cant_connect": "<text>Cannot connect to <token>$SIM</token> card.<br></br>Please insert card.</text>",
  "sim_card_not_ready": "<text>Waiting for Modem to start.<br></br>This may take a moment.</text>",
  "app_desktop_press_to_unlock": "<text size='27'>Press <b>Unlock</b> and then <b>#</b></text>",
  "app_desktop_press_to_complete_unlock": "<text size='27'>Press <b>#</b> to unlock</text>",
  "app_desktop_unread_messages": "<text>Unread <b>messages</b></text>",
  "app_desktop_missed_calls": "<text>Missed <b>calls</b></text>",
  "app_desktop_alarm_snooze": "<text>Snooze</text>",
  "app_desktop_menu_phone": "CALLS",
  "app_desktop_menu_contacts": "CONTACTS",
  "app_desktop_menu_messages": "MESSAGES",
  "app_desktop_menu_calendar": "CALENDAR",
  "app_desktop_menu_alarm": "ALARM",
  "app_desktop_menu_meditation": "MEDITATION",
  "app_desktop_menu_music": "MUSIC",
  "app_desktop_menu_tools": "TOOLS",
  "app_desktop_menu_settings": "SETTINGS",
  "app_desktop_menu_title": "Menu",
  "app_desktop_tools_title": "Tools",
  "app_desktop_tools_notes": "NOTES",
  "app_desktop_tools_calculator": "CALCULATOR",
  "app_desktop_tools_antenna": "ANTENNA TEST",
  "app_desktop_poweroff_title": "Turn off",
  "app_desktop_poweroff_question": "Turn off the phone?",
  "app_desktop_show": "SHOW",
  "app_desktop_calls": "CALLS",
  "app_desktop_clear": "CLEAR",
  "app_desktop_clear_all": "CLEAR ALL",
  "app_desktop_replay": "REPLY",
  "app_popup_volume_text": "VOLUME",
  "app_popup_bt_volume_text": "BLUETOOTH VOLUME",
  "app_popup_music_volume_text": "MUSIC VOLUME",
  "app_popup_call_volume_text": "CALL VOLUME",
  "app_popup_muted_text": "MUTED",
  "app_popup_snooze_text": "SNOOZE",
  "app_popup_alarm_text": "alarm",
  "app_popup_alarm_snoozed_till": "snoozed till",
  "app_call_call": "CALL",
  "app_call_clear": "CLEAR",
  "app_call_reject": "REJECT",
  "app_call_answer": "ANSWER",
  "app_call_message": "MESSAGE",
  "app_call_end_call": "END CALL",
  "app_call_emergency": "Emergency call",
  "app_call_is_calling": "is calling",
  "app_call_calling": "calling...",
  "app_call_call_ended": "call ended",
  "app_call_call_rejected": "call rejected",
  "app_call_contact": "CONTACT",
  "app_call_mute": "MUTE",
  "app_call_muted": "MUTED",
  "app_call_speaker": "SPEAKER",
  "app_call_speaker_on": "SPEAKER ON",
  "app_call_bluetooth": "BLUETOOTH",
  "app_call_no_sim": "No SIM.\n\nTo make a call,\nplease insert a SIM card.",
  "app_call_no_network_connection": "No Network Connection.",
  "app_call_call_request_failed": "Something went wrong.",
  "app_call_offline": "You're Offline.\n\nTo make a call,\n switch the mode.",
  "app_sms_offline": "You're Offline.\n\nTo send message,\n switch the mode.",
  "app_call_emergency_text": "Emergency call",
  "app_call_wrong_emergency": "Can't make a call.\n$NUMBER is not an emergency number.",
  "app_messages_title_main": "Messages",
  "app_messages_no_messages": "<text align='center' color='4'>No messages yet.<p>Press <b>left arrow</b> to add new.</p></text>",
  "app_messages_thread_delete_confirmation": "Delete this conversation?",
  "app_messages_message_delete_confirmation": "Delete this message?",
  "app_messages_thread_no_result": "There are no results",
  "app_messages_message": "Message",
  "app_messages_templates": "Templates",
  "app_messages_no_sim": "No SIM.\n\nTo send a SMS,\nplease insert a SIM card.",
  "app_messages_thread_draft": "Draft: ",
  "app_messages_thread_not_sent": "Not sent: ",
  "app_messages_thread_you": "You: ",
  "app_onboarding_title": "Onboarding",
  "app_onboarding_start_configuration": "<text weight='light' size='46'><p>Hello!</p></text><br></br><text weight='regular' size='27'>Let's configure your Mudita Pure.</text>",
  "app_onboarding_eula_license": "License agreement (EULA)",
  "app_onboarding_select_sim": "Choose active SIM",
  "app_onboarding_select_sim_description": "<text>Only one SIM can be active at a time.<br></br>You can choose it now and switch in<br></br>the Settings whenever needed.</text>",
  "app_onboarding_no_sim_selected_title": "SIM setup",
  "app_onboarding_no_sim_selected_description": "<text>No SIM card set up.<br></br>To connect to network, set up <br></br> SIM cards in Settings.</text>>",
  "app_onboarding_title_configuration": "Configuration",
  "app_onboarding_title_update_info": "MuditaOS update",
  "app_onboarding_skip_confirm": "<text>SIM setup is required for network connection. Skip the setup anyway? </text>",
  "app_onboarding_configuration_successful": "<text>Your Mudita Pure<br></br>is ready to use.</text>",
  "app_onboarding_no_configuration": "<text>Your Mudita Pure has not been<br></br>configured. You can go to<br></br>Settings to set it up.</text>",
  "app_onboarding_update_info": "<text>The current version of MuditaOS is<br></br><token>$VERSION</token><br></br> Updates with new features and fixes appear often.<br></br>To update your Phone please <br></br> visit: </text><text weight='bold' size='27'>www.mudita.com/updateos</text><br></br><text>and follow the instructions.</text>",
  "app_settings_title_main": "Settings",
  "app_settings_advanced": "Advanced",
  "app_settings_bt": "Bluetooth",
  "app_settings_bluetooth_add_device": "Add device",
  "app_settings_bluetooth_all_devices": "All devices",
  "app_settings_bluetooth_searching_devices": "Searching devices... \nIt may take a moment.",
  "app_settings_bluetooth_main": "Bluetooth",
  "app_settings_bluetooth_phone_name": "Phone name",
  "app_settings_bluetooth_phone_visibility": "Phone visibility",
  "app_settings_bluetooth_init_error_message": "<text weight='regular' size='27'>Bluetooth initialization process has failed.</text>",
  "app_settings_bluetooth_pairing_error_message": "<text weight='regular' size='27'>Pairing process has failed.<br></br>Check the device and </text> <text weight='bold' size='27'>TRY AGAIN.</text>",
  "app_settings_bluetooth_unpairing_error_message": "<text weight='regular' size='27'>Unpairing process has failed.<br></br>Check the device and </text> <text weight='bold' size='27'>TRY AGAIN.</text>",
  "app_settings_bluetooth_connecting_error_message": "<text weight='regular' size='27'>Connection process has failed.<br></br>Check the device and </text> <text weight='bold' size='27'>TRY AGAIN.</text>",
  "app_settings_net": "Network",
  "app_settings_disp_key": "Display and keypad",
  "app_settings_display_display_light": "Display light",
  "app_settings_display_dark_mode": "Dark mode (Beta)",
  "app_settings_display_light_main": "Frontlight",
  "app_settings_display_light_auto": "Automatic",
  "app_settings_display_light_brightness": "Brightness",
  "app_settings_display_font_size": "Font size",
  "app_settings_display_locked_screen": "Locked screen",
  "app_settings_display_keypad_light": "Keypad light",
  "app_settings_display_input_language": "Input language",
  "app_settings_display_wallpaper": "Wallpaper",
  "app_settings_display_wallpaper_logo": "Mudita logo",
  "app_settings_display_wallpaper_clock": "Clock",
  "app_settings_display_wallpaper_quotes": "Quotes",
  "app_settings_display_wallpaper_edit_quotes": "Edit quotes",
  "app_settings_display_wallpaper_edit_custom_quotes": "Edit custom quotes",
  "app_settings_display_wallpaper_quotes_edit": "Edit",
  "app_settings_display_wallpaper_quotes_delete": "Delete",
  "app_settings_display_wallpaper_quotes_new": "New quote",
  "app_settings_display_wallpaper_quotes_delete_confirmation": "Delete this quote?",
  "app_settings_display_wallpaper_quotes_delete_success": "Quote deleted.",
  "app_settings_display_wallpaper_quotes_note": "Note",
  "app_settings_display_wallpaper_quotes_author": "Author",
  "app_settings_display_wallpaper_quotes_our_favourites": "Our favorites",
  "app_settings_display_wallpaper_quotes_custom": "Custom",
  "app_settings_display_wallpaper_quotes_categories": "Select categories",
  "app_settings_system": "System",
  "app_settings_apps": "Apps",
  "app_settings_apps_phone": "Phone",
  "app_settings_apps_messages": "Messages",
  "app_settings_show_unread_first": "Show unread first",
  "app_settings_apps_alarm_clock": "Alarm clock",
  "app_settings_apps_alarm_clock_manual_volume": "Manual sound volume",
  "app_settings_vibration": "Vibration",
  "app_settings_sound": "Sound",
  "app_settings_volume": "Volume",
  "app_settings_call_ringtome": "Call ringtone",
  "app_settings_message_sound": "Message sound",
  "app_settings_notification_sound": "Notification sound",
  "app_settings_Templates": "Templates",
  "app_settings_title_torch": "Torch",
  "app_settings_torch_sunset_red_light_option": "Sunset red light",
  "app_settings_torch_nightshift_time_option": "Nightshift time",
  "app_settings_torch_description": "In the nightshift, the torch will use\nsunset red light so it won't disturb\nyour and others sleep.",
  "app_settings_title_nightshift": "Nightshift",
  "app_settings_nightshift_from": "From",
  "app_settings_nightshift_to": "To",
  "app_settings_date_and_time": "Date and time",
  "app_settings_date_and_time_automatic_date_and_time": "Automatic date and time",
  "app_settings_date_and_time_change_date_and_time": "Change date and time",
  "app_settings_date_and_time_automatic_time_zone": "Automatic time zone",
  "app_settings_date_and_time_change_time_zone": "Change time zone",
  "app_settings_date_and_time_time_format": "Time format",
  "app_settings_date_and_time_date_format": "Date format",
  "app_settings_date_and_time_time_zone": "Time zone",
  "app_settings_title_day": "Day",
  "app_settings_title_month": "Month",
  "app_settings_title_year": "Year",
  "app_settings_title_time": "Time",
  "app_settings_cellular_passthrough": "Cellular <-> USB",
  "app_settings_display": "Display",
  "app_settings_phone_modes": "Phone modes",
  "app_settings_security": "Security",
  "app_settings_language": "Language",
  "app_settings_factory_reset": "Factory reset",
  "app_settings_display_factory_reset_confirmation": "You need to restart your Pure \nto finalize factory reset.\n Turn off the phone now?",
  "app_settings_about_your_pure": "About your Pure",
  "app_settings_technical_information": "Technical Information",
  "app_settings_tech_info_model": "Model",
  "app_settings_tech_info_serial_number": "Serial number",
  "app_settings_tech_info_os_version": "OS Version",
  "app_settings_tech_info_imei": "IMEI",
  "app_settings_tech_info_battery": "Battery",
  "app_settings_tech_info_pcb_mb": "pcbMB",
  "app_settings_tech_info_pcb_lm": "pcbLM",
  "app_settings_tech_info_pcb_um": "pcmUM",
  "app_settings_tech_info_pcb_am": "pcbAM",
  "app_settings_certification": "Certification",
  "app_settings_us_fcc_id": "US FCC ID",
  "app_settings_canada_ic": "Canada IC",
  "app_settings_europe": "Europe",
  "app_settings_sar": "SAR",
  "app_settings_about": "About Mudita Pure",
  "app_settings_title_languages": "Language selection",
  "app_settings_network_sim_cards": "SIM cards",
  "app_settings_network_active_card": "Active card",
  "app_settings_network_operator_auto_select": "Operator auto-select",
  "app_settings_network_all_operators": "All operators",
  "app_settings_network_pin_settings": "PIN settings",
  "app_settings_network_pin": "PIN",
  "app_settings_network_pin_change_code": "Change PIN code",
  "app_settings_network_import_contacts": "Import contacts",
  "app_settings_network_import_contacts_duplicates": "Duplicates from SIM",
  "app_settings_network_import_contacts_from_sim_card": "Import contacts from SIM card",
  "app_settings_network_import_contacts_from_sim_card_reading": "<text>Importing in progress...<br></br>Please wait a moment.</text>",
  "app_settings_network_import_contacts_from_sim_card_no_contacts": "<text>There are no contacts<br></br>on this SIM card.</text>",
  "app_settings_network_import_contacts_from_sim_card_duplicates": "<text>We found <token>$DUPLICATES</token> duplicates. Do you want<br></br>to import duplicated contacts and<br></br>replace existing ones.</text>",
  "app_settings_network_import_contacts_from_sim_card_success": "Contacts imported successfully.",
  "app_settings_network_sim1": "SIM1",
  "app_settings_network_sim2": "SIM2",
  "app_settings_network_sim_none": "No SIM",
  "app_settings_network_voice_over_lte": "VoLTE (experimental)",
  "app_settings_network_apn_settings": "APN settings",
  "app_settings_toggle_on": "ON",
  "app_settings_toggle_off": "OFF",
  "app_settings_security_phone_lock": "Lock screen passcode",
  "app_settings_display_security_autolock": "Auto lock",
  "app_settings_security_change_phone_lock": "Change passcode",
  "phone_lock_unlock": "<text>Enter the passcode<br></br>to unlock:</text>",
  "phone_lock_blocked_information": "<text>Wrong passcode.<br></br>Try again in <token>$TIME</token>.</text>",
  "phone_lock_blocked_information_seconds": " seconds",
  "phone_lock_blocked_information_minute": "a minute",
  "phone_lock_blocked_information_minutes": " minutes",
  "phone_lock_notification": "<text>Passcode lock for <token>$TIME</token></text>",
  "phone_lock_unlock_invalid": "<text>Wrong passcode.<br></br>Please try again.</text>",
  "phone_lock_blocked": "Sorry, phone blocked",
  "phone_lock_current": "Type current passcode",
  "phone_lock_enter_new": "Enter new passcode",
  "phone_lock_confirm_new": "Confirm new passcode",
  "phone_lock_invalid": "Wrong passcode!",
  "phone_lock_changed_successfully": "Passcode changed successfully!",
  "phone_lock_disabled": "Passcode disabled!",
  "phone_lock_set": "<text>Set passcode that unlocks <br></br> the phone</text>",
  "phone_lock_confirm": "Confirm the passcode",
  "phone_lock_invalid_retry": "<text>Wrong passcode. <br></br> Configure passcode again.</text>",
  "phone_lock_configure": "Configure passcode",
  "app_settings_security_usb_passcode": "USB security",
  "app_settings_apn_settings_no_apns": "<text align='center' color='5'>No APNs yet.<p>Press <b>left arrow</b> to add new.</p></text>",
  "app_settings_apn_options_delete": "Delete",
  "app_settings_apn_options_edit": "Edit",
  "app_settings_apn_options_set_as_default": "Set as default",
  "app_settings_new_edit_apn": "New/Edit APN",
  "app_settings_apn_name": "Name",
  "app_settings_apn_APN": "APN",
  "app_settings_apn_username": "Username",
  "app_settings_apn_password": "Password",
  "app_settings_apn_authtype": "Authentication type",
  "app_settings_apn_apntype": "APN type",
  "app_settings_apn_apnprotocol": "APN protocol",
  "app_settings_title_color_test": "Display available colors",
  "app_settings_toolbar_reset": "RESET",
  "app_settings_option_connected": "CONNECTED",
  "app_settings_option_connected_audio": "CONNECTED AUDIO",
  "app_settings_option_connected_voice": "CONNECTED VOICE",
  "app_settings_option_connected_both": "CONNECTED VOICE, AUDIO",
  "app_settings_option_connecting": "CONNECTING",
  "app_settings_option_pairing": "PAIRING",
  "app_settings_title_do_not_disturb": "Do not disturb",
  "app_settings_title_offline": "Offline",
  "app_settings_title_connection_frequency": "Connection frequency",
  "app_settings_connected": "Connected",
  "app_settings_notifications_when_locked": "Notifications when locked",
  "app_settings_calls_from_favorites": "Calls from favorites",
  "app_settings_allow": "Allow",
  "app_settings_no_network_connection_flight_mode": "Flight mode",
  "app_settings_messages_only": "Messages only",
  "app_settings_info_dnd": "Silently receive all notifications. You can allow full notifications from favorite contacts.",
  "app_settings_info_offline_flight_mode": "Fully disconnected. Calls, messages and tethering are unavailable.",
  "app_settings_info_offline_messages_only": "Send and download messages based on connection intervals. No calls nor tethering.",
  "app_phonebook_title_main": "Contacts",
  "common_search_uc": "Search",
  "common_search_results": "Search results",
  "app_phonebook_search_no_results": "No contacts found.",
  "app_phonebook_no_contacts": "<text align='center' color='4'>No contacts yet.<p>Press <b>left arrow</b> to add new.</p></text>",
  "app_phonebook_no_contacts_yet": "<text align='center' color='4'>No contacts yet.</text>",
  "app_phonebook_contact_title": "Add contact",
  "app_phonebook_contact_no_name": "no name",
  "app_phonebook_contact_information": "Information",
  "app_phonebook_contact_flag_fav": "FAVORITES",
  "app_phonebook_contact_flag_speed_dial": "SPEED DIAL",
  "app_phonebook_contact_flag_ice": "ICE",
  "app_phonebook_contact_flag_blocked": "BLOCKED",
  "app_phonebook_ice_contacts_title": "Emergency Contacts",
  "app_phonebook_favorite_contacts_title": "Favorites",
  "app_phonebook_duplicate_numbers": "<text>This number is assigned to \n <token>$CONTACT_FORMATTED_NAME$</token>. Replace it?</text>",
  "app_phonebook_duplicate_speed_dial": "<text>Number <token>$CONTACT_SPEED_DIAL$</token> is assigned to \n <token>$CONTACT_FORMATTED_NAME$</token>. Replace it?</text>",
  "app_phonebook_duplicate_speed_dial_title": "<text>Speed dial key (<token>$CONTACT_SPEED_DIAL$</token>)</text>",
  "app_phonebook_options_edit": "Edit contact",
  "app_phonebook_options_block": "Block",
  "app_phonebook_options_block_confirm": "Block this contact?",
  "app_phonebook_options_block_notification": "Contact blocked.",
  "app_phonebook_options_unblock": "Unblock",
  "app_phonebook_options_unblock_confirm": "Unblock this contact?",
  "app_phonebook_options_unblock_notification": "Contact unblocked.",
  "app_phonebook_options_delete": "Delete",
  "app_phonebook_options_delete_confirm": "Do you really want to delete\nthis contact?",
  "app_phonebook_options_delete_notification": "This contact has been deleted\nsuccessfully.",
  "app_phonebook_options_forward_namecard": "Forward namecard",
  "app_phonebook_options_send_sms": "Send via SMS",
  "app_phonebook_new_contact_first_name": "First name",
  "app_phonebook_new_contact_last_name": "Last name",
  "app_phonebook_new_contact_number": "Phone number",
  "app_phonebook_new_contact_second_number": "Second phone number",
  "app_phonebook_new_contact_email": "Email",
  "app_phonebook_new_contact_address": "Address",
  "app_phonebook_new_contact_note": "Note",
  "app_phonebook_new_speed_dial_key": "Speed dial key",
  "app_phonebook_new_contact_invalid_number": "<text>Cannot save this contact.<br></br>The phone number you entered <br></br>is in an invalid format.</text>",
  "app_phonebook_new_add_to_fav": "Add to favorites",
  "app_phonebook_new_add_to_ice": "Emergency Contact (ICE)",
  "app_phonebook_check": "CHECK",
  "app_phonebook_uncheck": "UNCHECK",
  "app_phonebook_multiple_numbers_first": "Number",
  "app_phonebook_multiple_numbers_second": "Second number",
  "app_meditation_title_main": "Meditation timer",
  "app_meditation_preparation_time": "Preparation time",
  "app_meditation_put_down_phone_and_wait": "<text>Put down the phone<br>and wait for the gong.</text>",
  "app_meditation_thank_you_for_session": "<text>Thank you for this<br>meditation session.</text>",
  "app_meditation_option_show_counter": "Show meditation counter",
  "app_meditation_interval_chime": "Interval chime",
  "app_meditation_interval_none": "None",
  "app_meditation_interval_every_x_minutes": "Every %0 minutes",
  "app_meditation_minute": "MINUTE",
  "app_meditation_minutes": "MINUTES",
  "app_music_player_all_songs": "<text><b>All songs</b></text>",
  "app_music_player_artists": "<text color='9'>Artists</text>",
  "app_music_player_albums": "<text color='9'>Albums</text>",
  "app_music_player_playlists": "<text color='9'>Playlists</text>",
  "app_music_player_uknown_title": "Unknown Title",
  "app_music_player_uknown_artist": "Unknown Artist",
  "app_music_player_music_library_window_name": "Music Library",
  "app_music_player_music_library": "MUSIC LIBRARY",
  "app_music_player_quit": "QUIT",
  "app_music_player_empty_track_notification": "Please choose a song from library",
  "app_music_player_start_window_notification": "<text color='5'>Press <b>down arrow</b> to choose<br></br> a song from the library</text>",
  "app_music_player_music_empty_window_notification": "<text color='5'>No songs yet</text>",
  "app_special_input_window": "Special characters",
  "app_emoji_input_window": "Emoji",
  "sms_add_rec_num": "Add contact or type a number",
  "sms_title_message": "New message",
  "sms_call_text": "Call ",
  "sms_resend_failed": "Send again",
  "sms_delete_conversation": "Delete conversation",
  "sms_forward_message": "Forward message",
  "sms_copy": "Copy",
  "sms_delete_message": "Delete message",
  "sms_use_template": "Use template",
  "sms_paste": "Paste",
  "sms_temp_reply": "Reply",
  "sms_mark_read": "Mark as read",
  "sms_mark_unread": "Mark as unread",
  "app_desktop_update": "Update",
  "app_desktop_update_to": "Update to",
  "app_desktop_update_apply": "Do you want to apply this update?",
  "app_desktop_update_current": "Current",
  "app_desktop_update_start": "Update start",
  "app_desktop_update_size": "size",
  "app_desktop_update_bytes": "bytes",
  "app_desktop_update_unpacking": "Unpacking",
  "app_desktop_update_preparing": "Preparing MuditaOS update ver.",
  "app_desktop_update_muditaos": "MuditaOS update",
  "app_desktop_update_in_progress": "Update in progress...",
  "app_desktop_update_ready_for_reset": "Ready for reset...",
  "app_desktop_update_success": "MuditaOS has been updated to ver. $VERSION succesfully.",
  "app_call_private_number": "Private number",
  "app_call_ending_call": "Disconnecting call",
  "tethering": "Tethering",
  "tethering_turn_off_question": "Turn tethering off?",
  "tethering_enable_question": "<text>You're connected to the computer.<br />Turn tethering on?<br /><text color='5'>(some functions may be disabled)</text></text>",
  "tethering_phone_mode_change_prohibited": "<text>Tethering is on.<br /><br />Other modes (Connected, DND,<br />Offline) are overriden by this mode<br />and are not working.</text>",
  "tethering_menu_access_decline": "<text>Tethering is on.<br /><br />To access menu,<br />turn tethering off.</text>",
  "bluetooth_popup": "Bluetooth",
  "bluetooth_popup_pin": "Enter PIN:",
  "bluetooth_popup_passkey": "Enter passkey:",
  "bluetooth_popup_pair_cancel_code": "<text>Device </text><text weight='bold'><token>$DEVICE</token></text><text> would like to pair<br /> with your Pure. Please confirm<br /> the code: <text align='center' weight='bold'><token>$CODE</token></text></text>",
  "bluetooth_popup_pair_cancel_no_code": "<text>Device </text><text weight='bold'><token>$DEVICE</token></text><text> would like to pair with your Pure.</text>",
  "bluetooth_popup_confirm": "Confirm",
  "bluetooth_popup_cancel": "Cancel",
  "bluetooth_info_popup_success": "<text>Your phone is paired with: <br></br></text><text weight='bold'><token>$DEVICE</token></text>",
  "bluetooth_info_popup_error": "<text>Pairing process with </text><text weight='bold'><token>$DEVICE</token></text><br></br><text> has failed. Error code: <token>$ERROR</token></text>",
  "app_bell_settings_time_units_time_fmt_top_message": "Time format",
  "app_bell_settings_time_units_time_message": "Time",
  "app_bellmain_alarm": "Alarm",
  "app_bellmain_bedtime": "Bedtime",
  "app_bell_bedtime_notification": "It is your bedtime",
  "app_bellmain_power_nap": "Power nap",
  "app_bellmain_meditation_timer": "Meditation",
  "app_bellmain_background_sounds": "Relaxation",
  "app_bellmain_settings": "Settings",
  "app_bellmain_main_window_title": "Mudita Harmony",
  "app_bell_meditation_timer": "Meditation",
  "app_bell_meditation_settings": "Settings",
  "app_bell_meditation_start": "Meditate now",
  "app_bell_meditation_statistics": "Statistics",
  "app_bell_meditation_chime_volume": "Chime volume",
  "app_bell_meditation_chime_interval": "Chime interval",
  "app_bell_meditation_chime_interval_bottom": "of the meditation",
  "app_bell_meditation_start_delay": "Start delay",
  "app_bell_meditation_progress": "Meditation timer",
  "app_bell_meditation_interval_none": "None",
  "app_bell_meditation_put_down_and_wait": "<text>Put down Mudita Harmony<br>and wait for the gong</text>",
  "app_bell_meditation_thank_you_for_session": "<text>Thank you for<br>the session</text>",
  "app_bell_onboarding_welcome_message": "<text>Mudita Harmony<br/>is switched OFF</text>",
  "app_bell_onboarding_info_rotate": "<text weight='regular' size='38'>Rotate </text><text weight='light' size='38'>to select</text>",
  "app_bell_onboarding_info_light_click": "<text weight='regular' size='38'>Light click </text><text weight='light' size='38'>to continue</text>",
  "app_bell_onboarding_info_deep_click_warning": "<text weight='light' size='38'>You've </text><text weight='regular' size='38'>deep pressed</text>",
  "app_bell_onboarding_info_deep_click_correction": "<text weight='light' size='38'>Be more gentle,<br></br>try </text><text weight='regular' size='38'>light click </text><text weight='light' size='38'>this time</text>",
  "app_bell_onboarding_welcome": "Welcome",
  "app_bell_settings_advanced": "Advanced",
  "app_bell_settings_time_units": "Time",
  "app_bell_settings_temp_scale": "Temperature scale",
  "app_bell_settings_language": "Language",
  "app_bell_settings_layout": "Clock face",
  "app_bell_settings_about": "About",
  "app_bell_settings_about_product": "Mudita Harmony",
  "app_bell_settings_about_version": "<text>OS version: <token>$VERSION</token></text>",
  "app_bell_settings_about_storage_title": "Storage",
  "app_bell_settings_about_storage_text": "<text><token>$USED_MEMORY</token>MB of <token>$TOTAL_MEMORY</token>MB used</text>",
  "app_bell_settings_about_info_title": "Manual & certification info",
  "app_bell_settings_about_info_text": "www.mudita.com",
  "app_bell_settings_alarm_settings": "Alarm",
  "app_bell_settings_alarm_settings_title": "Alarm settings",
  "app_bell_settings_alarm_settings_tone": "Alarm tone",
  "app_bell_settings_alarm_settings_volume": "Alarm volume",
  "app_bell_settings_alarm_settings_light": "Alarm light",
  "app_bell_settings_alarm_settings_prewake_up": "Pre-wake up",
  "app_bell_settings_alarm_settings_prewake_up_chime_top_description": "Pre-wake up chime",
  "app_bell_settings_alarm_settings_prewake_up_chime_bottom_description": "before the alarm",
  "app_bell_settings_alarm_settings_prewake_up_chime_tone": "Pre-wake up chime tone",
  "app_bell_settings_alarm_settings_prewake_up_chime_volume": "Pre-wake up chime volume",
  "app_bell_settings_alarm_settings_prewake_up_light_top_description": "Pre-wake up light",
  "app_bell_settings_alarm_settings_prewake_up_light_bottom_description": "before the alarm",
  "app_bell_settings_alarm_settings_alarm_tone_and_light": "Main alarm",
  "app_bell_settings_bedtime_tone": "Bedtime tone",
  "app_bell_settings_bedtime_settings_tone": "Bedtime tone",
  "app_bell_settings_bedtime_settings_volume": "<text>Bedtime tone<br />volume</text>",
  "app_bell_settings_home_view": "Home view",
  "app_bell_settings_turn_off": "Turn off",
  "app_bell_settings_alarm_settings_snooze": "Snooze",
  "app_bell_settings_alarm_settings_snooze_length": "Snooze length",
  "app_bell_settings_alarm_settings_snooze_chime_interval": "Snooze chime interval",
  "app_bell_settings_alarm_settings_snooze_chime_interval_bot_desc": "<text>recurring during<br />snooze</text>",
  "app_bell_settings_alarm_settings_snooze_chime_tone": "Snooze chime tone",
  "app_bell_settings_alarm_settings_snooze_chime_volume": "Snooze chime volume",
  "app_bellmain_home_screen_bottom_desc": "Next alarm will ring",
  "app_bellmain_home_screen_bottom_desc_in": "in",
  "app_bellmain_home_screen_bottom_desc_and": "&",
  "app_bellmain_home_screen_bottom_desc_less_than": "less than",
  "app_bellmain_home_screen_bottom_desc_dp": "Deep press to activate",
  "app_bell_alarm_deactivated": "<text>Alarm deactivated</text>",
  "app_bell_alarm_set_not_active": "<text>Alarm set.<br />Deep press to activate.</text>",
  "app_bell_settings_frontlight": "Frontlight",
  "app_bell_settings_frontlight_top_message": "Frontlight intensity",
  "app_bell_settings_frontlight_mode_top_message": "Frontlight mode",
  "app_bell_settings_frontlight_mode_auto": "auto",
  "app_bell_settings_frontlight_mode_on_demand": "on demand",
  "app_bell_background_sounds_timer_title": "Auto turn off",
  "app_bell_turn_off_question": "Turn off Mudita Harmony?",
  "app_bell_goodbye": "Goodbye",
  "app_bell_reset_message": "<text>Resetting Mudita<br />Harmony</text>",
  "app_bell_greeting_msg": [
    "<text>Good Morning!<br />It's a Beautiful Day!</text>",
    "<text>Rise and shine!<br />Seize the day</text>",
    "<text>Greetings & Salutations!<br />It's Wake-up time!</text>",
    "<text>Morning!<br />Enjoy the day!</text>",
    "<text>Good day to you!<br />Today is a new day.</text>",
    "<text>It's a Beautiful Day<br />Time to rise & shine</text>",
    "<text>It's a Brand New Day!<br />Make the most of it!</text>",
    "<text>Hello!<br />Have an AMAZING DAY</text>",
    "<text>Bonjour!<br />Let the day begin!</text>",
    "<text>What a lovely day!<br />Don't stay in bed!</text>",
    "<text>Hello Sunshine!<br />Go brighten the world!</text>",
    "<text>Morning Greetings!<br />Go forth with a smile</text>",
    "<text>Good Morning!<br />It's time to get up!</text>",
    "<text>Buenos Dias!<br />Rise & shine all day!</text>",
    "<text>Dzien Dobry!<br />Make it a GREAT day!</text>",
    "<text>Howdy!<br />Time to take charge!</text>",
    "<text>Salutations!<br />Salute the day!</text>",
    "<text>Cześć<br />Welcome to TODAY!</text>",
    "<text>Ciao!<br />Let's do something amazing!</text>",
    "<text>Morning is here!<br />A new day has arrived!</text>",
    "<text>Time to get up!<br />All we have is NOW!</text>",
    "<text>Hallå!<br />Life is better with Fika!</text>",
    "<text>Namaste!<br />Make it a GREAT day!</text>",
    "<text>Ahoy!<br />It's time to set sail</text>",
    "<text>What's happening!<br />How did you sleep?</text>",
    "<text>G'day!<br />Take on the day!</text>",
    "<text>How's it going?<br />Did you sleep well?</text>",
    "<text>Hej!<br />It's a spectacular day!</text>",
    "<text>Buon giorno!<br />Everyday is a miracle!</text>",
    "<text>Hello Sunshine!<br />Make your day sparkle!</text>",
    "<text>Cheers to the day!<br />The future starts NOW!</text>",
    "<text>Wakey Wakey!<br />The morning awaits!</text>",
    "<text>Hejsan!<br />Have a wonderful day!</text>"
  ],
  "app_bell_settings_factory_reset": "Factory reset",
  "app_bell_settings_display_factory_reset_confirmation": "<text>Reset to factory<br></br>settings ?</text>",
  "app_meditation_summary": "<text>You've meditated for<br />",
  "app_meditation_countdown_desc": "Starts in",
  "app_meditation_summary_total": "<text>Total:<br /><token>$VALUE</token></text>",
  "app_meditation_summary_average": "Average/day:",
  "app_meditation_summary_title": "<text>Last <token>$VALUE</token> days</text>"
}

M module-services/service-db/test/test-factory-settings.cpp => module-services/service-db/test/test-factory-settings.cpp +26 -16
@@ 2,23 2,38 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include <Helpers.hpp>
#include <service-db/agents/settings/FactorySettings.hpp>
#include <service-db/EntryPath.hpp>

const std::string valid_json   = "{\"serial\" : \"00000000000000\", \"case_colour\" : \"nocase\"}";
const std::string invalid_json = "{\"serial\" : \"00000000000000\", \"case_colour\" : \"nocase\"}}";

constexpr auto schema = "CREATE TABLE IF NOT EXISTS settings_tab ( path TEXT NOT NULL UNIQUE PRIMARY KEY,value TEXT);";

const auto valid_path   = "valid.json";
const auto invalid_path = "invalid.json";

TEST_CASE("Factory Settings")
namespace
{

    SECTION("valid file")
    void spawnAndFillFile(std::filesystem::path path, std::string content)
    {
        if (std::filesystem::exists(path)) {
            std::filesystem::remove(path);
        }

        auto file = std::fopen(valid_path, "w");
        std::fwrite(valid_json.data(), sizeof(char), valid_json.size(), file);
        REQUIRE(file != nullptr);
        std::fwrite(content.data(), sizeof(char), content.size(), file);
        std::fclose(file);
    }
} // namespace

TEST_CASE("Factory Settings")
{
    SECTION("valid file")
    {
        spawnAndFillFile(valid_path, valid_json);

        settings::FactorySettings factory{valid_path};
        auto entries = factory.getMfgEntries();


@@ 30,9 45,7 @@ TEST_CASE("Factory Settings")

    SECTION("invalid file")
    {
        auto file = std::fopen(invalid_path, "w");
        std::fwrite(invalid_json.data(), sizeof(char), invalid_json.size(), file);
        std::fclose(file);
        spawnAndFillFile(invalid_path, invalid_json);

        settings::FactorySettings factory{invalid_path};
        auto entries = factory.getMfgEntries();


@@ 42,24 55,21 @@ TEST_CASE("Factory Settings")

TEST_CASE("Factory Settings Init")
{
    Database::initialize();
    db::tests::DatabaseUnderTest<Database> db{"settings_v2.db"};
    REQUIRE(db.get().query(schema));

    SECTION("Init db with factory entries")
    {
        auto file = std::fopen(valid_path, "w");
        std::fwrite(valid_json.data(), sizeof(char), valid_json.size(), file);
        std::fclose(file);

        auto dbPath = purefs::dir::getUserDiskPath() / "settings_v2.db";
        Database db(dbPath.c_str());
        spawnAndFillFile(valid_path, valid_json);

        settings::FactorySettings factory{valid_path};
        factory.initDb(&db);
        factory.initDb(&db.get());

        settings::EntryPath variablePath{
            "", "", "", settings::factory::entry_key + std::string("/serial"), settings::SettingsScope::Global};

        auto results = db.query(settings::Statements::getValue, variablePath.to_string().c_str());
        const auto results = db.get().query(settings::Statements::getValue, variablePath.to_string().c_str());
        REQUIRE(results);
        REQUIRE(results->getRowCount() == 1);
    }
}

M module-services/service-db/test/test-service-db-quotes.cpp => module-services/service-db/test/test-service-db-quotes.cpp +23 -9
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>
#include <Helpers.hpp>

#include "test-service-db-quotes.hpp"
#include <purefs/filesystem_paths.hpp>


@@ 11,22 12,37 @@

using namespace Quotes;

constexpr auto totalNumOfCategories = 3;
constexpr auto totalNumOfQuotes     = 5;
constexpr auto totalNumOfCategories = 5;

namespace
{
    std::filesystem::path getScriptsPath()
    {
        const std::string path              = __FILE__;
        const std::filesystem::path scripts = "../../../products/PurePhone/services/db/databases/scripts";
        return std::filesystem::path{path.substr(0, path.rfind('/'))} / scripts;
    }

    std::filesystem::path getLanguagePath()
    {
        const std::string path = __FILE__;
        return std::filesystem::path{path.substr(0, path.rfind('/'))};
    }
} // namespace

TEST_CASE("Quotes")
{
    Database::initialize();
    auto predefinedDB =
        std::make_unique<Database>((purefs::dir::getUserDiskPath() / "predefined_quotes.db").string().c_str());
    auto customDB = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "custom_quotes.db").string().c_str());
    db::tests::DatabaseUnderTest<Database> predefinedDb{"predefined_quotes.db", getScriptsPath(), true};
    db::tests::DatabaseUnderTest<Database> customDb{"custom_quotes.db", getScriptsPath(), true};

    std::string timestampString{};
    std::string quotesString{};
    auto settings = std::make_unique<Quotes::SettingsMock>(quotesString, timestampString);
    auto tester   = std::make_unique<QuotesAgentTester>(predefinedDB.get(), customDB.get(), std::move(settings));
    auto tester   = std::make_unique<QuotesAgentTester>(&predefinedDb.get(), &customDb.get(), std::move(settings));

    SECTION("Get all categories")
    {
        utils::resetAssetsPath(getLanguagePath());
        utils::setDisplayLanguage("English");
        tester->informLanguageChange();
        auto categories = tester->getCategoriesList();


@@ 236,8 252,6 @@ TEST_CASE("Quotes")
        // No crash expected
        REQUIRE_NOTHROW(tester->readRandomizedQuote());
    }

    Database::deinitialize();
}

TEST_CASE("Serializer test")

M module-services/service-fileindexer/ServiceFileIndexer.cpp => module-services/service-fileindexer/ServiceFileIndexer.cpp +1 -5
@@ 10,10 10,6 @@

namespace
{
    inline auto getMusicPath()
    {
        return purefs::createPath(purefs::dir::getUserDiskPath(), "music").string();
    }
    inline constexpr auto fileIndexerServiceStackSize = 1024 * 5;
} // namespace



@@ 35,7 31,7 @@ namespace service
    sys::ReturnCodes ServiceFileIndexer::InitHandler()
    {
        if (mInotifyHandler.init(shared_from_this())) {
            mInotifyHandler.addWatch(getMusicPath());
            mInotifyHandler.addWatch(purefs::dir::getUserAudioPath().c_str());

            // Start the initial indexer
            mStartupIndexer.start(shared_from_this(), service::name::file_indexer);

M module-services/service-fileindexer/include/service-fileindexer/ServiceFileIndexer.hpp => module-services/service-fileindexer/include/service-fileindexer/ServiceFileIndexer.hpp +1 -1
@@ 15,7 15,7 @@ namespace service
    class ServiceFileIndexer final : public sys::Service
    {
      public:
        ServiceFileIndexer(const std::vector<std::string> &paths);
        explicit ServiceFileIndexer(const std::vector<std::string> &paths);
        virtual ~ServiceFileIndexer()                  = default;
        ServiceFileIndexer(const ServiceFileIndexer &) = delete;
        ServiceFileIndexer &operator=(const ServiceFileIndexer &) = delete;

M module-vfs/paths/filesystem_paths.cpp => module-vfs/paths/filesystem_paths.cpp +5 -0
@@ 8,6 8,7 @@ namespace
    constexpr inline auto PATH_SYS          = "/sys";
    constexpr inline auto PATH_CONF         = "/mfgconf";
    constexpr inline auto PATH_USER         = "user";
    constexpr inline auto PATH_DB           = "db";
    constexpr inline auto PATH_OS          = "os";
    constexpr inline auto PATH_CURRENT      = "current";
    constexpr inline auto PATH_PREVIOUS     = "previous";


@@ 46,6 47,10 @@ namespace purefs
        {
            return std::filesystem::path{eMMC_disk} / PATH_USER;
        }
        std::filesystem::path getDatabasesPath() noexcept
        {
            return getUserDiskPath() / PATH_DB;
        }

        std::filesystem::path getCurrentOSPath() noexcept
        {

M module-vfs/paths/include/purefs/filesystem_paths.hpp => module-vfs/paths/include/purefs/filesystem_paths.hpp +1 -0
@@ 14,6 14,7 @@ namespace purefs
        std::filesystem::path getRootDiskPath() noexcept;
        std::filesystem::path getMfgConfPath() noexcept;
        std::filesystem::path getUserDiskPath() noexcept;
        std::filesystem::path getDatabasesPath() noexcept;
        std::filesystem::path getCurrentOSPath() noexcept;
        std::filesystem::path getPreviousOSPath() noexcept;
        std::filesystem::path getUpdatesOSPath() noexcept;

M products/BellHybrid/BellHybridMain.cpp => products/BellHybrid/BellHybridMain.cpp +1 -1
@@ 15,7 15,7 @@
#include <application-bell-powernap/ApplicationBellPowerNap.hpp>

// modules
#include <module-db/Databases/MultimediaFilesDB.hpp>
#include <module-db/databases/MultimediaFilesDB.hpp>
#include <module-db/Interface/MultimediaFilesRecord.hpp>

// services

M products/BellHybrid/CMakeLists.txt => products/BellHybrid/CMakeLists.txt +14 -3
@@ 82,7 82,7 @@ include(BinaryAssetsVersions.cmake)
include(AddVersionJson)

add_directories(
        TARGET user_directories
        TARGET create_user_directories
        PREFIX ${CMAKE_BINARY_DIR}/sysroot/sys/user/audio
        DEPENDS user_directories_common
        DIRECTORIES relaxation


@@ 95,7 95,14 @@ if (${PROJECT_TARGET} STREQUAL "TARGET_RT1051")
        PRODUCT BellHybrid
        SYSROOT sysroot
        LUTS Luts.bin
        DEPENDS user_directories assets updater.bin-target ecoboot.bin-target BellHybrid-boot.bin BellHybrid-version.json-target
        DEPENDS
            create_product_databases
            create_user_directories
            assets
            updater.bin-target
            ecoboot.bin-target
            BellHybrid-boot.bin
            BellHybrid-version.json-target
    )
    add_version_rt1051_json(BellHybrid)
else()


@@ 103,7 110,11 @@ else()
        PRODUCT BellHybrid
        SYSROOT sysroot
        LUTS ""
        DEPENDS user_directories assets BellHybrid-version.json-target
        DEPENDS
            create_product_databases
            create_user_directories
            assets
            BellHybrid-version.json-target
    )
    add_version_linux_json(BellHybrid)
endif()

M products/BellHybrid/apps/application-bell-bedtime/CMakeLists.txt => products/BellHybrid/apps/application-bell-bedtime/CMakeLists.txt +0 -1
@@ 27,7 27,6 @@ target_include_directories(application-bell-bedtime
target_link_libraries(application-bell-bedtime
    PRIVATE
        app
        bellgui
        bell::app-common
        bell::db
        date::date

M products/BellHybrid/apps/application-bell-main/CMakeLists.txt => products/BellHybrid/apps/application-bell-main/CMakeLists.txt +0 -1
@@ 40,7 40,6 @@ target_link_libraries(application-bell-main
    PRIVATE
        app
        apps-common
        bellgui
        i18n
        module-gui
        service-gui

M products/BellHybrid/apps/application-bell-meditation-timer/data/MeditationCommon.hpp => products/BellHybrid/apps/application-bell-meditation-timer/data/MeditationCommon.hpp +1 -1
@@ 19,7 19,7 @@ namespace app::meditation

    inline std::filesystem::path getMeditationAudioPath()
    {
        return paths::audio::proprietary() / "Meditation_Gong.mp3";
        return paths::audio::proprietary() / paths::audio::meditation() / "Meditation_Gong.mp3";
    }

} // namespace app::meditation

M products/BellHybrid/apps/application-bell-settings/CMakeLists.txt => products/BellHybrid/apps/application-bell-settings/CMakeLists.txt +0 -1
@@ 121,7 121,6 @@ target_link_libraries(application-bell-settings
    PRIVATE
        app
        bell::audio
        bellgui
        bell::db
        bell::paths
        bell::app-main

M products/BellHybrid/apps/common/include/common/data/BatteryUtils.hpp => products/BellHybrid/apps/common/include/common/data/BatteryUtils.hpp +1 -1
@@ 3,10 3,10 @@

#pragma once

#include <cstdint>
#include <algorithm>
#include <optional>
#include <string>
#include <array>

#include <Units.hpp>


M products/BellHybrid/services/db/ServiceDB.cpp => products/BellHybrid/services/db/ServiceDB.cpp +4 -4
@@ 5,8 5,8 @@

#include "agents/MeditationStatsAgent.hpp"

#include <module-db/Databases/EventsDB.hpp>
#include <module-db/Databases/MultimediaFilesDB.hpp>
#include <module-db/databases/EventsDB.hpp>
#include <module-db/databases/MultimediaFilesDB.hpp>

#include <module-db/Interface/AlarmEventRecord.hpp>
#include <module-db/Interface/MultimediaFilesRecord.hpp>


@@ 77,9 77,9 @@ sys::ReturnCodes ServiceDB::InitHandler()
    }

    // Create databases
    eventsDB          = std::make_unique<EventsDB>((purefs::dir::getUserDiskPath() / "events.db").c_str());
    eventsDB          = std::make_unique<EventsDB>((purefs::dir::getDatabasesPath() / "events.db").c_str());
    multimediaFilesDB = std::make_unique<db::multimedia_files::MultimediaFilesDB>(
        (purefs::dir::getUserDiskPath() / "multimedia.db").c_str());
        (purefs::dir::getDatabasesPath() / "multimedia.db").c_str());

    // Create record interfaces
    alarmEventRecordInterface = std::make_unique<AlarmEventRecordInterface>(eventsDB.get());

M products/BellHybrid/services/db/agents/MeditationStatsAgent.cpp => products/BellHybrid/services/db/agents/MeditationStatsAgent.cpp +1 -1
@@ 42,7 42,7 @@ namespace service::db::agents

    auto MeditationStats::getDbFilePath() -> const std::string
    {
        return (purefs::dir::getUserDiskPath() / dbName).string();
        return (purefs::dir::getDatabasesPath() / dbName).string();
    }
    auto MeditationStats::getAgentName() -> const std::string
    {

M products/BellHybrid/services/db/databases/MeditationStatisticsTable.cpp => products/BellHybrid/services/db/databases/MeditationStatisticsTable.cpp +8 -7
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "MeditationStatisticsTable.hpp"
#include <Common/Types.hpp>
#include <time.h>
#include <chrono>
#include <date/date.h>


@@ 40,8 41,8 @@ namespace
            return {};
        }

        const auto retQuery = db->query("SELECT * from %s where timestamp BETWEEN "
                                        "datetime('now','-%lu %s') and datetime('now');",
        const auto retQuery = db->query("SELECT * from " str_ " where timestamp BETWEEN "
                                        "datetime('now','-" u32_ " %s') and datetime('now');",
                                        tableName,
                                        x,
                                        modifier.data());


@@ 106,16 107,16 @@ namespace db::meditation_stats

    auto MeditationStatsTable::add(const TableRow entry) -> bool
    {
        return db->execute("INSERT INTO '%s' (timestamp,duration) "
                           "VALUES('%s', '%lu') ",
        return db->execute("INSERT INTO " str_ " (timestamp,duration) "
                           "VALUES(" str_c u32_ ")",
                           tableName,
                           prepare_timestamp(entry.timestamp).c_str(),
                           static_cast<std::uint32_t>(entry.duration.count()));
    }
    auto MeditationStatsTable::getLimitOffset(const uint32_t offset, const uint32_t limit) -> std::vector<TableRow>
    {
        const auto retQuery =
            db->query("SELECT * from '%s' ORDER BY timestamp DESC LIMIT %lu OFFSET %lu;", tableName, limit, offset);
        const auto retQuery = db->query(
            "SELECT * from " str_ " ORDER BY timestamp DESC LIMIT " u32_ " OFFSET " u32_ ";", tableName, limit, offset);

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


@@ 136,7 137,7 @@ namespace db::meditation_stats
    }
    auto MeditationStatsTable::count() -> uint32_t
    {
        const auto queryRet = db->query("SELECT COUNT(*) FROM '%s';", tableName);
        const auto queryRet = db->query("SELECT COUNT(*) FROM" str_ ";", tableName);
        if (queryRet == nullptr || queryRet->getRowCount() == 0) {
            return 0;
        }

R image/user/db/meditation_stats_001.sql => products/BellHybrid/services/db/databases/scripts/meditation_stats_001.sql +0 -0
R image/user/db/settings_bell_001.sql => products/BellHybrid/services/db/databases/scripts/settings_bell_001.sql +0 -0
R image/user/db/settings_bell_002.sql => products/BellHybrid/services/db/databases/scripts/settings_bell_002.sql +0 -0
M products/BellHybrid/services/db/tests/CMakeLists.txt => products/BellHybrid/services/db/tests/CMakeLists.txt +1 -1
@@ 6,5 6,5 @@ add_catch2_executable(

        LIBS
        bell::db::meditation_stats
        USE_FS
        module-db::test::helpers
)
\ No newline at end of file

M products/BellHybrid/services/db/tests/MeditationStatisticsTable_tests.cpp => products/BellHybrid/services/db/tests/MeditationStatisticsTable_tests.cpp +8 -34
@@ 4,6 4,7 @@
#include <catch2/catch.hpp>

#include <MeditationStatisticsDB.hpp>
#include <Helpers.hpp>

#include <filesystem>
#include <iostream>


@@ 22,44 23,17 @@ namespace
        return std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    }

    template <typename Db>
    class TestDatabase
    std::filesystem::path getScriptsPath()
    {
      public:
        explicit TestDatabase(std::filesystem::path name)
        {
            Database::initialize();

            if (std::filesystem::exists(name)) {
                REQUIRE(std::filesystem::remove(name));
            }

            db = std::make_unique<Db>(name.c_str());

            if (not db->isInitialized()) {
                throw std::runtime_error("Could not initialize database");
            }
        }

        ~TestDatabase()
        {
            Database::deinitialize();
        }

        Db &get()
        {
            return *db;
        }

      private:
        std::filesystem::path name;
        std::unique_ptr<Db> db;
    };
        const std::string path = __FILE__;
        const auto dir         = std::filesystem::path{path.substr(0, path.rfind('/'))};
        return dir / "../databases/scripts";
    }
} // namespace

TEST_CASE("Meditation statistics Table - API basic checks")
{
    TestDatabase<MeditationStatisticsDB> db{"meditation_stats.db"};
    db::tests::DatabaseUnderTest<MeditationStatisticsDB> db{"meditation_stats.db", getScriptsPath()};

    const auto timestamp = get_utc_time();
    REQUIRE(db.get().table.add({Record{0}, timestamp, std::chrono::minutes{20}}));


@@ 88,7 62,7 @@ TEST_CASE("Meditation statistics Table - API basic checks")

TEST_CASE("Meditation statistics Table - get by days")
{
    TestDatabase<MeditationStatisticsDB> db{"meditation_stats.db"};
    db::tests::DatabaseUnderTest<MeditationStatisticsDB> db{"meditation_stats.db", getScriptsPath()};

    REQUIRE(db.get().table.add({Record{0}, subtract_time(std::chrono::hours{23}), std::chrono::minutes{1}}));
    REQUIRE(db.get().table.getByDays(1).size() == 1);

M products/PurePhone/CMakeLists.txt => products/PurePhone/CMakeLists.txt +14 -4
@@ 86,7 86,7 @@ target_link_libraries(PurePhone
    )

if (${PROJECT_TARGET} STREQUAL "TARGET_Linux")
    add_dependencies(Pure service_renderer)
    add_dependencies(PurePhone service_renderer)
endif()




@@ 107,7 107,7 @@ include(BinaryAssetsVersions.cmake)
include(AddVersionJson)

add_directories(
        TARGET user_directories
        TARGET create_user_directories
        PREFIX ${CMAKE_BINARY_DIR}/sysroot/sys/user/audio
        DEPENDS user_directories_common
        DIRECTORIES music_player


@@ 120,7 120,13 @@ if (${PROJECT_TARGET} STREQUAL "TARGET_RT1051")
        PRODUCT PurePhone
        SYSROOT sysroot
        LUTS Luts.bin
        DEPENDS user_directories assets updater.bin-target ecoboot.bin-target PurePhone-boot.bin PurePhone-version.json-target
        DEPENDS
            create_product_databases
            create_user_directories
            assets updater.bin-target
            ecoboot.bin-target
            PurePhone-boot.bin
            PurePhone-version.json-target
    )
    add_version_rt1051_json(PurePhone)
else()


@@ 128,7 134,11 @@ else()
        PRODUCT PurePhone
        SYSROOT sysroot
        LUTS ""
        DEPENDS user_directories assets PurePhone-version.json-target
        DEPENDS
            create_product_databases
            create_user_directories
            assets
            PurePhone-version.json-target
    )
    add_version_linux_json(PurePhone)
endif()

M products/PurePhone/PurePhoneMain.cpp => products/PurePhone/PurePhoneMain.cpp +2 -6
@@ 55,12 55,8 @@
#endif

// modules
#include <module-db/Databases/CountryCodesDB.hpp>
#include <module-db/Databases/EventsDB.hpp>
#include <module-db/Databases/NotificationsDB.hpp>
#include <module-db/Databases/MultimediaFilesDB.hpp>
#include <module-db/Interface/AlarmEventRecord.hpp>
#include <module-db/Interface/CountryCodeRecord.hpp>
#include "module-db/databases/NotificationsDB.hpp"
#include <module-db/databases/MultimediaFilesDB.hpp>
#include <module-db/Interface/NotesRecord.hpp>
#include <module-db/Interface/NotificationsRecord.hpp>
#include <module-db/Interface/MultimediaFilesRecord.hpp>

M products/PurePhone/services/db/ServiceDB.cpp => products/PurePhone/services/db/ServiceDB.cpp +12 -19
@@ 3,13 3,11 @@

#include <db/ServiceDB.hpp>

#include <module-db/Databases/CountryCodesDB.hpp>
#include <module-db/Databases/EventsDB.hpp>
#include <module-db/Databases/MultimediaFilesDB.hpp>
#include <module-db/Databases/NotificationsDB.hpp>
#include <module-db/databases/EventsDB.hpp>
#include <module-db/databases/MultimediaFilesDB.hpp>
#include "module-db/databases/NotificationsDB.hpp"
#include <module-db/Interface/AlarmEventRecord.hpp>
#include <module-db/Interface/CalllogRecord.hpp>
#include <module-db/Interface/CountryCodeRecord.hpp>
#include <module-db/Interface/MultimediaFilesRecord.hpp>
#include <module-db/Interface/NotesRecord.hpp>
#include <module-db/Interface/NotificationsRecord.hpp>


@@ 35,7 33,6 @@ ServiceDB::~ServiceDB()
    contactsDB.reset();
    smsDB.reset();
    notesDB.reset();
    countryCodesDB.reset();
    notificationsDB.reset();
    predefinedQuotesDB.reset();
    customQuotesDB.reset();


@@ 62,8 59,6 @@ db::Interface *ServiceDB::getInterface(db::Interface::Name interface)
        return notesRecordInterface.get();
    case db::Interface::Name::Calllog:
        return calllogRecordInterface.get();
    case db::Interface::Name::CountryCodes:
        return countryCodeRecordInterface.get();
    case db::Interface::Name::Notifications:
        return notificationsRecordInterface.get();
    case db::Interface::Name::Quotes:


@@ 254,17 249,16 @@ sys::ReturnCodes ServiceDB::InitHandler()
    }

    // Create databases
    eventsDB        = std::make_unique<EventsDB>((purefs::dir::getUserDiskPath() / "events.db").c_str());
    contactsDB      = std::make_unique<ContactsDB>((purefs::dir::getUserDiskPath() / "contacts.db").c_str());
    smsDB           = std::make_unique<SmsDB>((purefs::dir::getUserDiskPath() / "sms.db").c_str());
    notesDB         = std::make_unique<NotesDB>((purefs::dir::getUserDiskPath() / "notes.db").c_str());
    calllogDB       = std::make_unique<CalllogDB>((purefs::dir::getUserDiskPath() / "calllog.db").c_str());
    countryCodesDB  = std::make_unique<CountryCodesDB>("country-codes.db");
    notificationsDB = std::make_unique<NotificationsDB>((purefs::dir::getUserDiskPath() / "notifications.db").c_str());
    predefinedQuotesDB = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "predefined_quotes.db").c_str());
    customQuotesDB     = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "custom_quotes.db").c_str());
    eventsDB        = std::make_unique<EventsDB>((purefs::dir::getDatabasesPath() / "events.db").c_str());
    contactsDB      = std::make_unique<ContactsDB>((purefs::dir::getDatabasesPath() / "contacts.db").c_str());
    smsDB           = std::make_unique<SmsDB>((purefs::dir::getDatabasesPath() / "sms.db").c_str());
    notesDB         = std::make_unique<NotesDB>((purefs::dir::getDatabasesPath() / "notes.db").c_str());
    calllogDB       = std::make_unique<CalllogDB>((purefs::dir::getDatabasesPath() / "calllog.db").c_str());
    notificationsDB = std::make_unique<NotificationsDB>((purefs::dir::getDatabasesPath() / "notifications.db").c_str());
    predefinedQuotesDB = std::make_unique<Database>((purefs::dir::getDatabasesPath() / "predefined_quotes.db").c_str());
    customQuotesDB     = std::make_unique<Database>((purefs::dir::getDatabasesPath() / "custom_quotes.db").c_str());
    multimediaFilesDB  = std::make_unique<db::multimedia_files::MultimediaFilesDB>(
        (purefs::dir::getUserDiskPath() / "multimedia.db").c_str());
        (purefs::dir::getDatabasesPath() / "multimedia.db").c_str());

    // Create record interfaces
    alarmEventRecordInterface  = std::make_unique<AlarmEventRecordInterface>(eventsDB.get());


@@ 274,7 268,6 @@ sys::ReturnCodes ServiceDB::InitHandler()
    smsTemplateRecordInterface = std::make_unique<SMSTemplateRecordInterface>(smsDB.get());
    notesRecordInterface       = std::make_unique<NotesRecordInterface>(notesDB.get());
    calllogRecordInterface     = std::make_unique<CalllogRecordInterface>(calllogDB.get(), contactsDB.get());
    countryCodeRecordInterface = std::make_unique<CountryCodeRecordInterface>(countryCodesDB.get());
    notificationsRecordInterface =
        std::make_unique<NotificationsRecordInterface>(notificationsDB.get(), contactRecordInterface.get());
    multimediaFilesRecordInterface =

R image/user/db/alarms_001.sql => products/PurePhone/services/db/databases/scripts/alarms_001.sql +0 -0
R image/user/db/alarms_002.sql => products/PurePhone/services/db/databases/scripts/alarms_002.sql +0 -0
R image/user/db/calllog_001.sql => products/PurePhone/services/db/databases/scripts/calllog_001.sql +0 -0
R image/user/db/calllog_002-devel.sql => products/PurePhone/services/db/databases/scripts/calllog_002-devel.sql +0 -0
R image/user/db/contacts_001.sql => products/PurePhone/services/db/databases/scripts/contacts_001.sql +0 -0
R image/user/db/contacts_002.sql => products/PurePhone/services/db/databases/scripts/contacts_002.sql +0 -0
R image/user/db/contacts_003-devel.sql => products/PurePhone/services/db/databases/scripts/contacts_003-devel.sql +0 -0
R image/user/db/custom_quotes_001.sql => products/PurePhone/services/db/databases/scripts/custom_quotes_001.sql +0 -0
R image/user/db/custom_quotes_002-devel.sql => products/PurePhone/services/db/databases/scripts/custom_quotes_002-devel.sql +0 -0
R image/user/db/notes_001.sql => products/PurePhone/services/db/databases/scripts/notes_001.sql +0 -0
R image/user/db/notes_002-devel.sql => products/PurePhone/services/db/databases/scripts/notes_002-devel.sql +0 -0
R image/user/db/notifications_001.sql => products/PurePhone/services/db/databases/scripts/notifications_001.sql +0 -0
R image/user/db/notifications_002.sql => products/PurePhone/services/db/databases/scripts/notifications_002.sql +0 -0
R image/user/db/predefined_quotes_001.sql => products/PurePhone/services/db/databases/scripts/predefined_quotes_001.sql +0 -0
R image/user/db/predefined_quotes_002-devel.sql => products/PurePhone/services/db/databases/scripts/predefined_quotes_002-devel.sql +0 -0
R image/user/db/predefined_quotes_002.sql => products/PurePhone/services/db/databases/scripts/predefined_quotes_002.sql +0 -0
R image/user/db/settings_v2_001.sql => products/PurePhone/services/db/databases/scripts/settings_v2_001.sql +0 -0
R image/user/db/settings_v2_002-devel.sql => products/PurePhone/services/db/databases/scripts/settings_v2_002-devel.sql +1 -1
@@ 8,7 8,7 @@ INSERT OR REPLACE INTO dictionary_tab (path, value) VALUES
    ('system/phone_mode', 'dnd');

-- ----------- insert default values -------------------
INSERT OR IGNORE INTO settings_tab (path, value) VALUES
INSERT OR REPLACE INTO settings_tab (path, value) VALUES
    ('system/phone_mode', 'online'),
    ('gs_time_format', '0'),
    ('gs_date_format', '1'),

R image/user/db/settings_v2_002.sql => products/PurePhone/services/db/databases/scripts/settings_v2_002.sql +0 -0
R image/user/db/sms_001.sql => products/PurePhone/services/db/databases/scripts/sms_001.sql +0 -0
R image/user/db/sms_002.sql => products/PurePhone/services/db/databases/scripts/sms_002.sql +0 -0
R image/user/db/sms_003.sql => products/PurePhone/services/db/databases/scripts/sms_003.sql +0 -0
R image/user/db/sms_004-devel.sql => products/PurePhone/services/db/databases/scripts/sms_004-devel.sql +0 -0
M products/PurePhone/services/db/include/db/ServiceDB.hpp => products/PurePhone/services/db/include/db/ServiceDB.hpp +0 -5
@@ 7,13 7,11 @@
#include <service-db/ServiceDBCommon.hpp>

class AlarmEventRecordInterface;
class AlarmsRecordInterface;
class CalllogDB;
class CalllogRecordInterface;
class ContactRecordInterface;
class ContactsDB;
class CountryCodeRecordInterface;
class CountryCodesDB;
class DatabaseAgent;
class EventsDB;
class NotesDB;


@@ 22,7 20,6 @@ class NotificationsDB;
class NotificationsRecordInterface;
class SMSRecordInterface;
class SMSTemplateRecordInterface;
class SettingsDB;

class SmsDB;
class ThreadRecordInterface;


@@ 51,7 48,6 @@ class ServiceDB : public ServiceDBCommon
    std::unique_ptr<ContactsDB> contactsDB;
    std::unique_ptr<NotesDB> notesDB;
    std::unique_ptr<CalllogDB> calllogDB;
    std::unique_ptr<CountryCodesDB> countryCodesDB;
    std::unique_ptr<NotificationsDB> notificationsDB;
    std::unique_ptr<Database> predefinedQuotesDB;
    std::unique_ptr<Database> customQuotesDB;


@@ 64,7 60,6 @@ class ServiceDB : public ServiceDBCommon
    std::unique_ptr<ContactRecordInterface> contactRecordInterface;
    std::unique_ptr<NotesRecordInterface> notesRecordInterface;
    std::unique_ptr<CalllogRecordInterface> calllogRecordInterface;
    std::unique_ptr<CountryCodeRecordInterface> countryCodeRecordInterface;
    std::unique_ptr<NotificationsRecordInterface> notificationsRecordInterface;
    std::unique_ptr<Quotes::QuotesAgent> quotesRecordInterface;
    std::unique_ptr<db::multimedia_files::MultimediaFilesRecordInterface> multimediaFilesRecordInterface;

M products/PurePhone/test/test-settings/Database.cpp => products/PurePhone/test/test-settings/Database.cpp +1 -3
@@ 123,9 123,7 @@ std::unique_ptr<QueryResult> stubQuery(const std::string &format, const std::str
    return queryResult;
}

Database::Database(const char *name, bool)
    : dbName(name), queryStatementBuffer{nullptr}, isInitialized_(false),
      initializer(std::make_unique<DatabaseInitializer>(this))
Database::Database(const char *name, bool) : dbName(name), queryStatementBuffer{nullptr}, isInitialized_(false)
{
    isInitialized_ = true;
}

M products/PurePhone/test/test-settings/test-service-db-settings-api.cpp => products/PurePhone/test/test-settings/test-service-db-settings-api.cpp +6 -10
@@ 12,23 12,19 @@
#include <evtmgr/EventManager.hpp>
#include <sys/SystemManager.hpp>

#include <service-evtmgr/Constants.hpp>

#include "test-service-db-settings-testmsgs.hpp"
#include "test-service-db-settings-testservices.hpp"
#include "test-service-db-settings-testapps.hpp"
#include "Database.cpp"

#include <module-db/Databases/CalllogDB.hpp>
#include <module-db/Databases/CountryCodesDB.hpp>
#include <module-db/Databases/EventsDB.hpp>
#include <module-db/Databases/MultimediaFilesDB.hpp>
#include <module-db/Databases/NotesDB.hpp>
#include <module-db/Databases/NotificationsDB.hpp>
#include <module-db/Databases/SmsDB.hpp>
#include "module-db/databases/CalllogDB.hpp"
#include <module-db/databases/EventsDB.hpp>
#include <module-db/databases/MultimediaFilesDB.hpp>
#include "module-db/databases/NotesDB.hpp"
#include "module-db/databases/NotificationsDB.hpp"
#include "module-db/databases/SmsDB.hpp"
#include <module-db/Interface/AlarmEventRecord.hpp>
#include <module-db/Interface/CalllogRecord.hpp>
#include <module-db/Interface/CountryCodeRecord.hpp>
#include <module-db/Interface/MultimediaFilesRecord.hpp>
#include <module-db/Interface/NotesRecord.hpp>
#include <module-db/Interface/NotificationsRecord.hpp>

M tools/generate_image.sh => tools/generate_image.sh +1 -1
@@ 96,7 96,7 @@ fi
cd "${SYSROOT}/sys"

#Copy FAT data
CURRENT_DATA="assets country-codes.db ${LUTS}"
CURRENT_DATA="assets ${LUTS}"

mmd -i "$PART1" ::/current
mmd -i "$PART1" ::/current/sys

M tools/generate_update_image.sh => tools/generate_update_image.sh +0 -2
@@ 32,7 32,6 @@ function setVars() {
        "sysroot/sys/current/assets"
        "sysroot/sys/user"
        "sysroot/sys/current/${SOURCE_TARGET}-boot.bin"
        "sysroot/sys/current/country-codes.db"
        "sysroot/sys/current/Luts.bin"
        "version.json"
        "ecoboot.bin"


@@ 75,7 74,6 @@ function linkInStageing(){

    ln -s ../sysroot/sys/user
    ln -s ../sysroot/sys/current/${SOURCE_TARGET}-boot.bin boot.bin
    ln -s ../sysroot/sys/current/country-codes.db
    ln -s ../sysroot/sys/current/Luts.bin
    ln -s ../ecoboot.bin
    ln -s ../updater.bin

A tools/init_databases.py => tools/init_databases.py +95 -0
@@ 0,0 1,95 @@
#!/usr/bin/python3
# Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

# import required module
import os
import sqlite3
import argparse
import logging
import sys

log = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s [%(levelname)s]: %(message)s', level=logging.INFO)


# this helper script creates DBs from SQL schema files

def initialize_database(script_path: os.path, dst_directory: os.path) -> int:
    file_name = os.path.basename(script_path)
    connection = None

    db_name = file_name.split("_0")[0]
    db_name = f"{db_name}.db"
    dst_db_path = os.path.join(dst_directory, db_name)
    log.debug(f"Executing {script_path} script into {dst_db_path} database")

    ret = 0
    try:
        connection = sqlite3.connect(dst_db_path)
        with open(script_path) as fp:
            connection.executescript(fp.read())
        connection.commit()
    except OSError as e:
        log.error(f"System error: {e}")
        ret = 1
    except sqlite3.Error as e:
        log.error(f"[SQLite] {db_name} database error: {e}")
        ret = 1
    finally:
        if connection:
            connection.close()

    return ret


def main() -> int:
    parser = argparse.ArgumentParser(description='Create databases from schema scripts')
    parser.add_argument('--input_path',
                        metavar='schema_path',
                        type=str,
                        help='path to schema scripts',
                        required=True)

    parser.add_argument('--output_path',
                        metavar='db_path',
                        type=str,
                        help='destination path for databases',
                        required=True)

    parser.add_argument('--development',
                        metavar='devel',
                        type=bool,
                        help='with development schema scripts',
                        default=False)

    args = parser.parse_args()

    db_script_files = [
        os.path.join(args.input_path, file)
        for file in os.listdir(args.input_path)
        if os.path.isfile(os.path.join(args.input_path, file)) and ".sql" in file
    ]
    db_script_devel = [file for file in db_script_files if "devel" in file]
    db_script_no_devel = list(set(db_script_files) - set(db_script_devel))

    db_script_devel.sort()
    db_script_no_devel.sort()

    if not os.path.exists(args.output_path):
        os.makedirs(args.output_path, exist_ok=True)

    ret = 0

    for script in db_script_no_devel:
        ret |= initialize_database(script, args.output_path)

    if args.development:
        for script in db_script_devel:
            ret |= initialize_database(script, args.output_path)

    return ret


if __name__ == "__main__":
    sys.exit(main())