/* * Copyright 2023 jacqueline * * SPDX-License-Identifier: GPL-3.0-only */ #pragma once #include #include #include #include #include #include #include #include #include #include #include "collation.hpp" #include "cppbor.h" #include "database/index.hpp" #include "database/records.hpp" #include "database/tag_parser.hpp" #include "database/track.hpp" #include "database/track_finder.hpp" #include "ff.h" #include "leveldb/cache.h" #include "leveldb/db.h" #include "leveldb/iterator.h" #include "leveldb/options.h" #include "leveldb/slice.h" #include "leveldb/write_batch.h" #include "memory_resource.hpp" #include "result.hpp" #include "tasks.hpp" namespace database { const uint8_t kCurrentDbVersion = 8; struct SearchKey; class Record; class Iterator; /* * Handle to an open database. This can be used to store large amounts of * persistent data on the SD card, in a manner that can be retrieved later very * quickly. * * A database includes a number of 'indexes'. Each index is a sorted, * hierarchical view of all the playable tracks on the device. */ class Database { public: enum DatabaseError { ALREADY_OPEN, FAILED_TO_OPEN, }; static auto Open(ITagParser& tag_parser, locale::ICollator& collator, tasks::WorkerPool& bg_worker) -> cpp::result; static auto Destroy() -> void; ~Database(); auto schemaVersion() -> std::string; auto sizeOnDiskBytes() -> size_t; /* Adds an arbitrary record to the database. */ auto put(const std::string& key, const std::string& val) -> void; /* Retrives a value previously stored with `put`. */ auto get(const std::string& key) -> std::optional; auto getTrackPath(TrackId id) -> std::optional; auto getTrack(TrackId id) -> std::shared_ptr; auto getTrackID(std::string path) -> std::optional; auto setTrackData(TrackId id, const TrackData& data) -> void; auto getIndexes() -> std::vector; auto updateIndexes() -> void; auto isUpdating() -> bool; // Cannot be copied or moved. Database(const Database&) = delete; Database& operator=(const Database&) = delete; private: friend class Iterator; // Owned. Dumb pointers because destruction needs to be done in an explicit // order. leveldb::DB* db_; leveldb::Cache* cache_; TrackFinder track_finder_; // Not owned. ITagParser& tag_parser_; locale::ICollator& collator_; /* Internal utility for tracking a currently in-progress index update. */ class UpdateTracker { public: UpdateTracker(); ~UpdateTracker(); auto onTrackVerified() -> void; auto onVerificationFinished() -> void; auto onTrackAdded() -> void; private: uint32_t num_old_tracks_; uint32_t num_new_tracks_; uint64_t start_time_; uint64_t verification_finish_time_; }; std::atomic is_updating_; std::unique_ptr update_tracker_; std::atomic next_track_id_; Database(leveldb::DB* db, leveldb::Cache* cache, tasks::WorkerPool& pool, ITagParser& tag_parser, locale::ICollator& collator); auto processCandidateCallback(FILINFO&, std::string_view) -> void; auto indexingCompleteCallback() -> void; auto dbCalculateNextTrackId() -> void; auto dbMintNewTrackId() -> TrackId; auto dbGetTrackData(leveldb::ReadOptions, TrackId id) -> std::shared_ptr; auto dbCreateIndexesForTrack(const Track&, leveldb::WriteBatch&) -> void; auto dbCreateIndexesForTrack(const TrackData&, const TrackTags&, leveldb::WriteBatch&) -> void; auto dbRemoveIndexes(std::shared_ptr) -> void; auto dbIngestTagHashes(const TrackTags&, std::pmr::unordered_map&, leveldb::WriteBatch&) -> void; auto dbRecoverTagsFromHashes(const std::pmr::unordered_map&) -> std::shared_ptr; auto getRecord(const SearchKey& c) -> std::optional>; auto countRecords(const SearchKey& c) -> size_t; }; class Handle { public: Handle(std::shared_ptr& db); auto lock() -> std::shared_ptr; private: std::shared_ptr& db_; }; /* * Container for the data needed to iterate through database records. This is a * lower-level type that the higher-level iterators are built from; most users * outside this namespace shouldn't need to work with continuations. */ struct SearchKey { std::pmr::string prefix; /* If not given, then iteration starts from `prefix`. */ std::optional key; int offset; auto startKey() const -> std::string_view; }; /* * A record belonging to one of the database's indexes. This may either be a * leaf record, containing a track id, or a branch record, containing a new * Header to retrieve results at the next level of the index. */ class Record { public: Record(const IndexKey&, const leveldb::Slice&); Record(const Record&) = default; Record& operator=(const Record& other) = default; auto text() const -> std::string_view; auto contents() const -> const std::variant&; private: std::pmr::string text_; std::variant contents_; }; /* * Utility for accessing a large set of database records, one record at a time. */ class Iterator { public: Iterator(std::shared_ptr, IndexId); Iterator(std::shared_ptr, const IndexKey::Header&); Iterator(const Iterator&) = default; Iterator& operator=(const Iterator& other) = default; auto value() const -> const std::optional&; std::optional operator*() const { return value(); } auto next() -> void; std::optional operator++() { next(); return value(); } std::optional operator++(int) { auto val = value(); next(); return val; } auto prev() -> void; std::optional operator--() { prev(); return value(); } std::optional operator--(int) { auto val = value(); prev(); return val; } auto count() const -> size_t; private: auto iterate(const SearchKey& key) -> void; friend class TrackIterator; std::weak_ptr db_; SearchKey key_; std::optional current_; }; class TrackIterator { public: TrackIterator(const Iterator&); TrackIterator(const TrackIterator&) = default; TrackIterator& operator=(TrackIterator&& other) = default; auto value() const -> std::optional; std::optional operator*() const { return value(); } auto next() -> void; std::optional operator++() { next(); return value(); } std::optional operator++(int) { auto val = value(); next(); return val; } auto count() const -> size_t; private: TrackIterator(std::weak_ptr); auto next(bool advance) -> void; std::weak_ptr db_; std::vector levels_; }; } // namespace database