Compare commits
18 Commits
5bd780ef07
...
3dc16db278
Author | SHA1 | Date | |
---|---|---|---|
3dc16db278 | |||
e81227e5f7 | |||
fd83c263e1 | |||
9ca857c98e | |||
afcba6b086 | |||
ef50acc867 | |||
f12833f36c | |||
b26788819e | |||
f905de7c42 | |||
8466a8850f | |||
af8e2cddcb | |||
0089866142 | |||
ca892c7f11 | |||
2a33000ace | |||
bd8da9b987 | |||
ca4111ac77 | |||
868e8ae124 | |||
51358c7110 |
@ -1,4 +1,5 @@
|
||||
_lseeki64
|
||||
_mkgmtime
|
||||
_sh_denyno
|
||||
_sh_denyrd
|
||||
_sh_denyrw
|
||||
@ -136,6 +137,7 @@ linkflags
|
||||
mbig
|
||||
msvc
|
||||
msvcr120
|
||||
msys2
|
||||
mtune
|
||||
nana
|
||||
ncrypt
|
||||
@ -148,6 +150,7 @@ oleaut32
|
||||
openal_version
|
||||
openssldir
|
||||
pkgconfig
|
||||
plex
|
||||
project_enable_fontconfig
|
||||
project_enable_gtkmm
|
||||
project_enable_libdsm
|
||||
@ -160,6 +163,7 @@ puint32
|
||||
pvoid
|
||||
pwstr
|
||||
remote_winfsp
|
||||
renterd
|
||||
richtext
|
||||
rocksdb_library
|
||||
rpcrt4
|
||||
@ -211,4 +215,4 @@ wsign-conversion
|
||||
wunused
|
||||
wuseless
|
||||
wxwidgets_version
|
||||
xattr
|
||||
xattr
|
27
README.md
27
README.md
@ -1,6 +1,6 @@
|
||||
# Repertory
|
||||
|
||||
Repertory allows you to mount AWS S3 and Sia via FUSE on Linux~~/OS X~~ or via WinFSP
|
||||
Repertory allows you to mount AWS S3 and Sia via FUSE on Linux ~~/OS X~~ or via WinFSP
|
||||
on Windows.
|
||||
|
||||
## Details and Features
|
||||
@ -8,9 +8,9 @@ on Windows.
|
||||
* Optimized for [Plex Media Server](https://www.plex.tv/)
|
||||
* Single application to mount AWS S3 and/or Sia
|
||||
* Only 1 Sia mount and 1 S3 mount (per bucket) per user is supported.
|
||||
* Remote mounting of Repertory instances on Linux~~, OS X~~ and Windows
|
||||
* Remote mounting of Repertory instances on Linux ~~, OS X~~ and Windows
|
||||
* Securely share your mounts over TCP/IP (`XChaCha20-Poly1305` stream cipher)
|
||||
* Cross-platform support (Linux 64-bit, Linux arm64/aarch64,~~OS X,~~ Windows 64-bit)
|
||||
* Cross-platform support (Linux 64-bit, Linux arm64/aarch64, ~~OS X,~~ Windows 64-bit)
|
||||
|
||||
## Minimum Requirements
|
||||
|
||||
@ -29,6 +29,27 @@ on Windows.
|
||||
* ~~OS X Mojave and above~~
|
||||
* Windows 64-bit 10, 11
|
||||
|
||||
## Compiling
|
||||
|
||||
* Successful compilation will result in all required files being placed in the `dist/` directory
|
||||
* Linux
|
||||
* Ensure `docker` is installed
|
||||
* For x86_64:
|
||||
* Release: `scripts/make_unix.sh x86_64`
|
||||
* Debug: `scripts/make_unix.sh x86_64 debug`
|
||||
* For aarch64:
|
||||
* Release: `scripts/make_unix.sh aarch64`
|
||||
* Debug: `scripts/make_unix.sh aarch64 debug`
|
||||
* Windows
|
||||
* RECOMMENDED: Cross-compiling on Linux
|
||||
* Ensure `docker` is installed
|
||||
* Release: `scripts/make_win32.sh`
|
||||
* Debug: `scripts/make_win32.sh debug`
|
||||
* Compiling on Windows
|
||||
* Ensure latest [MSYS2](https://www.msys2.org/) is installed
|
||||
* Release: `scripts/make_win32.cmd`
|
||||
* Debug: `scripts/make_win32.cmd debug`
|
||||
|
||||
## Credits
|
||||
|
||||
* [boost c++ libraries](https://www.boost.org/)
|
||||
|
@ -72,27 +72,22 @@ E_SIMPLE3(download_restore_failed, error, true,
|
||||
std::string, error, err, E_STRING
|
||||
);
|
||||
|
||||
E_SIMPLE2(download_resumed, info, true,
|
||||
std::string, api_path, ap, E_STRING,
|
||||
std::string, dest_path, dest, E_STRING
|
||||
);
|
||||
|
||||
E_SIMPLE2(download_stored, info, true,
|
||||
std::string, api_path, ap, E_STRING,
|
||||
std::string, dest_path, dest, E_STRING
|
||||
);
|
||||
|
||||
E_SIMPLE2(download_stored_removed, info, true,
|
||||
std::string, api_path, ap, E_STRING,
|
||||
std::string, dest_path, dest, E_STRING
|
||||
);
|
||||
|
||||
E_SIMPLE3(download_stored_failed, error, true,
|
||||
E_SIMPLE3(download_resume_add_failed, error, true,
|
||||
std::string, api_path, ap, E_STRING,
|
||||
std::string, dest_path, dest, E_STRING,
|
||||
std::string, error, err, E_STRING
|
||||
);
|
||||
|
||||
E_SIMPLE2(download_resume_added, info, true,
|
||||
std::string, api_path, ap, E_STRING,
|
||||
std::string, dest_path, dest, E_STRING
|
||||
);
|
||||
|
||||
E_SIMPLE2(download_resume_removed, info, true,
|
||||
std::string, api_path, ap, E_STRING,
|
||||
std::string, dest_path, dest, E_STRING
|
||||
);
|
||||
|
||||
E_SIMPLE1(item_timeout, debug, true,
|
||||
std::string, api_path, ap, E_STRING
|
||||
);
|
||||
|
@ -380,21 +380,29 @@ auto file_manager::get_stored_downloads() const -> std::vector<json> {
|
||||
auto file_manager::handle_file_rename(const std::string &from_api_path,
|
||||
const std::string &to_api_path)
|
||||
-> api_error {
|
||||
auto should_upload{false};
|
||||
std::string source_path{};
|
||||
bool should_upload{upload_lookup_.contains(from_api_path)};
|
||||
if (should_upload) {
|
||||
source_path = upload_lookup_.at(from_api_path)->get_source_path();
|
||||
remove_upload(from_api_path);
|
||||
} else {
|
||||
auto result = db::db_select{*db_.get(), upload_table}
|
||||
.column("source_path")
|
||||
.where("api_path")
|
||||
.equals(from_api_path)
|
||||
.go();
|
||||
std::optional<db::db_select::row> row;
|
||||
should_upload = result.get_row(row) && row.has_value();
|
||||
auto file_iter = open_file_lookup_.find(from_api_path);
|
||||
if (file_iter != open_file_lookup_.end()) {
|
||||
should_upload = file_iter->second->is_modified();
|
||||
source_path = file_iter->second->get_source_path();
|
||||
}
|
||||
|
||||
if (not should_upload) {
|
||||
should_upload = upload_lookup_.contains(from_api_path);
|
||||
if (should_upload) {
|
||||
source_path = row->get_column("source_path").get_value<std::string>();
|
||||
source_path = upload_lookup_.at(from_api_path)->get_source_path();
|
||||
} else {
|
||||
auto result = db::db_select{*db_.get(), upload_table}
|
||||
.column("source_path")
|
||||
.where("api_path")
|
||||
.equals(from_api_path)
|
||||
.go();
|
||||
std::optional<db::db_select::row> row;
|
||||
should_upload = result.get_row(row) && row.has_value();
|
||||
if (should_upload) {
|
||||
source_path = row->get_column("source_path").get_value<std::string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -558,7 +566,6 @@ auto file_manager::remove_file(const std::string &api_path) -> api_error {
|
||||
utils::error::raise_api_path_error(
|
||||
function_name, fsi.api_path, fsi.source_path,
|
||||
utils::get_last_error_code(), "failed to delete source");
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
@ -572,7 +579,7 @@ void file_manager::remove_resume(const std::string &api_path,
|
||||
.equals(api_path)
|
||||
.go();
|
||||
if (result.ok()) {
|
||||
event_system::instance().raise<download_stored_removed>(api_path,
|
||||
event_system::instance().raise<download_resume_removed>(api_path,
|
||||
source_path);
|
||||
}
|
||||
}
|
||||
@ -757,11 +764,6 @@ auto file_manager::rename_file(const std::string &from_api_path,
|
||||
return api_error::item_exists;
|
||||
}
|
||||
|
||||
// Don't rename if destination file has open handles
|
||||
if (get_open_file_count(to_api_path) != 0U) {
|
||||
return api_error::file_in_use;
|
||||
}
|
||||
|
||||
// Check destination parent directory exists
|
||||
res = provider_.is_directory(utils::path::get_parent_api_path(to_api_path),
|
||||
exists);
|
||||
@ -800,7 +802,7 @@ void file_manager::start() {
|
||||
if (not upload_thread_) {
|
||||
stop_requested_ = false;
|
||||
|
||||
struct active_item {
|
||||
struct active_item final {
|
||||
std::string api_path;
|
||||
std::string source_path;
|
||||
};
|
||||
@ -850,44 +852,48 @@ void file_manager::start() {
|
||||
|
||||
filesystem_item fsi{};
|
||||
auto res = provider_.get_filesystem_item(api_path, false, fsi);
|
||||
if (res == api_error::success) {
|
||||
if (source_path == fsi.source_path) {
|
||||
auto opt_size = utils::file::file{fsi.source_path}.size();
|
||||
if (opt_size.has_value()) {
|
||||
auto file_size{opt_size.value()};
|
||||
if (file_size == fsi.size) {
|
||||
auto closeable_file = std::make_shared<open_file>(
|
||||
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;
|
||||
event_system::instance().raise<download_restored>(
|
||||
fsi.api_path, fsi.source_path);
|
||||
} else {
|
||||
event_system::instance().raise<download_restore_failed>(
|
||||
fsi.api_path, fsi.source_path,
|
||||
"file size mismatch|expected|" + std::to_string(fsi.size) +
|
||||
"|actual|" + std::to_string(file_size));
|
||||
}
|
||||
} else {
|
||||
event_system::instance().raise<download_restore_failed>(
|
||||
fsi.api_path, fsi.source_path,
|
||||
"failed to get file size: " +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
} else {
|
||||
event_system::instance().raise<download_restore_failed>(
|
||||
fsi.api_path, fsi.source_path,
|
||||
"source path mismatch|expected|" + source_path + "|actual|" +
|
||||
fsi.source_path);
|
||||
}
|
||||
} else {
|
||||
if (res != api_error::success) {
|
||||
event_system::instance().raise<download_restore_failed>(
|
||||
api_path, source_path,
|
||||
"failed to get filesystem item|" + api_error_to_string(res));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (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|" +
|
||||
fsi.source_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto opt_size = utils::file::file{fsi.source_path}.size();
|
||||
if (not opt_size.has_value()) {
|
||||
event_system::instance().raise<download_restore_failed>(
|
||||
fsi.api_path, fsi.source_path,
|
||||
"failed to get file size: " +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto file_size{opt_size.value()};
|
||||
if (file_size != fsi.size) {
|
||||
event_system::instance().raise<download_restore_failed>(
|
||||
fsi.api_path, fsi.source_path,
|
||||
"file size mismatch|expected|" + std::to_string(fsi.size) +
|
||||
"|actual|" + std::to_string(file_size));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto closeable_file = std::make_shared<open_file>(
|
||||
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;
|
||||
event_system::instance().raise<download_restored>(fsi.api_path,
|
||||
fsi.source_path);
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::raise_error(function_name, ex, "query error");
|
||||
}
|
||||
@ -946,12 +952,12 @@ void file_manager::store_resume(const i_open_file &file) {
|
||||
.column_value("data", create_resume_entry(file).dump())
|
||||
.go();
|
||||
if (result.ok()) {
|
||||
event_system::instance().raise<download_stored>(file.get_api_path(),
|
||||
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_stored_failed>(
|
||||
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());
|
||||
|
@ -63,18 +63,28 @@ auto s3_provider::convert_api_date(std::string_view date) -> std::uint64_t {
|
||||
// 2009-10-12T17:50:30.000Z
|
||||
auto date_parts = utils::string::split(date, '.', true);
|
||||
auto date_time = date_parts.at(0U);
|
||||
auto nanos = utils::string::to_uint64(
|
||||
utils::string::split(date_parts.at(1U), 'Z', true).at(0U));
|
||||
auto nanos =
|
||||
(date_parts.size() <= 1U)
|
||||
? 0U
|
||||
: utils::string::to_uint64(
|
||||
utils::string::split(date_parts.at(1U), 'Z', true).at(0U)) *
|
||||
1000000UL;
|
||||
|
||||
struct tm tm1 {};
|
||||
#if defined(_WIN32)
|
||||
utils::time::strptime(date_time.c_str(), "%Y-%m-%dT%T", &tm1);
|
||||
#else
|
||||
auto utc_time = _mkgmtime(&tm1);
|
||||
localtime_s(&tm1, &utc_time);
|
||||
return nanos + utils::time::windows_time_t_to_unix_time(mktime(&tm1));
|
||||
#else // !defined(_WIN32)
|
||||
strptime(date_time.c_str(), "%Y-%m-%dT%T", &tm1);
|
||||
#endif
|
||||
|
||||
auto utc_time = timegm(&tm1);
|
||||
auto *utc_tm = localtime(&utc_time);
|
||||
if (utc_tm != nullptr) {
|
||||
}
|
||||
return nanos + (static_cast<std::uint64_t>(mktime(&tm1)) *
|
||||
utils::time::NANOS_PER_SECOND);
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
|
||||
auto s3_provider::create_directory_impl(const std::string &api_path,
|
||||
|
@ -409,14 +409,14 @@ TEST(file_manager, download_is_stored_after_write_if_partially_downloaded) {
|
||||
const auto source_path = utils::path::combine(
|
||||
cfg.get_cache_directory(), {utils::create_uuid_string()});
|
||||
|
||||
event_consumer es("download_stored", [&source_path](const event &e) {
|
||||
const auto &ee = dynamic_cast<const download_stored &>(e);
|
||||
event_consumer es("download_resume_added", [&source_path](const event &e) {
|
||||
const auto &ee = dynamic_cast<const download_resume_added &>(e);
|
||||
EXPECT_STREQ("/test_write_partial_download.txt",
|
||||
ee.get_api_path().get<std::string>().c_str());
|
||||
EXPECT_STREQ(source_path.c_str(),
|
||||
ee.get_dest_path().get<std::string>().c_str());
|
||||
});
|
||||
event_capture ec({"download_stored"},
|
||||
event_capture ec({"download_resume_added"},
|
||||
{"file_upload_completed", "file_upload_queued"});
|
||||
|
||||
const auto now = utils::time::get_time_now();
|
||||
@ -499,7 +499,7 @@ TEST(file_manager, download_is_stored_after_write_if_partially_downloaded) {
|
||||
fm.stop();
|
||||
ec.wait_for_empty();
|
||||
|
||||
event_capture ec2({"download_restored", "download_stored"},
|
||||
event_capture ec2({"download_restored", "download_resume_added"},
|
||||
{"file_upload_completed", "file_upload_queued"});
|
||||
EXPECT_EQ(std::size_t(0u), fm.get_open_file_count());
|
||||
EXPECT_EQ(std::size_t(0u), fm.get_open_handle_count());
|
||||
@ -1633,7 +1633,7 @@ TEST(file_manager, can_queue_and_remove_upload) {
|
||||
event_system::instance().start();
|
||||
|
||||
{
|
||||
event_capture ec({"file_upload_queued", "download_stored_removed"});
|
||||
event_capture ec({"file_upload_queued", "download_resume_removed"});
|
||||
|
||||
app_config cfg(provider_type::sia, file_manager_dir);
|
||||
cfg.set_enable_chunk_downloader_timeout(false);
|
||||
@ -1661,60 +1661,6 @@ TEST(file_manager, can_queue_and_remove_upload) {
|
||||
EXPECT_TRUE(utils::file::directory(file_manager_dir).remove_recursively());
|
||||
}
|
||||
|
||||
TEST(file_manager, remove_file_fails_if_open_file_is_modified) {
|
||||
EXPECT_TRUE(utils::file::directory(file_manager_dir).remove_recursively());
|
||||
|
||||
{
|
||||
app_config cfg(provider_type::sia, file_manager_dir);
|
||||
cfg.set_enable_chunk_downloader_timeout(false);
|
||||
mock_provider mp;
|
||||
|
||||
EXPECT_CALL(mp, is_direct_only()).WillRepeatedly(Return(false));
|
||||
|
||||
file_manager fm(cfg, mp);
|
||||
|
||||
auto of = std::make_shared<mock_open_file>();
|
||||
EXPECT_CALL(*of, add).WillOnce(Return());
|
||||
EXPECT_CALL(*of, get_api_path).WillRepeatedly(Return("/test_remove.txt"));
|
||||
EXPECT_CALL(*of, get_source_path).WillRepeatedly(Return(""));
|
||||
EXPECT_CALL(*of, is_modified).WillOnce(Return(true));
|
||||
EXPECT_CALL(*of, is_directory).WillOnce(Return(false));
|
||||
|
||||
EXPECT_CALL(mp, get_filesystem_item)
|
||||
.WillOnce([](const std::string &api_path, bool directory,
|
||||
filesystem_item &fsi) -> api_error {
|
||||
EXPECT_STREQ("/test_remove.txt", api_path.c_str());
|
||||
EXPECT_FALSE(directory);
|
||||
fsi.api_path = api_path;
|
||||
fsi.api_parent = utils::path::get_parent_api_path(api_path);
|
||||
fsi.directory = directory;
|
||||
fsi.size = 0U;
|
||||
return api_error::success;
|
||||
});
|
||||
|
||||
EXPECT_CALL(mp, set_item_meta(_, _, _))
|
||||
.WillOnce([](const std::string &api_path, const std::string &key,
|
||||
const std::string &value) -> api_error {
|
||||
EXPECT_STREQ("/test_remove.txt", api_path.c_str());
|
||||
EXPECT_STREQ(META_SOURCE.c_str(), key.c_str());
|
||||
EXPECT_FALSE(value.empty());
|
||||
return api_error::success;
|
||||
});
|
||||
|
||||
std::uint64_t handle{};
|
||||
std::shared_ptr<i_open_file> f{};
|
||||
#if defined(_WIN32)
|
||||
EXPECT_EQ(api_error::success, fm.open(of, {}, handle, f));
|
||||
#else
|
||||
EXPECT_EQ(api_error::success, fm.open(of, O_RDWR, handle, f));
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(api_error::file_in_use, fm.remove_file("/test_remove.txt"));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(utils::file::directory(file_manager_dir).remove_recursively());
|
||||
}
|
||||
|
||||
TEST(file_manager, file_is_closed_after_download_timeout) {
|
||||
{
|
||||
console_consumer c;
|
||||
|
@ -25,27 +25,30 @@
|
||||
#include "utils/file.hpp"
|
||||
|
||||
namespace repertory {
|
||||
#if defined(_WIN32)
|
||||
TEST(utils, convert_api_date) {
|
||||
LARGE_INTEGER li{};
|
||||
li.QuadPart = s3_provider::convert_api_date("2009-10-12T17:50:30.111Z");
|
||||
#if defined(_WIN32)
|
||||
auto file_time = utils::time::unix_time_to_filetime(
|
||||
s3_provider::convert_api_date("2009-10-12T17:50:30.111Z"));
|
||||
|
||||
SYSTEMTIME st{};
|
||||
FileTimeToSystemTime(reinterpret_cast<FILETIME *>(&li), &st);
|
||||
FileTimeToSystemTime(&file_time, &st);
|
||||
|
||||
SYSTEMTIME lt{};
|
||||
SystemTimeToTzSpecificLocalTime(nullptr, &st, <);
|
||||
EXPECT_EQ(2009, st.wYear);
|
||||
EXPECT_EQ(10, st.wMonth);
|
||||
EXPECT_EQ(12, st.wDay);
|
||||
|
||||
EXPECT_EQ(2009, lt.wYear);
|
||||
EXPECT_EQ(10, lt.wMonth);
|
||||
EXPECT_EQ(12, lt.wDay);
|
||||
|
||||
EXPECT_EQ(17, lt.wHour);
|
||||
EXPECT_EQ(50, lt.wMinute);
|
||||
EXPECT_EQ(20, lt.wSecond);
|
||||
EXPECT_EQ(111, lt.wMilliseconds);
|
||||
}
|
||||
EXPECT_EQ(17, st.wHour);
|
||||
EXPECT_EQ(50, st.wMinute);
|
||||
EXPECT_EQ(30, st.wSecond);
|
||||
EXPECT_EQ(111, st.wMilliseconds);
|
||||
#else // !defined(_WIN32)
|
||||
auto unix_time = s3_provider::convert_api_date("2009-10-12T17:50:30.111Z");
|
||||
auto *tm_data = gmtime(&unix_time);
|
||||
EXPECT_TRUE(tm_data != nullptr);
|
||||
if (tm_data != nullptr) {
|
||||
}
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
|
||||
TEST(utils, generate_sha256) {
|
||||
auto res = utils::file::file{__FILE__}.sha256();
|
||||
|
Reference in New Issue
Block a user