A image/user/db/multimedia_001.sql => image/user/db/multimedia_001.sql +21 -0
@@ 0,0 1,21 @@
+-- Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+CREATE TABLE IF NOT EXISTS files
+(
+ _id INTEGER PRIMARY KEY,
+ path TEXT,
+ media_type TEXT,
+ size INTEGER,
+ title TEXT,
+ artist TEXT,
+ album TEXT,
+ comment TEXT,
+ genre TEXT,
+ year INTEGER,
+ track INTEGER,
+ song_length INTEGER,
+ bitrate INTEGER,
+ sample_rate INTEGER,
+ channels INTEGER
+);
M module-db/CMakeLists.txt => module-db/CMakeLists.txt +6 -4
@@ 17,14 17,15 @@ set(SOURCES
Database/sqlite3vfs.cpp
${SQLITE3_SOURCE}
- Databases/ContactsDB.cpp
- Databases/EventsDB.cpp
- Databases/SmsDB.cpp
Databases/AlarmsDB.cpp
- Databases/NotesDB.cpp
Databases/CalllogDB.cpp
+ Databases/ContactsDB.cpp
Databases/CountryCodesDB.cpp
+ Databases/EventsDB.cpp
+ Databases/MultimediaFilesDB.cpp
+ Databases/NotesDB.cpp
Databases/NotificationsDB.cpp
+ Databases/SmsDB.cpp
Tables/AlarmEventsTable.cpp
Tables/Table.cpp
@@ 43,6 44,7 @@ set(SOURCES
Tables/CountryCodesTable.cpp
Tables/SMSTemplateTable.cpp
Tables/NotificationsTable.cpp
+ Tables/MultimediaFilesTable.cpp
Interface/AlarmEventRecord.cpp
Interface/EventRecord.cpp
A module-db/Databases/MultimediaFilesDB.cpp => module-db/Databases/MultimediaFilesDB.cpp +7 -0
@@ 0,0 1,7 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "MultimediaFilesDB.hpp"
+
+MultimediaFilesDB::MultimediaFilesDB(const char *name) : Database(name), files(this)
+{}
A module-db/Databases/MultimediaFilesDB.hpp => module-db/Databases/MultimediaFilesDB.hpp +15 -0
@@ 0,0 1,15 @@
+// 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/MultimediaFilesTable.hpp>
+
+class MultimediaFilesDB : public Database
+{
+ public:
+ explicit MultimediaFilesDB(const char *name);
+
+ MultimediaFilesTable files;
+};
A module-db/Tables/MultimediaFilesTable.cpp => module-db/Tables/MultimediaFilesTable.cpp +188 -0
@@ 0,0 1,188 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "MultimediaFilesTable.hpp"
+
+#include <Interface/AlarmEventRecord.hpp>
+#include <Utils.hpp>
+#include <magic_enum.hpp>
+
+MultimediaFilesTableRow CreateMultimediaFilesTableRow(const QueryResult &result)
+{
+ if (result.getFieldCount() != magic_enum::enum_count<MultimediaFilesTableFields>() + 1) {
+ return MultimediaFilesTableRow{};
+ }
+
+ return MultimediaFilesTableRow{
+ result[0].getUInt32(), // ID
+ result[1].getString(), // path
+ result[2].getString(), // mediaType
+ result[3].getUInt32(), // size
+ {result[4].getString(), // title
+ result[5].getString(), // artist
+ result[6].getString(), // album
+ result[7].getString(), // comment
+ result[8].getString(), // genre
+ result[9].getUInt32(), // year
+ result[10].getUInt32()}, // track
+ {result[11].getUInt32(), // songLength
+ result[12].getUInt32(), // bitrate
+ result[13].getUInt32(), // sample rate
+ result[14].getUInt32()}, // channels
+ };
+}
+
+namespace
+{
+ std::vector<MultimediaFilesTableRow> retQueryUnpack(std::unique_ptr<QueryResult> retQuery)
+ {
+ if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
+ return {};
+ }
+
+ std::vector<MultimediaFilesTableRow> outVector;
+
+ do {
+ outVector.push_back(CreateMultimediaFilesTableRow(*retQuery));
+ } while (retQuery->nextRow());
+ return outVector;
+ }
+} // namespace
+
+auto MultimediaFilesTableRow::isValid() const -> bool
+{
+ return (!path.empty() && Record::isValid());
+}
+
+MultimediaFilesTable::MultimediaFilesTable(Database *db) : Table(db)
+{}
+
+bool MultimediaFilesTable::create()
+{
+ return true;
+}
+
+bool MultimediaFilesTable::add(MultimediaFilesTableRow entry)
+{
+ return db->execute("INSERT or ignore INTO files (path, media_type, size, title, artist, album,"
+ "comment, genre, year, track, song_length, bitrate, sample_rate, channels)"
+ "VALUES('%q', '%q', %lu, '%q', '%q', '%q', '%q', '%q', %lu, %lu, %lu, %lu, %lu, %lu);",
+ entry.path.c_str(),
+ entry.mediaType.c_str(),
+ entry.size,
+ entry.tags.title.c_str(),
+ entry.tags.artist.c_str(),
+ entry.tags.album.c_str(),
+ entry.tags.comment.c_str(),
+ entry.tags.genre.c_str(),
+ entry.tags.year,
+ entry.tags.track,
+ entry.audioProperties.songLength,
+ entry.audioProperties.bitrate,
+ entry.audioProperties.sampleRate,
+ entry.audioProperties.channels);
+}
+
+bool MultimediaFilesTable::removeById(uint32_t id)
+{
+ return db->execute("DELETE FROM files WHERE _id = %lu;", id);
+}
+
+bool MultimediaFilesTable::removeByField(MultimediaFilesTableFields field, const char *str)
+{
+ const auto &fieldName = getFieldName(field);
+
+ if (fieldName.empty()) {
+ return false;
+ }
+
+ return db->execute("DELETE FROM files WHERE %q = '%q';", fieldName.c_str(), str);
+}
+
+bool MultimediaFilesTable::removeAll()
+{
+ return db->execute("DELETE FROM files;");
+}
+
+bool MultimediaFilesTable::update(MultimediaFilesTableRow entry)
+{
+ return db->execute("UPDATE files SET path = '%q', media_type = '%q', size = %lu, title = '%q', artist = '%q',"
+ "album = '%q', comment = '%q', genre = '%q', year = %lu, track = %lu, song_length = %lu,"
+ "bitrate = %lu, sample_rate = %lu, channels = %lu WHERE _id = %lu;",
+ entry.path.c_str(),
+ entry.mediaType.c_str(),
+ entry.size,
+ entry.tags.title.c_str(),
+ entry.tags.artist.c_str(),
+ entry.tags.album.c_str(),
+ entry.tags.comment.c_str(),
+ entry.tags.genre.c_str(),
+ entry.tags.year,
+ entry.tags.track,
+ entry.audioProperties.songLength,
+ entry.audioProperties.bitrate,
+ entry.audioProperties.sampleRate,
+ entry.audioProperties.channels,
+ entry.ID);
+}
+
+MultimediaFilesTableRow MultimediaFilesTable::getById(uint32_t id)
+{
+ auto retQuery = db->query("SELECT * FROM files WHERE _id = %lu;", id);
+
+ if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
+ return MultimediaFilesTableRow();
+ }
+
+ return CreateMultimediaFilesTableRow(*retQuery);
+}
+
+std::vector<MultimediaFilesTableRow> MultimediaFilesTable::getLimitOffset(uint32_t offset, uint32_t limit)
+{
+ auto retQuery = db->query("SELECT * from files LIMIT %lu OFFSET %lu;", limit, offset);
+
+ return retQueryUnpack(std::move(retQuery));
+}
+
+std::vector<MultimediaFilesTableRow> MultimediaFilesTable::getLimitOffsetByField(uint32_t offset,
+ uint32_t limit,
+ MultimediaFilesTableFields field,
+ const char *str)
+{
+ std::unique_ptr<QueryResult> retQuery = nullptr;
+ const auto &fieldName = getFieldName(field);
+
+ if (fieldName.empty()) {
+ return {};
+ }
+
+ retQuery =
+ db->query("SELECT * FROM files WHERE %q = '%q' LIMIT %lu OFFSET %lu;", fieldName.c_str(), str, limit, offset);
+
+ return retQueryUnpack(std::move(retQuery));
+}
+
+uint32_t MultimediaFilesTable::count()
+{
+ auto queryRet = db->query("SELECT COUNT(*) FROM files;");
+ if (!queryRet || queryRet->getRowCount() == 0) {
+ return 0;
+ }
+
+ return (*queryRet)[0].getUInt32();
+}
+
+uint32_t MultimediaFilesTable::countByFieldId(const char *field, uint32_t id)
+{
+ auto queryRet = db->query("SELECT COUNT(*) FROM files WHERE %q=%lu;", field, id);
+ if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
+ return 0;
+ }
+
+ return (*queryRet)[0].getUInt32();
+}
+
+std::string MultimediaFilesTable::getFieldName(MultimediaFilesTableFields field)
+{
+ return utils::enumToString(field);
+}
A module-db/Tables/MultimediaFilesTable.hpp => module-db/Tables/MultimediaFilesTable.hpp +78 -0
@@ 0,0 1,78 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "EventsTable.hpp"
+
+#include <Common/Common.hpp>
+#include <Database/Database.hpp>
+
+#include <string>
+
+struct MultimediaFilesTableRow : public Record
+{
+ std::string path;
+ std::string mediaType; /// mime type e.g. "audio/mp3"
+ std::size_t size{};
+ struct
+ {
+ std::string title;
+ std::string artist;
+ std::string album;
+ std::string comment;
+ std::string genre;
+ unsigned year{};
+ unsigned track{};
+ } tags;
+ struct
+ {
+ unsigned songLength{};
+ unsigned bitrate{};
+ unsigned sampleRate{};
+ unsigned channels{}; /// 1 - mono, 2 - stereo
+ } audioProperties;
+
+ auto isValid() const -> bool;
+};
+
+enum class MultimediaFilesTableFields
+{
+ path,
+ media_type,
+ size,
+ title,
+ artist,
+ album,
+ comment,
+ genre,
+ year,
+ track,
+ song_length,
+ bitrate,
+ sample_rate,
+ channels
+};
+
+class MultimediaFilesTable : public Table<MultimediaFilesTableRow, MultimediaFilesTableFields>
+{
+ public:
+ explicit MultimediaFilesTable(Database *db);
+ virtual ~MultimediaFilesTable() = default;
+
+ auto create() -> bool override;
+ auto add(MultimediaFilesTableRow entry) -> bool override;
+ auto removeById(uint32_t id) -> bool override;
+ auto removeByField(MultimediaFilesTableFields field, const char *str) -> bool override;
+ bool removeAll() override final;
+ auto update(MultimediaFilesTableRow entry) -> bool override;
+ auto getById(uint32_t id) -> MultimediaFilesTableRow override;
+ auto getLimitOffset(uint32_t offset, uint32_t limit) -> std::vector<MultimediaFilesTableRow> override;
+ auto getLimitOffsetByField(uint32_t offset, uint32_t limit, MultimediaFilesTableFields field, const char *str)
+ -> std::vector<MultimediaFilesTableRow> override;
+ auto count() -> uint32_t override;
+ auto countByFieldId(const char *field, uint32_t id) -> uint32_t override;
+
+ private:
+ auto getFieldName(MultimediaFilesTableFields field) -> std::string;
+};
M module-db/tests/CMakeLists.txt => module-db/tests/CMakeLists.txt +1 -0
@@ 16,6 16,7 @@ add_catch2_executable(
ContactsTable_tests.cpp
DbInitializer.cpp
EventRecord_tests.cpp
+ MultimediaFilesTable_tests.cpp
NotesRecord_tests.cpp
NotesTable_tests.cpp
NotificationsRecord_tests.cpp
A module-db/tests/MultimediaFilesTable_tests.cpp => module-db/tests/MultimediaFilesTable_tests.cpp +149 -0
@@ 0,0 1,149 @@
+// 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 <Databases/MultimediaFilesDB.hpp>
+
+constexpr auto artist1 = "Super artist";
+constexpr auto artist2 = "Mega artist";
+constexpr auto artist3 = "Just an artist";
+
+constexpr auto song1 = "Super song";
+constexpr auto song2 = "Mega song";
+constexpr auto song3 = "Just a song";
+
+constexpr auto album1 = "Super album";
+constexpr auto album2 = "Mega album";
+constexpr auto album3 = "Just an album";
+
+const MultimediaFilesTableRow records[] = {
+ {Record{DB_ID_NONE},
+ .path = "user/music",
+ .mediaType = "audio/mp3",
+ .size = 100,
+ .tags = {.title = song1, .artist = artist1, .album = album1, .comment = "", .genre = "", .year = 2011, .track = 1},
+ .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}},
+ {Record{DB_ID_NONE},
+ .path = "user/music",
+ .mediaType = "audio/mp3",
+ .size = 100,
+ .tags = {.title = song2, .artist = artist1, .album = album1, .comment = "", .genre = "", .year = 2011, .track = 1},
+ .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}},
+ {Record{DB_ID_NONE},
+ .path = "user/music",
+ .mediaType = "audio/mp3",
+ .size = 100,
+ .tags = {.title = song2, .artist = artist1, .album = album2, .comment = "", .genre = "", .year = 2011, .track = 1},
+ .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}},
+ {Record{DB_ID_NONE},
+ .path = "user/music",
+ .mediaType = "audio/mp3",
+ .size = 100,
+ .tags = {.title = song2, .artist = artist2, .album = album1, .comment = "", .genre = "", .year = 2011, .track = 1},
+ .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}},
+ {Record{DB_ID_NONE},
+ .path = "user/music",
+ .mediaType = "audio/mp3",
+ .size = 100,
+ .tags = {.title = song3, .artist = artist3, .album = album2, .comment = "", .genre = "", .year = 2011, .track = 1},
+ .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}},
+ {Record{DB_ID_NONE},
+ .path = "user/music",
+ .mediaType = "audio/mp3",
+ .size = 100,
+ .tags = {.title = song3, .artist = artist2, .album = album3, .comment = "", .genre = "", .year = 2011, .track = 1},
+ .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}};
+
+TEST_CASE("Multimedia DB tests")
+{
+ REQUIRE(Database::initialize());
+
+ const auto path = (std::filesystem::path{"sys/user"} / "multimedia.db");
+ if (std::filesystem::exists(path)) {
+ REQUIRE(std::filesystem::remove(path));
+ }
+
+ MultimediaFilesDB db(path.c_str());
+ REQUIRE(db.isInitialized());
+
+ constexpr auto PageSize = 8;
+
+ SECTION("MultimediaFilesTableRow")
+ {
+ auto record = MultimediaFilesTableRow{};
+ REQUIRE(!record.isValid());
+
+ record.ID = 1;
+ record.path = "music";
+
+ REQUIRE(record.isValid());
+ }
+
+ SECTION("Empty")
+ {
+ REQUIRE(db.files.count() == 0);
+ REQUIRE(db.files.getLimitOffset(0, PageSize).empty());
+ }
+
+ SECTION("Add, get and remove")
+ {
+ const auto path = "music/user";
+ MultimediaFilesTableRow record;
+ record.path = path;
+ REQUIRE(!record.isValid());
+ REQUIRE(db.files.add(record));
+
+ REQUIRE(db.files.count() == 1);
+ auto result = db.files.getById(1);
+ REQUIRE(result.isValid());
+
+ SECTION("Remove by ID")
+ {
+ REQUIRE(db.files.removeById(1));
+ REQUIRE(db.files.count() == 0);
+ auto result = db.files.getById(1);
+ REQUIRE(!result.isValid());
+ }
+ SECTION("Remove by field")
+ {
+ REQUIRE(db.files.removeByField(MultimediaFilesTableFields::path, path));
+ REQUIRE(db.files.count() == 0);
+ auto result = db.files.getById(1);
+ REQUIRE(!result.isValid());
+ }
+ }
+
+ for (const auto &record : records) {
+ REQUIRE(db.files.add(record));
+ }
+
+ SECTION("Remove all")
+ {
+ REQUIRE(db.files.count() == 6);
+ REQUIRE(db.files.removeAll());
+ REQUIRE(db.files.count() == 0);
+ }
+
+ SECTION("Update")
+ {
+ auto resultPre = db.files.getById(2);
+ resultPre.mediaType = "bla bla";
+ REQUIRE(db.files.update(resultPre));
+ auto resultPost = db.files.getById(2);
+ REQUIRE((resultPre.ID == resultPost.ID && resultPre.mediaType == resultPost.mediaType));
+ }
+
+ SECTION("getLimitOffset")
+ {
+ REQUIRE(db.files.getLimitOffset(0, 4).size() == 4);
+ REQUIRE(db.files.getLimitOffset(4, 4).size() == 2);
+ }
+
+ SECTION("getLimitOffsetByField")
+ {
+ REQUIRE(db.files.getLimitOffsetByField(0, 4, MultimediaFilesTableFields::artist, artist1).size() == 3);
+ }
+
+ REQUIRE(Database::deinitialize());
+}