refactor file manager db

This commit is contained in:
Scott E. Graves 2024-12-08 10:29:53 -06:00
parent f276356172
commit 7567e3289c
11 changed files with 914 additions and 272 deletions

View File

@ -0,0 +1,87 @@
/*
Copyright <2018-2024> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef REPERTORY_INCLUDE_DB_I_FILE_MGR_DB_HPP_
#define REPERTORY_INCLUDE_DB_I_FILE_MGR_DB_HPP_
#include "types/repertory.hpp"
namespace repertory {
class i_file_mgr_db {
INTERFACE_SETUP(i_file_mgr_db);
public:
struct resume_entry final {
std::string api_path;
std::uint64_t chunk_size{};
boost::dynamic_bitset<> read_state;
std::string source_path;
};
struct upload_active_entry final {
std::string api_path;
std::string source_path;
};
struct upload_entry final {
std::string api_path;
std::uint64_t date_time{};
std::string source_path;
};
public:
[[nodiscard]] virtual auto add_resume(resume_entry entry) -> bool = 0;
[[nodiscard]] virtual auto add_upload(upload_entry entry) -> bool = 0;
[[nodiscard]] virtual auto
add_upload_active(upload_active_entry entry) -> bool = 0;
virtual void clear() = 0;
[[nodiscard]] virtual auto
get_next_upload() const -> std::optional<upload_entry> = 0;
[[nodiscard]] virtual auto
get_resume_list() const -> std::vector<resume_entry> = 0;
[[nodiscard]] virtual auto get_upload(const std::string &api_path) const
-> std::optional<upload_entry> = 0;
[[nodiscard]] virtual auto
get_upload_active_list() const -> std::vector<upload_active_entry> = 0;
[[nodiscard]] virtual auto
remove_resume(const std::string &api_path) -> bool = 0;
[[nodiscard]] virtual auto
remove_upload(const std::string &api_path) -> bool = 0;
[[nodiscard]] virtual auto
remove_upload_active(const std::string &api_path) -> bool = 0;
[[nodiscard]] virtual auto
rename_resume(const std::string &from_api_path,
const std::string &to_api_path) -> bool = 0;
};
} // namespace repertory
#endif // REPERTORY_INCLUDE_DB_I_FILE_MGR_DB_HPP_

View File

@ -0,0 +1,91 @@
/*
Copyright <2018-2024> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef REPERTORY_INCLUDE_DB_RDB_FILE_MGR_DB_HPP_
#define REPERTORY_INCLUDE_DB_RDB_FILE_MGR_DB_HPP_
#include "db/i_file_mgr_db.hpp"
namespace repertory {
class app_config;
class rdb_file_mgr_db final : public i_file_mgr_db {
public:
rdb_file_mgr_db(const app_config &cfg);
~rdb_file_mgr_db() override;
rdb_file_mgr_db(const rdb_file_mgr_db &) = delete;
rdb_file_mgr_db(rdb_file_mgr_db &&) = delete;
auto operator=(const rdb_file_mgr_db &) -> rdb_file_mgr_db & = delete;
auto operator=(rdb_file_mgr_db &&) -> rdb_file_mgr_db & = delete;
private:
const app_config &cfg_;
private:
std::unique_ptr<rocksdb::DB> db_;
std::atomic<std::uint64_t> id_{0U};
rocksdb::ColumnFamilyHandle *resume_family_{};
rocksdb::ColumnFamilyHandle *upload_active_family_{};
rocksdb::ColumnFamilyHandle *upload_family_{};
private:
void create_or_open(bool clear);
public:
[[nodiscard]] auto add_resume(resume_entry entry) -> bool override;
[[nodiscard]] auto add_upload(upload_entry entry) -> bool override;
[[nodiscard]] auto
add_upload_active(upload_active_entry entry) -> bool override;
void clear() override;
[[nodiscard]] auto
get_next_upload() const -> std::optional<upload_entry> override;
[[nodiscard]] auto
get_resume_list() const -> std::vector<resume_entry> override;
[[nodiscard]] auto get_upload(const std::string &api_path) const
-> std::optional<upload_entry> override;
[[nodiscard]] auto
get_upload_active_list() const -> std::vector<upload_active_entry> override;
[[nodiscard]] auto
remove_resume(const std::string &api_path) -> bool override;
[[nodiscard]] auto
remove_upload(const std::string &api_path) -> bool override;
[[nodiscard]] auto
remove_upload_active(const std::string &api_path) -> bool override;
[[nodiscard]] auto
rename_resume(const std::string &from_api_path,
const std::string &to_api_path) -> bool override;
};
} // namespace repertory
#endif // REPERTORY_INCLUDE_DB_RDB_FILE_MGR_DB_HPP_

View File

@ -0,0 +1,82 @@
/*
Copyright <2018-2024> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef REPERTORY_INCLUDE_DB_SQLITE_FILE_MGR_DB_HPP_
#define REPERTORY_INCLUDE_DB_SQLITE_FILE_MGR_DB_HPP_
#include "db/i_file_mgr_db.hpp"
#include "utils/db/sqlite/db_common.hpp"
namespace repertory {
class app_config;
class sqlite_file_mgr_db final : public i_file_mgr_db {
public:
sqlite_file_mgr_db(const app_config &cfg);
~sqlite_file_mgr_db() override;
sqlite_file_mgr_db(const sqlite_file_mgr_db &) = delete;
sqlite_file_mgr_db(sqlite_file_mgr_db &&) = delete;
auto operator=(const sqlite_file_mgr_db &) -> sqlite_file_mgr_db & = delete;
auto operator=(sqlite_file_mgr_db &&) -> sqlite_file_mgr_db & = delete;
private:
utils::db::sqlite::db3_t db_;
public:
[[nodiscard]] auto add_resume(resume_entry entry) -> bool override;
[[nodiscard]] auto add_upload(upload_entry entry) -> bool override;
[[nodiscard]] auto
add_upload_active(upload_active_entry entry) -> bool override;
void clear() override;
[[nodiscard]] auto
get_next_upload() const -> std::optional<upload_entry> override;
[[nodiscard]] auto
get_resume_list() const -> std::vector<resume_entry> override;
[[nodiscard]] auto get_upload(const std::string &api_path) const
-> std::optional<upload_entry> override;
[[nodiscard]] auto
get_upload_active_list() const -> std::vector<upload_active_entry> override;
[[nodiscard]] auto
remove_resume(const std::string &api_path) -> bool override;
[[nodiscard]] auto
remove_upload(const std::string &api_path) -> bool override;
[[nodiscard]] auto
remove_upload_active(const std::string &api_path) -> bool override;
[[nodiscard]] auto
rename_resume(const std::string &from_api_path,
const std::string &to_api_path) -> bool override;
};
} // namespace repertory
#endif // REPERTORY_INCLUDE_DB_SQLITE_FILE_MGR_DB_HPP_

View File

@ -22,15 +22,14 @@
#ifndef REPERTORY_INCLUDE_FILE_MANAGER_FILE_MANAGER_HPP_
#define REPERTORY_INCLUDE_FILE_MANAGER_FILE_MANAGER_HPP_
#include "db/i_file_mgr_db.hpp"
#include "events/event_system.hpp"
#include "events/events.hpp"
#include "file_manager/i_file_manager.hpp"
#include "file_manager/i_open_file.hpp"
#include "file_manager/i_upload_manager.hpp"
#include "file_manager/upload.hpp"
#include "platform/platform.hpp"
#include "types/repertory.hpp"
#include "utils/db/sqlite/db_common.hpp"
#include "utils/file.hpp"
namespace repertory {
@ -57,7 +56,7 @@ private:
i_provider &provider_;
private:
utils::db::sqlite::db3_t db_;
std::unique_ptr<i_file_mgr_db> mgr_db_;
std::atomic<std::uint64_t> next_handle_{0U};
mutable std::recursive_mutex open_file_mtx_;
std::unordered_map<std::string, std::shared_ptr<i_closeable_open_file>>
@ -132,12 +131,13 @@ public:
[[nodiscard]] auto get_open_handle_count() const -> std::size_t;
[[nodiscard]] auto get_stored_downloads() const -> std::vector<json>;
[[nodiscard]] auto
get_stored_downloads() const -> std::vector<i_file_mgr_db::resume_entry>;
[[nodiscard]] auto has_no_open_file_handles() const -> bool override;
[[nodiscard]] auto is_processing(const std::string &api_path) const
-> bool override;
[[nodiscard]] auto
is_processing(const std::string &api_path) const -> bool override;
#if defined(PROJECT_TESTING)
[[nodiscard]] auto open(std::shared_ptr<i_closeable_open_file> of,
@ -150,13 +150,13 @@ public:
[[nodiscard]] auto remove_file(const std::string &api_path) -> api_error;
[[nodiscard]] auto rename_directory(const std::string &from_api_path,
const std::string &to_api_path)
-> api_error;
[[nodiscard]] auto
rename_directory(const std::string &from_api_path,
const std::string &to_api_path) -> api_error;
[[nodiscard]] auto rename_file(const std::string &from_api_path,
const std::string &to_api_path, bool overwrite)
-> api_error;
const std::string &to_api_path,
bool overwrite) -> api_error;
void start();

View File

@ -0,0 +1,116 @@
/*
Copyright <2018-2024> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "db/rdb_file_mgr_db.hpp"
#include "app_config.hpp"
#include "types/startup_exception.hpp"
#include "utils/config.hpp"
#include "utils/error_utils.hpp"
#include "utils/file.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp"
namespace {
[[nodiscard]] auto
create_rocksdb(const repertory::app_config &cfg, const std::string &name,
const std::vector<rocksdb::ColumnFamilyDescriptor> &families,
std::vector<rocksdb::ColumnFamilyHandle *> &handles,
bool clear) -> std::unique_ptr<rocksdb::DB> {
REPERTORY_USES_FUNCTION_NAME();
auto path = repertory::utils::path::combine(cfg.get_data_directory(), {name});
if (clear &&
not repertory::utils::file::directory{path}.remove_recursively()) {
repertory::utils::error::raise_error(
function_name, "failed to remove file mgr db|" + path);
}
rocksdb::Options options{};
options.create_if_missing = true;
options.create_missing_column_families = true;
options.db_log_dir = cfg.get_log_directory();
options.keep_log_file_num = 10;
rocksdb::DB *ptr{};
auto status = rocksdb::DB::Open(options, path, families, &handles, &ptr);
if (not status.ok()) {
repertory::utils::error::raise_error(function_name, status.ToString());
throw repertory::startup_exception(status.ToString());
}
return std::unique_ptr<rocksdb::DB>(ptr);
}
} // namespace
namespace repertory {
rdb_file_mgr_db::rdb_file_mgr_db(const app_config &cfg) : cfg_(cfg) {
create_or_open(false);
}
rdb_file_mgr_db::~rdb_file_mgr_db() { db_.reset(); }
void rdb_file_mgr_db::create_or_open(bool clear) {
db_.reset();
auto families = std::vector<rocksdb::ColumnFamilyDescriptor>();
families.emplace_back(rocksdb::kDefaultColumnFamilyName,
rocksdb::ColumnFamilyOptions());
families.emplace_back("upload_active", rocksdb::ColumnFamilyOptions());
families.emplace_back("upload", rocksdb::ColumnFamilyOptions());
auto handles = std::vector<rocksdb::ColumnFamilyHandle *>();
db_ = create_rocksdb(cfg_, "mgr", families, handles, clear);
std::size_t idx{};
resume_family_ = handles[idx++];
upload_active_family_ = handles[idx++];
upload_family_ = handles[idx++];
}
auto rdb_file_mgr_db::add_resume(resume_entry entry) -> bool {}
auto rdb_file_mgr_db::add_upload(upload_entry entry) -> bool {}
auto rdb_file_mgr_db::add_upload_active(upload_active_entry entry) -> bool {}
void rdb_file_mgr_db::clear() {}
auto rdb_file_mgr_db::get_next_upload() const -> std::optional<upload_entry> {}
auto rdb_file_mgr_db::get_resume_list() const -> std::vector<resume_entry> {}
auto rdb_file_mgr_db::get_upload(const std::string &api_path) const
-> std::optional<upload_entry> {}
auto rdb_file_mgr_db::get_upload_active_list() const
-> std::vector<upload_active_entry> {}
auto rdb_file_mgr_db::remove_resume(const std::string &api_path) -> bool {}
auto rdb_file_mgr_db::remove_upload(const std::string &api_path) -> bool {}
auto rdb_file_mgr_db::remove_upload_active(const std::string &api_path)
-> bool {}
auto rdb_file_mgr_db::rename_resume(const std::string &from_api_path,
const std::string &to_api_path) -> bool {}
} // namespace repertory

View File

@ -0,0 +1,274 @@
/*
Copyright <2018-2024> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "db/sqlite_file_mgr_db.hpp"
#include "app_config.hpp"
#include "utils/config.hpp"
#include "utils/db/sqlite/db_common.hpp"
#include "utils/db/sqlite/db_delete.hpp"
#include "utils/db/sqlite/db_insert.hpp"
#include "utils/db/sqlite/db_select.hpp"
#include "utils/db/sqlite/db_update.hpp"
#include "utils/error_utils.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp"
namespace {
const std::string resume_table = "resume";
const std::string upload_table = "upload";
const std::string upload_active_table = "upload_active";
const std::map<std::string, std::string> sql_create_tables{
{
{resume_table},
{
"CREATE TABLE IF NOT EXISTS " + resume_table +
"("
"api_path TEXT PRIMARY KEY ASC, "
"chunk_size INTEGER, "
"read_state TEXT, "
"source_path TEXT"
");",
},
},
{
{upload_table},
{
"CREATE TABLE IF NOT EXISTS " + upload_table +
"("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"api_path TEXT UNIQUE, "
"date_time INTEGER, "
"source_path TEXT"
");",
},
},
{
{upload_active_table},
{
"CREATE TABLE IF NOT EXISTS " + upload_active_table +
"("
"api_path TEXT PRIMARY KEY ASC, "
"source_path TEXT"
");",
},
},
};
} // namespace
namespace repertory {
sqlite_file_mgr_db::sqlite_file_mgr_db(const app_config &cfg) {
db_ = utils::db::sqlite::create_db(
utils::path::combine(cfg.get_data_directory(), {"mgr.db"}),
sql_create_tables);
}
sqlite_file_mgr_db::~sqlite_file_mgr_db() { db_.reset(); }
auto sqlite_file_mgr_db::add_resume(resume_entry entry) -> bool {
return utils::db::sqlite::db_insert{*db_, resume_table}
.or_replace()
.column_value("api_path", entry.api_path)
.column_value("chunk_size", static_cast<std::int64_t>(entry.chunk_size))
.column_value("read_state",
utils::string::from_dynamic_bitset(entry.read_state))
.column_value("source_path", entry.source_path)
.go()
.ok();
}
auto sqlite_file_mgr_db::add_upload(upload_entry entry) -> bool {
return utils::db::sqlite::db_insert{*db_, upload_table}
.or_replace()
.column_value("api_path", entry.api_path)
.column_value("date_time", static_cast<std::int64_t>(entry.date_time))
.column_value("source_path", entry.source_path)
.go()
.ok();
}
auto sqlite_file_mgr_db::add_upload_active(upload_active_entry entry) -> bool {
return utils::db::sqlite::db_insert{*db_, upload_table}
.or_replace()
.column_value("api_path", entry.api_path)
.column_value("source_path", entry.source_path)
.go()
.ok();
}
void sqlite_file_mgr_db::clear() {
REPERTORY_USES_FUNCTION_NAME();
auto result = utils::db::sqlite::db_delete{*db_, resume_table}.go();
if (not result.ok()) {
utils::error::raise_error(function_name,
"failed to clear resume table|" +
std::to_string(result.get_error()));
}
result = utils::db::sqlite::db_delete{*db_, upload_active_table}.go();
if (not result.ok()) {
utils::error::raise_error(function_name,
"failed to clear upload active table|" +
std::to_string(result.get_error()));
}
result = utils::db::sqlite::db_delete{*db_, upload_table}.go();
if (not result.ok()) {
utils::error::raise_error(function_name,
"failed to clear upload table|" +
std::to_string(result.get_error()));
}
}
auto sqlite_file_mgr_db::get_next_upload() const
-> std::optional<upload_entry> {
auto result = utils::db::sqlite::db_select{*db_, upload_table}
.order_by("id", true)
.limit(1)
.go();
std::optional<utils::db::sqlite::db_result::row> row;
if (not result.get_row(row) || not row.has_value()) {
return std::nullopt;
}
return upload_entry{
row->get_column("api_path").get_value<std::string>(),
static_cast<std::uint64_t>(
row->get_column("date_time").get_value<std::int64_t>()),
row->get_column("source_path").get_value<std::string>(),
};
}
auto sqlite_file_mgr_db::get_resume_list() const -> std::vector<resume_entry> {
REPERTORY_USES_FUNCTION_NAME();
std::vector<resume_entry> ret;
auto result = utils::db::sqlite::db_select{*db_, resume_table}.go();
while (result.has_row()) {
try {
std::optional<utils::db::sqlite::db_result::row> row;
if (not result.get_row(row)) {
continue;
}
if (not row.has_value()) {
continue;
}
ret.push_back(resume_entry{
row->get_column("api_path").get_value<std::string>(),
static_cast<std::uint64_t>(
row->get_column("chunk_size").get_value<std::int64_t>()),
utils::string::to_dynamic_bitset(
row->get_column("read_state").get_value<std::string>()),
row->get_column("source_path").get_value<std::string>(),
});
} catch (const std::exception &ex) {
utils::error::raise_error(function_name, ex, "query error");
}
}
return ret;
}
auto sqlite_file_mgr_db::get_upload(const std::string &api_path) const
-> std::optional<upload_entry> {
auto result = utils::db::sqlite::db_select{*db_, upload_table}
.column("source_path")
.where("api_path")
.equals(api_path)
.go();
std::optional<utils::db::sqlite::db_result::row> row;
if (not result.get_row(row) || not row.has_value()) {
return std::nullopt;
}
return upload_entry{
row->get_column("api_path").get_value<std::string>(),
static_cast<std::uint64_t>(
row->get_column("date_time").get_value<std::int64_t>()),
row->get_column("source_path").get_value<std::string>(),
};
}
auto sqlite_file_mgr_db::get_upload_active_list() const
-> std::vector<upload_active_entry> {
REPERTORY_USES_FUNCTION_NAME();
std::vector<upload_active_entry> ret;
auto result = utils::db::sqlite::db_select{*db_, upload_active_table}.go();
while (result.has_row()) {
try {
std::optional<utils::db::sqlite::db_result::row> row;
if (not result.get_row(row)) {
continue;
}
if (not row.has_value()) {
continue;
}
ret.push_back(upload_active_entry{
row->get_column("api_path").get_value<std::string>(),
row->get_column("source_path").get_value<std::string>(),
});
} catch (const std::exception &ex) {
utils::error::raise_error(function_name, ex, "query error");
}
}
return ret;
}
auto sqlite_file_mgr_db::remove_resume(const std::string &api_path) -> bool {
return utils::db::sqlite::db_delete{*db_, resume_table}
.where("api_path")
.equals(api_path)
.go()
.ok();
}
auto sqlite_file_mgr_db::remove_upload(const std::string &api_path) -> bool {
return utils::db::sqlite::db_delete{*db_, upload_table}
.where("api_path")
.equals(api_path)
.go()
.ok();
}
auto sqlite_file_mgr_db::remove_upload_active(const std::string &api_path)
-> bool {
return utils::db::sqlite::db_delete{*db_, upload_active_table}
.where("api_path")
.equals(api_path)
.go()
.ok();
}
auto sqlite_file_mgr_db::rename_resume(const std::string &from_api_path,
const std::string &to_api_path) -> bool {
return utils::db::sqlite::db_update{*db_, resume_table}
.column_value("api_path", to_api_path)
.where("api_path")
.equals(from_api_path)
.go()
.ok();
}
} // namespace repertory

View File

@ -64,7 +64,8 @@ void sqlite_meta_db::clear() {
}
utils::error::raise_error(function_name,
"failed to clear meta db|" + result.get_error());
"failed to clear meta db|" +
std::to_string(result.get_error()));
}
auto sqlite_meta_db::get_api_path(const std::string &source_path,

View File

@ -22,19 +22,16 @@
#include "file_manager/file_manager.hpp"
#include "app_config.hpp"
#include "db/sqlite_file_mgr_db.hpp"
#include "file_manager/events.hpp"
#include "file_manager/open_file.hpp"
#include "file_manager/open_file_base.hpp"
#include "file_manager/ring_buffer_open_file.hpp"
#include "file_manager/upload.hpp"
#include "platform/platform.hpp"
#include "providers/i_provider.hpp"
#include "types/repertory.hpp"
#include "utils/common.hpp"
#include "utils/db/sqlite/db_common.hpp"
#include "utils/db/sqlite/db_delete.hpp"
#include "utils/db/sqlite/db_insert.hpp"
#include "utils/db/sqlite/db_select.hpp"
#include "utils/db/sqlite/db_update.hpp"
#include "utils/encrypting_reader.hpp"
#include "utils/error_utils.hpp"
#include "utils/file.hpp"
@ -42,85 +39,24 @@
#include "utils/polling.hpp"
#include "utils/time.hpp"
namespace {
[[nodiscard]] auto
create_resume_entry(const repertory::i_open_file &file) -> json {
return {
{"chunk_size", file.get_chunk_size()},
{"path", file.get_api_path()},
{"read_state",
repertory::utils::string::from_dynamic_bitset(file.get_read_state())},
{"source", file.get_source_path()},
};
}
void restore_resume_entry(const json &resume_entry, std::string &api_path,
std::size_t &chunk_size,
boost::dynamic_bitset<> &read_state,
std::string &source_path) {
api_path = resume_entry["path"].get<std::string>();
chunk_size = resume_entry["chunk_size"].get<std::size_t>();
read_state = repertory::utils::string::to_dynamic_bitset(
resume_entry["read_state"].get<std::string>());
source_path = resume_entry["source"].get<std::string>();
}
const std::string resume_table = "resume";
const std::string upload_table = "upload";
const std::string upload_active_table = "upload_active";
const std::map<std::string, std::string> sql_create_tables{
{
{resume_table},
{
"CREATE TABLE IF NOT EXISTS " + resume_table +
"("
"api_path TEXT PRIMARY KEY ASC, "
"data TEXT"
");",
},
},
{
{upload_table},
{
"CREATE TABLE IF NOT EXISTS " + upload_table +
"("
"api_path TEXT PRIMARY KEY ASC, "
"date_time INTEGER, "
"source_path TEXT"
");",
},
},
{
{upload_active_table},
{
"CREATE TABLE IF NOT EXISTS " + upload_active_table +
"("
"api_path TEXT PRIMARY KEY ASC, "
"source_path TEXT"
");",
},
},
};
} // namespace
namespace repertory {
file_manager::file_manager(app_config &config, i_provider &provider)
: config_(config), provider_(provider) {
db_ = utils::db::sqlite::create_db(
utils::path::combine(config_.get_data_directory(), {"file_manager.db"}),
sql_create_tables);
mgr_db_ = std::make_unique<sqlite_file_mgr_db>(config_);
if (not provider_.is_read_only()) {
E_SUBSCRIBE_EXACT(file_upload_completed,
[this](const file_upload_completed &completed) {
this->upload_completed(completed);
});
if (provider_.is_read_only()) {
return;
}
E_SUBSCRIBE_EXACT(file_upload_completed,
[this](const file_upload_completed &completed) {
this->upload_completed(completed);
});
}
file_manager::~file_manager() {
stop();
db_.reset();
mgr_db_.reset();
E_CONSUMER_RELEASE();
}
@ -336,32 +272,15 @@ auto file_manager::get_open_handle_count() const -> std::size_t {
});
}
auto file_manager::get_stored_downloads() const -> std::vector<json> {
auto file_manager::get_stored_downloads() const
-> std::vector<i_file_mgr_db::resume_entry> {
REPERTORY_USES_FUNCTION_NAME();
if (provider_.is_read_only()) {
return {};
}
std::vector<json> ret;
auto result = utils::db::sqlite::db_select{*db_, resume_table}.go();
while (result.has_row()) {
try {
std::optional<utils::db::sqlite::db_result::row> row;
if (not result.get_row(row)) {
continue;
}
if (not row.has_value()) {
continue;
}
ret.push_back(row.value().get_column("data").get_value_as_json());
} catch (const std::exception &ex) {
utils::error::raise_error(function_name, ex, "query error");
}
}
return ret;
return mgr_db_->get_resume_list();
}
auto file_manager::handle_file_rename(const std::string &from_api_path,
@ -379,15 +298,10 @@ auto file_manager::handle_file_rename(const std::string &from_api_path,
source_path = upload_lookup_.at(from_api_path)->get_source_path();
}
} else {
auto result = utils::db::sqlite::db_select{*db_, upload_table}
.column("source_path")
.where("api_path")
.equals(from_api_path)
.go();
std::optional<utils::db::sqlite::db_result::row> row;
should_upload = result.get_row(row) && row.has_value();
auto upload = mgr_db_->get_upload(from_api_path);
should_upload = upload.has_value();
if (should_upload && source_path.empty()) {
source_path = row->get_column("source_path").get_value<std::string>();
source_path = upload->source_path;
}
}
@ -427,8 +341,8 @@ auto file_manager::is_processing(const std::string &api_path) const -> bool {
}
upload_lock.unlock();
utils::db::sqlite::db_select query{*db_, upload_table};
if (query.where("api_path").equals(api_path).go().has_row()) {
auto upload = mgr_db_->get_upload(api_path);
if (upload.has_value()) {
return true;
};
@ -510,21 +424,16 @@ void file_manager::queue_upload(const std::string &api_path,
remove_upload(api_path, true);
auto result =
utils::db::sqlite::db_insert{*db_, upload_table}
.or_replace()
.column_value("api_path", api_path)
.column_value("date_time",
static_cast<std::int64_t>(utils::time::get_time_now()))
.column_value("source_path", source_path)
.go();
if (result.ok()) {
if (mgr_db_->add_upload({
api_path,
utils::time::get_time_now(),
source_path,
})) {
remove_resume(api_path, source_path);
event_system::instance().raise<file_upload_queued>(api_path, source_path);
} else {
event_system::instance().raise<file_upload_failed>(
api_path, source_path,
std::to_string(result.get_error()) + '|' + result.get_error_str());
api_path, source_path, "failed to queue upload");
}
if (not no_lock) {
@ -561,14 +470,12 @@ auto file_manager::remove_file(const std::string &api_path) -> api_error {
void file_manager::remove_resume(const std::string &api_path,
const std::string &source_path) {
auto result = utils::db::sqlite::db_delete{*db_, resume_table}
.where("api_path")
.equals(api_path)
.go();
if (result.ok()) {
event_system::instance().raise<download_resume_removed>(api_path,
source_path);
if (not mgr_db_->remove_resume(api_path)) {
return;
}
event_system::instance().raise<download_resume_removed>(api_path,
source_path);
}
void file_manager::remove_upload(const std::string &api_path) {
@ -587,21 +494,13 @@ void file_manager::remove_upload(const std::string &api_path, bool no_lock) {
lock = std::make_unique<mutex_lock>(upload_mtx_);
}
auto result = utils::db::sqlite::db_delete{*db_, upload_table}
.where("api_path")
.equals(api_path)
.go();
if (not result.ok()) {
utils::error::raise_api_path_error(function_name, api_path,
api_error::error,
"failed to remove from upload table");
if (not mgr_db_->remove_upload(api_path)) {
utils::error::raise_api_path_error(
function_name, api_path, api_error::error, "failed to remove upload");
}
result = utils::db::sqlite::db_delete{*db_, upload_active_table}
.where("api_path")
.equals(api_path)
.go();
if (not result.ok()) {
auto removed = not mgr_db_->remove_upload_active(api_path);
if (not removed) {
utils::error::raise_api_path_error(
function_name, api_path, api_error::error,
"failed to remove from upload_active table");
@ -612,7 +511,7 @@ void file_manager::remove_upload(const std::string &api_path, bool no_lock) {
upload_lookup_.erase(api_path);
}
if (result.ok()) {
if (removed) {
event_system::instance().raise<file_upload_removed>(api_path);
}
@ -779,15 +678,6 @@ void file_manager::start() {
stop_requested_ = false;
polling::instance().set_callback({
"db_cleanup",
polling::frequency::medium,
[this](auto && /* stop_requested */) {
mutex_lock lock(upload_mtx_);
sqlite3_db_release_memory(db_.get());
},
});
polling::instance().set_callback({
"timed_out_close",
polling::frequency::second,
@ -799,67 +689,25 @@ void file_manager::start() {
return;
}
struct active_item final {
std::string api_path;
std::string source_path;
};
for (const auto &entry : mgr_db_->get_upload_active_list()) {
queue_upload(entry.api_path, entry.source_path, false);
}
std::vector<active_item> active_items{};
auto result = utils::db::sqlite::db_select{*db_, upload_active_table}.go();
while (result.has_row()) {
for (const auto &entry : mgr_db_->get_resume_list()) {
try {
std::optional<utils::db::sqlite::db_result::row> row;
if (result.get_row(row) && row.has_value()) {
active_items.emplace_back(active_item{
row->get_column("api_path").get_value<std::string>(),
row->get_column("source_path").get_value<std::string>(),
});
}
} catch (const std::exception &ex) {
utils::error::raise_error(function_name, ex, "query error");
}
}
for (auto &&active_item : active_items) {
queue_upload(active_item.api_path, active_item.source_path, false);
}
active_items.clear();
result = utils::db::sqlite::db_select{*db_, resume_table}.go();
if (not result.ok()) {
return;
}
while (result.has_row()) {
try {
std::optional<utils::db::sqlite::db_result::row> row;
if (not(result.get_row(row) && row.has_value())) {
return;
}
auto resume_entry = row.value().get_column("data").get_value_as_json();
std::string api_path;
std::string source_path;
std::size_t chunk_size{};
boost::dynamic_bitset<> read_state;
restore_resume_entry(resume_entry, api_path, chunk_size, read_state,
source_path);
filesystem_item fsi{};
auto res = provider_.get_filesystem_item(api_path, false, fsi);
auto res = provider_.get_filesystem_item(entry.api_path, false, fsi);
if (res != api_error::success) {
event_system::instance().raise<download_restore_failed>(
api_path, source_path,
entry.api_path, entry.source_path,
"failed to get filesystem item|" + api_error_to_string(res));
continue;
}
if (source_path != fsi.source_path) {
if (entry.source_path != fsi.source_path) {
event_system::instance().raise<download_restore_failed>(
fsi.api_path, fsi.source_path,
"source path mismatch|expected|" + source_path + "|actual|" +
"source path mismatch|expected|" + entry.source_path + "|actual|" +
fsi.source_path);
continue;
}
@ -883,12 +731,12 @@ void file_manager::start() {
}
auto closeable_file = std::make_shared<open_file>(
chunk_size,
entry.chunk_size,
config_.get_enable_chunk_download_timeout()
? config_.get_chunk_downloader_timeout_secs()
: 0U,
fsi, provider_, read_state, *this);
open_file_lookup_[api_path] = closeable_file;
fsi, provider_, entry.read_state, *this);
open_file_lookup_[entry.api_path] = closeable_file;
event_system::instance().raise<download_restored>(fsi.api_path,
fsi.source_path);
} catch (const std::exception &ex) {
@ -948,21 +796,19 @@ void file_manager::store_resume(const i_open_file &file) {
return;
}
auto result = utils::db::sqlite::db_insert{*db_, resume_table}
.or_replace()
.column_value("api_path", file.get_api_path())
.column_value("data", create_resume_entry(file).dump())
.go();
if (result.ok()) {
if (mgr_db_->add_resume({
file.get_api_path(),
file.get_chunk_size(),
file.get_read_state(),
file.get_source_path(),
})) {
event_system::instance().raise<download_resume_added>(
file.get_api_path(), file.get_source_path());
return;
}
event_system::instance().raise<download_resume_add_failed>(
file.get_api_path(), file.get_source_path(),
"failed to insert|" + std::to_string(result.get_error()) + '|' +
result.get_error_str());
file.get_api_path(), file.get_source_path(), "failed to store resume");
}
void file_manager::swap_renamed_items(std::string from_api_path,
@ -981,16 +827,13 @@ void file_manager::swap_renamed_items(std::string from_api_path,
return;
}
auto result = utils::db::sqlite::db_update{*db_, resume_table}
.column_value("api_path", to_api_path)
.where("api_path")
.equals(from_api_path)
.go();
if (not result.ok()) {
utils::error::raise_api_path_error(function_name, to_api_path,
api_error::error,
"failed to update resume table");
if (mgr_db_->rename_resume(from_api_path, to_api_path)) {
return;
}
utils::error::raise_api_path_error(function_name, to_api_path,
api_error::error,
"failed to update resume table");
}
void file_manager::upload_completed(const file_upload_completed &evt) {
@ -1001,11 +844,8 @@ void file_manager::upload_completed(const file_upload_completed &evt) {
if (not utils::string::to_bool(evt.get_cancelled().get<std::string>())) {
auto err = api_error_from_string(evt.get_result().get<std::string>());
if (err == api_error::success) {
auto result = utils::db::sqlite::db_delete{*db_, upload_active_table}
.where("api_path")
.equals(evt.get_api_path().get<std::string>())
.go();
if (not result.ok()) {
if (not mgr_db_->remove_upload_active(
evt.get_api_path().get<std::string>())) {
utils::error::raise_api_path_error(
function_name, evt.get_api_path().get<std::string>(),
evt.get_source().get<std::string>(),
@ -1046,25 +886,17 @@ void file_manager::upload_handler() {
}
if (upload_lookup_.size() < config_.get_max_upload_count()) {
auto result = utils::db::sqlite::db_select{*db_, upload_table}
.order_by("api_path", true)
.limit(1)
.go();
try {
std::optional<utils::db::sqlite::db_result::row> row;
if (result.get_row(row) && row.has_value()) {
auto api_path = row->get_column("api_path").get_value<std::string>();
auto source_path =
row->get_column("source_path").get_value<std::string>();
auto entry = mgr_db_->get_next_upload();
if (entry.has_value()) {
filesystem_item fsi{};
auto res = provider_.get_filesystem_item(api_path, false, fsi);
auto res = provider_.get_filesystem_item(entry->api_path, false, fsi);
switch (res) {
case api_error::item_not_found: {
should_wait = false;
event_system::instance().raise<file_upload_not_found>(api_path,
source_path);
remove_upload(api_path, true);
event_system::instance().raise<file_upload_not_found>(
entry->api_path, entry->source_path);
remove_upload(entry->api_path, true);
} break;
case api_error::success: {
@ -1072,28 +904,22 @@ void file_manager::upload_handler() {
upload_lookup_[fsi.api_path] =
std::make_unique<upload>(fsi, provider_);
auto del_res = utils::db::sqlite::db_delete{*db_, upload_table}
.where("api_path")
.equals(api_path)
.go();
if (del_res.ok()) {
auto ins_res =
utils::db::sqlite::db_insert{*db_, upload_active_table}
.column_value("api_path", api_path)
.column_value("source_path", source_path)
.go();
if (not ins_res.ok()) {
if (mgr_db_->remove_upload(entry->api_path)) {
if (not mgr_db_->add_upload_active({
entry->api_path,
entry->source_path,
})) {
utils::error::raise_api_path_error(
function_name, api_path, source_path,
function_name, entry->api_path, entry->source_path,
"failed to add to upload_active table");
}
}
} break;
default: {
event_system::instance().raise<file_upload_retry>(api_path,
source_path, res);
queue_upload(api_path, source_path, true);
event_system::instance().raise<file_upload_retry>(
entry->api_path, entry->source_path, res);
queue_upload(entry->api_path, entry->source_path, true);
} break;
}
}

View File

@ -0,0 +1,72 @@
/*
Copyright <2018-2024> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_FILE_MGR_DB_FIXTURE_HPP
#define REPERTORY_TEST_INCLUDE_FIXTURES_FILE_MGR_DB_FIXTURE_HPP
#include "test_common.hpp"
#include "app_config.hpp"
// #include "db/rdb_meta_db.hpp"
#include "db/sqlite_file_mgr_db.hpp"
#include "events/consumers/console_consumer.hpp"
#include "events/event_system.hpp"
namespace repertory {
template <typename db_t> class file_mgr_db_test : public ::testing::Test {
protected:
static std::unique_ptr<app_config> config;
static console_consumer console_;
static std::unique_ptr<db_t> file_mgr_db;
protected:
static void SetUpTestCase() {
static std::uint64_t idx{};
event_system::instance().start();
auto cfg_directory = utils::path::combine(test::get_test_output_dir(),
{
"file_mgr_db_test",
std::to_string(++idx),
});
config = std::make_unique<app_config>(provider_type::s3, cfg_directory);
file_mgr_db = std::make_unique<db_t>(*config);
}
static void TearDownTestCase() {
file_mgr_db.reset();
config.reset();
event_system::instance().stop();
}
};
using file_mgr_db_types = ::testing::Types<sqlite_file_mgr_db>;
template <typename db_t>
std::unique_ptr<app_config> file_mgr_db_test<db_t>::config;
template <typename db_t> console_consumer file_mgr_db_test<db_t>::console_;
template <typename db_t>
std::unique_ptr<db_t> file_mgr_db_test<db_t>::file_mgr_db;
} // namespace repertory
#endif // REPERTORY_TEST_INCLUDE_FIXTURES_FILE_MGR_DB_FIXTURE_HPP

View File

@ -496,20 +496,17 @@ TEST_F(file_manager_test,
auto stored_downloads = mgr.get_stored_downloads();
EXPECT_EQ(std::size_t(1u), stored_downloads.size());
std::cout << stored_downloads[0u].dump(2) << std::endl;
EXPECT_STREQ("/test_write_partial_download.txt",
stored_downloads[0u]["path"].get<std::string>().c_str());
stored_downloads[0U].api_path.c_str());
EXPECT_EQ(utils::encryption::encrypting_reader::get_data_chunk_size(),
stored_downloads[0u]["chunk_size"].get<std::size_t>());
auto read_state = utils::string::to_dynamic_bitset(
stored_downloads[0u]["read_state"].get<std::string>());
EXPECT_TRUE(read_state[0u]);
for (std::size_t i = 1u; i < read_state.size(); i++) {
stored_downloads[0U].chunk_size);
auto read_state = stored_downloads[0U].read_state;
EXPECT_TRUE(read_state[0U]);
for (std::size_t i = 1U; i < read_state.size(); i++) {
EXPECT_FALSE(read_state[i]);
}
EXPECT_STREQ(source_path.c_str(),
stored_downloads[0u]["source"].get<std::string>().c_str());
EXPECT_STREQ(source_path.c_str(), stored_downloads[0u].source_path.c_str());
mgr.start();

View File

@ -0,0 +1,96 @@
/*
Copyright <2018-2024> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "fixtures/file_mgr_db_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(file_mgr_db_test, file_mgr_db_types);
TYPED_TEST(file_mgr_db_test, can_add_and_remove_resume) {
this->file_mgr_db->clear();
EXPECT_TRUE(this->file_mgr_db->add_resume({
"/test0",
2ULL,
{},
"/src/test0",
}));
auto list = this->file_mgr_db->get_resume_list();
EXPECT_EQ(1U, list.size());
EXPECT_EQ(1U, list.size());
EXPECT_STREQ("/test0", list.at(0U).api_path.c_str());
EXPECT_EQ(2ULL, list.at(0U).chunk_size);
EXPECT_STREQ("/src/test0", list.at(0U).source_path.c_str());
EXPECT_TRUE(this->file_mgr_db->remove_resume("/test0"));
list = this->file_mgr_db->get_resume_list();
EXPECT_TRUE(list.empty());
}
TYPED_TEST(file_mgr_db_test, can_get_resume_list) {
this->file_mgr_db->clear();
for (auto idx = 0U; idx < 5U; ++idx) {
EXPECT_TRUE(this->file_mgr_db->add_resume({
"/test1_" + std::to_string(idx),
2ULL + idx,
{},
"/src/test1_" + std::to_string(idx),
}));
}
auto list = this->file_mgr_db->get_resume_list();
EXPECT_EQ(5U, list.size());
for (auto idx = 0U; idx < list.size(); ++idx) {
EXPECT_STREQ(("/test1_" + std::to_string(idx)).c_str(),
list.at(idx).api_path.c_str());
EXPECT_EQ(2ULL + idx, list.at(idx).chunk_size);
EXPECT_STREQ(("/src/test1_" + std::to_string(idx)).c_str(),
list.at(idx).source_path.c_str());
}
}
TYPED_TEST(file_mgr_db_test, can_replace_resume) {
this->file_mgr_db->clear();
EXPECT_TRUE(this->file_mgr_db->add_resume({
"/test0",
2ULL,
{},
"/src/test0",
}));
EXPECT_TRUE(this->file_mgr_db->add_resume({
"/test0",
3ULL,
{},
"/src/test1",
}));
auto list = this->file_mgr_db->get_resume_list();
EXPECT_EQ(1U, list.size());
EXPECT_STREQ("/test0", list.at(0U).api_path.c_str());
EXPECT_EQ(3ULL, list.at(0U).chunk_size);
EXPECT_STREQ("/src/test1", list.at(0U).source_path.c_str());
EXPECT_TRUE(this->file_mgr_db->remove_resume("/test0"));
}
} // namespace repertory