2.0.0-rc (#9)
### Issues
* \#1 \[bug\] Unable to mount S3 due to 'item_not_found' exception
* \#2 Require bucket name for S3 mounts
* \#3 \[bug\] File size is not being updated in S3 mount
* \#4 Upgrade to libfuse-3.x.x
* \#5 Switch to renterd for Sia support
* \#6 Switch to cpp-httplib to further reduce dependencies
* \#7 Remove global_data and calculate used disk space per provider
* \#8 Switch to libcurl for S3 mount support
### Changes from v1.x.x
* Added read-only encrypt provider
* Pass-through mount point that transparently encrypts source data using `XChaCha20-Poly1305`
* Added S3 encryption support via `XChaCha20-Poly1305`
* Added replay protection to remote mounts
* Added support base64 writes in remote FUSE
* Created static linked Linux binaries for `amd64` and `aarch64` using `musl-libc`
* Removed legacy Sia renter support
* Removed Skynet support
* Fixed multiple remote mount WinFSP API issues on \*NIX servers
* Implemented chunked read and write
* Writes for non-cached files are performed in chunks of 8Mib
* Removed `repertory-ui` support
* Removed `FreeBSD` support
* Switched to `libsodium` over `CryptoPP`
* Switched to `XChaCha20-Poly1305` for remote mounts
* Updated `GoogleTest` to v1.14.0
* Updated `JSON for Modern C++` to v3.11.2
* Updated `OpenSSL` to v1.1.1w
* Updated `RocksDB` to v8.5.3
* Updated `WinFSP` to 2023
* Updated `boost` to v1.78.0
* Updated `cURL` to v8.3.0
* Updated `zlib` to v1.3
* Use `upload_manager` for all providers
* Adds a delay to uploads to prevent excessive API calls
* Supports re-upload after mount restart for incomplete uploads
* NOTE: Uploads for all providers are full file (no resume support)
* Multipart upload support is planned for S3
Reviewed-on: #9
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,677 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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.
|
||||
*/
|
||||
#if defined(REPERTORY_ENABLE_S3)
|
||||
#include "comm/aws_s3/aws_s3_comm.hpp"
|
||||
#include "app_config.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/polling.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
static const i_s3_comm::get_key_callback empty_key = []() { return ""; };
|
||||
|
||||
aws_s3_comm::aws_s3_comm(const app_config &config)
|
||||
: config_(config), s3_config_(config.get_s3_config()) {
|
||||
s3_config_.bucket = utils::string::trim(s3_config_.bucket);
|
||||
Aws::InitAPI(sdk_options_);
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
Aws::Utils::Logging::InitializeAWSLogging(
|
||||
Aws::MakeShared<Aws::Utils::Logging::DefaultLogSystem>(
|
||||
"Repertory", Aws::Utils::Logging::LogLevel::Trace,
|
||||
utils::path::combine(config_.get_log_directory(), {"aws_sdk_"})));
|
||||
}
|
||||
|
||||
Aws::Auth::AWSCredentials credentials;
|
||||
credentials.SetAWSAccessKeyId(s3_config_.access_key);
|
||||
credentials.SetAWSSecretKey(s3_config_.secret_key);
|
||||
|
||||
Aws::Client::ClientConfiguration configuration;
|
||||
configuration.endpointOverride = s3_config_.url;
|
||||
configuration.httpRequestTimeoutMs = s3_config_.timeout_ms;
|
||||
configuration.region = s3_config_.region;
|
||||
configuration.requestTimeoutMs = s3_config_.timeout_ms;
|
||||
|
||||
configuration.connectTimeoutMs = s3_config_.timeout_ms;
|
||||
// TODO make configurable
|
||||
const auto enable_path_style = utils::string::begins_with(s3_config_.url, "http://localhost") ||
|
||||
utils::string::begins_with(s3_config_.url, "https://localhost") ||
|
||||
utils::string::begins_with(s3_config_.url, "http://127.0.0.1") ||
|
||||
utils::string::begins_with(s3_config_.url, "https://127.0.0.1");
|
||||
|
||||
s3_client_ = std::make_unique<Aws::S3::S3Client>(
|
||||
credentials, configuration, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never,
|
||||
not enable_path_style);
|
||||
polling::instance().set_callback(
|
||||
{"s3_directory_cache", false, [this]() { this->clear_expired_directories(); }});
|
||||
}
|
||||
|
||||
aws_s3_comm::~aws_s3_comm() {
|
||||
polling::instance().remove_callback("s3_directory_cache");
|
||||
Aws::ShutdownAPI(sdk_options_);
|
||||
Aws::Utils::Logging::ShutdownAWSLogging();
|
||||
s3_client_.reset();
|
||||
}
|
||||
|
||||
void aws_s3_comm::clear_expired_directories() {
|
||||
recur_mutex_lock l(cached_directories_mutex_);
|
||||
std::vector<std::string> expired_list;
|
||||
for (const auto &kv : cached_directories_) {
|
||||
if (kv.second.expiration <= std::chrono::system_clock::now()) {
|
||||
expired_list.emplace_back(kv.first);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &expired : expired_list) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, expired, "expired");
|
||||
cached_directories_.erase(expired);
|
||||
}
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::create_bucket(const std::string &api_path) {
|
||||
std::string bucket_name, object_name;
|
||||
get_bucket_name_and_object_name(api_path, empty_key, bucket_name, object_name);
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, bucket_name, "begin");
|
||||
}
|
||||
|
||||
auto ret = api_error::access_denied;
|
||||
if (s3_config_.bucket.empty()) {
|
||||
Aws::S3::Model::CreateBucketRequest request{};
|
||||
request.SetBucket(bucket_name);
|
||||
const auto outcome = s3_client_->CreateBucket(request);
|
||||
if (outcome.IsSuccess()) {
|
||||
remove_cached_directory(utils::path::get_parent_api_path(api_path));
|
||||
ret = api_error::success;
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
__FUNCTION__, error.GetExceptionName() + "|" + error.GetMessage());
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, bucket_name,
|
||||
"end|" + std::to_string(static_cast<int>(ret)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool aws_s3_comm::exists(const std::string &api_path, const get_key_callback &get_key) const {
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path, "begin");
|
||||
}
|
||||
if (api_path == "/") {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ret = false;
|
||||
if (not get_cached_file_exists(api_path, ret)) {
|
||||
std::string bucket_name, object_name;
|
||||
get_bucket_name_and_object_name(api_path, get_key, bucket_name, object_name);
|
||||
|
||||
Aws::S3::Model::HeadObjectRequest request{};
|
||||
request.SetBucket(bucket_name);
|
||||
request.SetKey(object_name);
|
||||
ret = s3_client_->HeadObject(request).IsSuccess();
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path, "end|" + std::to_string(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void aws_s3_comm::get_bucket_name_and_object_name(const std::string &api_path,
|
||||
const get_key_callback &get_key,
|
||||
std::string &bucket_name,
|
||||
std::string &object_name) const {
|
||||
bucket_name = s3_config_.bucket;
|
||||
object_name = api_path.substr(1);
|
||||
|
||||
if (bucket_name.empty()) {
|
||||
bucket_name = utils::string::split(api_path, '/')[1];
|
||||
object_name = object_name.substr(bucket_name.size());
|
||||
if (not object_name.empty() && (object_name[0] == '/')) {
|
||||
object_name = object_name.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
const auto key = get_key();
|
||||
if (not key.empty()) {
|
||||
auto parts = utils::string::split(object_name, '/', false);
|
||||
parts[parts.size() - 1u] = key;
|
||||
object_name = utils::string::join(parts, '/');
|
||||
}
|
||||
}
|
||||
|
||||
bool aws_s3_comm::get_cached_directory_item_count(const std::string &api_path,
|
||||
std::size_t &count) const {
|
||||
recur_mutex_lock l(cached_directories_mutex_);
|
||||
if (cached_directories_.find(api_path) != cached_directories_.end()) {
|
||||
count = cached_directories_.at(api_path).items.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool aws_s3_comm::get_cached_directory_items(const std::string &api_path,
|
||||
const meta_provider_callback &meta_provider,
|
||||
directory_item_list &list) const {
|
||||
unique_recur_mutex_lock l(cached_directories_mutex_);
|
||||
if (cached_directories_.find(api_path) != cached_directories_.end()) {
|
||||
auto &cachedEntry = cached_directories_.at(api_path);
|
||||
list = cachedEntry.items;
|
||||
cached_directories_[api_path].reset_timeout(
|
||||
std::chrono::seconds(config_.get_s3_config().cache_timeout_secs));
|
||||
l.unlock();
|
||||
|
||||
for (auto &item : list) {
|
||||
meta_provider(item, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool aws_s3_comm::get_cached_file_exists(const std::string &api_path, bool &exists) const {
|
||||
exists = false;
|
||||
|
||||
unique_recur_mutex_lock l(cached_directories_mutex_);
|
||||
const auto parent_api_path = utils::path::get_parent_api_path(api_path);
|
||||
if (cached_directories_.find(parent_api_path) != cached_directories_.end()) {
|
||||
auto &entry = cached_directories_.at(parent_api_path);
|
||||
exists =
|
||||
std::find_if(entry.items.begin(), entry.items.end(), [&api_path](const auto &item) -> bool {
|
||||
return not item.directory && (api_path == item.api_path);
|
||||
}) != entry.items.end();
|
||||
if (exists) {
|
||||
cached_directories_[api_path].reset_timeout(
|
||||
std::chrono::seconds(config_.get_s3_config().cache_timeout_secs));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
aws_s3_comm::get_directory_item_count(const std::string &api_path,
|
||||
const meta_provider_callback &meta_provider) const {
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path, "begin");
|
||||
}
|
||||
|
||||
std::size_t ret = 0u;
|
||||
if (not(s3_config_.bucket.empty() && (api_path == "/"))) {
|
||||
if (not get_cached_directory_item_count(api_path, ret)) {
|
||||
directory_item_list list;
|
||||
grab_directory_items(api_path, meta_provider, list);
|
||||
return list.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path, "end|" + std::to_string(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::get_directory_items(const std::string &api_path,
|
||||
const meta_provider_callback &meta_provider,
|
||||
directory_item_list &list) const {
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path, "begin");
|
||||
}
|
||||
|
||||
auto ret = api_error::success;
|
||||
if (not get_cached_directory_items(api_path, meta_provider, list)) {
|
||||
ret = grab_directory_items(api_path, meta_provider, list);
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path,
|
||||
"end|" + std::to_string(std::uint8_t(ret)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::get_file(const std::string &api_path, const get_key_callback &get_key,
|
||||
const get_name_callback &get_name,
|
||||
const get_token_callback &get_token, api_file &file) const {
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path, "begin");
|
||||
}
|
||||
auto ret = api_error::success;
|
||||
|
||||
std::string bucket_name, object_name;
|
||||
get_bucket_name_and_object_name(api_path, get_key, bucket_name, object_name);
|
||||
|
||||
Aws::S3::Model::HeadObjectRequest request{};
|
||||
request.SetBucket(bucket_name);
|
||||
request.SetKey(object_name);
|
||||
|
||||
const auto outcome = s3_client_->HeadObject(request);
|
||||
if (outcome.IsSuccess()) {
|
||||
const auto key = get_key();
|
||||
auto object = outcome.GetResult();
|
||||
object_name = get_name(key, object_name);
|
||||
file.accessed_date = utils::get_file_time_now();
|
||||
file.api_path = utils::path::create_api_path(utils::path::combine(bucket_name, {object_name}));
|
||||
file.api_parent = utils::path::get_parent_api_path(file.api_path);
|
||||
file.changed_date = object.GetLastModified().Millis() * 1000u * 1000u;
|
||||
file.created_date = object.GetLastModified().Millis() * 1000u * 1000u;
|
||||
file.encryption_token = get_token();
|
||||
if (file.encryption_token.empty()) {
|
||||
file.file_size = object.GetContentLength();
|
||||
} else {
|
||||
file.file_size =
|
||||
utils::encryption::encrypting_reader::calculate_decrypted_size(object.GetContentLength());
|
||||
}
|
||||
file.modified_date = object.GetLastModified().Millis() * 1000u * 1000u;
|
||||
file.recoverable = true;
|
||||
file.redundancy = 3.0;
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, error.GetExceptionName() +
|
||||
"|" + error.GetMessage());
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path,
|
||||
"end|" + std::to_string(std::uint8_t(ret)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::get_file_list(const get_api_file_token_callback &get_api_file_token,
|
||||
const get_name_callback &get_name, api_file_list &list) const {
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, "/", "begin");
|
||||
}
|
||||
list.clear();
|
||||
auto ret = api_error::success;
|
||||
|
||||
const auto bucket_name = s3_config_.bucket;
|
||||
if (bucket_name.empty()) {
|
||||
const auto outcome = s3_client_->ListBuckets();
|
||||
if (outcome.IsSuccess()) {
|
||||
const auto &bucket_list = outcome.GetResult().GetBuckets();
|
||||
for (const auto &bucket : bucket_list) {
|
||||
get_file_list(bucket.GetName(), get_api_file_token, get_name, list);
|
||||
}
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
__FUNCTION__, error.GetExceptionName() + "|" + error.GetMessage());
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
} else {
|
||||
ret = get_file_list("", get_api_file_token, get_name, list);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::get_file_list(const std::string &bucket_name,
|
||||
const get_api_file_token_callback &get_api_file_token,
|
||||
const get_name_callback &get_name, api_file_list &list) const {
|
||||
auto ret = api_error::success;
|
||||
|
||||
Aws::S3::Model::ListObjectsRequest request{};
|
||||
request.SetBucket(bucket_name.empty() ? s3_config_.bucket : bucket_name);
|
||||
|
||||
const auto outcome = s3_client_->ListObjects(request);
|
||||
if (outcome.IsSuccess()) {
|
||||
const auto &object_list = outcome.GetResult().GetContents();
|
||||
for (auto const &object : object_list) {
|
||||
api_file file{};
|
||||
file.accessed_date = utils::get_file_time_now();
|
||||
|
||||
std::string object_name = object.GetKey();
|
||||
object_name =
|
||||
get_name(*(utils::string::split(object_name, '/', false).end() - 1u), object_name);
|
||||
file.api_path = utils::path::create_api_path(utils::path::combine(
|
||||
bucket_name.empty() ? s3_config_.bucket : bucket_name, {object_name}));
|
||||
file.api_parent = utils::path::get_parent_api_path(file.api_path);
|
||||
file.changed_date = object.GetLastModified().Millis() * 1000u * 1000u;
|
||||
file.created_date = object.GetLastModified().Millis() * 1000u * 1000u;
|
||||
file.encryption_token = get_api_file_token(file.api_path);
|
||||
if (file.encryption_token.empty()) {
|
||||
file.file_size = object.GetSize();
|
||||
} else {
|
||||
file.file_size =
|
||||
object.GetSize() -
|
||||
(utils::divide_with_ceiling(
|
||||
static_cast<std::uint64_t>(object.GetSize()),
|
||||
static_cast<std::uint64_t>(
|
||||
utils::encryption::encrypting_reader::get_encrypted_chunk_size())) *
|
||||
utils::encryption::encrypting_reader::get_header_size());
|
||||
}
|
||||
file.modified_date = object.GetLastModified().Millis() * 1000u * 1000u;
|
||||
file.recoverable = true;
|
||||
file.redundancy = 3.0;
|
||||
list.emplace_back(std::move(file));
|
||||
}
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, error.GetExceptionName() +
|
||||
"|" + error.GetMessage());
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::grab_directory_items(const std::string &api_path,
|
||||
const meta_provider_callback &meta_provider,
|
||||
directory_item_list &list) const {
|
||||
auto ret = api_error::success;
|
||||
if (s3_config_.bucket.empty() && (api_path == "/")) {
|
||||
const auto outcome = s3_client_->ListBuckets();
|
||||
if (outcome.IsSuccess()) {
|
||||
const auto &bucket_list = outcome.GetResult().GetBuckets();
|
||||
for (const auto &bucket : bucket_list) {
|
||||
directory_item di{};
|
||||
di.api_path = utils::path::create_api_path(utils::path::combine("/", {bucket.GetName()}));
|
||||
di.api_parent = utils::path::get_parent_api_path(di.api_path);
|
||||
di.directory = true;
|
||||
di.size = get_directory_item_count(di.api_path, meta_provider);
|
||||
meta_provider(di, true);
|
||||
list.emplace_back(std::move(di));
|
||||
}
|
||||
|
||||
set_cached_directory_items(api_path, list);
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
__FUNCTION__, error.GetExceptionName() + "|" + error.GetMessage());
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
} else {
|
||||
std::string bucket_name, object_name;
|
||||
get_bucket_name_and_object_name(api_path, empty_key, bucket_name, object_name);
|
||||
|
||||
Aws::S3::Model::ListObjectsRequest request{};
|
||||
request.SetBucket(bucket_name);
|
||||
request.SetDelimiter("/");
|
||||
request.SetPrefix(object_name.empty() ? object_name : object_name + "/");
|
||||
|
||||
const auto outcome = s3_client_->ListObjects(request);
|
||||
if (outcome.IsSuccess()) {
|
||||
const auto &object_list = outcome.GetResult().GetContents();
|
||||
for (auto const &object : object_list) {
|
||||
directory_item item{};
|
||||
item.api_path =
|
||||
utils::path::create_api_path(utils::path::combine(bucket_name, {object.GetKey()}));
|
||||
item.api_parent = utils::path::get_parent_api_path(item.api_path);
|
||||
item.directory = false;
|
||||
item.size = object.GetSize();
|
||||
meta_provider(item, true);
|
||||
list.emplace_back(std::move(item));
|
||||
}
|
||||
|
||||
set_cached_directory_items(api_path, list);
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
__FUNCTION__, error.GetExceptionName() + "|" + error.GetMessage());
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::read_file_bytes(const std::string &api_path, const std::size_t &size,
|
||||
const std::uint64_t &offset, std::vector<char> &data,
|
||||
const get_key_callback &get_key,
|
||||
const get_size_callback &get_size,
|
||||
const get_token_callback &get_token,
|
||||
const bool &stop_requested) const {
|
||||
auto ret = api_error::success;
|
||||
data.clear();
|
||||
|
||||
std::string bucket_name, object_name;
|
||||
get_bucket_name_and_object_name(api_path, get_key, bucket_name, object_name);
|
||||
|
||||
const auto encryption_token = get_token();
|
||||
const auto data_size = get_size();
|
||||
if (encryption_token.empty()) {
|
||||
Aws::S3::Model::GetObjectRequest request{};
|
||||
request.SetBucket(bucket_name);
|
||||
request.SetKey(object_name);
|
||||
request.SetResponseContentType("application/octet-stream");
|
||||
request.SetRange("bytes=" + utils::string::from_uint64(offset) + "-" +
|
||||
utils::string::from_uint64(offset + size - 1u));
|
||||
request.SetContinueRequestHandler(
|
||||
[&stop_requested](const Aws::Http::HttpRequest *) { return not stop_requested; });
|
||||
|
||||
auto outcome = s3_client_->GetObject(request);
|
||||
if (outcome.IsSuccess()) {
|
||||
auto result = outcome.GetResultWithOwnership();
|
||||
const auto len = result.GetContentLength();
|
||||
data.resize(len);
|
||||
result.GetBody().read(&data[0], len);
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
__FUNCTION__, error.GetExceptionName() + "|" + error.GetMessage());
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
} else {
|
||||
const auto key = utils::encryption::generate_key(encryption_token);
|
||||
ret = utils::encryption::read_encrypted_range(
|
||||
{offset, offset + size - 1}, key,
|
||||
[&](std::vector<char> &ct, const std::uint64_t &start_offset,
|
||||
const std::uint64_t &end_offset) -> api_error {
|
||||
return read_file_bytes(
|
||||
api_path, (end_offset - start_offset + 1u), start_offset, ct, get_key, get_size,
|
||||
[]() -> std::string { return ""; }, stop_requested);
|
||||
},
|
||||
data_size, data);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::remove_bucket(const std::string &api_path) {
|
||||
std::string bucket_name, object_name;
|
||||
get_bucket_name_and_object_name(api_path, empty_key, bucket_name, object_name);
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, bucket_name, "begin");
|
||||
}
|
||||
|
||||
auto ret = api_error::access_denied;
|
||||
if (s3_config_.bucket.empty()) {
|
||||
Aws::S3::Model::DeleteBucketRequest request{};
|
||||
request.SetBucket(bucket_name);
|
||||
const auto outcome = s3_client_->DeleteBucket(request);
|
||||
if (outcome.IsSuccess()) {
|
||||
remove_cached_directory(api_path);
|
||||
ret = api_error::success;
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
__FUNCTION__, error.GetExceptionName() + "|" + error.GetMessage());
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, bucket_name,
|
||||
"end|" + std::to_string(static_cast<int>(ret)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void aws_s3_comm::remove_cached_directory(const std::string &api_path) {
|
||||
recur_mutex_lock l(cached_directories_mutex_);
|
||||
cached_directories_.erase(api_path);
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::remove_file(const std::string &api_path, const get_key_callback &get_key) {
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path, "begin");
|
||||
}
|
||||
auto ret = api_error::success;
|
||||
|
||||
std::string bucket_name, object_name;
|
||||
get_bucket_name_and_object_name(api_path, get_key, bucket_name, object_name);
|
||||
|
||||
Aws::S3::Model::DeleteObjectRequest request{};
|
||||
request.SetBucket(bucket_name);
|
||||
request.SetKey(object_name);
|
||||
const auto outcome = s3_client_->DeleteObject(request);
|
||||
if (outcome.IsSuccess()) {
|
||||
remove_cached_directory(utils::path::get_parent_api_path(api_path));
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, error.GetExceptionName() +
|
||||
"|" + error.GetMessage());
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path,
|
||||
"end|" + std::to_string(std::uint8_t(ret)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::rename_file(const std::string & /*api_path*/,
|
||||
const std::string & /*new_api_path*/) {
|
||||
return api_error::not_implemented;
|
||||
/* if (config_.get_event_level() >= event_level::debug) { */
|
||||
/* event_system::instance().raise<debug_log>(__FUNCTION__, api_path, "begin"); */
|
||||
/* } */
|
||||
/* auto ret = api_error::success; */
|
||||
/* */
|
||||
/* std::string bucket_name, object_name; */
|
||||
/* get_bucket_name_and_object_name(api_path, bucket_name, object_name); */
|
||||
/* */
|
||||
/* std::string new_object_name; */
|
||||
/* get_bucket_name_and_object_name(new_api_path, bucket_name, new_object_name); */
|
||||
/* */
|
||||
/* Aws::S3::Model::CopyObjectRequest request{}; */
|
||||
/* request.SetBucket(bucket_name); */
|
||||
/* request.SetCopySource(bucket_name + '/' + object_name); */
|
||||
/* request.SetKey(new_object_name); */
|
||||
/* */
|
||||
/* const auto outcome = s3_client_->CopyObject(request); */
|
||||
/* if (outcome.IsSuccess()) { */
|
||||
/* ret = remove_file(api_path); */
|
||||
/* } else { */
|
||||
/* const auto &error = outcome.GetError(); */
|
||||
/* event_system::instance().raise<repertory_exception>(__FUNCTION__, error.GetExceptionName()
|
||||
* +
|
||||
* "|" + */
|
||||
/* error.GetMessage()); */
|
||||
/* ret = api_error::comm_error; */
|
||||
/* } */
|
||||
/* */
|
||||
/* if (config_.get_event_level() >= event_level::debug) { */
|
||||
/* event_system::instance().raise<debug_log>(__FUNCTION__, api_path, */
|
||||
/* "end|" + std::to_string(std::uint8_t(ret))); */
|
||||
/* } */
|
||||
/* return ret; */
|
||||
}
|
||||
|
||||
void aws_s3_comm::set_cached_directory_items(const std::string &api_path,
|
||||
directory_item_list list) const {
|
||||
recur_mutex_lock l(cached_directories_mutex_);
|
||||
cached_directories_[api_path].items = std::move(list);
|
||||
cached_directories_[api_path].reset_timeout(
|
||||
std::chrono::seconds(config_.get_s3_config().cache_timeout_secs));
|
||||
}
|
||||
|
||||
api_error aws_s3_comm::upload_file(const std::string &api_path, const std::string &source_path,
|
||||
const std::string &encryption_token,
|
||||
const get_key_callback &get_key, const set_key_callback &set_key,
|
||||
const bool &stop_requested) {
|
||||
static const auto no_stop = false;
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path, "begin");
|
||||
}
|
||||
auto ret = api_error::success;
|
||||
|
||||
std::string bucket_name, object_name;
|
||||
get_bucket_name_and_object_name(api_path, get_key, bucket_name, object_name);
|
||||
|
||||
std::shared_ptr<Aws::IOStream> file_stream;
|
||||
if (encryption_token.empty()) {
|
||||
file_stream = Aws::MakeShared<Aws::FStream>(&source_path[0], &source_path[0],
|
||||
std::ios_base::in | std::ios_base::binary);
|
||||
} else {
|
||||
const auto file_name = ([&api_path]() -> std::string {
|
||||
return *(utils::string::split(api_path, '/', false).end() - 1u);
|
||||
})();
|
||||
|
||||
const auto reader =
|
||||
utils::encryption::encrypting_reader(file_name, source_path, no_stop, encryption_token, -1);
|
||||
auto key = get_key();
|
||||
if (key.empty()) {
|
||||
key = reader.get_encrypted_file_name();
|
||||
set_key(key);
|
||||
|
||||
auto parts = utils::string::split(object_name, '/', false);
|
||||
parts[parts.size() - 1u] = key;
|
||||
object_name = utils::string::join(parts, '/');
|
||||
}
|
||||
|
||||
file_stream = reader.create_iostream();
|
||||
}
|
||||
file_stream->seekg(0);
|
||||
|
||||
Aws::S3::Model::PutObjectRequest request{};
|
||||
request.SetBucket(bucket_name);
|
||||
request.SetKey(object_name);
|
||||
request.SetBody(file_stream);
|
||||
request.SetContinueRequestHandler(
|
||||
[&stop_requested](const Aws::Http::HttpRequest *) { return not stop_requested; });
|
||||
|
||||
const auto outcome = s3_client_->PutObject(request);
|
||||
if (outcome.IsSuccess()) {
|
||||
remove_cached_directory(utils::path::get_parent_api_path(api_path));
|
||||
} else {
|
||||
const auto &error = outcome.GetError();
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, error.GetExceptionName() +
|
||||
"|" + error.GetMessage());
|
||||
ret = api_error::upload_failed;
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path,
|
||||
"end|" + std::to_string(std::uint8_t(ret)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif // REPERTORY_ENABLE_S3
|
||||
@@ -1,849 +1,176 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "comm/curl/curl_comm.hpp"
|
||||
#include "comm/curl/curl_resolver.hpp"
|
||||
#include "curl/curl.h"
|
||||
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/Base64.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
struct curl_setup {
|
||||
bool allow_timeout = false;
|
||||
const host_config &hc;
|
||||
const http_parameters *parameters = nullptr;
|
||||
http_headers *headers = nullptr;
|
||||
bool post = false;
|
||||
raw_write_data *write_data = nullptr;
|
||||
std::string *write_string = nullptr;
|
||||
std::unique_ptr<curl_resolver> resolver;
|
||||
};
|
||||
const curl_comm::write_callback curl_comm::write_data =
|
||||
static_cast<curl_comm::write_callback>([](char *buffer, size_t size,
|
||||
size_t nitems,
|
||||
void *outstream) -> size_t {
|
||||
auto &info = *reinterpret_cast<read_write_info *>(outstream);
|
||||
std::copy(buffer, buffer + (size * nitems),
|
||||
std::back_inserter(info.data));
|
||||
return info.stop_requested ? 0 : size * nitems;
|
||||
});
|
||||
|
||||
struct raw_write_data {
|
||||
std::vector<char> *buffer;
|
||||
const bool &stop_requested;
|
||||
};
|
||||
|
||||
struct read_data {
|
||||
const bool *stop_requested = nullptr;
|
||||
native_file *nf = nullptr;
|
||||
std::uint64_t offset = 0u;
|
||||
};
|
||||
|
||||
CURL *curl_comm::common_curl_setup(const std::string &path, curl_setup &setup, std::string &url,
|
||||
std::string &fields) {
|
||||
return common_curl_setup(utils::create_curl(), path, setup, url, fields);
|
||||
}
|
||||
|
||||
CURL *curl_comm::common_curl_setup(CURL *curl_handle, const std::string &path, curl_setup &setup,
|
||||
std::string &url, std::string &fields) {
|
||||
url = construct_url(curl_handle, path, setup.hc);
|
||||
if (setup.post) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 0);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
|
||||
if (setup.parameters && not setup.parameters->empty()) {
|
||||
for (const auto ¶m : *setup.parameters) {
|
||||
if (not fields.empty()) {
|
||||
fields += "&";
|
||||
}
|
||||
if (param.first ==
|
||||
"new" + app_config::get_provider_path_name(config_.get_provider_type())) {
|
||||
fields += (param.first + "=" + url_encode(curl_handle, param.second));
|
||||
} else {
|
||||
fields += (param.first + "=" + param.second);
|
||||
}
|
||||
const curl_comm::write_callback curl_comm::write_headers =
|
||||
static_cast<curl_comm::write_callback>([](char *buffer, size_t size,
|
||||
size_t nitems,
|
||||
void *outstream) -> size_t {
|
||||
auto &headers = *reinterpret_cast<http_headers *>(outstream);
|
||||
const auto header = std::string(buffer, size * nitems);
|
||||
const auto parts = utils::string::split(header, ':');
|
||||
if (parts.size() > 1u) {
|
||||
auto data = header.substr(parts[0u].size() + 1u);
|
||||
utils::string::left_trim(data);
|
||||
utils::string::right_trim(data, '\r');
|
||||
utils::string::right_trim(data, '\n');
|
||||
utils::string::right_trim(data, '\r');
|
||||
headers[utils::string::to_lower(parts[0u])] = data;
|
||||
}
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, &fields[0]);
|
||||
} else {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, "");
|
||||
return size * nitems;
|
||||
});
|
||||
|
||||
curl_comm::curl_comm(host_config hc)
|
||||
: host_config_(std::move(hc)), s3_config_(std::nullopt) {}
|
||||
|
||||
curl_comm::curl_comm(s3_config s3)
|
||||
: host_config_(std::nullopt), s3_config_(std::move(s3)) {}
|
||||
|
||||
auto curl_comm::construct_url(CURL *curl, const std::string &relative_path,
|
||||
const host_config &hc) -> std::string {
|
||||
auto custom_port =
|
||||
(((hc.protocol == "http") && (hc.api_port == 80U || hc.api_port == 0U)) ||
|
||||
((hc.protocol == "https") && (hc.api_port == 443U || hc.api_port == 0U)))
|
||||
? ""
|
||||
: ":" + std::to_string(hc.api_port);
|
||||
auto url = hc.protocol + "://" +
|
||||
utils::string::trim_copy(hc.host_name_or_ip) + custom_port;
|
||||
|
||||
static const auto complete_url = [](const std::string ¤t_path,
|
||||
const std::string &parent_path,
|
||||
std::string &final_url) -> std::string & {
|
||||
final_url += utils::path::create_api_path(current_path);
|
||||
if (utils::string::ends_with(parent_path, "/")) {
|
||||
final_url += '/';
|
||||
}
|
||||
} else {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POST, 0);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 1L);
|
||||
if (setup.parameters && not setup.parameters->empty()) {
|
||||
url += "?";
|
||||
for (const auto ¶m : *setup.parameters) {
|
||||
if (url[url.size() - 1] != '?') {
|
||||
url += "&";
|
||||
}
|
||||
url += (param.first + "=" + url_encode(curl_handle, param.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (not setup.hc.agent_string.empty()) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, &setup.hc.agent_string[0]);
|
||||
}
|
||||
|
||||
if (setup.write_data) {
|
||||
setup.write_data->buffer->clear();
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, setup.write_data);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data_callback_);
|
||||
} else if (setup.write_string) {
|
||||
setup.write_string->clear();
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, setup.write_string);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_string_callback_);
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
|
||||
|
||||
if (setup.allow_timeout) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT_MS, setup.hc.timeout_ms);
|
||||
}
|
||||
|
||||
if (not setup.hc.api_password.empty()) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERNAME, "");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, &setup.hc.api_password[0]);
|
||||
}
|
||||
|
||||
if (setup.headers) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, setup.headers);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, write_header_callback_);
|
||||
}
|
||||
|
||||
std::vector<std::string> items = {"localhost:" + std::to_string(setup.hc.api_port) +
|
||||
":127.0.0.1"};
|
||||
setup.resolver = std::make_unique<curl_resolver>(curl_handle, items);
|
||||
|
||||
return curl_handle;
|
||||
}
|
||||
|
||||
std::string curl_comm::construct_url(CURL *curl_handle, const std::string &relative_path,
|
||||
const host_config &hc) {
|
||||
auto custom_port = (((hc.protocol == "http") && (hc.api_port == 80u)) ||
|
||||
((hc.protocol == "https") && (hc.api_port == 443u)))
|
||||
? ""
|
||||
: ":" + std::to_string(hc.api_port);
|
||||
auto ret = hc.protocol + "://" + utils::string::trim_copy(hc.host_name_or_ip) + custom_port;
|
||||
auto path = utils::path::combine("/", {hc.path});
|
||||
if (relative_path.empty()) {
|
||||
ret += utils::path::create_api_path(path);
|
||||
if (utils::string::ends_with(hc.path, "/")) {
|
||||
ret += '/';
|
||||
}
|
||||
} else {
|
||||
path = utils::path::combine(path, {url_encode(curl_handle, relative_path, true)});
|
||||
ret += utils::path::create_api_path(path);
|
||||
if (utils::string::ends_with(relative_path, "/")) {
|
||||
ret += '/';
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool curl_comm::create_auth_session(CURL *&curl_handle, const app_config &config, host_config hc,
|
||||
std::string &session) {
|
||||
auto ret = true;
|
||||
if (not curl_handle) {
|
||||
curl_handle = utils::create_curl();
|
||||
}
|
||||
|
||||
if (not hc.auth_url.empty() && not hc.auth_user.empty()) {
|
||||
event_system::instance().raise<comm_auth_begin>(hc.auth_url, hc.auth_user);
|
||||
ret = false;
|
||||
session = utils::create_uuid_string();
|
||||
const auto cookie_path = utils::path::combine(config.get_data_directory(), {session + ".txt"});
|
||||
http_headers headers{};
|
||||
auto url = utils::string::right_trim(utils::string::trim(hc.auth_url), '/') + "/api/login";
|
||||
|
||||
if (not hc.agent_string.empty()) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, &hc.agent_string[0]);
|
||||
}
|
||||
|
||||
if (not hc.api_password.empty()) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERNAME, "");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, &hc.api_password[0]);
|
||||
}
|
||||
|
||||
struct curl_slist *hs = nullptr;
|
||||
hs = curl_slist_append(hs, "Content-Type: application/json");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hs);
|
||||
|
||||
const auto payload = json({
|
||||
{"email", hc.auth_user},
|
||||
{"password", hc.auth_password},
|
||||
})
|
||||
.dump(2);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_COOKIEFILE, cookie_path.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_COOKIEJAR, cookie_path.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, &headers);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, write_header_callback_);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 0L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT_MS, hc.timeout_ms);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, payload.c_str());
|
||||
|
||||
long code{};
|
||||
auto res = curl_easy_perform(curl_handle);
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_HTTP_CODE, &code);
|
||||
if ((res == CURLE_OK) && ((code >= 200) && (code < 300))) {
|
||||
auto cookie = headers["set-cookie"];
|
||||
if (cookie.empty()) {
|
||||
code = 400;
|
||||
} else {
|
||||
cookie = headers["set-cookie"];
|
||||
if (cookie.empty() || not utils::string::contains(cookie, "skynet-jwt=")) {
|
||||
code = 401;
|
||||
} else {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_COOKIELIST, "FLUSH");
|
||||
update_auth_session(utils::reset_curl(curl_handle), config, session);
|
||||
} else {
|
||||
curl_easy_cleanup(curl_handle);
|
||||
release_auth_session(config, hc, session);
|
||||
}
|
||||
curl_slist_free_all(hs);
|
||||
|
||||
event_system::instance().raise<comm_auth_end>(hc.auth_url, hc.auth_user, res, code);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error curl_comm::get_or_post(const host_config &hc, const bool &post, const std::string &path,
|
||||
const http_parameters ¶meters, json &data, json &error,
|
||||
http_headers *headers, std::function<void(CURL *curl_handle)> cb) {
|
||||
std::string result;
|
||||
auto setup = curl_setup{not post, hc, ¶meters, headers, post, nullptr, &result, nullptr};
|
||||
|
||||
std::string fields;
|
||||
std::string url;
|
||||
auto *curl_handle = common_curl_setup(path, setup, url, fields);
|
||||
if (cb) {
|
||||
cb(curl_handle);
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::verbose) {
|
||||
if (post) {
|
||||
event_system::instance().raise<comm_post_begin>(url, fields);
|
||||
} else {
|
||||
event_system::instance().raise<comm_get_begin>(url);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<std::chrono::system_clock::time_point> tp(nullptr);
|
||||
if (config_.get_enable_comm_duration_events()) {
|
||||
tp = std::make_unique<std::chrono::system_clock::time_point>(std::chrono::system_clock::now());
|
||||
}
|
||||
|
||||
const auto curl_code = curl_easy_perform(curl_handle);
|
||||
if (curl_code == CURLE_OPERATION_TIMEDOUT) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, "CURL timeout: " + path);
|
||||
}
|
||||
|
||||
long http_code = -1;
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
if (tp != nullptr) {
|
||||
const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now() - *tp);
|
||||
event_system::instance().raise<comm_duration>(url, std::to_string(duration.count()));
|
||||
}
|
||||
|
||||
const auto ret = process_json_response(url, curl_code, http_code, result, data, error);
|
||||
if (config_.get_event_level() >= event_level::verbose) {
|
||||
if (post) {
|
||||
event_system::instance().raise<comm_post_end>(
|
||||
url, curl_code, http_code, ((ret == api_error::success) ? data.dump(2) : error.dump(2)));
|
||||
} else {
|
||||
event_system::instance().raise<comm_get_end>(
|
||||
url, curl_code, http_code, ((ret == api_error::success) ? data.dump(2) : error.dump(2)));
|
||||
}
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl_handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error curl_comm::get_range(const host_config &hc, const std::string &path,
|
||||
const std::uint64_t &data_size, const http_parameters ¶meters,
|
||||
const std::string &encryption_token, std::vector<char> &data,
|
||||
const http_ranges &ranges, json &error, http_headers *headers,
|
||||
const bool &stop_requested) {
|
||||
if (encryption_token.empty()) {
|
||||
return get_range_unencrypted(hc, path, parameters, data, ranges, error, headers,
|
||||
stop_requested);
|
||||
}
|
||||
|
||||
if (ranges.empty()) {
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
const auto key = utils::encryption::generate_key(encryption_token);
|
||||
for (const auto &range : ranges) {
|
||||
const auto result = utils::encryption::read_encrypted_range(
|
||||
range, key,
|
||||
[&](std::vector<char> &ct, const std::uint64_t &start_offset,
|
||||
const std::uint64_t &end_offset) -> api_error {
|
||||
const auto ret =
|
||||
get_range_unencrypted(hc, path, parameters, ct, {{start_offset, end_offset}}, error,
|
||||
headers, stop_requested);
|
||||
headers = nullptr;
|
||||
return ret;
|
||||
},
|
||||
data_size, data);
|
||||
if (result != api_error::success) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
api_error curl_comm::get_range_unencrypted(const host_config &hc, const std::string &path,
|
||||
const http_parameters ¶meters,
|
||||
std::vector<char> &data, const http_ranges &ranges,
|
||||
json &error, http_headers *headers,
|
||||
const bool &stop_requested) {
|
||||
raw_write_data wd = {&data, stop_requested};
|
||||
|
||||
auto setup = curl_setup{false, hc, ¶meters, headers, false, &wd, nullptr, nullptr};
|
||||
|
||||
std::string fields;
|
||||
std::string url;
|
||||
auto *curl_handle = common_curl_setup(path, setup, url, fields);
|
||||
|
||||
std::string range_list;
|
||||
if (not ranges.empty()) {
|
||||
range_list = std::accumulate(
|
||||
std::next(ranges.begin()), ranges.end(), http_range_to_string(ranges[0]),
|
||||
[](const auto &l, const auto &r) { return l + ',' + http_range_to_string(r); });
|
||||
curl_easy_setopt(curl_handle, CURLOPT_RANGE, &range_list[0]);
|
||||
}
|
||||
|
||||
return execute_binary_operation<comm_get_range_begin, comm_get_range_end>(curl_handle, url, data,
|
||||
error, stop_requested);
|
||||
}
|
||||
|
||||
api_error curl_comm::get_raw(const host_config &hc, const std::string &path,
|
||||
const http_parameters ¶meters, std::vector<char> &data,
|
||||
json &error, const bool &stop_requested) {
|
||||
raw_write_data wd = {&data, stop_requested};
|
||||
|
||||
auto setup = curl_setup{false, hc, ¶meters, nullptr, false, &wd, nullptr, nullptr};
|
||||
|
||||
std::string fields;
|
||||
std::string url;
|
||||
return execute_binary_operation<comm_get_range_begin, comm_get_range_end>(
|
||||
common_curl_setup(path, setup, url, fields), url, data, error, stop_requested);
|
||||
}
|
||||
|
||||
std::string curl_comm::http_range_to_string(const http_range &range) {
|
||||
return std::to_string(range.begin) + '-' + std::to_string(range.end);
|
||||
}
|
||||
|
||||
api_error curl_comm::post_file(const host_config &hc, const std::string &path,
|
||||
const std::string &source_path, const http_parameters ¶meters,
|
||||
json &data, json &error, const bool &stop_requested) {
|
||||
auto ret = api_error::os_error;
|
||||
std::uint64_t file_size{};
|
||||
if (utils::file::get_file_size(source_path, file_size)) {
|
||||
std::string result;
|
||||
auto setup = curl_setup{false, hc, ¶meters, nullptr, true, nullptr, &result, nullptr};
|
||||
|
||||
std::string fields;
|
||||
std::string url;
|
||||
auto *curl_handle = common_curl_setup(path, setup, url, fields);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_UPLOAD, 1L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_INFILESIZE_LARGE, file_size);
|
||||
|
||||
native_file::native_file_ptr nf;
|
||||
native_file::create_or_open(source_path, nf);
|
||||
if (nf) {
|
||||
read_data rd = {&stop_requested};
|
||||
rd.nf = nf.get();
|
||||
curl_easy_setopt(curl_handle, CURLOPT_READDATA, &rd);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_data_callback_);
|
||||
|
||||
ret = execute_json_operation<comm_post_file_begin, comm_post_file_end>(
|
||||
curl_handle, url, result, data, error, stop_requested);
|
||||
nf->close();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error curl_comm::post_multipart_file(const host_config &hc, const std::string &path,
|
||||
const std::string &file_name,
|
||||
const std::string &source_path,
|
||||
const std::string &encryption_token, json &data,
|
||||
json &error, const bool &stop_requested) {
|
||||
std::string result;
|
||||
std::uint64_t file_size{};
|
||||
std::unique_ptr<utils::encryption::encrypting_reader> reader;
|
||||
if (encryption_token.empty()) {
|
||||
if (not utils::file::get_file_size(source_path, file_size)) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
reader = std::make_unique<utils::encryption::encrypting_reader>(
|
||||
file_name, source_path, stop_requested, encryption_token);
|
||||
file_size = reader->get_total_size();
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, e.what());
|
||||
return api_error::error;
|
||||
}
|
||||
}
|
||||
|
||||
std::string session;
|
||||
CURL *curl_handle = nullptr;
|
||||
if (not session_manager_.create_auth_session(curl_handle, config_, hc, session)) {
|
||||
return api_error::access_denied;
|
||||
}
|
||||
|
||||
if (config_.get_provider_type() == provider_type::skynet) {
|
||||
curl_easy_cleanup(curl_handle);
|
||||
|
||||
const auto fn = reader ? reader->get_encrypted_file_name() : file_name;
|
||||
const auto fs = reader ? reader->get_total_size() : file_size;
|
||||
|
||||
std::string location;
|
||||
if (tus_upload_create(hc, fn, fs, location)) {
|
||||
std::string skylink;
|
||||
if (tus_upload(hc, source_path, fn, fs, location, skylink, stop_requested, reader.get())) {
|
||||
data["skylink"] = skylink;
|
||||
return api_error::success;
|
||||
}
|
||||
}
|
||||
|
||||
return api_error::comm_error;
|
||||
}
|
||||
|
||||
auto setup = curl_setup{false, hc, nullptr, nullptr, true, nullptr, &result, nullptr};
|
||||
|
||||
std::string fields;
|
||||
std::string url;
|
||||
common_curl_setup(curl_handle, path, setup, url, fields);
|
||||
|
||||
auto *curl_mime = curl_mime_init(curl_handle);
|
||||
auto *mime_part = curl_mime_addpart(curl_mime);
|
||||
curl_mime_name(mime_part, "file");
|
||||
|
||||
auto ret = api_error::success;
|
||||
if (encryption_token.empty()) {
|
||||
curl_mime_filename(mime_part, &file_name[0]);
|
||||
curl_mime_filedata(mime_part, &source_path[0]);
|
||||
} else {
|
||||
try {
|
||||
curl_mime_filename(mime_part, reader->get_encrypted_file_name().c_str());
|
||||
curl_mime_data_cb(
|
||||
mime_part, reader->get_total_size(),
|
||||
static_cast<curl_read_callback>(utils::encryption::encrypting_reader::reader_function),
|
||||
nullptr, nullptr, reader.get());
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, e.what());
|
||||
ret = api_error::error;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == api_error::success) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_MIMEPOST, curl_mime);
|
||||
|
||||
ret = execute_json_operation<comm_post_multi_part_file_begin, comm_post_multi_part_file_end>(
|
||||
curl_handle, url, result, data, error, stop_requested,
|
||||
(ret == api_error::success) ? CURLE_OK : CURLE_SEND_ERROR);
|
||||
}
|
||||
|
||||
curl_mime_free(curl_mime);
|
||||
curl_easy_cleanup(curl_handle);
|
||||
|
||||
/* if (not session.empty() && (ret == api_error::success)) { */
|
||||
/* auto pin_hc = hc; */
|
||||
/* */
|
||||
/* const auto skylink = data["skylink"].get<std::string>(); */
|
||||
/* utils::string::replace(pin_hc.path, "/skyfile", "/pin/" + skylink); */
|
||||
/* */
|
||||
/* ret = api_error::comm_error; */
|
||||
/* for (std::uint8_t i = 0u; not stop_requested && (i < 30u) && (ret != api_error::success);
|
||||
* i++) { */
|
||||
/* if (i) { */
|
||||
/* event_system::instance().raise<repertory_exception>( */
|
||||
/* __FUNCTION__, "RETRY [" + std::to_string(i) + "] Pin failed for file: " +
|
||||
* file_name); */
|
||||
/* std::this_thread::sleep_for(1s); */
|
||||
/* } */
|
||||
/* */
|
||||
/* json response; */
|
||||
/* http_headers headers{}; */
|
||||
/* ret = get_or_post(pin_hc, true, "", {}, response, error, &headers, [&](CURL *curl_handle)
|
||||
* { */
|
||||
/* session_manager_.update_auth_session(curl_handle, config_, hc); */
|
||||
/* }); */
|
||||
/* } */
|
||||
/* } */
|
||||
|
||||
session_manager_.release_auth_session(config_, hc, session);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error curl_comm::process_binary_response(const std::string &url, const CURLcode &res,
|
||||
const long &http_code, std::vector<char> data,
|
||||
json &error) {
|
||||
const auto ret = process_response(
|
||||
url, res, http_code, data.size(),
|
||||
[&]() -> std::string { return (data.empty() ? "" : std::string(&data[0], data.size())); },
|
||||
nullptr, error);
|
||||
|
||||
if (ret != api_error::success) {
|
||||
data.clear();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error curl_comm::process_json_response(const std::string &url, const CURLcode &res,
|
||||
const long &http_code, const std::string &result,
|
||||
json &data, json &error) {
|
||||
const auto ret = process_response(
|
||||
url, res, http_code, result.size(), [&]() -> std::string { return result; },
|
||||
[&]() {
|
||||
if (result.length()) {
|
||||
data = json::parse(result.c_str());
|
||||
}
|
||||
},
|
||||
error);
|
||||
|
||||
if (ret != api_error::success) {
|
||||
data.clear();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error curl_comm::process_response(const std::string &url, const CURLcode &res,
|
||||
const long &http_code, const std::size_t &data_size,
|
||||
const std::function<std::string()> &to_string_convertor,
|
||||
const std::function<void()> &success_handler,
|
||||
json &error) const {
|
||||
auto ret = api_error::success;
|
||||
|
||||
auto construct_error = [&]() {
|
||||
ret = api_error::comm_error;
|
||||
const auto *curl_string = curl_easy_strerror(res);
|
||||
std::string error_string(curl_string ? curl_string : "");
|
||||
error["message"] = error_string + ":" + std::to_string(http_code);
|
||||
error["url"] = url;
|
||||
return final_url;
|
||||
};
|
||||
|
||||
if ((res == CURLE_OK) && ((http_code >= 200) && (http_code < 300))) {
|
||||
if (success_handler) {
|
||||
success_handler();
|
||||
}
|
||||
} else if (data_size == 0u) {
|
||||
construct_error();
|
||||
} else {
|
||||
try {
|
||||
const auto tmp = json::parse(to_string_convertor().c_str());
|
||||
if (tmp.find("message") != tmp.end()) {
|
||||
ret = api_error::comm_error;
|
||||
error = tmp;
|
||||
error["url"] = url;
|
||||
} else {
|
||||
construct_error();
|
||||
}
|
||||
} catch (...) {
|
||||
construct_error();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
auto path = utils::path::combine("/", {hc.path});
|
||||
return relative_path.empty()
|
||||
? complete_url(path, hc.path, url)
|
||||
: complete_url(utils::path::combine(
|
||||
path, {url_encode(curl, relative_path, true)}),
|
||||
relative_path, url);
|
||||
}
|
||||
|
||||
void curl_comm::release_auth_session(const app_config &config, host_config hc,
|
||||
const std::string &session) {
|
||||
if (not hc.auth_url.empty() && not hc.auth_user.empty()) {
|
||||
event_system::instance().raise<comm_auth_logout_begin>(hc.auth_url, hc.auth_user);
|
||||
const auto cookie_path = utils::path::combine(config.get_data_directory(), {session + ".txt"});
|
||||
const auto url =
|
||||
utils::string::right_trim(utils::string::trim(hc.auth_url), '/') + "/api/logout";
|
||||
auto curl_comm::create_host_config(const s3_config &config,
|
||||
bool use_s3_path_style) -> host_config {
|
||||
host_config hc{};
|
||||
hc.api_password = config.secret_key;
|
||||
hc.api_user = config.access_key;
|
||||
|
||||
auto *curl_handle = utils::create_curl();
|
||||
if (not hc.agent_string.empty()) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, &hc.agent_string[0]);
|
||||
auto pos = config.url.find(':');
|
||||
hc.host_name_or_ip = config.url.substr(pos + 3U);
|
||||
if (config.use_region_in_url && not config.region.empty()) {
|
||||
auto parts = utils::string::split(hc.host_name_or_ip, '.', false);
|
||||
if (parts.size() > 1U) {
|
||||
parts.insert(parts.begin() + 1U, config.region);
|
||||
hc.host_name_or_ip = utils::string::join(parts, '.');
|
||||
}
|
||||
|
||||
if (not hc.api_password.empty()) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERNAME, "");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, &hc.api_password[0]);
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_COOKIEFILE, cookie_path.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_COOKIEJAR, cookie_path.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 0L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, "");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT_MS, hc.timeout_ms);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_null_callback_);
|
||||
|
||||
const auto curl_code = curl_easy_perform(curl_handle);
|
||||
|
||||
long http_code = -1;
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_HTTP_CODE, &http_code);
|
||||
curl_easy_cleanup(curl_handle);
|
||||
|
||||
event_system::instance().raise<comm_auth_logout_end>(hc.auth_url, hc.auth_user, curl_code,
|
||||
http_code);
|
||||
|
||||
utils::file::delete_file(cookie_path);
|
||||
}
|
||||
|
||||
if (not use_s3_path_style) {
|
||||
hc.host_name_or_ip = config.bucket + '.' + hc.host_name_or_ip;
|
||||
}
|
||||
|
||||
hc.protocol = config.url.substr(0U, pos);
|
||||
if (use_s3_path_style) {
|
||||
hc.path = '/' + config.bucket;
|
||||
}
|
||||
|
||||
return hc;
|
||||
}
|
||||
|
||||
bool curl_comm::tus_upload(host_config hc, const std::string &source_path,
|
||||
const std::string &file_name, std::uint64_t file_size,
|
||||
const std::string &location, std::string &skylink,
|
||||
const bool &stop_requested,
|
||||
utils::encryption::encrypting_reader *reader) {
|
||||
static const constexpr std::uint64_t max_skynet_file_size = (1ull << 22ull) * 10ull;
|
||||
|
||||
auto tus_hc = hc;
|
||||
utils::string::replace(tus_hc.path, "/skyfile", "/tus");
|
||||
|
||||
auto ret = true;
|
||||
std::uint64_t offset = 0u;
|
||||
native_file::native_file_ptr nf;
|
||||
while (ret && file_size) {
|
||||
const auto chunk_size = std::min(max_skynet_file_size, file_size);
|
||||
|
||||
auto *curl_handle = utils::create_curl();
|
||||
curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 0);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 0);
|
||||
const auto upload_offset = "Upload-Offset: " + std::to_string(offset);
|
||||
const auto content_length = "Content-Length: " + std::to_string(chunk_size);
|
||||
|
||||
struct curl_slist *hs = nullptr;
|
||||
hs = curl_slist_append(hs, "tus-resumable: 1.0.0");
|
||||
hs = curl_slist_append(hs, "Content-Type: application/offset+octet-stream");
|
||||
hs = curl_slist_append(hs, content_length.c_str());
|
||||
hs = curl_slist_append(hs, upload_offset.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hs);
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_INFILESIZE_LARGE, chunk_size);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_UPLOAD, 1L);
|
||||
|
||||
session_manager_.update_auth_session(curl_handle, config_, hc);
|
||||
|
||||
read_data rd = {&stop_requested};
|
||||
if (reader) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_READDATA, reader);
|
||||
curl_easy_setopt(
|
||||
curl_handle, CURLOPT_READFUNCTION,
|
||||
static_cast<curl_read_callback>(utils::encryption::encrypting_reader::reader_function));
|
||||
} else {
|
||||
if (not nf) {
|
||||
native_file::create_or_open(source_path, nf);
|
||||
}
|
||||
|
||||
if (nf) {
|
||||
rd.nf = nf.get();
|
||||
rd.offset = offset;
|
||||
curl_easy_setopt(curl_handle, CURLOPT_READDATA, &rd);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_READFUNCTION, read_data_callback_);
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
event_system::instance().raise<comm_tus_upload_begin>(file_name, location, file_size, offset);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, location.c_str());
|
||||
|
||||
const auto curl_code = curl_easy_perform(curl_handle);
|
||||
|
||||
long http_code = -1;
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_HTTP_CODE, &http_code);
|
||||
|
||||
event_system::instance().raise<comm_tus_upload_end>(file_name, location, file_size, offset,
|
||||
curl_code, http_code);
|
||||
if ((ret = ((curl_code == CURLE_OK) && (http_code >= 200) && (http_code < 300)))) {
|
||||
file_size -= chunk_size;
|
||||
offset += chunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl_handle);
|
||||
curl_slist_free_all(hs);
|
||||
}
|
||||
|
||||
if (nf) {
|
||||
nf->close();
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
auto *curl_handle = utils::create_curl();
|
||||
|
||||
http_headers headers{};
|
||||
curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "HEAD");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, &headers);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, write_header_callback_);
|
||||
|
||||
session_manager_.update_auth_session(curl_handle, config_, hc);
|
||||
|
||||
struct curl_slist *hs = nullptr;
|
||||
hs = curl_slist_append(hs, "tus-resumable: 1.0.0");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hs);
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, location.c_str());
|
||||
|
||||
const auto res = curl_easy_perform(curl_handle);
|
||||
long http_code = -1;
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_HTTP_CODE, &http_code);
|
||||
|
||||
ret = ((res == CURLE_OK) && (http_code >= 200) && (http_code < 300));
|
||||
|
||||
skylink = headers["skynet-skylink"];
|
||||
|
||||
curl_easy_cleanup(curl_handle);
|
||||
curl_slist_free_all(hs);
|
||||
}
|
||||
|
||||
return ret;
|
||||
void curl_comm::enable_s3_path_style(bool enable) {
|
||||
use_s3_path_style_ = enable;
|
||||
}
|
||||
|
||||
bool curl_comm::tus_upload_create(host_config hc, const std::string &fileName,
|
||||
const std::uint64_t &fileSize, std::string &location) {
|
||||
auto tus_hc = hc;
|
||||
utils::string::replace(tus_hc.path, "/skyfile", "/tus");
|
||||
|
||||
auto *curl_handle = utils::create_curl();
|
||||
|
||||
const auto url = construct_url(curl_handle, "", tus_hc);
|
||||
event_system::instance().raise<comm_tus_upload_create_begin>(fileName, url);
|
||||
|
||||
http_headers headers{};
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 0);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, "");
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HEADERDATA, &headers);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, write_header_callback_);
|
||||
|
||||
const auto upload_length = "upload-length: " + utils::string::from_uint64(fileSize);
|
||||
const auto upload_metadata = "upload-metadata: filename " + macaron::Base64::Encode(fileName) +
|
||||
",filetype " + macaron::Base64::Encode("application/octet-stream");
|
||||
|
||||
struct curl_slist *hs = nullptr;
|
||||
hs = curl_slist_append(hs, "tus-resumable: 1.0.0");
|
||||
hs = curl_slist_append(hs, "Content-Length: 0");
|
||||
hs = curl_slist_append(hs, upload_length.c_str());
|
||||
hs = curl_slist_append(hs, upload_metadata.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hs);
|
||||
|
||||
session_manager_.update_auth_session(curl_handle, config_, hc);
|
||||
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
|
||||
|
||||
const auto res = curl_easy_perform(curl_handle);
|
||||
|
||||
long http_code = -1;
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_HTTP_CODE, &http_code);
|
||||
|
||||
const auto ret = (res == CURLE_OK && http_code == 201);
|
||||
if (ret) {
|
||||
location = headers["location"];
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl_handle);
|
||||
curl_slist_free_all(hs);
|
||||
|
||||
event_system::instance().raise<comm_tus_upload_create_end>(fileName, url, res, http_code);
|
||||
|
||||
return ret;
|
||||
auto curl_comm::make_request(const curl::requests::http_delete &del,
|
||||
long &response_code,
|
||||
stop_type &stop_requested) const -> bool {
|
||||
return make_request(
|
||||
s3_config_.has_value()
|
||||
? create_host_config(s3_config_.value(), use_s3_path_style_)
|
||||
: host_config_.value(),
|
||||
del, response_code, stop_requested);
|
||||
}
|
||||
|
||||
void curl_comm::update_auth_session(CURL *curl_handle, const app_config &config,
|
||||
const std::string &session) {
|
||||
const auto cookie_path = utils::path::combine(config.get_data_directory(), {session + ".txt"});
|
||||
curl_easy_setopt(curl_handle, CURLOPT_COOKIEFILE, cookie_path.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_COOKIEJAR, cookie_path.c_str());
|
||||
auto curl_comm::make_request(const curl::requests::http_get &get,
|
||||
long &response_code,
|
||||
stop_type &stop_requested) const -> bool {
|
||||
return make_request(
|
||||
s3_config_.has_value()
|
||||
? create_host_config(s3_config_.value(), use_s3_path_style_)
|
||||
: host_config_.value(),
|
||||
get, response_code, stop_requested);
|
||||
}
|
||||
|
||||
std::string curl_comm::url_encode(CURL *curl_handle, const std::string &data,
|
||||
const bool &allow_slash) {
|
||||
auto *value = curl_easy_escape(curl_handle, data.c_str(), 0);
|
||||
auto curl_comm::make_request(const curl::requests::http_head &head,
|
||||
long &response_code,
|
||||
stop_type &stop_requested) const -> bool {
|
||||
return make_request(
|
||||
s3_config_.has_value()
|
||||
? create_host_config(s3_config_.value(), use_s3_path_style_)
|
||||
: host_config_.value(),
|
||||
head, response_code, stop_requested);
|
||||
}
|
||||
|
||||
auto curl_comm::make_request(const curl::requests::http_put_file &put_file,
|
||||
long &response_code,
|
||||
stop_type &stop_requested) const -> bool {
|
||||
return make_request(
|
||||
s3_config_.has_value()
|
||||
? create_host_config(s3_config_.value(), use_s3_path_style_)
|
||||
: host_config_.value(),
|
||||
put_file, response_code, stop_requested);
|
||||
}
|
||||
|
||||
auto curl_comm::url_encode(CURL *curl, const std::string &data,
|
||||
bool allow_slash) -> std::string {
|
||||
auto *value =
|
||||
curl_easy_escape(curl, data.c_str(), static_cast<int>(data.size()));
|
||||
std::string ret = value;
|
||||
curl_free(value);
|
||||
|
||||
if (allow_slash) {
|
||||
utils::string::replace(ret, "%2F", "/");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
curl_comm::curl_read_callback curl_comm::read_data_callback_ =
|
||||
static_cast<curl_comm::curl_read_callback>(
|
||||
[](char *buffer, size_t size, size_t nitems, void *instream) -> size_t {
|
||||
auto *rd = reinterpret_cast<read_data *>(instream);
|
||||
std::size_t bytes_read{};
|
||||
const auto ret = rd->nf->read_bytes(buffer, size * nitems, rd->offset, bytes_read);
|
||||
if (ret) {
|
||||
rd->offset += bytes_read;
|
||||
}
|
||||
return ret && not *rd->stop_requested ? bytes_read : CURL_READFUNC_ABORT;
|
||||
});
|
||||
|
||||
curl_comm::curl_write_callback curl_comm::write_data_callback_ =
|
||||
static_cast<curl_comm::curl_write_callback>(
|
||||
[](char *buffer, size_t size, size_t nitems, void *outstream) -> size_t {
|
||||
auto *wd = reinterpret_cast<raw_write_data *>(outstream);
|
||||
std::copy(buffer, buffer + (size * nitems), std::back_inserter(*wd->buffer));
|
||||
return wd->stop_requested ? 0 : size * nitems;
|
||||
});
|
||||
|
||||
curl_comm::curl_write_callback curl_comm::write_header_callback_ =
|
||||
static_cast<curl_comm::curl_write_callback>(
|
||||
[](char *buffer, size_t size, size_t nitems, void *outstream) -> size_t {
|
||||
auto &headers = *reinterpret_cast<http_headers *>(outstream);
|
||||
const auto header = std::string(buffer, size * nitems);
|
||||
const auto parts = utils::string::split(header, ':');
|
||||
if (parts.size() > 1u) {
|
||||
auto data = header.substr(parts[0u].size() + 1u);
|
||||
utils::string::left_trim(data);
|
||||
utils::string::right_trim(data, '\r');
|
||||
utils::string::right_trim(data, '\n');
|
||||
utils::string::right_trim(data, '\r');
|
||||
headers[utils::string::to_lower(parts[0u])] = data;
|
||||
}
|
||||
return size * nitems;
|
||||
});
|
||||
|
||||
curl_comm::curl_write_callback curl_comm::write_null_callback_ =
|
||||
static_cast<curl_comm::curl_write_callback>(
|
||||
[](char *, size_t size, size_t nitems, void *) -> size_t { return size * nitems; });
|
||||
|
||||
curl_comm::curl_write_callback curl_comm::write_string_callback_ =
|
||||
static_cast<curl_comm::curl_write_callback>(
|
||||
[](char *buffer, size_t size, size_t nitems, void *outstream) -> size_t {
|
||||
(*reinterpret_cast<std::string *>(outstream)) += std::string(buffer, size * nitems);
|
||||
return size * nitems;
|
||||
});
|
||||
} // namespace repertory
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "comm/curl/curl_resolver.hpp"
|
||||
|
||||
namespace repertory {
|
||||
curl_resolver::curl_resolver(CURL *handle, std::vector<std::string> items, const bool &ignore_root)
|
||||
: items_(std::move(items)) {
|
||||
#ifndef _WIN32
|
||||
if (ignore_root && (getuid() == 0)) {
|
||||
items_.clear();
|
||||
}
|
||||
#endif
|
||||
for (const auto &item : items_) {
|
||||
host_list_ = curl_slist_append(host_list_, &item[0u]);
|
||||
}
|
||||
|
||||
if (host_list_) {
|
||||
curl_easy_setopt(handle, CURLOPT_RESOLVE, host_list_);
|
||||
}
|
||||
}
|
||||
|
||||
curl_resolver::~curl_resolver() {
|
||||
if (host_list_) {
|
||||
curl_slist_free_all(host_list_);
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,27 +1,33 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "comm/curl/multi_request.hpp"
|
||||
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
multi_request::multi_request(CURL *curl_handle, const bool &stop_requested)
|
||||
: curl_handle_(curl_handle), stop_requested_(stop_requested), multi_handle_(curl_multi_init()) {
|
||||
multi_request::multi_request(CURL *curl_handle, stop_type &stop_requested)
|
||||
: curl_handle_(curl_handle),
|
||||
stop_requested_(stop_requested),
|
||||
multi_handle_(curl_multi_init()) {
|
||||
curl_multi_add_handle(multi_handle_, curl_handle);
|
||||
}
|
||||
|
||||
@@ -48,9 +54,11 @@ void multi_request::get_result(CURLcode &curl_code, long &http_code) {
|
||||
|
||||
if (not stop_requested_) {
|
||||
int remaining_messages = 0;
|
||||
auto *multi_result = curl_multi_info_read(multi_handle_, &remaining_messages);
|
||||
auto *multi_result =
|
||||
curl_multi_info_read(multi_handle_, &remaining_messages);
|
||||
if (multi_result && (multi_result->msg == CURLMSG_DONE)) {
|
||||
curl_easy_getinfo(multi_result->easy_handle, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
curl_easy_getinfo(multi_result->easy_handle, CURLINFO_RESPONSE_CODE,
|
||||
&http_code);
|
||||
curl_code = multi_result->data.result;
|
||||
}
|
||||
}
|
||||
|
||||
80
src/comm/curl/requests/http_put_file.cpp
Normal file
80
src/comm/curl/requests/http_put_file.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "comm/curl/requests/http_put_file.hpp"
|
||||
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
namespace repertory::curl::requests {
|
||||
auto http_put_file::get_path() const -> std::string {
|
||||
if (reader) {
|
||||
auto updated_path = path;
|
||||
return utils::string::replace(updated_path, file_name,
|
||||
reader->get_encrypted_file_name());
|
||||
}
|
||||
|
||||
return http_request_base::get_path();
|
||||
}
|
||||
|
||||
auto http_put_file::set_method(CURL *curl, stop_type &stop_requested) const
|
||||
-> bool {
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
||||
|
||||
if (not source_path.empty()) {
|
||||
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
|
||||
if (not encryption_token.value_or("").empty()) {
|
||||
if (not reader) {
|
||||
reader = std::make_shared<utils::encryption::encrypting_reader>(
|
||||
file_name, source_path, stop_requested, encryption_token.value());
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_READDATA, reader.get());
|
||||
curl_easy_setopt(
|
||||
curl, CURLOPT_READFUNCTION,
|
||||
static_cast<curl_read_callback>(
|
||||
utils::encryption::encrypting_reader::reader_function));
|
||||
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE,
|
||||
reader->get_total_size());
|
||||
return true;
|
||||
}
|
||||
|
||||
read_info = std::make_shared<read_file_info>(read_file_info{
|
||||
stop_requested,
|
||||
});
|
||||
|
||||
if (native_file::open(source_path, read_info->nf) != api_error::success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
read_info->nf->set_auto_close(true);
|
||||
|
||||
std::uint64_t file_size{};
|
||||
if (not read_info->nf->get_file_size(file_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_READDATA, read_info.get());
|
||||
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_file_data);
|
||||
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, file_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace repertory::curl::requests
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "comm/curl/session_manager.hpp"
|
||||
#include "app_config.hpp"
|
||||
#include "comm/curl/curl_comm.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
bool session_manager::create_auth_session(CURL *&curl_handle, const app_config &config,
|
||||
host_config hc, std::string &session) {
|
||||
auto ret = true;
|
||||
if (not curl_handle) {
|
||||
curl_handle = utils::create_curl();
|
||||
}
|
||||
|
||||
if (not hc.auth_url.empty() && not hc.auth_user.empty()) {
|
||||
mutex_lock l(session_mutex_);
|
||||
if (session_.empty()) {
|
||||
if ((ret = curl_comm::create_auth_session(curl_handle, config, hc, session))) {
|
||||
session_ = session;
|
||||
}
|
||||
} else {
|
||||
session = session_;
|
||||
curl_comm::update_auth_session(curl_handle, config, session_);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
session_count_++;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void session_manager::release_auth_session(const app_config &config, host_config hc,
|
||||
const std::string &session) {
|
||||
if (not hc.auth_url.empty() && not hc.auth_user.empty()) {
|
||||
mutex_lock l(session_mutex_);
|
||||
if (not session_.empty() && (session == session_) && session_count_) {
|
||||
if (not --session_count_) {
|
||||
curl_comm::release_auth_session(config, hc, session_);
|
||||
session_.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void session_manager::update_auth_session(CURL *curl_handle, const app_config &config,
|
||||
const host_config &hc) {
|
||||
mutex_lock l(session_mutex_);
|
||||
if (hc.auth_url.empty() || hc.auth_user.empty() || session_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
curl_comm::update_auth_session(curl_handle, config, session_);
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,29 +1,35 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "comm/packet/client_pool.hpp"
|
||||
#include "events/events.hpp"
|
||||
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
void client_pool::pool::execute(const std::uint64_t &thread_id, const worker_callback &worker,
|
||||
const worker_complete_callback &worker_complete) {
|
||||
const auto index = static_cast<std::size_t>(thread_id % pool_queues_.size());
|
||||
void client_pool::pool::execute(
|
||||
std::uint64_t thread_id, const worker_callback &worker,
|
||||
const worker_complete_callback &worker_complete) {
|
||||
const auto index = thread_id % pool_queues_.size();
|
||||
auto wi = std::make_shared<work_item>(worker, worker_complete);
|
||||
auto &pool_queue = pool_queues_[index];
|
||||
|
||||
@@ -33,14 +39,15 @@ void client_pool::pool::execute(const std::uint64_t &thread_id, const worker_cal
|
||||
queue_lock.unlock();
|
||||
}
|
||||
|
||||
client_pool::pool::pool(const std::uint8_t &pool_size) {
|
||||
client_pool::pool::pool(std::uint8_t pool_size) {
|
||||
event_system::instance().raise<service_started>("client_pool");
|
||||
thread_index_ = 0u;
|
||||
for (std::uint8_t i = 0u; i < pool_size; i++) {
|
||||
pool_queues_.emplace_back(std::make_unique<work_queue>());
|
||||
}
|
||||
|
||||
for (std::size_t i = 0u; i < pool_queues_.size(); i++) {
|
||||
pool_threads_.emplace_back(std::thread([this]() {
|
||||
pool_threads_.emplace_back([this]() {
|
||||
const auto thread_index = thread_index_++;
|
||||
|
||||
auto &pool_queue = pool_queues_[thread_index];
|
||||
@@ -58,18 +65,18 @@ client_pool::pool::pool(const std::uint8_t &pool_size) {
|
||||
}
|
||||
|
||||
while (not queue.empty()) {
|
||||
auto workItem = queue.front();
|
||||
auto item = queue.front();
|
||||
queue.pop_front();
|
||||
queue_notify.notify_all();
|
||||
queue_lock.unlock();
|
||||
|
||||
try {
|
||||
const auto result = workItem->work();
|
||||
workItem->work_complete(result);
|
||||
const auto result = item->work();
|
||||
item->work_complete(result);
|
||||
} catch (const std::exception &e) {
|
||||
workItem->work_complete(utils::translate_api_error(api_error::error));
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__,
|
||||
e.what() ? e.what() : "unknown");
|
||||
item->work_complete(utils::from_api_error(api_error::error));
|
||||
utils::error::raise_error(__FUNCTION__, e,
|
||||
"exception occurred in work item");
|
||||
}
|
||||
|
||||
queue_lock.lock();
|
||||
@@ -86,14 +93,14 @@ client_pool::pool::pool(const std::uint8_t &pool_size) {
|
||||
queue_notify.notify_all();
|
||||
queue_lock.unlock();
|
||||
|
||||
wi->work_complete(utils::translate_api_error(api_error::download_stopped));
|
||||
wi->work_complete(utils::from_api_error(api_error::download_stopped));
|
||||
|
||||
queue_lock.lock();
|
||||
}
|
||||
|
||||
queue_notify.notify_all();
|
||||
queue_lock.unlock();
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +120,7 @@ void client_pool::pool::shutdown() {
|
||||
pool_threads_.clear();
|
||||
}
|
||||
|
||||
void client_pool::execute(const std::string &client_id, const std::uint64_t &thread_id,
|
||||
void client_pool::execute(const std::string &client_id, std::uint64_t thread_id,
|
||||
const worker_callback &worker,
|
||||
const worker_complete_callback &worker_complete) {
|
||||
unique_mutex_lock pool_lock(pool_mutex_);
|
||||
@@ -136,15 +143,18 @@ void client_pool::remove_client(const std::string &client_id) {
|
||||
}
|
||||
|
||||
void client_pool::shutdown() {
|
||||
unique_mutex_lock pool_lock(pool_mutex_);
|
||||
if (not shutdown_) {
|
||||
shutdown_ = true;
|
||||
event_system::instance().raise<service_shutdown>("client_pool");
|
||||
for (auto &kv : pool_lookup_) {
|
||||
kv.second->shutdown();
|
||||
event_system::instance().raise<service_shutdown_begin>("client_pool");
|
||||
unique_mutex_lock pool_lock(pool_mutex_);
|
||||
if (not shutdown_) {
|
||||
shutdown_ = true;
|
||||
for (auto &kv : pool_lookup_) {
|
||||
kv.second->shutdown();
|
||||
}
|
||||
pool_lookup_.clear();
|
||||
}
|
||||
pool_lookup_.clear();
|
||||
pool_lock.unlock();
|
||||
event_system::instance().raise<service_shutdown_end>("client_pool");
|
||||
}
|
||||
pool_lock.unlock();
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "comm/packet/packet.hpp"
|
||||
#include "events/events.hpp"
|
||||
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/remote.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
@@ -30,46 +35,47 @@ void packet::clear() {
|
||||
decode_offset_ = 0u;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::string &data) {
|
||||
auto packet::decode(std::string &data) -> packet::error_type {
|
||||
const auto *str = &buffer_[decode_offset_];
|
||||
const auto length = strnlen(str, buffer_.size() - decode_offset_);
|
||||
data = std::string(str, length);
|
||||
decode_offset_ += (length + 1);
|
||||
|
||||
return utils::translate_api_error(api_error::success);
|
||||
return utils::from_api_error(api_error::success);
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::wstring &data) {
|
||||
auto packet::decode(std::wstring &data) -> packet::error_type {
|
||||
std::string utf8_string;
|
||||
const auto ret = decode(utf8_string);
|
||||
if (ret == 0) {
|
||||
data = utils::string::from_utf8(utf8_string);
|
||||
}
|
||||
|
||||
return utils::translate_api_error(api_error::success);
|
||||
return utils::from_api_error(api_error::success);
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(void *&ptr) {
|
||||
auto packet::decode(void *&ptr) -> packet::error_type {
|
||||
return decode(reinterpret_cast<std::uint64_t &>(ptr));
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(void *buffer, const size_t &size) {
|
||||
auto packet::decode(void *buffer, std::size_t size) -> packet::error_type {
|
||||
if (size) {
|
||||
const auto read_size = utils::calculate_read_size(buffer_.size(), size, decode_offset_);
|
||||
const auto read_size =
|
||||
utils::calculate_read_size(buffer_.size(), size, decode_offset_);
|
||||
if (read_size == size) {
|
||||
memcpy(buffer, &buffer_[decode_offset_], size);
|
||||
decode_offset_ += size;
|
||||
return utils::translate_api_error(api_error::success);
|
||||
return utils::from_api_error(api_error::success);
|
||||
}
|
||||
|
||||
return ((decode_offset_ + size) > buffer_.size())
|
||||
? utils::translate_api_error(api_error::buffer_overflow)
|
||||
: utils::translate_api_error(api_error::buffer_too_small);
|
||||
? utils::from_api_error(api_error::buffer_overflow)
|
||||
: utils::from_api_error(api_error::buffer_too_small);
|
||||
}
|
||||
return utils::translate_api_error(api_error::success);
|
||||
return utils::from_api_error(api_error::success);
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::int8_t &i) {
|
||||
auto packet::decode(std::int8_t &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i);
|
||||
@@ -77,7 +83,7 @@ packet::error_type packet::decode(std::int8_t &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::uint8_t &i) {
|
||||
auto packet::decode(std::uint8_t &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i);
|
||||
@@ -85,7 +91,7 @@ packet::error_type packet::decode(std::uint8_t &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::int16_t &i) {
|
||||
auto packet::decode(std::int16_t &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i);
|
||||
@@ -93,7 +99,7 @@ packet::error_type packet::decode(std::int16_t &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::uint16_t &i) {
|
||||
auto packet::decode(std::uint16_t &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i);
|
||||
@@ -101,7 +107,7 @@ packet::error_type packet::decode(std::uint16_t &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::int32_t &i) {
|
||||
auto packet::decode(std::int32_t &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i);
|
||||
@@ -109,7 +115,7 @@ packet::error_type packet::decode(std::int32_t &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::uint32_t &i) {
|
||||
auto packet::decode(std::uint32_t &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i);
|
||||
@@ -117,7 +123,7 @@ packet::error_type packet::decode(std::uint32_t &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::int64_t &i) {
|
||||
auto packet::decode(std::int64_t &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i);
|
||||
@@ -125,7 +131,7 @@ packet::error_type packet::decode(std::int64_t &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(std::uint64_t &i) {
|
||||
auto packet::decode(std::uint64_t &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i);
|
||||
@@ -133,7 +139,7 @@ packet::error_type packet::decode(std::uint64_t &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(remote::setattr_x &i) {
|
||||
auto packet::decode(remote::setattr_x &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i.acctime);
|
||||
@@ -152,7 +158,7 @@ packet::error_type packet::decode(remote::setattr_x &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(remote::stat &i) {
|
||||
auto packet::decode(remote::stat &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i.st_mode);
|
||||
@@ -172,7 +178,7 @@ packet::error_type packet::decode(remote::stat &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(remote::statfs &i) {
|
||||
auto packet::decode(remote::statfs &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i.f_bavail);
|
||||
@@ -185,7 +191,7 @@ packet::error_type packet::decode(remote::statfs &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(remote::statfs_x &i) {
|
||||
auto packet::decode(remote::statfs_x &i) -> packet::error_type {
|
||||
auto ret = decode(*dynamic_cast<remote::statfs *>(&i));
|
||||
if (ret == 0) {
|
||||
ret = decode(&i.f_mntfromname[0], 1024);
|
||||
@@ -193,7 +199,7 @@ packet::error_type packet::decode(remote::statfs_x &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decode(remote::file_info &i) {
|
||||
auto packet::decode(remote::file_info &i) -> packet::error_type {
|
||||
const auto ret = decode(&i, sizeof(i));
|
||||
if (ret == 0) {
|
||||
boost::endian::big_to_native_inplace(i.AllocationSize);
|
||||
@@ -212,15 +218,14 @@ packet::error_type packet::decode(remote::file_info &i) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int packet::decode_json(packet &response, json &json_data) {
|
||||
auto packet::decode_json(packet &response, json &json_data) -> int {
|
||||
int ret = 0;
|
||||
std::string data;
|
||||
if ((ret = response.decode(data)) == 0) {
|
||||
try {
|
||||
json_data = json::parse(data);
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
__FUNCTION__, e.what() ? e.what() : "Failed to parse JSON string");
|
||||
utils::error::raise_error(__FUNCTION__, e, "failed to parse json string");
|
||||
ret = -EIO;
|
||||
}
|
||||
}
|
||||
@@ -228,25 +233,26 @@ int packet::decode_json(packet &response, json &json_data) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type packet::decrypt(const std::string &token) {
|
||||
auto ret = utils::translate_api_error(api_error::success);
|
||||
auto packet::decrypt(const std::string &token) -> packet::error_type {
|
||||
auto ret = utils::from_api_error(api_error::success);
|
||||
try {
|
||||
std::vector<char> result;
|
||||
data_buffer result;
|
||||
if (not utils::encryption::decrypt_data(token, &buffer_[decode_offset_],
|
||||
buffer_.size() - decode_offset_, result)) {
|
||||
throw std::runtime_error("Decryption failed");
|
||||
buffer_.size() - decode_offset_,
|
||||
result)) {
|
||||
throw std::runtime_error("decryption failed");
|
||||
}
|
||||
buffer_ = std::move(result);
|
||||
decode_offset_ = 0;
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, e.what());
|
||||
ret = utils::translate_api_error(api_error::error);
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
ret = utils::from_api_error(api_error::error);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void packet::encode(const void *buffer, const std::size_t &size, bool should_reserve) {
|
||||
void packet::encode(const void *buffer, std::size_t size, bool should_reserve) {
|
||||
if (size) {
|
||||
if (should_reserve) {
|
||||
buffer_.reserve(buffer_.size() + size);
|
||||
@@ -263,11 +269,17 @@ void packet::encode(const std::string &str) {
|
||||
buffer_.emplace_back(0);
|
||||
}
|
||||
|
||||
void packet::encode(wchar_t *str) { encode(utils::string::to_utf8(str ? str : L"")); }
|
||||
void packet::encode(wchar_t *str) {
|
||||
encode(utils::string::to_utf8(str ? str : L""));
|
||||
}
|
||||
|
||||
void packet::encode(const wchar_t *str) { encode(utils::string::to_utf8(str ? str : L"")); }
|
||||
void packet::encode(const wchar_t *str) {
|
||||
encode(utils::string::to_utf8(str ? str : L""));
|
||||
}
|
||||
|
||||
void packet::encode(const std::wstring &str) { encode(utils::string::to_utf8(str)); }
|
||||
void packet::encode(const std::wstring &str) {
|
||||
encode(utils::string::to_utf8(str));
|
||||
}
|
||||
|
||||
void packet::encode(std::int8_t i) {
|
||||
boost::endian::native_to_big_inplace(i);
|
||||
@@ -375,7 +387,8 @@ void packet::encode(remote::file_info i) {
|
||||
encode(&i, sizeof(i), true);
|
||||
}
|
||||
|
||||
void packet::encode_top(const void *buffer, const std::size_t &size, bool should_reserve) {
|
||||
void packet::encode_top(const void *buffer, std::size_t size,
|
||||
bool should_reserve) {
|
||||
if (size) {
|
||||
if (should_reserve) {
|
||||
buffer_.reserve(buffer_.size() + size);
|
||||
@@ -386,13 +399,15 @@ void packet::encode_top(const void *buffer, const std::size_t &size, bool should
|
||||
}
|
||||
|
||||
void packet::encode_top(const std::string &str) {
|
||||
const auto len = strnlen(&str[0], str.size());
|
||||
buffer_.reserve(len + 1 + buffer_.size());
|
||||
const auto len = strnlen(str.c_str(), str.size());
|
||||
buffer_.reserve(len + 1U + buffer_.size());
|
||||
encode_top(&str[0], len, false);
|
||||
buffer_.insert(buffer_.begin() + len, 0);
|
||||
buffer_.insert(buffer_.begin() + static_cast<std::int32_t>(len), 0);
|
||||
}
|
||||
|
||||
void packet::encode_top(const std::wstring &str) { encode_top(utils::string::to_utf8(str)); }
|
||||
void packet::encode_top(const std::wstring &str) {
|
||||
encode_top(utils::string::to_utf8(str));
|
||||
}
|
||||
|
||||
void packet::encode_top(std::int8_t i) {
|
||||
boost::endian::native_to_big_inplace(i);
|
||||
@@ -502,22 +517,22 @@ void packet::encode_top(remote::file_info i) {
|
||||
|
||||
void packet::encrypt(const std::string &token) {
|
||||
try {
|
||||
std::vector<char> result;
|
||||
data_buffer result;
|
||||
utils::encryption::encrypt_data(token, buffer_, result);
|
||||
buffer_ = std::move(result);
|
||||
encode_top(static_cast<std::uint32_t>(buffer_.size()));
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, e.what());
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
}
|
||||
|
||||
void packet::transfer_into(std::vector<char> &buffer) {
|
||||
void packet::transfer_into(data_buffer &buffer) {
|
||||
buffer = std::move(buffer_);
|
||||
buffer_ = std::vector<char>();
|
||||
buffer_ = data_buffer();
|
||||
decode_offset_ = 0;
|
||||
}
|
||||
|
||||
packet &packet::operator=(const std::vector<char> &buffer) noexcept {
|
||||
auto packet::operator=(const data_buffer &buffer) noexcept -> packet & {
|
||||
if (&buffer_ != &buffer) {
|
||||
buffer_ = buffer;
|
||||
decode_offset_ = 0;
|
||||
@@ -526,7 +541,7 @@ packet &packet::operator=(const std::vector<char> &buffer) noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
packet &packet::operator=(std::vector<char> &&buffer) noexcept {
|
||||
auto packet::operator=(data_buffer &&buffer) noexcept -> packet & {
|
||||
if (&buffer_ != &buffer) {
|
||||
buffer_ = std::move(buffer);
|
||||
decode_offset_ = 0;
|
||||
@@ -535,7 +550,7 @@ packet &packet::operator=(std::vector<char> &&buffer) noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
packet &packet::operator=(const packet &p) noexcept {
|
||||
auto packet::operator=(const packet &p) noexcept -> packet & {
|
||||
if (this != &p) {
|
||||
buffer_ = p.buffer_;
|
||||
decode_offset_ = p.decode_offset_;
|
||||
@@ -544,7 +559,7 @@ packet &packet::operator=(const packet &p) noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
packet &packet::operator=(packet &&p) noexcept {
|
||||
auto packet::operator=(packet &&p) noexcept -> packet & {
|
||||
if (this != &p) {
|
||||
buffer_ = std::move(p.buffer_);
|
||||
decode_offset_ = p.decode_offset_;
|
||||
|
||||
@@ -1,37 +1,44 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "comm/packet/packet_client.hpp"
|
||||
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/timeout.hpp"
|
||||
|
||||
namespace repertory {
|
||||
// clang-format off
|
||||
E_SIMPLE2(packet_client_timeout, error, true,
|
||||
std::string, event_name, en, E_STRING,
|
||||
E_SIMPLE2(packet_client_timeout, error, true,
|
||||
std::string, event_name, en, E_STRING,
|
||||
std::string, message, msg, E_STRING
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
packet_client::packet_client(std::string host_name_or_ip, const std::uint8_t &max_connections,
|
||||
const std::uint16_t &port, const std::uint16_t &receive_timeout,
|
||||
const std::uint16_t &send_timeout, std::string encryption_token)
|
||||
packet_client::packet_client(std::string host_name_or_ip,
|
||||
std::uint8_t max_connections, std::uint16_t port,
|
||||
std::uint16_t receive_timeout,
|
||||
std::uint16_t send_timeout,
|
||||
std::string encryption_token)
|
||||
: io_context_(),
|
||||
host_name_or_ip_(std::move(host_name_or_ip)),
|
||||
max_connections_(max_connections ? max_connections : 20u),
|
||||
@@ -47,10 +54,10 @@ packet_client::~packet_client() {
|
||||
io_context_.stop();
|
||||
}
|
||||
|
||||
void packet_client::close(client &client) const {
|
||||
void packet_client::close(client &cli) const {
|
||||
try {
|
||||
boost::system::error_code ec;
|
||||
client.socket.close(ec);
|
||||
cli.socket.close(ec);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
@@ -65,8 +72,7 @@ void packet_client::close_all() {
|
||||
unique_id_ = utils::create_uuid_string();
|
||||
}
|
||||
|
||||
bool packet_client::connect(client &c) {
|
||||
auto ret = false;
|
||||
void packet_client::connect(client &c) {
|
||||
try {
|
||||
resolve();
|
||||
boost::asio::connect(c.socket, resolve_results_);
|
||||
@@ -74,16 +80,16 @@ bool packet_client::connect(client &c) {
|
||||
c.socket.set_option(boost::asio::socket_base::linger(false, 0));
|
||||
|
||||
packet response;
|
||||
read_packet(c, response);
|
||||
|
||||
ret = true;
|
||||
const auto res = read_packet(c, response);
|
||||
if (res != 0) {
|
||||
throw std::runtime_error(std::to_string(res));
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, e.what());
|
||||
utils::error::raise_error(__FUNCTION__, e, "connection handshake failed");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::shared_ptr<packet_client::client> packet_client::get_client() {
|
||||
auto packet_client::get_client() -> std::shared_ptr<packet_client::client> {
|
||||
std::shared_ptr<client> ret;
|
||||
|
||||
unique_mutex_lock clients_lock(clients_mutex_);
|
||||
@@ -109,22 +115,25 @@ void packet_client::put_client(std::shared_ptr<client> &c) {
|
||||
}
|
||||
}
|
||||
|
||||
packet::error_type packet_client::read_packet(client &c, packet &response) {
|
||||
std::vector<char> buffer(sizeof(std::uint32_t));
|
||||
auto packet_client::read_packet(client &c, packet &response)
|
||||
-> packet::error_type {
|
||||
data_buffer buffer(sizeof(std::uint32_t));
|
||||
const auto read_buffer = [&]() {
|
||||
std::uint32_t offset = 0u;
|
||||
while (offset < buffer.size()) {
|
||||
const auto bytes_read =
|
||||
boost::asio::read(c.socket, boost::asio::buffer(&buffer[offset], buffer.size() - offset));
|
||||
const auto bytes_read = boost::asio::read(
|
||||
c.socket,
|
||||
boost::asio::buffer(&buffer[offset], buffer.size() - offset));
|
||||
if (bytes_read <= 0) {
|
||||
throw std::runtime_error("Read failed: " + std::to_string(bytes_read));
|
||||
throw std::runtime_error("read failed|" + std::to_string(bytes_read));
|
||||
}
|
||||
offset += static_cast<std::uint32_t>(bytes_read);
|
||||
}
|
||||
};
|
||||
read_buffer();
|
||||
|
||||
const auto size = boost::endian::big_to_native(*reinterpret_cast<std::uint32_t *>(&buffer[0u]));
|
||||
const auto size = boost::endian::big_to_native(
|
||||
*reinterpret_cast<std::uint32_t *>(&buffer[0u]));
|
||||
buffer.resize(size);
|
||||
|
||||
read_buffer();
|
||||
@@ -140,34 +149,37 @@ packet::error_type packet_client::read_packet(client &c, packet &response) {
|
||||
|
||||
void packet_client::resolve() {
|
||||
if (resolve_results_.empty()) {
|
||||
resolve_results_ =
|
||||
tcp::resolver(io_context_).resolve({host_name_or_ip_, std::to_string(port_)});
|
||||
resolve_results_ = tcp::resolver(io_context_)
|
||||
.resolve({host_name_or_ip_, std::to_string(port_)});
|
||||
}
|
||||
}
|
||||
|
||||
packet::error_type packet_client::send(const std::string &method, std::uint32_t &service_flags) {
|
||||
auto packet_client::send(const std::string &method,
|
||||
std::uint32_t &service_flags) -> packet::error_type {
|
||||
packet request;
|
||||
return send(method, request, service_flags);
|
||||
}
|
||||
|
||||
packet::error_type packet_client::send(const std::string &method, packet &request,
|
||||
std::uint32_t &service_flags) {
|
||||
auto packet_client::send(const std::string &method, packet &request,
|
||||
std::uint32_t &service_flags) -> packet::error_type {
|
||||
packet response;
|
||||
return send(method, request, response, service_flags);
|
||||
}
|
||||
|
||||
packet::error_type packet_client::send(const std::string &method, packet &request, packet &response,
|
||||
std::uint32_t &service_flags) {
|
||||
auto packet_client::send(const std::string &method, packet &request,
|
||||
packet &response, std::uint32_t &service_flags)
|
||||
-> packet::error_type {
|
||||
auto success = false;
|
||||
packet::error_type ret = utils::translate_api_error(api_error::error);
|
||||
packet::error_type ret = utils::from_api_error(api_error::error);
|
||||
request.encode_top(method);
|
||||
request.encode_top(utils::get_thread_id());
|
||||
request.encode_top(unique_id_);
|
||||
request.encode_top(PACKET_SERVICE_FLAGS);
|
||||
request.encode_top(get_repertory_version());
|
||||
|
||||
static const auto max_attempts = 2;
|
||||
for (auto i = 1; allow_connections_ && not success && (i <= max_attempts); i++) {
|
||||
static const std::uint8_t max_attempts = 5u;
|
||||
for (std::uint8_t i = 1u;
|
||||
allow_connections_ && not success && (i <= max_attempts); i++) {
|
||||
auto c = get_client();
|
||||
if (c) {
|
||||
try {
|
||||
@@ -176,7 +188,8 @@ packet::error_type packet_client::send(const std::string &method, packet &reques
|
||||
|
||||
timeout request_timeout(
|
||||
[this, method, c]() {
|
||||
event_system::instance().raise<packet_client_timeout>("Request", method);
|
||||
event_system::instance().raise<packet_client_timeout>("request",
|
||||
method);
|
||||
close(*c.get());
|
||||
},
|
||||
std::chrono::seconds(send_timeout_));
|
||||
@@ -184,9 +197,11 @@ packet::error_type packet_client::send(const std::string &method, packet &reques
|
||||
std::uint32_t offset = 0u;
|
||||
while (offset < request.get_size()) {
|
||||
const auto bytes_written = boost::asio::write(
|
||||
c->socket, boost::asio::buffer(&request[offset], request.get_size() - offset));
|
||||
c->socket, boost::asio::buffer(&request[offset],
|
||||
request.get_size() - offset));
|
||||
if (bytes_written <= 0) {
|
||||
throw std::runtime_error("Write failed: " + std::to_string(bytes_written));
|
||||
throw std::runtime_error("write failed|" +
|
||||
std::to_string(bytes_written));
|
||||
}
|
||||
offset += static_cast<std::uint32_t>(bytes_written);
|
||||
}
|
||||
@@ -194,7 +209,8 @@ packet::error_type packet_client::send(const std::string &method, packet &reques
|
||||
|
||||
timeout response_timeout(
|
||||
[this, method, c]() {
|
||||
event_system::instance().raise<packet_client_timeout>("Response", method);
|
||||
event_system::instance().raise<packet_client_timeout>("response",
|
||||
method);
|
||||
close(*c.get());
|
||||
},
|
||||
std::chrono::seconds(receive_timeout_));
|
||||
@@ -202,13 +218,17 @@ packet::error_type packet_client::send(const std::string &method, packet &reques
|
||||
ret = read_packet(*c, response);
|
||||
response_timeout.disable();
|
||||
if (ret == 0) {
|
||||
response.decode(service_flags);
|
||||
response.decode(ret);
|
||||
success = true;
|
||||
put_client(c);
|
||||
if ((ret = response.decode(service_flags)) == 0) {
|
||||
packet::error_type res{};
|
||||
if ((ret = response.decode(res)) == 0) {
|
||||
ret = res;
|
||||
success = true;
|
||||
put_client(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, e.what());
|
||||
utils::error::raise_error(__FUNCTION__, e, "send failed");
|
||||
close_all();
|
||||
if (allow_connections_ && (i < max_attempts)) {
|
||||
std::this_thread::sleep_for(1s);
|
||||
@@ -217,7 +237,7 @@ packet::error_type packet_client::send(const std::string &method, packet &reques
|
||||
}
|
||||
|
||||
if (not allow_connections_) {
|
||||
ret = utils::translate_api_error(api_error::error);
|
||||
ret = utils::from_api_error(api_error::error);
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,49 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "comm/packet/packet_server.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
|
||||
#include "comm/packet/packet.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
packet_server::packet_server(const std::uint16_t &port, std::string token, std::uint8_t pool_size,
|
||||
closed_callback closed, message_handler_callback message_handler)
|
||||
: encryption_token_(std::move(token)), closed_(closed), message_handler_(message_handler) {
|
||||
using std::thread;
|
||||
|
||||
packet_server::packet_server(std::uint16_t port, std::string token,
|
||||
std::uint8_t pool_size, closed_callback closed,
|
||||
message_handler_callback message_handler)
|
||||
: encryption_token_(std::move(token)),
|
||||
closed_(std::move(closed)),
|
||||
message_handler_(std::move(message_handler)) {
|
||||
initialize(port, pool_size);
|
||||
event_system::instance().raise<service_started>("packet_server");
|
||||
}
|
||||
|
||||
packet_server::~packet_server() {
|
||||
event_system::instance().raise<service_shutdown>("packet_server");
|
||||
event_system::instance().raise<service_shutdown_begin>("packet_server");
|
||||
std::thread([this]() {
|
||||
for (std::size_t i = 0u; i < service_threads_.size(); i++) {
|
||||
io_context_.stop();
|
||||
@@ -40,6 +52,7 @@ packet_server::~packet_server() {
|
||||
|
||||
server_thread_->join();
|
||||
server_thread_.reset();
|
||||
event_system::instance().raise<service_shutdown_end>("packet_server");
|
||||
}
|
||||
|
||||
void packet_server::add_client(connection &c, const std::string &client_id) {
|
||||
@@ -64,12 +77,13 @@ void packet_server::initialize(const uint16_t &port, uint8_t pool_size) {
|
||||
acceptor.bind(endpoint);
|
||||
acceptor.listen();
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, e.what());
|
||||
repertory::utils::error::raise_error(__FUNCTION__, e,
|
||||
"exception occurred");
|
||||
}
|
||||
listen_for_connection(acceptor);
|
||||
|
||||
for (std::uint8_t i = 0u; i < pool_size; i++) {
|
||||
service_threads_.emplace_back(std::thread([this]() { io_context_.run(); }));
|
||||
service_threads_.emplace_back([this]() { io_context_.run(); });
|
||||
}
|
||||
|
||||
for (auto &th : service_threads_) {
|
||||
@@ -80,14 +94,16 @@ void packet_server::initialize(const uint16_t &port, uint8_t pool_size) {
|
||||
|
||||
void packet_server::listen_for_connection(tcp::acceptor &acceptor) {
|
||||
auto c = std::make_shared<packet_server::connection>(io_context_, acceptor);
|
||||
acceptor.async_accept(
|
||||
c->socket, boost::bind(&packet_server::on_accept, this, c, boost::asio::placeholders::error));
|
||||
acceptor.async_accept(c->socket,
|
||||
boost::bind(&packet_server::on_accept, this, c,
|
||||
boost::asio::placeholders::error));
|
||||
}
|
||||
|
||||
void packet_server::on_accept(std::shared_ptr<connection> c, boost::system::error_code ec) {
|
||||
void packet_server::on_accept(std::shared_ptr<connection> c,
|
||||
boost::system::error_code ec) {
|
||||
listen_for_connection(c->acceptor);
|
||||
if (ec) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, ec.message());
|
||||
utils::error::raise_error(__FUNCTION__, ec.message());
|
||||
std::this_thread::sleep_for(1s);
|
||||
} else {
|
||||
c->socket.set_option(boost::asio::ip::tcp::no_delay(true));
|
||||
@@ -101,30 +117,34 @@ void packet_server::on_accept(std::shared_ptr<connection> c, boost::system::erro
|
||||
}
|
||||
|
||||
void packet_server::read_header(std::shared_ptr<connection> c) {
|
||||
static const std::string function_name = __FUNCTION__;
|
||||
|
||||
c->buffer.resize(sizeof(std::uint32_t));
|
||||
boost::asio::async_read(c->socket, boost::asio::buffer(&c->buffer[0u], c->buffer.size()),
|
||||
[this, c](boost::system::error_code ec, std::size_t) {
|
||||
if (ec) {
|
||||
remove_client(*c);
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__,
|
||||
ec.message());
|
||||
} else {
|
||||
auto to_read = *reinterpret_cast<std::uint32_t *>(&c->buffer[0u]);
|
||||
boost::endian::big_to_native_inplace(to_read);
|
||||
read_packet(c, to_read);
|
||||
}
|
||||
});
|
||||
boost::asio::async_read(
|
||||
c->socket, boost::asio::buffer(&c->buffer[0u], c->buffer.size()),
|
||||
[this, c](boost::system::error_code ec, std::size_t) {
|
||||
if (ec) {
|
||||
remove_client(*c);
|
||||
repertory::utils::error::raise_error(function_name, ec.message());
|
||||
} else {
|
||||
auto to_read = *reinterpret_cast<std::uint32_t *>(&c->buffer[0u]);
|
||||
boost::endian::big_to_native_inplace(to_read);
|
||||
read_packet(c, to_read);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void packet_server::read_packet(std::shared_ptr<connection> c, const std::uint32_t &data_size) {
|
||||
void packet_server::read_packet(std::shared_ptr<connection> c,
|
||||
std::uint32_t data_size) {
|
||||
try {
|
||||
const auto read_buffer = [&]() {
|
||||
std::uint32_t offset = 0u;
|
||||
while (offset < c->buffer.size()) {
|
||||
const auto bytes_read = boost::asio::read(
|
||||
c->socket, boost::asio::buffer(&c->buffer[offset], c->buffer.size() - offset));
|
||||
c->socket,
|
||||
boost::asio::buffer(&c->buffer[offset], c->buffer.size() - offset));
|
||||
if (bytes_read <= 0) {
|
||||
throw std::runtime_error("read failed: " + std::to_string(bytes_read));
|
||||
throw std::runtime_error("read failed|" + std::to_string(bytes_read));
|
||||
}
|
||||
offset += static_cast<std::uint32_t>(bytes_read);
|
||||
}
|
||||
@@ -147,7 +167,8 @@ void packet_server::read_packet(std::shared_ptr<connection> c, const std::uint32
|
||||
|
||||
std::string version;
|
||||
if ((ret = request->decode(version)) == 0) {
|
||||
if (utils::compare_version_strings(version, MIN_REMOTE_VERSION) >= 0) {
|
||||
if (utils::compare_version_strings(
|
||||
version, REPERTORY_MIN_REMOTE_VERSION) >= 0) {
|
||||
std::uint32_t service_flags = 0u;
|
||||
DECODE_OR_IGNORE(request, service_flags);
|
||||
|
||||
@@ -166,17 +187,18 @@ void packet_server::read_packet(std::shared_ptr<connection> c, const std::uint32
|
||||
}
|
||||
|
||||
should_send_response = false;
|
||||
message_handler_(service_flags, client_id, thread_id, method, request.get(),
|
||||
*response,
|
||||
[this, c, request, response](const packet::error_type &result) {
|
||||
message_handler_(service_flags, client_id, thread_id, method,
|
||||
request.get(), *response,
|
||||
[this, c, request,
|
||||
response](const packet::error_type &result) {
|
||||
this->send_response(c, result, *response);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ret = utils::translate_api_error(api_error::incompatible_version);
|
||||
ret = utils::from_api_error(api_error::incompatible_version);
|
||||
}
|
||||
} else {
|
||||
ret = utils::translate_api_error(api_error::invalid_version);
|
||||
ret = utils::from_api_error(api_error::invalid_version);
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error("invalid nonce");
|
||||
@@ -189,7 +211,7 @@ void packet_server::read_packet(std::shared_ptr<connection> c, const std::uint32
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
remove_client(*c);
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, e.what());
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,23 +225,26 @@ void packet_server::remove_client(connection &c) {
|
||||
}
|
||||
}
|
||||
|
||||
void packet_server::send_response(std::shared_ptr<connection> c, const packet::error_type &result,
|
||||
void packet_server::send_response(std::shared_ptr<connection> c,
|
||||
const packet::error_type &result,
|
||||
packet &response) {
|
||||
static const std::string function_name = __FUNCTION__;
|
||||
|
||||
response.encode_top(result);
|
||||
response.encode_top(PACKET_SERVICE_FLAGS);
|
||||
response.encode_top(c->nonce);
|
||||
response.encrypt(encryption_token_);
|
||||
response.transfer_into(c->buffer);
|
||||
|
||||
boost::asio::async_write(c->socket, boost::asio::buffer(c->buffer),
|
||||
[this, c](boost::system::error_code ec, std::size_t /*length*/) {
|
||||
if (ec) {
|
||||
remove_client(*c);
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__,
|
||||
ec.message());
|
||||
} else {
|
||||
read_header(c);
|
||||
}
|
||||
});
|
||||
boost::asio::async_write(
|
||||
c->socket, boost::asio::buffer(c->buffer),
|
||||
[this, c](boost::system::error_code ec, std::size_t /*length*/) {
|
||||
if (ec) {
|
||||
remove_client(*c);
|
||||
utils::error::raise_error(function_name, ec.message());
|
||||
} else {
|
||||
read_header(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
575
src/comm/s3/s3_comm.cpp
Normal file
575
src/comm/s3/s3_comm.cpp
Normal file
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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.
|
||||
*/
|
||||
#if defined(REPERTORY_ENABLE_S3)
|
||||
|
||||
#include "comm/s3/s3_comm.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "comm/curl/curl_comm.hpp"
|
||||
#include "comm/s3/s3_requests.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "types/s3.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/polling.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
static const get_key_callback empty_key = []() { return ""; };
|
||||
|
||||
s3_comm::s3_comm(const app_config &config)
|
||||
: config_(config), s3_config_(config.get_s3_config()) {
|
||||
s3_config_.bucket = utils::string::trim(s3_config_.bucket);
|
||||
|
||||
// TODO make configurable
|
||||
const auto enable_path_style =
|
||||
utils::string::begins_with(s3_config_.url, "http://localhost") ||
|
||||
utils::string::begins_with(s3_config_.url, "https://localhost") ||
|
||||
utils::string::begins_with(s3_config_.url, "http://127.0.0.1") ||
|
||||
utils::string::begins_with(s3_config_.url, "https://127.0.0.1");
|
||||
|
||||
s3_client_ = std::make_unique<curl_comm>(s3_config_);
|
||||
s3_client_->enable_s3_path_style(enable_path_style);
|
||||
|
||||
polling::instance().set_callback(
|
||||
{"s3_directory_cache", polling::frequency::high,
|
||||
[this]() { this->clear_expired_directories(); }});
|
||||
}
|
||||
|
||||
s3_comm::s3_comm(s3_comm &&comm)
|
||||
: config_(std::move(comm.config_)),
|
||||
s3_config_(std::move(comm.s3_config_)),
|
||||
s3_client_(std::move(comm.s3_client_)) {
|
||||
comm.active_ = false;
|
||||
|
||||
polling::instance().set_callback(
|
||||
{"s3_directory_cache", polling::frequency::high,
|
||||
[this]() { this->clear_expired_directories(); }});
|
||||
}
|
||||
|
||||
s3_comm::~s3_comm() {
|
||||
if (active_) {
|
||||
polling::instance().remove_callback("s3_directory_cache");
|
||||
}
|
||||
}
|
||||
|
||||
void s3_comm::clear_expired_directories() {
|
||||
recur_mutex_lock l(cached_directories_mutex_);
|
||||
std::vector<std::string> expired_list;
|
||||
for (const auto &kv : cached_directories_) {
|
||||
if (kv.second.expiration <= std::chrono::system_clock::now()) {
|
||||
expired_list.emplace_back(kv.first);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &expired : expired_list) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, expired, "expired");
|
||||
cached_directories_.erase(expired);
|
||||
}
|
||||
}
|
||||
|
||||
auto s3_comm::create_directory(const std::string &api_path) -> api_error {
|
||||
raise_begin(__FUNCTION__, api_path);
|
||||
|
||||
long response_code{};
|
||||
|
||||
auto object_name = get_object_name(api_path, empty_key) + '/';
|
||||
if (not create_directory_object_request(*s3_client_, s3_config_, object_name,
|
||||
response_code)) {
|
||||
return raise_end(__FUNCTION__, api_path, api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
return raise_end(__FUNCTION__, api_path,
|
||||
response_code == 200 ? api_error::success
|
||||
: api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
auto s3_comm::directory_exists(const std::string &api_path) const -> api_error {
|
||||
raise_begin(__FUNCTION__, api_path);
|
||||
|
||||
auto object_name = get_object_name(api_path, empty_key) + "/";
|
||||
|
||||
head_object_result result{};
|
||||
long response_code{};
|
||||
if (head_object_request(*s3_client_, s3_config_, object_name, result,
|
||||
response_code)) {
|
||||
if (response_code == 404) {
|
||||
return raise_end(__FUNCTION__, api_path, api_error::directory_not_found,
|
||||
response_code);
|
||||
}
|
||||
|
||||
return raise_end(__FUNCTION__, api_path, api_error::directory_exists,
|
||||
response_code);
|
||||
}
|
||||
|
||||
return raise_end(__FUNCTION__, api_path, api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
auto s3_comm::file_exists(const std::string &api_path,
|
||||
const get_key_callback &get_key) const -> api_error {
|
||||
raise_begin(__FUNCTION__, api_path);
|
||||
|
||||
if (get_cached_file_exists(api_path)) {
|
||||
return raise_end(__FUNCTION__, api_path, api_error::item_exists, 200);
|
||||
}
|
||||
|
||||
auto object_name = get_object_name(api_path, get_key);
|
||||
|
||||
head_object_result result{};
|
||||
long response_code{};
|
||||
if (head_object_request(*s3_client_, s3_config_, object_name, result,
|
||||
response_code)) {
|
||||
if (response_code == 404) {
|
||||
return raise_end(__FUNCTION__, api_path, api_error::item_not_found,
|
||||
response_code);
|
||||
}
|
||||
|
||||
return raise_end(__FUNCTION__, api_path, api_error::item_exists,
|
||||
response_code);
|
||||
}
|
||||
|
||||
return raise_end(__FUNCTION__, api_path, api_error::directory_exists,
|
||||
response_code);
|
||||
}
|
||||
|
||||
auto s3_comm::get_object_list(std::vector<directory_item> &list) const
|
||||
-> api_error {
|
||||
raise_begin(__FUNCTION__, "/");
|
||||
|
||||
long response_code{};
|
||||
auto success =
|
||||
list_objects_request(*s3_client_, s3_config_, list, response_code);
|
||||
return raise_end(__FUNCTION__, "/",
|
||||
success ? api_error::success : api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
auto s3_comm::get_object_name(const std::string &api_path,
|
||||
const get_key_callback &get_key) const
|
||||
-> std::string {
|
||||
auto object_name = utils::path::create_api_path(api_path).substr(1);
|
||||
|
||||
const auto key = get_key();
|
||||
if (not key.empty()) {
|
||||
auto parts = utils::string::split(object_name, '/', false);
|
||||
parts[parts.size() - 1u] = key;
|
||||
object_name = utils::string::join(parts, '/');
|
||||
}
|
||||
|
||||
return object_name;
|
||||
}
|
||||
|
||||
auto s3_comm::get_cached_directory_item_count(const std::string &api_path,
|
||||
std::size_t &count) const
|
||||
-> bool {
|
||||
recur_mutex_lock l(cached_directories_mutex_);
|
||||
if (cached_directories_.find(api_path) != cached_directories_.end()) {
|
||||
count = cached_directories_.at(api_path).items.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto s3_comm::get_cached_directory_items(const std::string &api_path,
|
||||
meta_provider_callback meta_provider,
|
||||
directory_item_list &list) const
|
||||
-> bool {
|
||||
unique_recur_mutex_lock l(cached_directories_mutex_);
|
||||
if (cached_directories_.find(api_path) != cached_directories_.end()) {
|
||||
auto &cachedEntry = cached_directories_.at(api_path);
|
||||
list = cachedEntry.items;
|
||||
cached_directories_[api_path].reset_timeout(
|
||||
std::chrono::seconds(config_.get_s3_config().cache_timeout_secs));
|
||||
l.unlock();
|
||||
|
||||
for (auto &item : list) {
|
||||
meta_provider(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto s3_comm::get_cached_file_exists(const std::string &api_path) const
|
||||
-> bool {
|
||||
unique_recur_mutex_lock l(cached_directories_mutex_);
|
||||
const auto parent_api_path = utils::path::get_parent_api_path(api_path);
|
||||
if (cached_directories_.find(parent_api_path) != cached_directories_.end()) {
|
||||
auto &entry = cached_directories_.at(parent_api_path);
|
||||
if (std::find_if(entry.items.begin(), entry.items.end(),
|
||||
[&api_path](const auto &item) -> bool {
|
||||
return not item.directory && (api_path == item.api_path);
|
||||
}) != entry.items.end()) {
|
||||
cached_directories_[api_path].reset_timeout(
|
||||
std::chrono::seconds(config_.get_s3_config().cache_timeout_secs));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto s3_comm::get_directory_item_count(
|
||||
const std::string &api_path, meta_provider_callback meta_provider) const
|
||||
-> std::size_t {
|
||||
raise_begin(__FUNCTION__, api_path);
|
||||
|
||||
std::size_t ret = 0u;
|
||||
if (not get_cached_directory_item_count(api_path, ret)) {
|
||||
directory_item_list list;
|
||||
const auto res = grab_directory_items(api_path, meta_provider, list);
|
||||
if (res != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, res,
|
||||
"failed to grab directory items");
|
||||
}
|
||||
return list.size();
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(__FUNCTION__, api_path,
|
||||
"end|" + std::to_string(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto s3_comm::get_directory_items(const std::string &api_path,
|
||||
meta_provider_callback meta_provider,
|
||||
directory_item_list &list) const
|
||||
-> api_error {
|
||||
raise_begin(__FUNCTION__, api_path);
|
||||
|
||||
auto ret = api_error::success;
|
||||
if (not get_cached_directory_items(api_path, meta_provider, list)) {
|
||||
ret = grab_directory_items(api_path, meta_provider, list);
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(
|
||||
__FUNCTION__, api_path, "end|" + api_error_to_string(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto s3_comm::get_directory_list(api_file_list &list) const -> api_error {
|
||||
raise_begin(__FUNCTION__, "/");
|
||||
|
||||
long response_code{};
|
||||
auto success =
|
||||
list_directories_request(*s3_client_, s3_config_, list, response_code);
|
||||
return raise_end(__FUNCTION__, "/",
|
||||
success ? api_error::success : api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
auto s3_comm::get_file(const std::string &api_path,
|
||||
const get_key_callback &get_key,
|
||||
const get_name_callback &get_name,
|
||||
const get_token_callback &get_token,
|
||||
api_file &file) const -> api_error {
|
||||
raise_begin(__FUNCTION__, api_path);
|
||||
|
||||
auto ret = api_error::success;
|
||||
|
||||
auto object_name = get_object_name(api_path, get_key);
|
||||
|
||||
head_object_result result{};
|
||||
long response_code{};
|
||||
if (head_object_request(*s3_client_, s3_config_, object_name, result,
|
||||
response_code)) {
|
||||
const auto key = get_key();
|
||||
object_name = get_name(key, object_name);
|
||||
file.accessed_date = utils::get_file_time_now();
|
||||
file.api_path = utils::path::create_api_path(object_name);
|
||||
file.api_parent = utils::path::get_parent_api_path(file.api_path);
|
||||
file.changed_date = utils::aws::format_time(result.last_modified);
|
||||
file.creation_date = utils::aws::format_time(result.last_modified);
|
||||
file.encryption_token = get_token();
|
||||
file.file_size =
|
||||
file.encryption_token.empty()
|
||||
? result.content_length
|
||||
: utils::encryption::encrypting_reader::calculate_decrypted_size(
|
||||
result.content_length);
|
||||
file.modified_date = utils::aws::format_time(result.last_modified);
|
||||
} else {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, response_code,
|
||||
"head object request failed");
|
||||
ret = api_error::comm_error;
|
||||
}
|
||||
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(
|
||||
__FUNCTION__, api_path, "end|" + api_error_to_string(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto s3_comm::get_file_list(
|
||||
const get_api_file_token_callback &get_api_file_token,
|
||||
const get_name_callback &get_name, api_file_list &list) const -> api_error {
|
||||
raise_begin(__FUNCTION__, "/");
|
||||
|
||||
long response_code{};
|
||||
auto success = list_files_request(*s3_client_, s3_config_, get_api_file_token,
|
||||
get_name, list, response_code);
|
||||
return raise_end(__FUNCTION__, "/",
|
||||
success ? api_error::success : api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
auto s3_comm::grab_directory_items(const std::string &api_path,
|
||||
meta_provider_callback meta_provider,
|
||||
directory_item_list &list) const
|
||||
-> api_error {
|
||||
auto object_name = get_object_name(api_path, empty_key);
|
||||
long response_code{};
|
||||
if (list_objects_in_directory_request(*s3_client_, s3_config_, object_name,
|
||||
meta_provider, list, response_code)) {
|
||||
if (response_code == 404) {
|
||||
return api_error::directory_not_found;
|
||||
}
|
||||
|
||||
if (response_code != 200) {
|
||||
return api_error::comm_error;
|
||||
}
|
||||
|
||||
set_cached_directory_items(api_path, list);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
return api_error::comm_error;
|
||||
}
|
||||
|
||||
void s3_comm::raise_begin(const std::string &function_name,
|
||||
const std::string &api_path) const {
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(function_name, api_path,
|
||||
"begin|");
|
||||
}
|
||||
}
|
||||
|
||||
auto s3_comm::raise_end(const std::string &function_name,
|
||||
const std::string &api_path, const api_error &error,
|
||||
long code) const -> api_error {
|
||||
if (config_.get_event_level() >= event_level::debug) {
|
||||
event_system::instance().raise<debug_log>(
|
||||
function_name, api_path,
|
||||
"end|" + api_error_to_string(error) + '|' + std::to_string(code));
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
auto s3_comm::read_file_bytes(const std::string &api_path, std::size_t size,
|
||||
std::uint64_t offset, data_buffer &data,
|
||||
const get_key_callback &get_key,
|
||||
const get_size_callback &get_size,
|
||||
const get_token_callback &get_token,
|
||||
stop_type &stop_requested) const -> api_error {
|
||||
data.clear();
|
||||
|
||||
auto object_name = get_object_name(api_path, get_key);
|
||||
|
||||
const auto encryption_token = get_token();
|
||||
const auto data_size = get_size();
|
||||
if (encryption_token.empty()) {
|
||||
long response_code{};
|
||||
if (not read_object_request(*s3_client_, s3_config_, object_name, size,
|
||||
offset, data, response_code, stop_requested)) {
|
||||
auto res =
|
||||
stop_requested ? api_error::download_stopped : api_error::comm_error;
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, res,
|
||||
"failed to read file bytes");
|
||||
return res;
|
||||
}
|
||||
|
||||
if (response_code < 200 || response_code >= 300) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, response_code,
|
||||
"failed to read file bytes");
|
||||
return api_error::comm_error;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
const auto key = utils::encryption::generate_key(encryption_token);
|
||||
return utils::encryption::read_encrypted_range(
|
||||
{offset, offset + size - 1}, key,
|
||||
[&](data_buffer &ct, std::uint64_t start_offset,
|
||||
std::uint64_t end_offset) -> api_error {
|
||||
return read_file_bytes(
|
||||
api_path, (end_offset - start_offset + 1u), start_offset, ct,
|
||||
get_key, get_size, []() -> std::string { return ""; },
|
||||
stop_requested);
|
||||
},
|
||||
data_size, data);
|
||||
}
|
||||
|
||||
void s3_comm::remove_cached_directory(const std::string &api_path) {
|
||||
recur_mutex_lock l(cached_directories_mutex_);
|
||||
cached_directories_.erase(api_path);
|
||||
}
|
||||
|
||||
auto s3_comm::remove_directory(const std::string &api_path) -> api_error {
|
||||
raise_begin(__FUNCTION__, api_path);
|
||||
|
||||
auto object_name = get_object_name(api_path, empty_key) + "/";
|
||||
|
||||
long response_code{};
|
||||
if (delete_object_request(*s3_client_, s3_config_, object_name,
|
||||
response_code)) {
|
||||
if (response_code == 404) {
|
||||
return raise_end(__FUNCTION__, api_path, api_error::directory_not_found,
|
||||
response_code);
|
||||
}
|
||||
|
||||
if (response_code != 204) {
|
||||
return raise_end(__FUNCTION__, api_path, api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
remove_cached_directory(utils::path::get_parent_api_path(api_path));
|
||||
remove_cached_directory(api_path);
|
||||
|
||||
return raise_end(__FUNCTION__, api_path, api_error::success, response_code);
|
||||
}
|
||||
|
||||
return raise_end(__FUNCTION__, api_path, api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
auto s3_comm::remove_file(const std::string &api_path,
|
||||
const get_key_callback &get_key) -> api_error {
|
||||
raise_begin(__FUNCTION__, api_path);
|
||||
|
||||
auto object_name = get_object_name(api_path, get_key);
|
||||
|
||||
long response_code{};
|
||||
if (delete_object_request(*s3_client_, s3_config_, object_name,
|
||||
response_code)) {
|
||||
if (response_code == 404) {
|
||||
return raise_end(__FUNCTION__, api_path, api_error::item_not_found,
|
||||
response_code);
|
||||
}
|
||||
|
||||
if (response_code != 204) {
|
||||
return raise_end(__FUNCTION__, api_path, api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
remove_cached_directory(utils::path::get_parent_api_path(api_path));
|
||||
return raise_end(__FUNCTION__, api_path, api_error::success, response_code);
|
||||
}
|
||||
|
||||
return raise_end(__FUNCTION__, api_path, api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
auto s3_comm::rename_file(const std::string & /*api_path*/,
|
||||
const std::string & /*new_api_path*/) -> api_error {
|
||||
return api_error::not_implemented;
|
||||
/* if (config_.get_event_level() >= event_level::debug) { */
|
||||
/* event_system::instance().raise<debug_log>(__FUNCTION__, api_path,
|
||||
* "begin"); */
|
||||
/* } */
|
||||
/* auto ret = api_error::success; */
|
||||
/* */
|
||||
/* std::string bucket_name, object_name; */
|
||||
/* get_object_name(api_path, bucket_name, object_name); */
|
||||
/* */
|
||||
/* std::string new_object_name; */
|
||||
/* get_object_name(new_api_path, bucket_name, new_object_name); */
|
||||
/* */
|
||||
/* Aws::S3::Model::CopyObjectRequest request{}; */
|
||||
/* request.SetBucket(bucket_name); */
|
||||
/* request.SetCopySource(bucket_name + '/' + object_name); */
|
||||
/* request.SetKey(new_object_name); */
|
||||
/* */
|
||||
/* const auto outcome = s3_client_->CopyObject(request); */
|
||||
/* if (outcome.IsSuccess()) { */
|
||||
/* ret = remove_file(api_path); */
|
||||
/* } else { */
|
||||
/* const auto &error = outcome.GetError(); */
|
||||
/* event_system::instance().raise<repertory_exception>(__FUNCTION__,
|
||||
* error.GetExceptionName()
|
||||
* +
|
||||
* "|" + */
|
||||
/* error.GetMessage());
|
||||
*/
|
||||
/* ret = api_error::comm_error; */
|
||||
/* } */
|
||||
/* */
|
||||
/* if (config_.get_event_level() >= event_level::debug) { */
|
||||
/* event_system::instance().raise<debug_log>(__FUNCTION__, api_path, */
|
||||
/* "end|" +
|
||||
* std::to_string(std::uint8_t(ret))); */
|
||||
/* } */
|
||||
/* return ret; */
|
||||
}
|
||||
|
||||
void s3_comm::set_cached_directory_items(const std::string &api_path,
|
||||
directory_item_list list) const {
|
||||
recur_mutex_lock l(cached_directories_mutex_);
|
||||
cached_directories_[api_path].items = std::move(list);
|
||||
cached_directories_[api_path].reset_timeout(
|
||||
std::chrono::seconds(config_.get_s3_config().cache_timeout_secs));
|
||||
}
|
||||
|
||||
auto s3_comm::upload_file(const std::string &api_path,
|
||||
const std::string &source_path,
|
||||
const std::string &encryption_token,
|
||||
const get_key_callback &get_key,
|
||||
const set_key_callback &set_key,
|
||||
stop_type &stop_requested) -> api_error {
|
||||
raise_begin(__FUNCTION__, api_path);
|
||||
|
||||
auto object_name = get_object_name(api_path, get_key);
|
||||
long response_code{};
|
||||
if (not put_object_request(*s3_client_, s3_config_, object_name, source_path,
|
||||
encryption_token, get_key, set_key, response_code,
|
||||
stop_requested)) {
|
||||
return raise_end(__FUNCTION__, api_path,
|
||||
stop_requested ? api_error::upload_stopped
|
||||
: api_error::upload_failed,
|
||||
response_code);
|
||||
}
|
||||
|
||||
if (response_code != 200) {
|
||||
return raise_end(__FUNCTION__, api_path, api_error::comm_error,
|
||||
response_code);
|
||||
}
|
||||
|
||||
remove_cached_directory(utils::path::get_parent_api_path(api_path));
|
||||
return raise_end(__FUNCTION__, api_path, api_error::success, response_code);
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif // REPERTORY_ENABLE_S3
|
||||
399
src/comm/s3/s3_requests_curl.cpp
Normal file
399
src/comm/s3/s3_requests_curl.cpp
Normal file
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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.
|
||||
*/
|
||||
#if defined(REPERTORY_ENABLE_S3)
|
||||
|
||||
#include "comm/s3/s3_requests.hpp"
|
||||
|
||||
#include "comm/curl/curl_comm.hpp"
|
||||
#include "comm/curl/requests/http_get.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
namespace {
|
||||
[[nodiscard]] auto
|
||||
get_object_list(i_http_comm &client, const s3_config &config,
|
||||
std::string &response_data, long &response_code,
|
||||
std::optional<std::string> delimiter = std::nullopt,
|
||||
std::optional<std::string> prefix = std::nullopt) -> bool {
|
||||
curl::requests::http_get get{};
|
||||
get.aws_service = "aws:amz:" + config.region + ":s3";
|
||||
get.path = '/';
|
||||
get.query["list-type"] = "2";
|
||||
if (delimiter.has_value() && not delimiter.value().empty()) {
|
||||
get.query["delimiter"] = delimiter.value();
|
||||
}
|
||||
if (prefix.has_value() && not prefix.value().empty()) {
|
||||
get.query["prefix"] = prefix.value();
|
||||
}
|
||||
get.response_handler = [&response_data](const data_buffer &data,
|
||||
long /*response_code*/) {
|
||||
response_data = std::string(data.begin(), data.end());
|
||||
};
|
||||
|
||||
stop_type stop_requested{};
|
||||
return client.make_request(get, response_code, stop_requested);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
auto create_directory_object_request_impl(i_http_comm &client,
|
||||
const s3_config &config,
|
||||
const std::string &object_name,
|
||||
long &response_code) -> bool {
|
||||
try {
|
||||
curl::requests::http_put_file put_file{};
|
||||
put_file.aws_service = "aws:amz:" + config.region + ":s3";
|
||||
put_file.file_name =
|
||||
*(utils::string::split(object_name, '/', false).end() - 1U);
|
||||
put_file.path = '/' + object_name;
|
||||
|
||||
stop_type stop_requested{false};
|
||||
return client.make_request(put_file, response_code, stop_requested);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto delete_object_request_impl(i_http_comm &client, const s3_config &config,
|
||||
const std::string &object_name,
|
||||
long &response_code) -> bool {
|
||||
try {
|
||||
head_object_result result{};
|
||||
if (not head_object_request_impl(client, config, object_name, result,
|
||||
response_code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response_code == 404) {
|
||||
return true;
|
||||
}
|
||||
|
||||
curl::requests::http_delete del{};
|
||||
del.aws_service = "aws:amz:" + config.region + ":s3";
|
||||
del.path = '/' + object_name;
|
||||
|
||||
stop_type stop_requested{false};
|
||||
return client.make_request(del, response_code, stop_requested);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto head_object_request_impl(i_http_comm &client, const s3_config &config,
|
||||
const std::string &object_name,
|
||||
head_object_result &result, long &response_code)
|
||||
-> bool {
|
||||
try {
|
||||
curl::requests::http_head head{};
|
||||
head.aws_service = "aws:amz:" + config.region + ":s3";
|
||||
head.path = '/' + object_name;
|
||||
head.response_headers = http_headers{};
|
||||
|
||||
stop_type stop_requested{false};
|
||||
if (not client.make_request(head, response_code, stop_requested)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response_code == 200) {
|
||||
result.from_headers(head.response_headers.value());
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto list_directories_request_impl(i_http_comm &client, const s3_config &config,
|
||||
list_directories_result &result,
|
||||
long &response_code) -> bool {
|
||||
try {
|
||||
std::string response_data{};
|
||||
if (not get_object_list(client, config, response_data, response_code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response_code != 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto res = doc.load_string(response_data.c_str());
|
||||
if (res.status != pugi::xml_parse_status::status_ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto node_list = doc.select_nodes("/ListBucketResult/Contents");
|
||||
for (const auto &node : node_list) {
|
||||
auto object_name =
|
||||
node.node().select_node("Key").node().text().as_string();
|
||||
if (utils::string::ends_with(object_name, "/")) {
|
||||
api_file directory{};
|
||||
directory.api_path = utils::path::create_api_path(object_name);
|
||||
directory.api_parent =
|
||||
utils::path::get_parent_api_path(directory.api_path);
|
||||
directory.accessed_date = utils::get_file_time_now();
|
||||
directory.changed_date = utils::convert_api_date(
|
||||
node.node().select_node("LastModified").node().text().as_string());
|
||||
directory.creation_date = directory.changed_date;
|
||||
directory.modified_date = directory.changed_date;
|
||||
result.emplace_back(std::move(directory));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto list_files_request_impl(
|
||||
i_http_comm &client, const s3_config &config,
|
||||
const get_api_file_token_callback &get_api_file_token,
|
||||
const get_name_callback &get_name, list_files_result &result,
|
||||
long &response_code) -> bool {
|
||||
try {
|
||||
std::string response_data{};
|
||||
if (not get_object_list(client, config, response_data, response_code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response_code != 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto res = doc.load_string(response_data.c_str());
|
||||
if (res.status != pugi::xml_parse_status::status_ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto node_list = doc.select_nodes("/ListBucketResult/Contents");
|
||||
for (const auto &node : node_list) {
|
||||
std::string object_name =
|
||||
node.node().select_node("Key").node().text().as_string();
|
||||
if (not utils::string::ends_with(object_name, "/")) {
|
||||
api_file file{};
|
||||
object_name = get_name(
|
||||
*(utils::string::split(object_name, '/', false).end() - 1u),
|
||||
object_name);
|
||||
file.api_path = utils::path::create_api_path(object_name);
|
||||
file.api_parent = utils::path::get_parent_api_path(file.api_path);
|
||||
file.accessed_date = utils::get_file_time_now();
|
||||
file.encryption_token = get_api_file_token(file.api_path);
|
||||
auto size = node.node().select_node("Size").node().text().as_ullong();
|
||||
file.file_size = file.encryption_token.empty()
|
||||
? size
|
||||
: utils::encryption::encrypting_reader::
|
||||
calculate_decrypted_size(size);
|
||||
file.changed_date = utils::convert_api_date(
|
||||
node.node().select_node("LastModified").node().text().as_string());
|
||||
file.creation_date = file.changed_date;
|
||||
file.modified_date = file.changed_date;
|
||||
result.emplace_back(std::move(file));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto list_objects_in_directory_request_impl(
|
||||
i_http_comm &client, const s3_config &config,
|
||||
const std::string &object_name, meta_provider_callback meta_provider,
|
||||
list_objects_result &result, long &response_code) -> bool {
|
||||
try {
|
||||
std::string response_data{};
|
||||
auto prefix = object_name.empty() ? object_name : object_name + "/";
|
||||
if (not get_object_list(client, config, response_data, response_code, "/",
|
||||
prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response_code != 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto res = doc.load_string(response_data.c_str());
|
||||
if (res.status != pugi::xml_parse_status::status_ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto add_directory_item =
|
||||
[&](bool directory, const std::string &name,
|
||||
std::function<std::uint64_t(const directory_item &)> get_size) {
|
||||
directory_item di{};
|
||||
di.api_path =
|
||||
utils::path::create_api_path(utils::path::combine("/", {name}));
|
||||
di.api_parent = utils::path::get_parent_api_path(di.api_path);
|
||||
di.directory = directory;
|
||||
di.size = get_size(di);
|
||||
meta_provider(di);
|
||||
result.emplace_back(std::move(di));
|
||||
};
|
||||
|
||||
auto node_list =
|
||||
doc.select_nodes("/ListBucketResult/CommonPrefixes/Prefix");
|
||||
for (const auto &node : node_list) {
|
||||
add_directory_item(
|
||||
true, node.node().text().as_string(),
|
||||
[](const directory_item &) -> std::uint64_t { return 0U; });
|
||||
}
|
||||
|
||||
node_list = doc.select_nodes("/ListBucketResult/Contents");
|
||||
for (const auto &node : node_list) {
|
||||
auto child_object_name =
|
||||
node.node().select_node("Key").node().text().as_string();
|
||||
if (child_object_name != prefix) {
|
||||
auto size = node.node().select_node("Size").node().text().as_ullong();
|
||||
add_directory_item(
|
||||
false, child_object_name,
|
||||
[&size](const directory_item &) -> std::uint64_t { return size; });
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto list_objects_request_impl(i_http_comm &client, const s3_config &config,
|
||||
list_objects_result &result, long &response_code)
|
||||
-> bool {
|
||||
try {
|
||||
std::string response_data{};
|
||||
if (not get_object_list(client, config, response_data, response_code)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response_code != 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto res = doc.load_string(response_data.c_str());
|
||||
if (res.status != pugi::xml_parse_status::status_ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto node_list = doc.select_nodes("/ListBucketResult/Contents");
|
||||
for (const auto &node : node_list) {
|
||||
auto object_name =
|
||||
node.node().select_node("Key").node().text().as_string();
|
||||
auto size = node.node().select_node("Size").node().text().as_ullong();
|
||||
directory_item di{};
|
||||
di.api_path = utils::path::create_api_path(object_name);
|
||||
di.api_parent = utils::path::get_parent_api_path(di.api_path);
|
||||
di.directory = utils::string::ends_with(object_name, "/");
|
||||
di.size = di.directory ? 0U : size;
|
||||
di.resolved = false;
|
||||
result.emplace_back(std::move(di));
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto put_object_request_impl(i_http_comm &client, const s3_config &config,
|
||||
std::string object_name,
|
||||
const std::string &source_path,
|
||||
const std::string &encryption_token,
|
||||
get_key_callback get_key, set_key_callback set_key,
|
||||
long &response_code, stop_type &stop_requested)
|
||||
-> bool {
|
||||
try {
|
||||
curl::requests::http_put_file put_file{};
|
||||
put_file.aws_service = "aws:amz:" + config.region + ":s3";
|
||||
put_file.encryption_token = encryption_token;
|
||||
put_file.file_name =
|
||||
*(utils::string::split(object_name, '/', false).end() - 1U);
|
||||
put_file.path = '/' + object_name;
|
||||
put_file.source_path = source_path;
|
||||
|
||||
if (not encryption_token.empty()) {
|
||||
static stop_type no_stop{false};
|
||||
|
||||
put_file.reader = std::make_shared<utils::encryption::encrypting_reader>(
|
||||
put_file.file_name, source_path, no_stop, encryption_token,
|
||||
std::nullopt, -1);
|
||||
auto key = get_key();
|
||||
if (key.empty()) {
|
||||
key = put_file.reader->get_encrypted_file_name();
|
||||
set_key(key);
|
||||
}
|
||||
}
|
||||
|
||||
return client.make_request(put_file, response_code, stop_requested);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto read_object_request_impl(i_http_comm &client, const s3_config &config,
|
||||
const std::string &object_name, std::size_t size,
|
||||
std::uint64_t offset, data_buffer &data,
|
||||
long &response_code, stop_type &stop_requested)
|
||||
-> bool {
|
||||
try {
|
||||
curl::requests::http_get get{};
|
||||
get.aws_service = "aws:amz:" + config.region + ":s3";
|
||||
get.headers["response-content-type"] = "binary/octet-stream";
|
||||
get.path = '/' + object_name;
|
||||
get.range = {{offset, offset + size - 1}};
|
||||
get.response_handler = [&data](const data_buffer &response_data,
|
||||
long /*response_code*/) {
|
||||
data = response_data;
|
||||
};
|
||||
|
||||
return client.make_request(get, response_code, stop_requested);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif
|
||||
@@ -1,29 +1,54 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "common.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
|
||||
const std::string &get_repertory_version() {
|
||||
namespace repertory {
|
||||
auto get_repertory_git_revision() -> const std::string & {
|
||||
static const std::string git_revision = "@REPERTORY_GIT_REV@";
|
||||
return git_revision;
|
||||
}
|
||||
|
||||
auto get_repertory_version() -> const std::string & {
|
||||
static const std::string version = "@REPERTORY_VERSION@";
|
||||
return version;
|
||||
}
|
||||
|
||||
const std::string &get_repertory_git_revision() {
|
||||
static const std::string git_revision = "@REPERTORY_GIT_REV@";
|
||||
return git_revision;
|
||||
void repertory_init() {
|
||||
#ifdef REPERTORY_MUSL
|
||||
pthread_attr_t a;
|
||||
memset(&a, 0, sizeof(pthread_attr_t));
|
||||
pthread_attr_setstacksize(&a, 8U * 1024U * 1024U);
|
||||
pthread_attr_setguardsize(&a, 4096U);
|
||||
pthread_setattr_default_np(&a);
|
||||
#endif
|
||||
|
||||
const auto err = sodium_init();
|
||||
if (err < 0) {
|
||||
std::cerr << "failed to initialize libsodium|err|" << err << std::endl;
|
||||
exit(static_cast<std::int32_t>(exit_code::init_failed));
|
||||
}
|
||||
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
}
|
||||
|
||||
void repertory_shutdown() { curl_global_cleanup(); }
|
||||
} // namespace repertory
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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/directory_db.hpp"
|
||||
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
void directory_db::directory_tree::add_path(const std::string &api_path,
|
||||
const std::vector<std::string> &files,
|
||||
rocksdb::DB &db) {
|
||||
void directory_db::directory_tree::add_path(
|
||||
const std::string &api_path, const std::vector<std::string> &files,
|
||||
rocksdb::DB &db) {
|
||||
const auto create_not_found = [&](const auto &create_path) {
|
||||
std::string value;
|
||||
if (not db.Get(rocksdb::ReadOptions(), create_path, &value).ok()) {
|
||||
@@ -43,83 +47,91 @@ void directory_db::directory_tree::add_path(const std::string &api_path,
|
||||
create_not_found("/");
|
||||
} else {
|
||||
auto &sub_directories = sub_directory_lookup_[previous_directory];
|
||||
if (std::find(sub_directories.begin(), sub_directories.end(), directory_part) ==
|
||||
sub_directories.end()) {
|
||||
if (std::find(sub_directories.begin(), sub_directories.end(),
|
||||
directory_part) == sub_directories.end()) {
|
||||
sub_directories.emplace_back(directory_part);
|
||||
}
|
||||
previous_directory =
|
||||
utils::path::create_api_path(utils::path::combine(previous_directory, {directory_part}));
|
||||
previous_directory = utils::path::create_api_path(
|
||||
utils::path::combine(previous_directory, {directory_part}));
|
||||
sub_directory_lookup_[previous_directory];
|
||||
create_not_found(previous_directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t directory_db::directory_tree::get_count(const std::string &api_path) const {
|
||||
auto directory_db::directory_tree::get_count(const std::string &api_path) const
|
||||
-> std::size_t {
|
||||
return (sub_directory_lookup_.find(api_path) == sub_directory_lookup_.end())
|
||||
? 0
|
||||
: sub_directory_lookup_.at(api_path).size();
|
||||
}
|
||||
|
||||
std::vector<std::string> directory_db::directory_tree::get_directories() const {
|
||||
auto directory_db::directory_tree::get_directories() const
|
||||
-> std::vector<std::string> {
|
||||
std::vector<std::string> ret;
|
||||
std::transform(sub_directory_lookup_.begin(), sub_directory_lookup_.end(),
|
||||
std::back_inserter(ret), [](const auto &kv) { return kv.first; });
|
||||
std::back_inserter(ret),
|
||||
[](const auto &kv) { return kv.first; });
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
directory_db::directory_tree::get_sub_directories(const std::string &api_path) const {
|
||||
auto directory_db::directory_tree::get_sub_directories(
|
||||
const std::string &api_path) const -> std::vector<std::string> {
|
||||
std::vector<std::string> ret;
|
||||
if (sub_directory_lookup_.find(api_path) != sub_directory_lookup_.end()) {
|
||||
const auto &lookup = sub_directory_lookup_.at(api_path);
|
||||
std::transform(
|
||||
lookup.begin(), lookup.end(), std::back_inserter(ret), [&api_path](const auto &directory) {
|
||||
return utils::path::create_api_path(utils::path::combine(api_path, {directory}));
|
||||
});
|
||||
std::transform(lookup.begin(), lookup.end(), std::back_inserter(ret),
|
||||
[&api_path](const auto &directory) {
|
||||
return utils::path::create_api_path(
|
||||
utils::path::combine(api_path, {directory}));
|
||||
});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool directory_db::directory_tree::is_directory(const std::string &api_path) const {
|
||||
auto directory_db::directory_tree::is_directory(
|
||||
const std::string &api_path) const -> bool {
|
||||
return sub_directory_lookup_.find(api_path) != sub_directory_lookup_.end();
|
||||
}
|
||||
|
||||
bool directory_db::directory_tree::remove_directory(const std::string &api_path, rocksdb::DB &db,
|
||||
const bool &allow_remove_root) {
|
||||
void directory_db::directory_tree::remove_directory(const std::string &api_path,
|
||||
rocksdb::DB &db,
|
||||
bool allow_remove_root) {
|
||||
if ((allow_remove_root || (api_path != "/")) && is_directory(api_path)) {
|
||||
sub_directory_lookup_.erase(api_path);
|
||||
db.Delete(rocksdb::WriteOptions(), api_path);
|
||||
|
||||
const auto parent_api_path = utils::path::get_parent_api_path(api_path);
|
||||
const auto parts = utils::string::split(api_path, '/', false);
|
||||
utils::remove_element_from(sub_directory_lookup_[parent_api_path], parts[parts.size() - 1]);
|
||||
return true;
|
||||
utils::remove_element_from(sub_directory_lookup_[parent_api_path],
|
||||
parts[parts.size() - 1]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
directory_db::directory_db(const app_config &config) {
|
||||
utils::db::create_rocksdb(config, DIRDB_NAME, db_);
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(db_->NewIterator(rocksdb::ReadOptions()));
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions()));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
auto directory_data = json::parse(iterator->value().ToString());
|
||||
tree_.add_path(directory_data["path"].get<std::string>(),
|
||||
directory_data["files"].get<std::vector<std::string>>(), *db_);
|
||||
directory_data["files"].get<std::vector<std::string>>(),
|
||||
*db_);
|
||||
}
|
||||
}
|
||||
|
||||
directory_db::~directory_db() { db_.reset(); }
|
||||
|
||||
api_error directory_db::create_directory(const std::string &api_path, const bool &create_always) {
|
||||
auto directory_db::create_directory(const std::string &api_path,
|
||||
bool create_always) -> api_error {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
auto ret = api_error::directory_exists;
|
||||
if (not is_directory(api_path)) {
|
||||
ret = api_error::directory_not_found;
|
||||
if (create_always || (api_path == "/") ||
|
||||
is_directory(utils::path::get_parent_api_path(api_path))) {
|
||||
ret = api_error::file_exists;
|
||||
ret = api_error::item_exists;
|
||||
if (not is_file(api_path)) {
|
||||
tree_.add_path(api_path, {}, *db_);
|
||||
ret = api_error::success;
|
||||
@@ -130,7 +142,7 @@ api_error directory_db::create_directory(const std::string &api_path, const bool
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error directory_db::create_file(const std::string &api_path) {
|
||||
auto directory_db::create_file(const std::string &api_path) -> api_error {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
if (is_directory(api_path)) {
|
||||
return api_error::directory_exists;
|
||||
@@ -144,7 +156,7 @@ api_error directory_db::create_file(const std::string &api_path) {
|
||||
|
||||
const auto file_name = utils::path::strip_to_file_name(api_path);
|
||||
if (utils::collection_includes(directory_data["files"], file_name)) {
|
||||
return api_error::file_exists;
|
||||
return api_error::item_exists;
|
||||
}
|
||||
|
||||
directory_data["files"].emplace_back(file_name);
|
||||
@@ -152,32 +164,36 @@ api_error directory_db::create_file(const std::string &api_path) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
json directory_db::get_directory_data(const std::string &api_path) const {
|
||||
auto directory_db::get_directory_data(const std::string &api_path) const
|
||||
-> json {
|
||||
std::string data;
|
||||
db_->Get(rocksdb::ReadOptions(), api_path, &data);
|
||||
if (data.empty()) {
|
||||
return json();
|
||||
return {};
|
||||
}
|
||||
|
||||
return json::parse(data);
|
||||
}
|
||||
|
||||
std::uint64_t directory_db::get_directory_item_count(const std::string &api_path) const {
|
||||
auto directory_db::get_directory_item_count(const std::string &api_path) const
|
||||
-> std::uint64_t {
|
||||
auto directory_data = get_directory_data(api_path);
|
||||
const auto sub_directory_count = get_sub_directory_count(api_path);
|
||||
const auto file_count = (directory_data.empty() ? 0 : directory_data["files"].size());
|
||||
const auto file_count =
|
||||
(directory_data.empty() ? 0 : directory_data["files"].size());
|
||||
return sub_directory_count + file_count;
|
||||
}
|
||||
|
||||
api_error directory_db::get_file(const std::string &api_path, api_file &file,
|
||||
api_file_provider_callback api_file_provider) const {
|
||||
auto directory_db::get_file(const std::string &api_path, api_file &file,
|
||||
api_file_provider_callback api_file_provider) const
|
||||
-> api_error {
|
||||
const auto parent_api_path = utils::path::get_parent_api_path(api_path);
|
||||
auto directory_data = get_directory_data(parent_api_path);
|
||||
if (not directory_data.empty()) {
|
||||
const auto file_name = utils::path::strip_to_file_name(api_path);
|
||||
if (utils::collection_includes(directory_data["files"], file_name)) {
|
||||
file.api_path =
|
||||
utils::path::create_api_path(utils::path::combine(parent_api_path, {file_name})),
|
||||
file.api_path = utils::path::create_api_path(
|
||||
utils::path::combine(parent_api_path, {file_name})),
|
||||
api_file_provider(file);
|
||||
return api_error::success;
|
||||
}
|
||||
@@ -186,15 +202,17 @@ api_error directory_db::get_file(const std::string &api_path, api_file &file,
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
api_error directory_db::get_file_list(api_file_list &list,
|
||||
api_file_provider_callback api_file_provider) const {
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(db_->NewIterator(rocksdb::ReadOptions()));
|
||||
auto directory_db::get_file_list(
|
||||
api_file_list &list, api_file_provider_callback api_file_provider) const
|
||||
-> api_error {
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions()));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
auto directory_data = json::parse(iterator->value().ToString());
|
||||
for (const auto &directory_file : directory_data["files"]) {
|
||||
api_file file{
|
||||
utils::path::create_api_path(utils::path::combine(iterator->key().ToString(),
|
||||
{directory_file.get<std::string>()})),
|
||||
utils::path::create_api_path(utils::path::combine(
|
||||
iterator->key().ToString(), {directory_file.get<std::string>()})),
|
||||
};
|
||||
api_file_provider(file);
|
||||
list.emplace_back(file);
|
||||
@@ -204,30 +222,33 @@ api_error directory_db::get_file_list(api_file_list &list,
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
std::size_t directory_db::get_sub_directory_count(const std::string &api_path) const {
|
||||
auto directory_db::get_sub_directory_count(const std::string &api_path) const
|
||||
-> std::size_t {
|
||||
recur_mutex_lock directoryLock(directory_mutex_);
|
||||
return tree_.get_count(api_path);
|
||||
}
|
||||
|
||||
std::uint64_t directory_db::get_total_item_count() const {
|
||||
auto directory_db::get_total_item_count() const -> std::uint64_t {
|
||||
unique_recur_mutex_lock directory_lock(directory_mutex_);
|
||||
const auto directories = tree_.get_directories();
|
||||
directory_lock.unlock();
|
||||
|
||||
return std::accumulate(directories.begin(), directories.end(), std::uint64_t(directories.size()),
|
||||
[this](std::uint64_t c, const std::string &directory) {
|
||||
const auto dirData = this->get_directory_data(directory);
|
||||
return c + (dirData.empty() ? 0 : dirData["files"].size());
|
||||
});
|
||||
return std::accumulate(
|
||||
directories.begin(), directories.end(), std::uint64_t(directories.size()),
|
||||
[this](std::uint64_t c, const std::string &directory) {
|
||||
const auto dirData = this->get_directory_data(directory);
|
||||
return c + (dirData.empty() ? 0 : dirData["files"].size());
|
||||
});
|
||||
}
|
||||
|
||||
bool directory_db::is_directory(const std::string &api_path) const {
|
||||
auto directory_db::is_directory(const std::string &api_path) const -> bool {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
return tree_.is_directory(api_path);
|
||||
}
|
||||
|
||||
bool directory_db::is_file(const std::string &api_path) const {
|
||||
auto directory_data = get_directory_data(utils::path::get_parent_api_path(api_path));
|
||||
auto directory_db::is_file(const std::string &api_path) const -> bool {
|
||||
auto directory_data =
|
||||
get_directory_data(utils::path::get_parent_api_path(api_path));
|
||||
if (directory_data.empty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -236,9 +257,9 @@ bool directory_db::is_file(const std::string &api_path) const {
|
||||
return utils::collection_includes(directory_data["files"], file_name);
|
||||
}
|
||||
|
||||
void directory_db::populate_directory_files(const std::string &api_path,
|
||||
const meta_provider_callback &meta_provider,
|
||||
directory_item_list &list) const {
|
||||
void directory_db::populate_directory_files(
|
||||
const std::string &api_path, meta_provider_callback meta_provider,
|
||||
directory_item_list &list) const {
|
||||
auto directory_data = get_directory_data(api_path);
|
||||
if (not directory_data.empty()) {
|
||||
for (const auto &directory_file : directory_data["files"]) {
|
||||
@@ -246,48 +267,47 @@ void directory_db::populate_directory_files(const std::string &api_path,
|
||||
di.api_path = utils::path::create_api_path(
|
||||
utils::path::combine(api_path, {directory_file.get<std::string>()}));
|
||||
di.directory = false;
|
||||
meta_provider(di, true);
|
||||
meta_provider(di);
|
||||
di.size = utils::string::to_uint64(di.meta[META_SIZE]);
|
||||
list.emplace_back(std::move(di));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void directory_db::populate_sub_directories(const std::string &api_path,
|
||||
const meta_provider_callback &meta_provider,
|
||||
directory_item_list &list) const {
|
||||
void directory_db::populate_sub_directories(
|
||||
const std::string &api_path, meta_provider_callback meta_provider,
|
||||
directory_item_list &list) const {
|
||||
unique_recur_mutex_lock directory_lock(directory_mutex_);
|
||||
const auto directories = tree_.get_sub_directories(api_path);
|
||||
directory_lock.unlock();
|
||||
|
||||
std::size_t offset = 0u;
|
||||
std::size_t offset{};
|
||||
for (const auto &directory : directories) {
|
||||
if (std::find_if(list.begin(), list.end(), [&directory](const auto &di) -> bool {
|
||||
return directory == di.api_path;
|
||||
}) == list.end()) {
|
||||
if (std::find_if(list.begin(), list.end(),
|
||||
[&directory](const auto &di) -> bool {
|
||||
return directory == di.api_path;
|
||||
}) == list.end()) {
|
||||
directory_item di{};
|
||||
di.api_path = directory;
|
||||
di.api_parent = utils::path::get_parent_api_path(directory);
|
||||
di.directory = true;
|
||||
di.size = get_sub_directory_count(directory);
|
||||
meta_provider(di, true);
|
||||
list.insert(list.begin() + offset++, std::move(di));
|
||||
meta_provider(di);
|
||||
|
||||
list.insert(list.begin() + static_cast<std::int64_t>(offset++),
|
||||
std::move(di));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api_error directory_db::remove_directory(const std::string &api_path,
|
||||
const bool &allow_remove_root) {
|
||||
auto directory_db::remove_directory(const std::string &api_path,
|
||||
bool allow_remove_root) -> api_error {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
if ((api_path == "/") && not allow_remove_root) {
|
||||
return api_error::access_denied;
|
||||
}
|
||||
|
||||
if (is_file(api_path)) {
|
||||
return api_error::item_is_file;
|
||||
}
|
||||
|
||||
if (not is_directory(api_path)) {
|
||||
if (is_file(api_path) || not is_directory(api_path)) {
|
||||
return api_error::directory_not_found;
|
||||
}
|
||||
|
||||
@@ -302,7 +322,7 @@ api_error directory_db::remove_directory(const std::string &api_path,
|
||||
return api_error::directory_not_empty;
|
||||
}
|
||||
|
||||
bool directory_db::remove_file(const std::string &api_path) {
|
||||
auto directory_db::remove_file(const std::string &api_path) -> bool {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
|
||||
if (is_directory(api_path)) {
|
||||
@@ -325,8 +345,8 @@ bool directory_db::remove_file(const std::string &api_path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
api_error directory_db::rename_file(const std::string &from_api_path,
|
||||
const std::string &to_api_path) {
|
||||
auto directory_db::rename_file(const std::string &from_api_path,
|
||||
const std::string &to_api_path) -> api_error {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
|
||||
if (is_directory(from_api_path) || is_directory(to_api_path)) {
|
||||
@@ -338,7 +358,7 @@ api_error directory_db::rename_file(const std::string &from_api_path,
|
||||
}
|
||||
|
||||
if (is_file(to_api_path)) {
|
||||
return api_error::file_exists;
|
||||
return api_error::item_exists;
|
||||
}
|
||||
|
||||
if (not remove_file(from_api_path)) {
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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/meta_db.hpp"
|
||||
|
||||
#include "types/repertory.hpp"
|
||||
#include "types/startup_exception.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
@@ -27,84 +32,85 @@ namespace repertory {
|
||||
meta_db::meta_db(const app_config &config) {
|
||||
const auto create_resources = [this, &config](const std::string &name) {
|
||||
auto families = std::vector<rocksdb::ColumnFamilyDescriptor>();
|
||||
families.emplace_back(rocksdb::ColumnFamilyDescriptor{rocksdb::kDefaultColumnFamilyName,
|
||||
rocksdb::ColumnFamilyOptions()});
|
||||
families.emplace_back(
|
||||
rocksdb::ColumnFamilyDescriptor{"source", rocksdb::ColumnFamilyOptions()});
|
||||
families.emplace_back(rocksdb::ColumnFamilyDescriptor{"keys", rocksdb::ColumnFamilyOptions()});
|
||||
families.emplace_back(rocksdb::kDefaultColumnFamilyName,
|
||||
rocksdb::ColumnFamilyOptions());
|
||||
families.emplace_back("keys", rocksdb::ColumnFamilyOptions());
|
||||
families.emplace_back("source", rocksdb::ColumnFamilyOptions());
|
||||
|
||||
auto handles = std::vector<rocksdb::ColumnFamilyHandle *>();
|
||||
utils::db::create_rocksdb(config, name, families, handles, db_);
|
||||
default_family_.reset(handles[0u]);
|
||||
source_family_.reset(handles[1u]);
|
||||
keys_family_.reset(handles[2u]);
|
||||
|
||||
std::size_t idx{};
|
||||
default_family_ = handles[idx++];
|
||||
keys_family_ = handles[idx++];
|
||||
source_family_ = handles[idx++];
|
||||
};
|
||||
|
||||
create_resources(METADB_NAME);
|
||||
}
|
||||
|
||||
meta_db::~meta_db() { release_resources(); }
|
||||
meta_db::~meta_db() { db_.reset(); }
|
||||
|
||||
void meta_db::release_resources() {
|
||||
default_family_.reset();
|
||||
source_family_.reset();
|
||||
keys_family_.reset();
|
||||
db_.reset();
|
||||
auto meta_db::create_iterator(bool source_family) const
|
||||
-> std::shared_ptr<rocksdb::Iterator> {
|
||||
return std::shared_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(),
|
||||
source_family ? source_family_ : default_family_));
|
||||
}
|
||||
|
||||
std::shared_ptr<rocksdb::Iterator> meta_db::create_iterator(const bool &source_family) {
|
||||
return std::shared_ptr<rocksdb::Iterator>(db_->NewIterator(
|
||||
rocksdb::ReadOptions(), source_family ? source_family_.get() : default_family_.get()));
|
||||
}
|
||||
|
||||
api_error meta_db::get_api_path_from_key(const std::string &key, std::string &api_path) const {
|
||||
auto ret = api_error::item_not_found;
|
||||
if (not key.empty()) {
|
||||
if (db_->Get(rocksdb::ReadOptions(), keys_family_.get(), key, &api_path).ok()) {
|
||||
ret = api_error::success;
|
||||
}
|
||||
auto meta_db::get_api_path_from_key(const std::string &key,
|
||||
std::string &api_path) const -> api_error {
|
||||
if (key.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Get(rocksdb::ReadOptions(), keys_family_, key, &api_path);
|
||||
});
|
||||
}
|
||||
|
||||
api_error meta_db::get_api_path_from_source(const std::string &source_path,
|
||||
std::string &api_path) const {
|
||||
auto ret = api_error::item_not_found;
|
||||
if (not source_path.empty()) {
|
||||
if (db_->Get(rocksdb::ReadOptions(), source_family_.get(), source_path, &api_path).ok()) {
|
||||
ret = api_error::success;
|
||||
}
|
||||
auto meta_db::get_api_path_from_source(const std::string &source_path,
|
||||
std::string &api_path) const
|
||||
-> api_error {
|
||||
if (source_path.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Get(rocksdb::ReadOptions(), source_family_, source_path,
|
||||
&api_path);
|
||||
});
|
||||
}
|
||||
|
||||
api_error meta_db::get_item_meta_json(const std::string &api_path, json &json_data) const {
|
||||
auto ret = api_error::item_not_found;
|
||||
|
||||
auto meta_db::get_item_meta_json(const std::string &api_path,
|
||||
json &json_data) const -> api_error {
|
||||
std::string value;
|
||||
if (db_->Get(rocksdb::ReadOptions(), default_family_.get(), api_path, &value).ok()) {
|
||||
json_data = json::parse(value);
|
||||
ret = api_error::success;
|
||||
const auto res = perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Get(rocksdb::ReadOptions(), default_family_, api_path, &value);
|
||||
});
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return ret;
|
||||
json_data = json::parse(value);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
api_error meta_db::get_item_meta(const std::string &api_path, api_meta_map &meta) const {
|
||||
auto meta_db::get_item_meta(const std::string &api_path,
|
||||
api_meta_map &meta) const -> api_error {
|
||||
json json_data;
|
||||
const auto ret = get_item_meta_json(api_path, json_data);
|
||||
if (ret == api_error::success) {
|
||||
for (auto it = json_data.begin(); it != json_data.end(); it++) {
|
||||
meta.insert({it.key(), it.value().get<std::string>()});
|
||||
meta[it.key()] = it.value().get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error meta_db::get_item_meta(const std::string &api_path, const std::string &key,
|
||||
std::string &value) const {
|
||||
auto meta_db::get_item_meta(const std::string &api_path, const std::string &key,
|
||||
std::string &value) const -> api_error {
|
||||
json json_data;
|
||||
const auto ret = get_item_meta_json(api_path, json_data);
|
||||
if (ret == api_error::success) {
|
||||
@@ -116,20 +122,21 @@ api_error meta_db::get_item_meta(const std::string &api_path, const std::string
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool meta_db::get_item_meta_exists(const std::string &api_path) const {
|
||||
auto meta_db::get_item_meta_exists(const std::string &api_path) const -> bool {
|
||||
std::string value;
|
||||
return db_->Get(rocksdb::ReadOptions(), api_path, &value).ok();
|
||||
}
|
||||
|
||||
std::vector<std::string> meta_db::get_pinned_files() const {
|
||||
auto meta_db::get_pinned_files() const -> std::vector<std::string> {
|
||||
std::vector<std::string> ret;
|
||||
|
||||
auto iterator = const_cast<meta_db *>(this)->create_iterator(false);
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
auto api_path = iterator->key().ToString();
|
||||
std::string pinned;
|
||||
get_item_meta(api_path, META_PINNED, pinned);
|
||||
if (not pinned.empty() && utils::string::to_bool(pinned)) {
|
||||
const auto res = get_item_meta(api_path, META_PINNED, pinned);
|
||||
if ((res == api_error::success) && not pinned.empty() &&
|
||||
utils::string::to_bool(pinned)) {
|
||||
ret.emplace_back(api_path);
|
||||
}
|
||||
}
|
||||
@@ -137,96 +144,154 @@ std::vector<std::string> meta_db::get_pinned_files() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool meta_db::get_source_path_exists(const std::string &sourceFilePath) const {
|
||||
auto meta_db::get_source_path_exists(const std::string &source_path) const
|
||||
-> bool {
|
||||
std::string value;
|
||||
return db_->Get(rocksdb::ReadOptions(), source_family_.get(), sourceFilePath, &value).ok();
|
||||
return db_->Get(rocksdb::ReadOptions(), source_family_, source_path, &value)
|
||||
.ok();
|
||||
}
|
||||
|
||||
void meta_db::remove_item_meta(const std::string &api_path) {
|
||||
std::string source;
|
||||
get_item_meta(api_path, META_SOURCE, source);
|
||||
auto meta_db::perform_action(
|
||||
const std::string &function_name,
|
||||
const std::function<rocksdb::Status()> &action) const -> api_error {
|
||||
const auto res = action();
|
||||
if (res.ok()) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
std::string key;
|
||||
get_item_meta(api_path, META_SOURCE, key);
|
||||
if (not res.IsNotFound()) {
|
||||
utils::error::raise_error(function_name, res.ToString());
|
||||
}
|
||||
|
||||
db_->Delete(rocksdb::WriteOptions(), source_family_.get(), source);
|
||||
db_->Delete(rocksdb::WriteOptions(), default_family_.get(), api_path);
|
||||
db_->Delete(rocksdb::WriteOptions(), keys_family_.get(), key);
|
||||
return res.IsNotFound() ? api_error::item_not_found : api_error::error;
|
||||
}
|
||||
|
||||
api_error meta_db::remove_item_meta(const std::string &api_path, const std::string &key) {
|
||||
auto meta_db::get_total_item_count() const -> std::uint64_t {
|
||||
std::uint64_t ret = 0u;
|
||||
auto iter = create_iterator(false);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ret++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto meta_db::remove_item_meta(const std::string &api_path) -> api_error {
|
||||
json json_data;
|
||||
auto ret = get_item_meta_json(api_path, json_data);
|
||||
auto res = get_item_meta_json(api_path, json_data);
|
||||
if (res != api_error::success) {
|
||||
return res == api_error::item_not_found ? api_error::success : res;
|
||||
}
|
||||
|
||||
if (ret == api_error::success) {
|
||||
if ((key == META_KEY) && not json_data[META_KEY].empty()) {
|
||||
db_->Delete(rocksdb::WriteOptions(), keys_family_.get(),
|
||||
json_data[META_KEY].get<std::string>());
|
||||
if (not json_data[META_KEY].empty()) {
|
||||
if ((res = perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Delete(rocksdb::WriteOptions(), keys_family_,
|
||||
json_data[META_KEY].get<std::string>());
|
||||
})) != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
json_data.erase(key);
|
||||
ret = db_->Put(rocksdb::WriteOptions(), default_family_.get(), api_path, json_data.dump()).ok()
|
||||
? api_error::success
|
||||
: api_error::error;
|
||||
}
|
||||
|
||||
return ret;
|
||||
if (not json_data[META_SOURCE].empty()) {
|
||||
if ((res = perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Delete(rocksdb::WriteOptions(), source_family_,
|
||||
json_data[META_SOURCE].get<std::string>());
|
||||
})) != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Delete(rocksdb::WriteOptions(), default_family_, api_path);
|
||||
});
|
||||
}
|
||||
|
||||
api_error meta_db::rename_item_meta(const std::string &source_path,
|
||||
const std::string &from_api_path,
|
||||
const std::string &to_api_path) {
|
||||
auto ret = api_error::success;
|
||||
|
||||
std::string key;
|
||||
get_item_meta(from_api_path, META_KEY, key);
|
||||
|
||||
if (not key.empty()) {
|
||||
remove_item_meta(from_api_path, META_KEY);
|
||||
}
|
||||
|
||||
std::string value;
|
||||
if (db_->Get(rocksdb::ReadOptions(), default_family_.get(), from_api_path, &value).ok()) {
|
||||
db_->Delete(rocksdb::WriteOptions(), default_family_.get(), from_api_path);
|
||||
db_->Put(rocksdb::WriteOptions(), default_family_.get(), to_api_path, value);
|
||||
}
|
||||
|
||||
if (not key.empty()) {
|
||||
set_item_meta(to_api_path, META_KEY, key);
|
||||
}
|
||||
|
||||
db_->Put(rocksdb::WriteOptions(), source_family_.get(), source_path, to_api_path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error meta_db::set_item_meta(const std::string &api_path, const std::string &key,
|
||||
const std::string &value) {
|
||||
if (key == META_KEY) {
|
||||
remove_item_meta(api_path, META_KEY);
|
||||
}
|
||||
|
||||
auto meta_db::remove_item_meta(const std::string &api_path,
|
||||
const std::string &key) -> api_error {
|
||||
json json_data;
|
||||
get_item_meta_json(api_path, json_data);
|
||||
|
||||
json_data[key] = value;
|
||||
|
||||
auto ret =
|
||||
db_->Put(rocksdb::WriteOptions(), default_family_.get(), api_path, json_data.dump()).ok()
|
||||
? api_error::success
|
||||
: api_error::error;
|
||||
if ((key == META_KEY) && (ret == api_error::success)) {
|
||||
ret = db_->Put(rocksdb::WriteOptions(), keys_family_.get(), value, api_path).ok()
|
||||
? api_error::success
|
||||
: api_error::error;
|
||||
auto res = get_item_meta_json(api_path, json_data);
|
||||
if (res != api_error::success) {
|
||||
return res == api_error::item_not_found ? api_error::success : res;
|
||||
}
|
||||
|
||||
return ret;
|
||||
if ((key == META_KEY) && not json_data[META_KEY].empty()) {
|
||||
if ((res = perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Delete(rocksdb::WriteOptions(), keys_family_,
|
||||
json_data[META_KEY].get<std::string>());
|
||||
})) != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
if ((key == META_SOURCE) && not json_data[META_SOURCE].empty()) {
|
||||
if ((res = perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Delete(rocksdb::WriteOptions(), source_family_,
|
||||
json_data[META_SOURCE].get<std::string>());
|
||||
})) != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
json_data.erase(key);
|
||||
return perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Put(rocksdb::WriteOptions(), default_family_, api_path,
|
||||
json_data.dump());
|
||||
});
|
||||
}
|
||||
|
||||
api_error meta_db::set_item_meta(const std::string &api_path, const api_meta_map &meta) {
|
||||
auto meta_db::rename_item_meta(const std::string &source_path,
|
||||
const std::string &from_api_path,
|
||||
const std::string &to_api_path) -> api_error {
|
||||
api_meta_map meta{};
|
||||
auto res = get_item_meta(from_api_path, meta);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if ((res = remove_item_meta(from_api_path)) != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (not source_path.empty()) {
|
||||
meta[META_SOURCE] = source_path;
|
||||
}
|
||||
|
||||
return set_item_meta(to_api_path, meta);
|
||||
}
|
||||
|
||||
auto meta_db::set_item_meta(const std::string &api_path, const std::string &key,
|
||||
const std::string &value) -> api_error {
|
||||
if (key == META_SOURCE) {
|
||||
return set_source_path(api_path, value);
|
||||
}
|
||||
|
||||
if (key == META_KEY) {
|
||||
const auto res = remove_item_meta(api_path, META_KEY);
|
||||
if ((res != api_error::success) && (res != api_error::item_not_found)) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
auto res = store_item_meta(api_path, key, value);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (key == META_KEY) {
|
||||
return perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Put(rocksdb::WriteOptions(), keys_family_, value, api_path);
|
||||
});
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto meta_db::set_item_meta(const std::string &api_path,
|
||||
const api_meta_map &meta) -> api_error {
|
||||
auto ret = api_error::success;
|
||||
auto it = meta.begin();
|
||||
for (std::size_t i = 0u; (ret == api_error::success) && (i < meta.size()); i++) {
|
||||
for (std::size_t i = 0u; (ret == api_error::success) && (i < meta.size());
|
||||
i++) {
|
||||
ret = set_item_meta(api_path, it->first, it->second);
|
||||
it++;
|
||||
}
|
||||
@@ -234,18 +299,49 @@ api_error meta_db::set_item_meta(const std::string &api_path, const api_meta_map
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error meta_db::set_source_path(const std::string &api_path, const std::string &source_path) {
|
||||
auto meta_db::set_source_path(const std::string &api_path,
|
||||
const std::string &source_path) -> api_error {
|
||||
std::string current_source_path;
|
||||
get_item_meta(api_path, META_SOURCE, current_source_path);
|
||||
auto res = get_item_meta(api_path, META_SOURCE, current_source_path);
|
||||
if ((res != api_error::success) && (res != api_error::item_not_found)) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// TODO multiple db ops should be in transaction
|
||||
if (not current_source_path.empty()) {
|
||||
db_->Delete(rocksdb::WriteOptions(), source_family_.get(), current_source_path);
|
||||
if ((res = perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Delete(rocksdb::WriteOptions(), source_family_,
|
||||
current_source_path);
|
||||
})) != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
auto ret = set_item_meta(api_path, META_SOURCE, source_path);
|
||||
if (ret == api_error::success) {
|
||||
db_->Put(rocksdb::WriteOptions(), source_family_.get(), source_path, api_path);
|
||||
if (not source_path.empty()) {
|
||||
if ((res = perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Put(rocksdb::WriteOptions(), source_family_, source_path,
|
||||
api_path);
|
||||
})) != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return store_item_meta(api_path, META_SOURCE, source_path);
|
||||
}
|
||||
|
||||
auto meta_db::store_item_meta(const std::string &api_path,
|
||||
const std::string &key, const std::string &value)
|
||||
-> api_error {
|
||||
json json_data;
|
||||
auto res = get_item_meta_json(api_path, json_data);
|
||||
if ((res != api_error::success) && (res != api_error::item_not_found)) {
|
||||
return res;
|
||||
}
|
||||
|
||||
json_data[key] = value;
|
||||
return perform_action(__FUNCTION__, [&]() -> rocksdb::Status {
|
||||
return db_->Put(rocksdb::WriteOptions(), default_family_, api_path,
|
||||
json_data.dump());
|
||||
});
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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/retry_db.hpp"
|
||||
|
||||
namespace repertory {
|
||||
retry_db::retry_db(const app_config &config) {
|
||||
utils::db::create_rocksdb(config, ROCKS_DB_NAME, db_);
|
||||
}
|
||||
|
||||
retry_db::~retry_db() { db_.reset(); }
|
||||
|
||||
bool retry_db::exists(const std::string &api_path) const {
|
||||
std::string value;
|
||||
return db_->Get(rocksdb::ReadOptions(), api_path, &value).ok();
|
||||
}
|
||||
|
||||
void retry_db::pause() {
|
||||
mutex_lock processing_lock(processing_mutex_);
|
||||
paused_ = true;
|
||||
}
|
||||
|
||||
bool retry_db::process_all(const process_callback &process) {
|
||||
auto processed = false;
|
||||
if (not paused_) {
|
||||
unique_mutex_lock processing_lock(processing_mutex_);
|
||||
if (not paused_) {
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(db_->NewIterator(rocksdb::ReadOptions()));
|
||||
for (iterator->SeekToFirst(); not paused_ && iterator->Valid(); iterator->Next()) {
|
||||
const auto api_path = iterator->value().ToString();
|
||||
if (process(api_path)) {
|
||||
remove(api_path);
|
||||
processed = true;
|
||||
}
|
||||
|
||||
processing_lock.unlock();
|
||||
std::this_thread::sleep_for(1ms);
|
||||
processing_lock.lock();
|
||||
}
|
||||
}
|
||||
processing_lock.unlock();
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
void retry_db::remove(const std::string &api_path) {
|
||||
db_->Delete(rocksdb::WriteOptions(), api_path);
|
||||
}
|
||||
|
||||
void retry_db::rename(const std::string &from_api_path, const std::string &to_api_path) {
|
||||
if (exists(from_api_path)) {
|
||||
remove(from_api_path);
|
||||
set(to_api_path);
|
||||
}
|
||||
}
|
||||
|
||||
void retry_db::resume() {
|
||||
mutex_lock processing_lock(processing_mutex_);
|
||||
paused_ = false;
|
||||
}
|
||||
|
||||
void retry_db::set(const std::string &api_path) {
|
||||
db_->Put(rocksdb::WriteOptions(), api_path, api_path);
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "download/buffered_reader.hpp"
|
||||
#include "app_config.hpp"
|
||||
#include "download/reader_pool.hpp"
|
||||
|
||||
namespace repertory {
|
||||
buffered_reader::buffered_reader(const app_config &config, const filesystem_item &fsi,
|
||||
const api_reader_callback &api_reader,
|
||||
const std::size_t &chunk_size, const std::size_t &total_chunks,
|
||||
const std::size_t &start_chunk)
|
||||
: fsi_(fsi),
|
||||
api_reader_(api_reader),
|
||||
chunk_size_(chunk_size),
|
||||
total_chunks_(total_chunks),
|
||||
ring_state_(config.get_read_ahead_count()) {
|
||||
ring_data_.resize(ring_state_.size());
|
||||
const auto read_chunk = [this](const std::size_t &read_offset,
|
||||
std::unique_ptr<std::vector<char>> &chunk_ptr) -> api_error {
|
||||
auto ret = api_error::success;
|
||||
const auto read_size = utils::calculate_read_size(fsi_.size, chunk_size_, read_offset);
|
||||
if (read_size) {
|
||||
std::vector<char> data;
|
||||
if ((ret = api_reader_(fsi_.api_path, read_size, read_offset + read_offset_, data,
|
||||
stop_requested_)) == api_error::success) {
|
||||
chunk_ptr = std::make_unique<std::vector<char>>(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto first = std::async(std::launch::async, [this, &read_chunk]() -> api_error {
|
||||
return read_chunk(0u, first_chunk_data_);
|
||||
});
|
||||
|
||||
if (total_chunks_ > 1u) {
|
||||
auto last = std::async(std::launch::async, [this, &read_chunk]() -> api_error {
|
||||
return read_chunk(((total_chunks_ - 1u) * chunk_size_), last_chunk_data_);
|
||||
});
|
||||
error_ = last.get();
|
||||
}
|
||||
|
||||
if (error_ == api_error::success) {
|
||||
if ((error_ = first.get()) == api_error::success) {
|
||||
read_chunk_index_ = write_chunk_index_ =
|
||||
(first_chunk_data_ ? ((start_chunk == 0u) ? 1u : start_chunk) : start_chunk);
|
||||
if (ring_data_.size() > 1u) {
|
||||
reader_thread_ = std::make_unique<std::thread>([this]() { this->reader_thread(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffered_reader::~buffered_reader() {
|
||||
notify_stop_requested();
|
||||
|
||||
unique_mutex_lock write_lock(write_mutex_);
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
if (reader_thread_) {
|
||||
reader_thread_->join();
|
||||
reader_thread_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void buffered_reader::notify_stop_requested() {
|
||||
error_ = api_error::download_stopped;
|
||||
stop_requested_ = true;
|
||||
}
|
||||
|
||||
api_error buffered_reader::read_chunk(const std::size_t &chunk_index, std::vector<char> &data) {
|
||||
if (error_ == api_error::success) {
|
||||
data.clear();
|
||||
|
||||
if (last_chunk_data_ && (chunk_index == (total_chunks_ - 1u))) {
|
||||
data = *last_chunk_data_;
|
||||
} else if (first_chunk_data_ && (chunk_index == 0u)) {
|
||||
data = *first_chunk_data_;
|
||||
} else if (ring_state_.size() == 1u) {
|
||||
const auto read_offset = chunk_index * chunk_size_;
|
||||
std::size_t read_size = 0u;
|
||||
if ((read_size = utils::calculate_read_size(fsi_.size, chunk_size_, read_offset)) > 0u) {
|
||||
error_ = api_reader_(fsi_.api_path, read_size, read_offset + read_offset_, data,
|
||||
stop_requested_);
|
||||
}
|
||||
} else {
|
||||
mutex_lock read_lock(read_mutex_);
|
||||
const auto read_position = chunk_index % ring_state_.size();
|
||||
unique_mutex_lock write_lock(write_mutex_);
|
||||
while ((reset_reader_ ||
|
||||
((chunk_index < read_chunk_index_) || (chunk_index > write_chunk_index_))) &&
|
||||
is_active()) {
|
||||
reset_reader_ = true;
|
||||
read_chunk_index_ = write_chunk_index_ = chunk_index;
|
||||
read_notify_.notify_all();
|
||||
read_notify_.wait(write_lock);
|
||||
}
|
||||
|
||||
if (is_active()) {
|
||||
read_chunk_index_ = chunk_index;
|
||||
read_notify_.notify_all();
|
||||
while (not ring_state_[read_position] && is_active()) {
|
||||
read_notify_.wait(write_lock);
|
||||
}
|
||||
data = ring_data_[read_position];
|
||||
}
|
||||
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
return error_;
|
||||
}
|
||||
|
||||
void buffered_reader::reader_thread() {
|
||||
reader_pool pool(ring_state_.size(), api_reader_);
|
||||
|
||||
unique_mutex_lock write_lock(write_mutex_);
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
while (is_active()) {
|
||||
write_lock.lock();
|
||||
while ((write_chunk_index_ > read_chunk_index_) &&
|
||||
(write_chunk_index_ > (read_chunk_index_ + ring_state_.size() - 1u)) &&
|
||||
not reset_reader_ && is_active()) {
|
||||
read_notify_.wait(write_lock);
|
||||
}
|
||||
|
||||
if (is_active()) {
|
||||
if (reset_reader_) {
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
pool.restart();
|
||||
|
||||
write_lock.lock();
|
||||
write_chunk_index_ = read_chunk_index_;
|
||||
ring_state_.reset();
|
||||
reset_reader_ = false;
|
||||
}
|
||||
|
||||
const auto file_offset = write_chunk_index_ * chunk_size_;
|
||||
const auto write_chunk_index = write_chunk_index_;
|
||||
const auto write_position = write_chunk_index_ % ring_state_.size();
|
||||
ring_state_[write_position] = false;
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
const auto read_size = utils::calculate_read_size(fsi_.size, chunk_size_, file_offset);
|
||||
if (read_size > 0u) {
|
||||
if ((first_chunk_data_ && (write_chunk_index == 0u)) ||
|
||||
(last_chunk_data_ && (write_chunk_index == (total_chunks_ - 1u)))) {
|
||||
write_lock.lock();
|
||||
write_chunk_index_++;
|
||||
} else {
|
||||
const auto completed = [this, write_position](api_error error) {
|
||||
mutex_lock write_lock(write_mutex_);
|
||||
if (error == api_error::success) {
|
||||
ring_state_[write_position] = true;
|
||||
} else if (not reset_reader_) {
|
||||
error_ = error;
|
||||
}
|
||||
read_notify_.notify_all();
|
||||
};
|
||||
pool.queue_read_bytes(fsi_.api_path, read_size,
|
||||
((write_chunk_index * chunk_size_) + read_offset_),
|
||||
ring_data_[write_position], completed);
|
||||
|
||||
write_lock.lock();
|
||||
write_chunk_index_++;
|
||||
}
|
||||
} else {
|
||||
write_lock.lock();
|
||||
while (not reset_reader_ && ((write_chunk_index_ * chunk_size_) >= fsi_.size) &&
|
||||
is_active()) {
|
||||
read_notify_.wait(write_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "download/direct_download.hpp"
|
||||
#include "app_config.hpp"
|
||||
#include "download/buffered_reader.hpp"
|
||||
#include "download/events.hpp"
|
||||
#include "download/utils.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
|
||||
namespace repertory {
|
||||
direct_download::direct_download(const app_config &config, filesystem_item fsi,
|
||||
const api_reader_callback &api_reader, const std::uint64_t &handle)
|
||||
: config_(config), fsi_(std::move(fsi)), api_reader_(api_reader), handle_(handle) {}
|
||||
|
||||
direct_download::~direct_download() {
|
||||
stop_requested_ = true;
|
||||
|
||||
unique_mutex_lock read_lock(read_mutex_);
|
||||
if (buffered_reader_) {
|
||||
buffered_reader_->notify_stop_requested();
|
||||
}
|
||||
read_lock.unlock();
|
||||
|
||||
if (buffered_reader_) {
|
||||
buffered_reader_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void direct_download::notify_download_end() {
|
||||
unique_mutex_lock read_lock(read_mutex_);
|
||||
if (not disable_download_end_ && not download_end_notified_) {
|
||||
download_end_notified_ = true;
|
||||
read_lock.unlock();
|
||||
event_system::instance().raise<download_end>(fsi_.api_path, "direct", handle_, error_);
|
||||
}
|
||||
}
|
||||
|
||||
void direct_download::notify_stop_requested() {
|
||||
set_api_error(api_error::download_stopped);
|
||||
stop_requested_ = true;
|
||||
notify_download_end();
|
||||
}
|
||||
|
||||
api_error direct_download::read_bytes(const std::uint64_t &, std::size_t read_size,
|
||||
const std::uint64_t &read_offset, std::vector<char> &data) {
|
||||
data.clear();
|
||||
|
||||
if ((read_size = utils::calculate_read_size(fsi_.size, read_size, read_offset)) > 0u) {
|
||||
unique_mutex_lock read_lock(read_mutex_);
|
||||
if (is_active()) {
|
||||
if (not buffered_reader_) {
|
||||
const auto chunk_size = utils::encryption::encrypting_reader::get_data_chunk_size();
|
||||
const auto read_chunk = static_cast<std::size_t>(read_offset / chunk_size);
|
||||
event_system::instance().raise<download_begin>(fsi_.api_path, "direct");
|
||||
buffered_reader_ = std::make_unique<buffered_reader>(
|
||||
config_, fsi_, api_reader_, chunk_size,
|
||||
static_cast<std::size_t>(
|
||||
utils::divide_with_ceiling(fsi_.size, static_cast<std::uint64_t>(chunk_size))),
|
||||
read_chunk);
|
||||
}
|
||||
|
||||
auto read_chunk = static_cast<std::size_t>(read_offset / buffered_reader_->get_chunk_size());
|
||||
auto offset = static_cast<std::size_t>(read_offset % buffered_reader_->get_chunk_size());
|
||||
while (is_active() && (read_size > 0u)) {
|
||||
std::vector<char> buffer;
|
||||
auto ret = api_error::success;
|
||||
if ((ret = buffered_reader_->read_chunk(read_chunk, buffer)) == api_error::success) {
|
||||
const auto total_read = std::min(buffered_reader_->get_chunk_size() - offset, read_size);
|
||||
data.insert(data.end(), buffer.begin() + offset, buffer.begin() + offset + total_read);
|
||||
buffer.clear();
|
||||
read_chunk++;
|
||||
offset = 0u;
|
||||
read_size -= total_read;
|
||||
} else {
|
||||
set_api_error(ret);
|
||||
|
||||
read_lock.unlock();
|
||||
notify_download_end();
|
||||
read_lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
if (is_active()) {
|
||||
utils::download::notify_progress<download_progress>(
|
||||
config_, fsi_.api_path, "direct", static_cast<double>(data.size() + read_offset),
|
||||
static_cast<double>(fsi_.size), progress_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error_;
|
||||
}
|
||||
|
||||
void direct_download::set_api_error(const api_error &error) {
|
||||
if ((error_ == api_error::success) && (error_ != error)) {
|
||||
error_ = error;
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,855 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "download/download.hpp"
|
||||
#include "download/events.hpp"
|
||||
#include "download/utils.hpp"
|
||||
#include "drives/i_open_file_table.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/global_data.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
download::download(const app_config &config, filesystem_item &fsi,
|
||||
const api_reader_callback &api_reader, const std::size_t &chunk_size,
|
||||
i_open_file_table &oft)
|
||||
: config_(config),
|
||||
fsi_(fsi),
|
||||
api_reader_(api_reader),
|
||||
oft_(oft),
|
||||
chunk_size_(chunk_size),
|
||||
read_chunk_state_(static_cast<std::size_t>(
|
||||
utils::divide_with_ceiling(fsi.size, static_cast<std::uint64_t>(chunk_size)))),
|
||||
last_chunk_size_(static_cast<std::size_t>(fsi.size <= chunk_size ? fsi.size
|
||||
: fsi.size % chunk_size ? fsi.size % chunk_size
|
||||
: chunk_size)),
|
||||
write_chunk_state_(static_cast<std::size_t>(
|
||||
utils::divide_with_ceiling(fsi.size, static_cast<std::uint64_t>(chunk_size)))) {
|
||||
/* std::cout << "size:" << fsi.size << ":last:" << last_chunk_size_ << std::endl; */
|
||||
initialize_download(fsi, true);
|
||||
}
|
||||
|
||||
download::download(const app_config &config, filesystem_item &fsi,
|
||||
const api_reader_callback &api_reader, const std::size_t &chunk_size,
|
||||
std::size_t &last_chunk_size, boost::dynamic_bitset<> &read_state,
|
||||
boost::dynamic_bitset<> &write_state, i_open_file_table &oft)
|
||||
: config_(config),
|
||||
fsi_(fsi),
|
||||
api_reader_(api_reader),
|
||||
oft_(oft),
|
||||
chunk_size_(chunk_size),
|
||||
read_chunk_state_(read_state),
|
||||
last_chunk_size_(last_chunk_size),
|
||||
write_chunk_state_(write_state) {
|
||||
initialize_download(fsi, false);
|
||||
}
|
||||
|
||||
download::~download() {
|
||||
if (not get_complete()) {
|
||||
notify_stop_requested();
|
||||
}
|
||||
|
||||
if (io_thread_) {
|
||||
io_thread_->join();
|
||||
}
|
||||
|
||||
if (auto_close_) {
|
||||
native_file::attach(fsi_.handle)->close();
|
||||
fsi_.handle = REPERTORY_INVALID_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
api_error download::allocate(const std::uint64_t &, const std::uint64_t &size,
|
||||
const allocator_callback &allocator,
|
||||
const completer_callback &completer) {
|
||||
reset_timeout(false);
|
||||
/* std::cout << "allocate called" << std::endl; */
|
||||
|
||||
if (size == fsi_.size) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
if (size > fsi_.size) {
|
||||
download_chunk(read_chunk_state_.size() - 1u, false);
|
||||
} else {
|
||||
download_chunk(static_cast<std::size_t>(
|
||||
utils::divide_with_ceiling(size, static_cast<std::uint64_t>(chunk_size_))),
|
||||
false);
|
||||
}
|
||||
|
||||
if (error_ == api_error::success) {
|
||||
const auto original_size = fsi_.size;
|
||||
unique_mutex_lock read_write_lock(read_write_mutex_);
|
||||
const auto allocate_local_file = [&]() -> api_error {
|
||||
if ((error_ = allocator()) == api_error::success) {
|
||||
utils::file::get_file_size(fsi_.source_path, fsi_.size);
|
||||
completer(original_size, fsi_.size, true);
|
||||
oft_.force_schedule_upload(fsi_);
|
||||
}
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
return error_;
|
||||
};
|
||||
|
||||
if (processed_) {
|
||||
return allocate_local_file();
|
||||
}
|
||||
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
pause();
|
||||
|
||||
read_write_lock.lock();
|
||||
if (processed_) {
|
||||
/* std::cout << "recursive allocate" << std::endl; */
|
||||
return allocate_local_file();
|
||||
}
|
||||
|
||||
if ((error_ = allocator()) == api_error::success) {
|
||||
utils::file::get_file_size(fsi_.source_path, fsi_.size);
|
||||
|
||||
const auto end_chunk_index = static_cast<std::size_t>(
|
||||
utils::divide_with_ceiling(fsi_.size, static_cast<std::uint64_t>(chunk_size_)));
|
||||
if (end_chunk_index >= read_chunk_state_.size()) {
|
||||
write_chunk_state_.resize(end_chunk_index + 1u);
|
||||
read_chunk_state_.resize(end_chunk_index + 1u);
|
||||
}
|
||||
|
||||
last_chunk_size_ =
|
||||
static_cast<std::size_t>(fsi_.size <= chunk_size_ ? fsi_.size
|
||||
: fsi_.size % chunk_size_ ? fsi_.size % chunk_size_
|
||||
: chunk_size_);
|
||||
|
||||
read_chunk_state_[end_chunk_index] = write_chunk_state_[end_chunk_index] = true;
|
||||
completer(original_size, fsi_.size, true);
|
||||
}
|
||||
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
resume();
|
||||
}
|
||||
|
||||
return error_;
|
||||
}
|
||||
|
||||
void download::create_active_chunk(std::size_t chunk_index) {
|
||||
auto reader = [this, chunk_index]() {
|
||||
const auto chunk_read_size =
|
||||
(chunk_index == (read_chunk_state_.size() - 1u)) ? last_chunk_size_ : chunk_size_;
|
||||
const auto chunk_read_offset = chunk_index * chunk_size_;
|
||||
|
||||
api_error ret;
|
||||
if (not get_complete()) {
|
||||
std::vector<char> buffer;
|
||||
if ((ret = api_reader_(fsi_.api_path, chunk_read_size, chunk_read_offset + read_offset_,
|
||||
buffer, stop_requested_)) == api_error::success) {
|
||||
unique_mutex_lock read_write_lock(read_write_mutex_);
|
||||
if (not get_complete()) {
|
||||
write_queue_.emplace_back(
|
||||
std::make_shared<write_data>(chunk_index, std::move(buffer), chunk_read_offset));
|
||||
}
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
}
|
||||
|
||||
if (ret != api_error::success) {
|
||||
error_ = ((error_ == api_error::success) ? ret : error_);
|
||||
|
||||
mutex_lock read_write_lock(read_write_mutex_);
|
||||
read_write_notify_.notify_all();
|
||||
}
|
||||
}
|
||||
};
|
||||
active_chunks_.insert({chunk_index, std::make_shared<active_chunk>(std::thread(reader))});
|
||||
}
|
||||
|
||||
void download::download_chunk(std::size_t chunk_index, bool inactive_only) {
|
||||
unique_mutex_lock read_write_lock(read_write_mutex_);
|
||||
|
||||
const auto should_download = [&]() -> bool {
|
||||
return (chunk_index < read_chunk_state_.size()) && not read_chunk_state_[chunk_index] &&
|
||||
not get_complete();
|
||||
};
|
||||
|
||||
if (should_download()) {
|
||||
while (paused_ && not get_complete()) {
|
||||
read_write_notify_.notify_all();
|
||||
read_write_notify_.wait(read_write_lock);
|
||||
}
|
||||
|
||||
if (should_download()) {
|
||||
auto created = false;
|
||||
if (active_chunks_.find(chunk_index) == active_chunks_.end()) {
|
||||
create_active_chunk(chunk_index);
|
||||
created = true;
|
||||
}
|
||||
|
||||
if (not inactive_only || created) {
|
||||
auto chunk = active_chunks_[chunk_index];
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
if (not inactive_only) {
|
||||
reset_timeout(false);
|
||||
}
|
||||
|
||||
utils::spin_wait_for_mutex(
|
||||
[this, chunk_index]() -> bool {
|
||||
return read_chunk_state_[chunk_index] || get_complete();
|
||||
},
|
||||
chunk->notify, chunk->mutex, "download_chunk");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api_error download::download_all() {
|
||||
const auto total_chunks = read_chunk_state_.size();
|
||||
for (std::size_t chunk = 0u; not get_complete() && (chunk < total_chunks); chunk++) {
|
||||
download_chunk(chunk, false);
|
||||
}
|
||||
utils::spin_wait_for_mutex(processed_, processed_notify_, read_write_mutex_, "download_all");
|
||||
return error_;
|
||||
}
|
||||
|
||||
bool download::get_complete() const {
|
||||
return read_chunk_state_.all() || (error_ != api_error::success) || stop_requested_ || processed_;
|
||||
}
|
||||
|
||||
bool download::get_timeout_enabled() const {
|
||||
return not paused_ && not write_chunk_state_.any() &&
|
||||
config_.get_enable_chunk_download_timeout() && (oft_.get_open_count(fsi_.api_path) == 0u);
|
||||
}
|
||||
|
||||
void download::get_state_information(filesystem_item &fsi, std::size_t &chunk_size,
|
||||
std::size_t &last_chunk_size,
|
||||
boost::dynamic_bitset<> &read_state,
|
||||
boost::dynamic_bitset<> &write_state) {
|
||||
fsi = fsi_;
|
||||
chunk_size = chunk_size_;
|
||||
last_chunk_size = last_chunk_size_;
|
||||
read_state = read_chunk_state_;
|
||||
write_state = write_chunk_state_;
|
||||
}
|
||||
|
||||
void download::handle_active_chunk_complete(std::size_t chunk_index, unique_mutex_lock &lock) {
|
||||
lock.lock();
|
||||
auto activeChunk = active_chunks_[chunk_index];
|
||||
active_chunks_.erase(chunk_index);
|
||||
lock.unlock();
|
||||
|
||||
unique_mutex_lock completed_lock(activeChunk->mutex);
|
||||
activeChunk->notify.notify_all();
|
||||
completed_lock.unlock();
|
||||
|
||||
activeChunk->worker.join();
|
||||
}
|
||||
|
||||
void download::initialize_download(filesystem_item &fsi, const bool &delete_existing) {
|
||||
const auto current_source_path = fsi.source_path;
|
||||
|
||||
if (delete_existing) {
|
||||
// Handle should always be invalid (just in case it's not - no leaking).
|
||||
unique_recur_mutex_lock item_lock(*fsi.lock);
|
||||
if (fsi.handle != REPERTORY_INVALID_HANDLE) {
|
||||
native_file::attach(fsi.handle)->close();
|
||||
fsi.handle = REPERTORY_INVALID_HANDLE;
|
||||
}
|
||||
item_lock.unlock();
|
||||
|
||||
// Update current open file information to non-existing source file and free open handle.
|
||||
fsi.source_path =
|
||||
utils::path::combine(config_.get_cache_directory(), {utils::create_uuid_string()});
|
||||
fsi.source_path_changed = true;
|
||||
|
||||
// Update cache space if existing file size is >0 and delete
|
||||
std::uint64_t file_size = 0u;
|
||||
utils::file::get_file_size(current_source_path, file_size);
|
||||
if (file_size) {
|
||||
global_data::instance().update_used_space(file_size, 0u, true);
|
||||
}
|
||||
|
||||
utils::file::delete_file(current_source_path);
|
||||
|
||||
// Update completed file information and create new lock mutex
|
||||
fsi_.source_path =
|
||||
utils::path::combine(config_.get_cache_directory(), {utils::create_uuid_string()});
|
||||
fsi_.lock = std::make_shared<std::recursive_mutex>();
|
||||
}
|
||||
// For resume-only, open file to report correct file size in fuse/winfsp operations
|
||||
else if ((error_ = oft_.open(fsi_, open_file_handle_)) != api_error::success) {
|
||||
event_system::instance().raise<download_restore_failed>(
|
||||
fsi_.api_path, fsi_.source_path, "open failed: " + api_error_to_string(error_));
|
||||
}
|
||||
|
||||
if (error_ == api_error::success) {
|
||||
event_system::instance().raise<download_begin>(fsi_.api_path, fsi_.source_path);
|
||||
// Create new file for reading and writing
|
||||
if ((error_ = native_file::create_or_open(fsi_.source_path, read_write_file_)) ==
|
||||
api_error::success) {
|
||||
// Pre-allocate full file and update used cache space
|
||||
if (read_write_file_->allocate(fsi_.size)) {
|
||||
if (not delete_existing) {
|
||||
// Update used cache space
|
||||
global_data::instance().update_used_space(0u, fsi_.size, true);
|
||||
}
|
||||
|
||||
// Launch read-ahead workers
|
||||
for (std::uint8_t i = 0u; i < config_.get_read_ahead_count(); i++) {
|
||||
background_workers_.emplace_back(std::thread([this]() { read_ahead_worker(); }));
|
||||
}
|
||||
|
||||
// Launch read-behind worker
|
||||
background_workers_.emplace_back(std::thread([this]() { read_behind_worker(); }));
|
||||
|
||||
// Launch read-end worker
|
||||
if (fsi.size > (chunk_size_ * config_.get_read_ahead_count())) {
|
||||
background_workers_.emplace_back(std::thread([this]() { read_end_worker(); }));
|
||||
}
|
||||
|
||||
// Launch read/write IO worker
|
||||
io_thread_ = std::make_unique<std::thread>([this]() { io_data_worker(); });
|
||||
} else if (delete_existing) {
|
||||
const auto temp_error_code = utils::get_last_error_code();
|
||||
utils::file::delete_file(fsi_.source_path);
|
||||
utils::set_last_error_code(temp_error_code);
|
||||
error_ = api_error::os_error;
|
||||
}
|
||||
}
|
||||
|
||||
if (error_ != api_error::success) {
|
||||
event_system::instance().raise<download_end>(fsi_.api_path, fsi_.source_path, 0u, error_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void download::io_data_worker() {
|
||||
unique_mutex_lock read_write_lock(read_write_mutex_);
|
||||
read_write_lock.unlock();
|
||||
|
||||
do {
|
||||
process_timeout(read_write_lock);
|
||||
process_read_queue(read_write_lock);
|
||||
process_write_queue(read_write_lock);
|
||||
notify_progress();
|
||||
process_download_complete(read_write_lock);
|
||||
wait_for_io(read_write_lock);
|
||||
} while (not processed_);
|
||||
|
||||
shutdown(read_write_lock);
|
||||
}
|
||||
|
||||
void download::notify_progress() {
|
||||
if (read_chunk_state_.any()) {
|
||||
utils::download::notify_progress<download_progress>(
|
||||
config_, fsi_.api_path, fsi_.source_path, static_cast<double>(read_chunk_state_.count()),
|
||||
static_cast<double>(read_chunk_state_.size()), progress_);
|
||||
}
|
||||
}
|
||||
|
||||
void download::notify_stop_requested() {
|
||||
if (not stop_requested_) {
|
||||
unique_mutex_lock read_write_lock(read_write_mutex_);
|
||||
error_ =
|
||||
write_chunk_state_.any() ? api_error::download_incomplete : api_error::download_stopped;
|
||||
stop_requested_ = true;
|
||||
paused_ = false;
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
bool download::pause() {
|
||||
unique_mutex_lock read_write_lock(read_write_mutex_);
|
||||
if (not paused_) {
|
||||
paused_ = true;
|
||||
|
||||
while (not active_chunks_.empty() && not stop_requested_) {
|
||||
read_write_notify_.notify_all();
|
||||
read_write_notify_.wait_for(read_write_lock, 2s);
|
||||
}
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
if (stop_requested_) {
|
||||
paused_ = false;
|
||||
} else {
|
||||
event_system::instance().raise<download_paused>(fsi_.api_path, fsi_.source_path);
|
||||
}
|
||||
|
||||
return paused_;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void download::process_download_complete(unique_mutex_lock &lock) {
|
||||
if (get_complete() && not processed_) {
|
||||
lock.lock();
|
||||
if (not processed_) {
|
||||
if ((error_ == api_error::success) && read_chunk_state_.all()) {
|
||||
const auto assign_handle_and_auto_close = [this]() {
|
||||
if (fsi_.handle != REPERTORY_INVALID_HANDLE) {
|
||||
native_file::attach(fsi_.handle)->close();
|
||||
fsi_.handle = REPERTORY_INVALID_HANDLE;
|
||||
}
|
||||
fsi_.handle = read_write_file_->get_handle();
|
||||
auto_close_ = true;
|
||||
};
|
||||
|
||||
if (not oft_.perform_locked_operation(
|
||||
[this, &assign_handle_and_auto_close](i_open_file_table &oft,
|
||||
i_provider &provider) -> bool {
|
||||
filesystem_item *fsi = nullptr;
|
||||
if (oft.get_open_file(fsi_.api_path, fsi)) {
|
||||
unique_recur_mutex_lock item_lock(*fsi->lock);
|
||||
// Handle should always be invalid (just in case it's not - no leaking).
|
||||
if (fsi->handle != REPERTORY_INVALID_HANDLE) {
|
||||
native_file::attach(fsi->handle)->close();
|
||||
fsi->handle = REPERTORY_INVALID_HANDLE;
|
||||
}
|
||||
fsi->handle = read_write_file_->get_handle();
|
||||
item_lock.unlock();
|
||||
|
||||
fsi->source_path = fsi_.source_path;
|
||||
fsi->source_path_changed = true;
|
||||
fsi->changed = false;
|
||||
|
||||
fsi_ = *fsi;
|
||||
} else {
|
||||
provider.set_source_path(fsi_.api_path, fsi_.source_path);
|
||||
assign_handle_and_auto_close();
|
||||
}
|
||||
|
||||
return true;
|
||||
})) {
|
||||
assign_handle_and_auto_close();
|
||||
}
|
||||
} else if (error_ == api_error::success) {
|
||||
error_ = api_error::download_failed;
|
||||
}
|
||||
|
||||
stop_requested_ = processed_ = true;
|
||||
read_write_notify_.notify_all();
|
||||
lock.unlock();
|
||||
|
||||
process_write_queue(lock);
|
||||
process_read_queue(lock);
|
||||
|
||||
lock.lock();
|
||||
|
||||
if (error_ != api_error::success) {
|
||||
read_write_file_->close();
|
||||
if (error_ != api_error::download_incomplete) {
|
||||
if (utils::file::delete_file(fsi_.source_path)) {
|
||||
global_data::instance().update_used_space(fsi_.size, 0u, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processed_notify_.notify_all();
|
||||
read_write_notify_.notify_all();
|
||||
lock.unlock();
|
||||
|
||||
if (write_chunk_state_.any() && (error_ == api_error::success)) {
|
||||
oft_.force_schedule_upload(fsi_);
|
||||
}
|
||||
|
||||
if (open_file_handle_ != static_cast<std::uint64_t>(REPERTORY_API_INVALID_HANDLE)) {
|
||||
oft_.close(open_file_handle_);
|
||||
open_file_handle_ = REPERTORY_API_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (not disable_download_end_) {
|
||||
event_system::instance().raise<download_end>(fsi_.api_path, fsi_.source_path, 0u, error_);
|
||||
}
|
||||
} else {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void download::process_read_queue(unique_mutex_lock &lock) {
|
||||
lock.lock();
|
||||
while (not read_queue_.empty()) {
|
||||
auto rd = read_queue_.front();
|
||||
read_queue_.pop_front();
|
||||
lock.unlock();
|
||||
|
||||
if (error_ == api_error::success) {
|
||||
std::size_t bytes_read = 0u;
|
||||
if (not read_write_file_->read_bytes(&rd->data[0u], rd->data.size(), rd->offset,
|
||||
bytes_read)) {
|
||||
error_ = ((error_ == api_error::success) ? api_error::os_error : error_);
|
||||
}
|
||||
}
|
||||
|
||||
rd->complete = true;
|
||||
unique_mutex_lock read_lock(rd->mutex);
|
||||
rd->notify.notify_all();
|
||||
read_lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
}
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
void download::process_timeout(unique_mutex_lock &lock) {
|
||||
if ((std::chrono::system_clock::now() >= timeout_) && not get_complete() &&
|
||||
get_timeout_enabled()) {
|
||||
error_ = api_error::download_timeout;
|
||||
stop_requested_ = true;
|
||||
lock.lock();
|
||||
read_write_notify_.notify_all();
|
||||
lock.unlock();
|
||||
event_system::instance().raise<download_timeout>(fsi_.api_path, fsi_.source_path);
|
||||
}
|
||||
}
|
||||
|
||||
void download::process_write_queue(unique_mutex_lock &lock) {
|
||||
lock.lock();
|
||||
do {
|
||||
if (not write_queue_.empty()) {
|
||||
auto wd = write_queue_.front();
|
||||
write_queue_.pop_front();
|
||||
lock.unlock();
|
||||
|
||||
if (not get_complete() || (error_ == api_error::download_incomplete)) {
|
||||
auto &bytes_written = wd->written;
|
||||
if (read_write_file_->write_bytes(&wd->data[0u], wd->data.size(), wd->offset,
|
||||
bytes_written)) {
|
||||
read_write_file_->flush();
|
||||
if (wd->from_read) {
|
||||
lock.lock();
|
||||
read_chunk_state_[wd->chunk_index] = true;
|
||||
lock.unlock();
|
||||
} else {
|
||||
const auto start_chunk_index = static_cast<std::size_t>(wd->offset / chunk_size_);
|
||||
const auto end_chunk_index =
|
||||
static_cast<std::size_t>((wd->data.size() + wd->offset) / chunk_size_);
|
||||
lock.lock();
|
||||
for (std::size_t chunk_index = start_chunk_index; (chunk_index <= end_chunk_index);
|
||||
chunk_index++) {
|
||||
write_chunk_state_[chunk_index] = true;
|
||||
}
|
||||
lock.unlock();
|
||||
}
|
||||
} else {
|
||||
error_ = ((error_ == api_error::success) ? api_error::os_error : error_);
|
||||
}
|
||||
}
|
||||
|
||||
if (wd->from_read) {
|
||||
handle_active_chunk_complete(wd->chunk_index, lock);
|
||||
} else {
|
||||
wd->complete = true;
|
||||
unique_mutex_lock write_lock(wd->mutex);
|
||||
wd->notify.notify_all();
|
||||
write_lock.unlock();
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
}
|
||||
} while (not write_queue_.empty() && read_queue_.empty());
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
void download::read_ahead_worker() {
|
||||
const auto total_chunks = read_chunk_state_.size();
|
||||
|
||||
auto first_chunk_index = current_chunk_index_;
|
||||
const auto get_next_chunk =
|
||||
[&first_chunk_index](const std::size_t ¤t_chunk_index,
|
||||
const std::size_t &last_chunk_index) -> std::size_t {
|
||||
if (current_chunk_index == first_chunk_index) {
|
||||
return last_chunk_index + 1ul;
|
||||
}
|
||||
|
||||
first_chunk_index = current_chunk_index;
|
||||
return current_chunk_index + 1ul;
|
||||
};
|
||||
|
||||
while (not get_complete()) {
|
||||
for (auto chunk_index = get_next_chunk(current_chunk_index_, 0u);
|
||||
(chunk_index < total_chunks) && not get_complete();) {
|
||||
download_chunk(chunk_index, true);
|
||||
chunk_index = get_next_chunk(current_chunk_index_, chunk_index);
|
||||
}
|
||||
|
||||
utils::spin_wait_for_mutex(
|
||||
[this, &first_chunk_index]() -> bool {
|
||||
return get_complete() || (first_chunk_index != current_chunk_index_);
|
||||
},
|
||||
read_write_notify_, read_write_mutex_, "read_ahead_worker");
|
||||
}
|
||||
}
|
||||
|
||||
void download::read_behind_worker() {
|
||||
auto first_chunk_index = current_chunk_index_;
|
||||
const auto get_next_chunk =
|
||||
[&first_chunk_index](const std::size_t ¤t_chunk_index,
|
||||
const std::size_t &last_chunk_index) -> std::size_t {
|
||||
if (current_chunk_index == first_chunk_index) {
|
||||
return last_chunk_index ? last_chunk_index - 1ul : 0u;
|
||||
}
|
||||
|
||||
first_chunk_index = current_chunk_index;
|
||||
return current_chunk_index ? current_chunk_index - 1ul : 0u;
|
||||
};
|
||||
|
||||
while (not get_complete()) {
|
||||
for (auto chunk_index = get_next_chunk(current_chunk_index_, current_chunk_index_);
|
||||
not get_complete();) {
|
||||
download_chunk(chunk_index, true);
|
||||
if (not chunk_index) {
|
||||
break;
|
||||
}
|
||||
chunk_index = get_next_chunk(current_chunk_index_, chunk_index);
|
||||
}
|
||||
|
||||
utils::spin_wait_for_mutex(
|
||||
[this, &first_chunk_index]() -> bool {
|
||||
return get_complete() || (first_chunk_index != current_chunk_index_);
|
||||
},
|
||||
read_write_notify_, read_write_mutex_, "read_behind_worker");
|
||||
}
|
||||
}
|
||||
|
||||
api_error download::read_bytes(const std::uint64_t &, std::size_t read_size,
|
||||
const std::uint64_t &read_offset, std::vector<char> &data) {
|
||||
/* std::cout << "read called:" << read_size << ':' << read_offset << std::endl; */
|
||||
data.clear();
|
||||
if ((read_size > 0u) && (error_ == api_error::success)) {
|
||||
unique_mutex_lock read_write_lock(read_write_mutex_);
|
||||
const auto read_local_file = [&]() -> api_error {
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
error_ = (error_ == api_error::success)
|
||||
? utils::file::read_from_source(fsi_, read_size, read_offset, data)
|
||||
: error_;
|
||||
|
||||
return error_;
|
||||
};
|
||||
|
||||
if (processed_) {
|
||||
return read_local_file();
|
||||
}
|
||||
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
const auto start_chunk_index = current_chunk_index_ =
|
||||
static_cast<std::size_t>(read_offset / chunk_size_);
|
||||
const auto end_chunk_index = static_cast<std::size_t>((read_size + read_offset) / chunk_size_);
|
||||
for (std::size_t chunk = start_chunk_index; not get_complete() && (chunk <= end_chunk_index);
|
||||
chunk++) {
|
||||
download_chunk(chunk, false);
|
||||
}
|
||||
|
||||
if (error_ == api_error::success) {
|
||||
data.resize(read_size);
|
||||
auto rd = std::make_shared<read_data>(data, read_offset);
|
||||
|
||||
read_write_lock.lock();
|
||||
if (processed_) {
|
||||
/* std::cout << "recursive read:" << read_size << ':' << read_offset << std::endl; */
|
||||
return read_local_file();
|
||||
}
|
||||
|
||||
read_queue_.emplace_front(rd);
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
reset_timeout(false);
|
||||
utils::spin_wait_for_mutex(rd->complete, rd->notify, rd->mutex, "read_bytes");
|
||||
|
||||
reset_timeout(false);
|
||||
}
|
||||
}
|
||||
|
||||
return error_;
|
||||
}
|
||||
|
||||
void download::read_end_worker() {
|
||||
const auto total_chunks = read_chunk_state_.size();
|
||||
auto to_read = utils::divide_with_ceiling(std::size_t(262144u), chunk_size_);
|
||||
for (auto chunk_index = total_chunks ? total_chunks - 1ul : 0u;
|
||||
chunk_index && to_read && not get_complete(); chunk_index--, to_read--) {
|
||||
download_chunk(chunk_index, true);
|
||||
}
|
||||
}
|
||||
|
||||
void download::reset_timeout(const bool &) {
|
||||
mutex_lock read_write_lock(read_write_mutex_);
|
||||
timeout_ = std::chrono::system_clock::now() +
|
||||
std::chrono::seconds(config_.get_chunk_downloader_timeout_secs());
|
||||
read_write_notify_.notify_all();
|
||||
}
|
||||
|
||||
void download::resume() {
|
||||
reset_timeout(false);
|
||||
|
||||
mutex_lock read_write_lock(read_write_mutex_);
|
||||
if (paused_) {
|
||||
paused_ = false;
|
||||
event_system::instance().raise<download_resumed>(fsi_.api_path, fsi_.source_path);
|
||||
}
|
||||
read_write_notify_.notify_all();
|
||||
}
|
||||
|
||||
void download::set_api_path(const std::string &api_path) {
|
||||
mutex_lock read_write_lock(read_write_mutex_);
|
||||
fsi_.api_path = api_path;
|
||||
fsi_.api_parent = utils::path::get_parent_api_path(api_path);
|
||||
read_write_notify_.notify_all();
|
||||
}
|
||||
|
||||
void download::shutdown(unique_mutex_lock &lock) {
|
||||
lock.lock();
|
||||
while (not active_chunks_.empty()) {
|
||||
const auto chunk_index = active_chunks_.begin()->first;
|
||||
lock.unlock();
|
||||
handle_active_chunk_complete(chunk_index, lock);
|
||||
lock.lock();
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
for (auto &worker : background_workers_) {
|
||||
worker.join();
|
||||
}
|
||||
background_workers_.clear();
|
||||
}
|
||||
|
||||
void download::wait_for_io(unique_mutex_lock &lock) {
|
||||
lock.lock();
|
||||
if (not get_complete() && read_queue_.empty() && write_queue_.empty()) {
|
||||
if (get_timeout_enabled()) {
|
||||
read_write_notify_.wait_until(lock, timeout_);
|
||||
} else {
|
||||
read_write_notify_.wait(lock);
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
api_error download::write_bytes(const std::uint64_t &, const std::uint64_t &write_offset,
|
||||
std::vector<char> data, std::size_t &bytes_written,
|
||||
const completer_callback &completer) {
|
||||
/* std::cout << "write called:" << write_offset << std::endl; */
|
||||
bytes_written = 0u;
|
||||
|
||||
if (not data.empty() && (error_ == api_error::success)) {
|
||||
const auto start_chunk_index = current_chunk_index_ =
|
||||
static_cast<std::size_t>(write_offset / chunk_size_);
|
||||
const auto end_chunk_index =
|
||||
static_cast<std::size_t>((data.size() + write_offset) / chunk_size_);
|
||||
for (std::size_t chunk_index = start_chunk_index;
|
||||
not get_complete() &&
|
||||
(chunk_index <= std::min(read_chunk_state_.size() - 1u, end_chunk_index));
|
||||
chunk_index++) {
|
||||
download_chunk(chunk_index, false);
|
||||
}
|
||||
|
||||
if (error_ == api_error::success) {
|
||||
const auto original_size = fsi_.size;
|
||||
|
||||
unique_mutex_lock read_write_lock(read_write_mutex_);
|
||||
const auto write_local_file = [&]() -> api_error {
|
||||
error_ = (error_ == api_error::success)
|
||||
? utils::file::write_to_source(fsi_, write_offset, data, bytes_written)
|
||||
: error_;
|
||||
|
||||
if (error_ == api_error::success) {
|
||||
utils::file::get_file_size(fsi_.source_path, fsi_.size);
|
||||
completer(original_size, fsi_.size, true);
|
||||
oft_.force_schedule_upload(fsi_);
|
||||
}
|
||||
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
return error_;
|
||||
};
|
||||
|
||||
if (processed_) {
|
||||
return write_local_file();
|
||||
}
|
||||
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
const auto size = (write_offset + data.size());
|
||||
if (size > fsi_.size) {
|
||||
pause();
|
||||
read_write_lock.lock();
|
||||
|
||||
if (end_chunk_index >= write_chunk_state_.size()) {
|
||||
const auto first_new_chunk_index = write_chunk_state_.size();
|
||||
write_chunk_state_.resize(end_chunk_index + 1u);
|
||||
read_chunk_state_.resize(end_chunk_index + 1u);
|
||||
for (std::size_t chunk_index = first_new_chunk_index; chunk_index <= end_chunk_index;
|
||||
chunk_index++) {
|
||||
read_chunk_state_[chunk_index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
fsi_.size = size;
|
||||
last_chunk_size_ =
|
||||
static_cast<std::size_t>(fsi_.size <= chunk_size_ ? fsi_.size
|
||||
: fsi_.size % chunk_size_ ? fsi_.size % chunk_size_
|
||||
: chunk_size_);
|
||||
read_write_lock.unlock();
|
||||
resume();
|
||||
}
|
||||
|
||||
auto wd = std::make_shared<write_data>(write_offset, std::move(data));
|
||||
|
||||
read_write_lock.lock();
|
||||
if (processed_) {
|
||||
/* std::cout << "recursive write:" << write_offset << std::endl; */
|
||||
return write_local_file();
|
||||
}
|
||||
|
||||
write_queue_.emplace_back(wd);
|
||||
read_write_notify_.notify_all();
|
||||
read_write_lock.unlock();
|
||||
|
||||
reset_timeout(false);
|
||||
|
||||
utils::spin_wait_for_mutex(wd->complete, wd->notify, wd->mutex, "write_bytes");
|
||||
|
||||
reset_timeout(false);
|
||||
|
||||
bytes_written = wd->written;
|
||||
if (bytes_written && (size > fsi_.size)) {
|
||||
fsi_.size = size;
|
||||
completer(original_size, fsi_.size, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error_;
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,502 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "download/download_manager.hpp"
|
||||
#include "download/direct_download.hpp"
|
||||
#include "download/download.hpp"
|
||||
#include "download/events.hpp"
|
||||
#include "download/ring_download.hpp"
|
||||
#include "drives/i_open_file_table.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/global_data.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/rocksdb_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
static json create_resume_entry(i_download &d) {
|
||||
filesystem_item fsi;
|
||||
std::size_t chunk_size;
|
||||
std::size_t last_chunk_size;
|
||||
boost::dynamic_bitset<> read_state, write_state;
|
||||
d.get_state_information(fsi, chunk_size, last_chunk_size, read_state, write_state);
|
||||
|
||||
return {
|
||||
{"chunk_size", chunk_size},
|
||||
{"encryption_token", fsi.encryption_token},
|
||||
{"last_chunk_size", last_chunk_size},
|
||||
{"path", fsi.api_path},
|
||||
{"read_state", utils::string::from_dynamic_bitset(read_state)},
|
||||
{"source", fsi.source_path},
|
||||
{"write_state", utils::string::from_dynamic_bitset(write_state)},
|
||||
};
|
||||
}
|
||||
|
||||
static void restore_resume_entry(const json &resume_entry, filesystem_item &fsi,
|
||||
std::size_t &chunk_size, std::size_t &last_chunk_size,
|
||||
boost::dynamic_bitset<> &read_state,
|
||||
boost::dynamic_bitset<> &write_state) {
|
||||
chunk_size = resume_entry["chunk_size"].get<std::size_t>();
|
||||
fsi.encryption_token = resume_entry["encryption_token"].get<std::string>();
|
||||
last_chunk_size = resume_entry["last_chunk_size"].get<std::size_t>();
|
||||
fsi.api_path = resume_entry["path"].get<std::string>();
|
||||
read_state = utils::string::to_dynamic_bitset(resume_entry["read_state"].get<std::string>());
|
||||
fsi.source_path = resume_entry["source"].get<std::string>();
|
||||
write_state = utils::string::to_dynamic_bitset(resume_entry["write_state"].get<std::string>());
|
||||
fsi.api_path = utils::path::get_parent_api_path(fsi.api_path);
|
||||
}
|
||||
|
||||
static void create_source_file_if_empty(filesystem_item &fsi, const app_config &config) {
|
||||
unique_recur_mutex_lock item_lock(*fsi.lock);
|
||||
if (fsi.source_path.empty()) {
|
||||
fsi.source_path =
|
||||
utils::path::combine(config.get_cache_directory(), {utils::create_uuid_string()});
|
||||
fsi.source_path_changed = true;
|
||||
}
|
||||
item_lock.unlock();
|
||||
}
|
||||
|
||||
download_manager::download_manager(const app_config &config, api_reader_callback api_reader,
|
||||
const bool &force_download)
|
||||
: config_(config), api_reader_(api_reader), force_download_(force_download) {
|
||||
utils::db::create_rocksdb(config, "resume_db", restore_db_);
|
||||
|
||||
E_SUBSCRIBE_EXACT(download_end, [this](const download_end &downloadEnd) {
|
||||
this->handle_download_end(downloadEnd);
|
||||
});
|
||||
|
||||
E_SUBSCRIBE_EXACT(filesystem_item_handle_closed,
|
||||
[this](const filesystem_item_handle_closed &fileHandleClosed) {
|
||||
this->on_handle_closed(fileHandleClosed);
|
||||
});
|
||||
|
||||
E_SUBSCRIBE_EXACT(filesystem_item_closed, [this](const filesystem_item_closed &fileClosed) {
|
||||
reset_timeout(fileClosed.get_api_path(), true);
|
||||
});
|
||||
|
||||
E_SUBSCRIBE_EXACT(filesystem_item_opened, [this](const filesystem_item_opened &fileOpened) {
|
||||
reset_timeout(fileOpened.get_api_path(), false);
|
||||
});
|
||||
}
|
||||
|
||||
download_manager::~download_manager() {
|
||||
E_CONSUMER_RELEASE();
|
||||
stop();
|
||||
}
|
||||
|
||||
api_error download_manager::allocate(const std::uint64_t &handle, filesystem_item &fsi,
|
||||
const std::uint64_t &size,
|
||||
const i_download::allocator_callback &allocator) {
|
||||
create_source_file_if_empty(fsi, config_);
|
||||
|
||||
if (stop_requested_) {
|
||||
return api_error::download_stopped;
|
||||
}
|
||||
|
||||
const auto completer = [&](std::uint64_t old_size, std::uint64_t new_size, bool changed) {
|
||||
fsi.changed = changed;
|
||||
if (old_size != new_size) {
|
||||
fsi.size = new_size;
|
||||
global_data::instance().update_used_space(old_size, new_size, false);
|
||||
}
|
||||
|
||||
const auto modified = std::to_string(utils::get_file_time_now());
|
||||
oft_->set_item_meta(fsi.api_path, META_MODIFIED, modified);
|
||||
oft_->set_item_meta(fsi.api_path, META_WRITTEN, modified);
|
||||
};
|
||||
|
||||
if (utils::file::is_file(fsi.source_path)) {
|
||||
std::uint64_t file_size = 0u;
|
||||
if (utils::file::get_file_size(fsi.source_path, file_size)) {
|
||||
if (fsi.size == file_size) {
|
||||
native_file_ptr nf;
|
||||
auto ret = utils::file::assign_and_get_native_file(fsi, nf);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = allocator()) == api_error::success) {
|
||||
std::uint64_t new_file_size{};
|
||||
utils::file::get_file_size(fsi.source_path, new_file_size);
|
||||
completer(fsi.size, new_file_size, true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
return api_error::os_error;
|
||||
}
|
||||
}
|
||||
|
||||
auto d = get_download(handle, fsi, true);
|
||||
const auto result = d->get_result();
|
||||
return ((result == api_error::success) ? d->allocate(handle, size, allocator, completer)
|
||||
: result);
|
||||
}
|
||||
|
||||
bool download_manager::contains_handle(const std::string &api_path,
|
||||
const std::uint64_t &handle) const {
|
||||
return ((download_lookup_.find(api_path) != download_lookup_.end()) &&
|
||||
(download_lookup_.at(api_path).find(handle) != download_lookup_.at(api_path).end()));
|
||||
}
|
||||
|
||||
bool download_manager::contains_restore(const std::string &api_path) const {
|
||||
std::string value;
|
||||
restore_db_->Get(rocksdb::ReadOptions(), api_path, &value);
|
||||
return not value.empty();
|
||||
}
|
||||
|
||||
api_error download_manager::download_file(const std::uint64_t &handle, filesystem_item &fsi) {
|
||||
create_source_file_if_empty(fsi, config_);
|
||||
|
||||
if (stop_requested_) {
|
||||
return api_error::download_stopped;
|
||||
} else if (fsi.size > 0u) {
|
||||
std::uint64_t file_size = 0u;
|
||||
if (utils::file::get_file_size(fsi.source_path, file_size)) {
|
||||
if (file_size == fsi.size) {
|
||||
native_file_ptr nf;
|
||||
return utils::file::assign_and_get_native_file(fsi, nf);
|
||||
}
|
||||
}
|
||||
|
||||
auto d = get_download(handle, fsi, true);
|
||||
return ((d->get_result() == api_error::success) ? d->download_all() : d->get_result());
|
||||
} else if (fsi.handle == REPERTORY_INVALID_HANDLE) {
|
||||
native_file_ptr nf;
|
||||
return utils::file::assign_and_get_native_file(fsi, nf);
|
||||
} else {
|
||||
return api_error::success;
|
||||
}
|
||||
}
|
||||
|
||||
download_ptr download_manager::get_download(std::uint64_t handle, filesystem_item &fsi,
|
||||
const bool &write_supported) {
|
||||
const auto create_download = [this, &handle, &fsi, &write_supported]() -> download_ptr {
|
||||
const auto chunk_size = utils::encryption::encrypting_reader::get_data_chunk_size();
|
||||
if (write_supported || force_download_) {
|
||||
handle = 0u;
|
||||
return std::dynamic_pointer_cast<i_download>(
|
||||
std::make_shared<download>(config_, fsi, api_reader_, chunk_size, *oft_));
|
||||
} else {
|
||||
const auto dt = config_.get_preferred_download_type();
|
||||
const std::size_t ring_buffer_size = config_.get_ring_buffer_file_size() * 1024ull * 1024ull;
|
||||
const auto ring_allowed = (fsi.size > ring_buffer_size);
|
||||
|
||||
const auto no_space_remaining =
|
||||
(utils::file::get_available_drive_space(config_.get_cache_directory()) < fsi.size);
|
||||
if (ring_allowed && (dt != download_type::direct) &&
|
||||
(no_space_remaining || (dt == download_type::ring_buffer))) {
|
||||
return std::dynamic_pointer_cast<i_download>(std::make_shared<ring_download>(
|
||||
config_, fsi, api_reader_, handle, chunk_size, ring_buffer_size));
|
||||
}
|
||||
|
||||
if (no_space_remaining || (dt == download_type::direct)) {
|
||||
return std::dynamic_pointer_cast<i_download>(
|
||||
std::make_shared<direct_download>(config_, fsi, api_reader_, handle));
|
||||
} else {
|
||||
handle = 0u;
|
||||
return std::dynamic_pointer_cast<i_download>(
|
||||
std::make_shared<download>(config_, fsi, api_reader_, chunk_size, *oft_));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
download_ptr d;
|
||||
{
|
||||
recur_mutex_lock download_lock(download_mutex_);
|
||||
if (contains_handle(fsi.api_path, 0u)) {
|
||||
d = download_lookup_[fsi.api_path][0u];
|
||||
} else if (contains_handle(fsi.api_path, handle)) {
|
||||
d = download_lookup_[fsi.api_path][handle];
|
||||
} else {
|
||||
d = create_download();
|
||||
download_lookup_[fsi.api_path][handle] = d;
|
||||
}
|
||||
if (write_supported && not d->get_write_supported()) {
|
||||
for (auto &kv : download_lookup_[fsi.api_path]) {
|
||||
kv.second->set_disable_download_end(true);
|
||||
}
|
||||
d = create_download();
|
||||
download_lookup_[fsi.api_path] = {{0u, d}};
|
||||
}
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
std::string download_manager::get_source_path(const std::string &api_path) const {
|
||||
recur_mutex_lock download_lock(download_mutex_);
|
||||
if (contains_handle(api_path, 0u)) {
|
||||
return download_lookup_.at(api_path).at(0u)->get_source_path();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void download_manager::handle_download_end(const download_end &de) {
|
||||
download_ptr d;
|
||||
const auto api_path = de.get_api_path();
|
||||
const auto handle = utils::string::to_uint64(de.get_handle());
|
||||
unique_recur_mutex_lock download_lock(download_mutex_);
|
||||
if (contains_handle(api_path, handle)) {
|
||||
d = download_lookup_[api_path][handle];
|
||||
}
|
||||
download_lock.unlock();
|
||||
if (d) {
|
||||
download_lock.lock();
|
||||
download_lookup_[api_path].erase(handle);
|
||||
if (download_lookup_[api_path].empty()) {
|
||||
download_lookup_.erase(api_path);
|
||||
}
|
||||
|
||||
if (d->get_result() == api_error::download_incomplete) {
|
||||
const auto res = restore_db_->Put(rocksdb::WriteOptions(), api_path.get<std::string>(),
|
||||
create_resume_entry(*d).dump());
|
||||
if (res.ok()) {
|
||||
event_system::instance().raise<download_stored>(api_path, d->get_source_path());
|
||||
} else {
|
||||
event_system::instance().raise<download_store_failed>(api_path.get<std::string>(),
|
||||
d->get_source_path(), res.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
download_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void download_manager::on_handle_closed(const filesystem_item_handle_closed &handle_closed) {
|
||||
download_ptr d;
|
||||
const auto api_path = handle_closed.get_api_path();
|
||||
const auto handle = utils::string::to_uint64(handle_closed.get_handle());
|
||||
unique_recur_mutex_lock download_lock(download_mutex_);
|
||||
if (contains_handle(api_path, handle)) {
|
||||
d = download_lookup_[api_path][handle];
|
||||
}
|
||||
download_lock.unlock();
|
||||
if (d) {
|
||||
d->notify_stop_requested();
|
||||
}
|
||||
}
|
||||
|
||||
bool download_manager::is_processing(const std::string &api_path) const {
|
||||
recur_mutex_lock download_lock(download_mutex_);
|
||||
return download_lookup_.find(api_path) != download_lookup_.end();
|
||||
}
|
||||
|
||||
bool download_manager::pause_download(const std::string &api_path) {
|
||||
auto ret = true;
|
||||
|
||||
recur_mutex_lock download_lock(download_mutex_);
|
||||
if (download_lookup_.find(api_path) != download_lookup_.end()) {
|
||||
ret = false;
|
||||
if (download_lookup_[api_path].find(0u) != download_lookup_[api_path].end()) {
|
||||
if ((ret = (download_lookup_[api_path].size() == 1))) {
|
||||
ret = download_lookup_[api_path][0u]->pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error download_manager::read_bytes(const std::uint64_t &handle, filesystem_item &fsi,
|
||||
std::size_t read_size, const std::uint64_t &read_offset,
|
||||
std::vector<char> &data) {
|
||||
create_source_file_if_empty(fsi, config_);
|
||||
|
||||
if (stop_requested_) {
|
||||
return api_error::download_stopped;
|
||||
}
|
||||
|
||||
read_size = utils::calculate_read_size(fsi.size, read_size, read_offset);
|
||||
if (read_size == 0u) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
if (utils::file::is_file(fsi.source_path)) {
|
||||
std::uint64_t file_size = 0u;
|
||||
if (utils::file::get_file_size(fsi.source_path, file_size)) {
|
||||
if ((read_offset + read_size) <= file_size) {
|
||||
return utils::file::read_from_source(fsi, read_size, read_offset, data);
|
||||
}
|
||||
} else {
|
||||
return api_error::os_error;
|
||||
}
|
||||
}
|
||||
|
||||
auto d = get_download(handle, fsi, false);
|
||||
const auto result = d->get_result();
|
||||
return ((result == api_error::success) ? d->read_bytes(handle, read_size, read_offset, data)
|
||||
: result);
|
||||
}
|
||||
|
||||
void download_manager::rename_download(const std::string &from_api_path,
|
||||
const std::string &to_api_path) {
|
||||
recur_mutex_lock download_lock(download_mutex_);
|
||||
if (download_lookup_.find(from_api_path) != download_lookup_.end()) {
|
||||
if (download_lookup_[from_api_path].find(0u) != download_lookup_[from_api_path].end()) {
|
||||
download_lookup_[from_api_path][0u]->set_api_path(to_api_path);
|
||||
download_lookup_[to_api_path][0u] = download_lookup_[from_api_path][0u];
|
||||
download_lookup_.erase(from_api_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void download_manager::reset_timeout(const std::string &api_path, const bool &file_closed) {
|
||||
download_ptr d;
|
||||
unique_recur_mutex_lock download_lock(download_mutex_);
|
||||
if (contains_handle(api_path, 0u)) {
|
||||
d = download_lookup_[api_path][0u];
|
||||
}
|
||||
download_lock.unlock();
|
||||
if (d) {
|
||||
d->reset_timeout(file_closed);
|
||||
}
|
||||
}
|
||||
|
||||
api_error download_manager::resize(const std::uint64_t &handle, filesystem_item &fsi,
|
||||
const std::uint64_t &size) {
|
||||
return allocate(handle, fsi, size,
|
||||
[&]() -> api_error { return utils::file::truncate_source(fsi, size); });
|
||||
}
|
||||
|
||||
void download_manager::resume_download(const std::string &api_path) {
|
||||
recur_mutex_lock download_lock(download_mutex_);
|
||||
if (download_lookup_.find(api_path) != download_lookup_.end()) {
|
||||
if (download_lookup_[api_path].find(0u) != download_lookup_[api_path].end()) {
|
||||
download_lookup_[api_path][0u]->resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void download_manager::start(i_open_file_table *oft) {
|
||||
recur_mutex_lock start_stop_lock(start_stop_mutex_);
|
||||
if (stop_requested_) {
|
||||
stop_requested_ = false;
|
||||
oft_ = oft;
|
||||
start_incomplete();
|
||||
}
|
||||
}
|
||||
|
||||
void download_manager::start_incomplete() {
|
||||
auto iterator =
|
||||
std::shared_ptr<rocksdb::Iterator>(restore_db_->NewIterator(rocksdb::ReadOptions()));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
filesystem_item fsi{};
|
||||
fsi.lock = std::make_shared<std::recursive_mutex>();
|
||||
|
||||
std::size_t chunk_size, last_chunk_size;
|
||||
boost::dynamic_bitset<> readState, writeState;
|
||||
restore_resume_entry(json::parse(iterator->value().ToString()), fsi, chunk_size,
|
||||
last_chunk_size, readState, writeState);
|
||||
if (utils::file::get_file_size(fsi.source_path, fsi.size)) {
|
||||
download_lookup_[fsi.api_path][0u] = std::make_shared<download>(
|
||||
config_, fsi, api_reader_, chunk_size, last_chunk_size, readState, writeState, *oft_);
|
||||
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,
|
||||
"failed to get file size: " + utils::string::from_uint64(utils::get_last_error_code()));
|
||||
}
|
||||
restore_db_->Delete(rocksdb::WriteOptions(), iterator->key());
|
||||
}
|
||||
}
|
||||
|
||||
void download_manager::stop() {
|
||||
unique_recur_mutex_lock start_stop_lock(start_stop_mutex_);
|
||||
if (not stop_requested_) {
|
||||
event_system::instance().raise<service_shutdown>("download_manager");
|
||||
stop_requested_ = true;
|
||||
start_stop_lock.unlock();
|
||||
|
||||
std::vector<download_ptr> downloads;
|
||||
unique_recur_mutex_lock download_lock(download_mutex_);
|
||||
for (auto &d : download_lookup_) {
|
||||
for (auto &kv : d.second) {
|
||||
auto download = kv.second;
|
||||
if (download) {
|
||||
downloads.emplace_back(download);
|
||||
}
|
||||
}
|
||||
}
|
||||
download_lock.unlock();
|
||||
|
||||
for (auto &d : downloads) {
|
||||
d->notify_stop_requested();
|
||||
}
|
||||
|
||||
while (not download_lookup_.empty()) {
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api_error download_manager::write_bytes(const std::uint64_t &handle, filesystem_item &fsi,
|
||||
const std::uint64_t &write_offset, std::vector<char> data,
|
||||
std::size_t &bytes_written) {
|
||||
bytes_written = 0u;
|
||||
create_source_file_if_empty(fsi, config_);
|
||||
|
||||
if (stop_requested_) {
|
||||
return api_error::download_stopped;
|
||||
}
|
||||
|
||||
if (data.empty()) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
const auto completer = [&](std::uint64_t old_size, std::uint64_t new_size, bool changed) {
|
||||
fsi.changed = changed;
|
||||
if (old_size != new_size) {
|
||||
fsi.size = new_size;
|
||||
global_data::instance().update_used_space(old_size, new_size, false);
|
||||
}
|
||||
|
||||
const auto modified = std::to_string(utils::get_file_time_now());
|
||||
oft_->set_item_meta(fsi.api_path, META_MODIFIED, modified);
|
||||
oft_->set_item_meta(fsi.api_path, META_WRITTEN, modified);
|
||||
};
|
||||
|
||||
if (utils::file::is_file(fsi.source_path)) {
|
||||
std::uint64_t file_size = 0u;
|
||||
if (utils::file::get_file_size(fsi.source_path, file_size)) {
|
||||
if (fsi.size == file_size) {
|
||||
auto ret = utils::file::write_to_source(fsi, write_offset, data, bytes_written);
|
||||
if (ret == api_error::success) {
|
||||
std::uint64_t new_size{};
|
||||
utils::file::get_file_size(fsi.source_path, new_size);
|
||||
completer(fsi.size, new_size, true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
return api_error::os_error;
|
||||
}
|
||||
}
|
||||
|
||||
auto d = get_download(handle, fsi, true);
|
||||
const auto result = d->get_result();
|
||||
return ((result == api_error::success)
|
||||
? d->write_bytes(handle, write_offset, std::move(data), bytes_written, completer)
|
||||
: result);
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "download/reader_pool.hpp"
|
||||
|
||||
namespace repertory {
|
||||
void reader_pool::pause() {
|
||||
unique_mutex_lock work_lock(work_mutex_);
|
||||
if (not paused_) {
|
||||
paused_ = true;
|
||||
while (not stop_requested_ && not work_queue_.empty()) {
|
||||
work_notify_.notify_all();
|
||||
work_notify_.wait_for(work_lock, 2s);
|
||||
}
|
||||
}
|
||||
work_notify_.notify_all();
|
||||
}
|
||||
|
||||
void reader_pool::process_work_item(pool_work_item &work) {
|
||||
const auto result =
|
||||
api_reader_(work.api_path, work.read_size, work.read_offset, work.data, restart_active_);
|
||||
work.completed(result);
|
||||
}
|
||||
|
||||
void reader_pool::queue_read_bytes(const std::string &api_path, const std::size_t &read_size,
|
||||
const std::uint64_t &read_offset, std::vector<char> &data,
|
||||
completed_callback completed) {
|
||||
data.clear();
|
||||
|
||||
unique_mutex_lock work_lock(work_mutex_);
|
||||
wait_for_resume(work_lock);
|
||||
if (not stop_requested_) {
|
||||
if (pool_size_ == 1ull) {
|
||||
work_notify_.notify_all();
|
||||
work_lock.unlock();
|
||||
completed(api_reader_(api_path, read_size, read_offset, data, restart_active_));
|
||||
} else {
|
||||
auto work =
|
||||
std::make_shared<pool_work_item>(api_path, read_size, read_offset, data, completed);
|
||||
if (not restart_active_) {
|
||||
while ((work_queue_.size() >= pool_size_) && not restart_active_) {
|
||||
work_notify_.wait(work_lock);
|
||||
}
|
||||
|
||||
if (not restart_active_) {
|
||||
work_queue_.emplace_back(work);
|
||||
work_notify_.notify_all();
|
||||
}
|
||||
|
||||
work_lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reader_pool::restart() {
|
||||
restart_active_ = true;
|
||||
|
||||
unique_mutex_lock work_lock(work_mutex_);
|
||||
wait_for_resume(work_lock);
|
||||
work_notify_.notify_all();
|
||||
work_lock.unlock();
|
||||
|
||||
while (not work_queue_.empty() || active_count_) {
|
||||
work_lock.lock();
|
||||
if (not work_queue_.empty() || active_count_) {
|
||||
work_notify_.wait(work_lock);
|
||||
}
|
||||
work_lock.unlock();
|
||||
}
|
||||
|
||||
work_lock.lock();
|
||||
restart_active_ = false;
|
||||
work_notify_.notify_all();
|
||||
work_lock.unlock();
|
||||
}
|
||||
|
||||
void reader_pool::resume() {
|
||||
unique_mutex_lock work_lock(work_mutex_);
|
||||
if (paused_) {
|
||||
paused_ = false;
|
||||
}
|
||||
work_notify_.notify_all();
|
||||
}
|
||||
|
||||
void reader_pool::start() {
|
||||
restart_active_ = stop_requested_ = false;
|
||||
active_count_ = 0u;
|
||||
if (pool_size_ > 1u) {
|
||||
for (std::size_t i = 0u; i < pool_size_; i++) {
|
||||
work_threads_.emplace_back(std::thread([this]() {
|
||||
unique_mutex_lock work_lock(work_mutex_);
|
||||
work_notify_.notify_all();
|
||||
work_lock.unlock();
|
||||
|
||||
while (not stop_requested_) {
|
||||
work_lock.lock();
|
||||
if (not stop_requested_) {
|
||||
if (work_queue_.empty()) {
|
||||
work_notify_.wait(work_lock);
|
||||
}
|
||||
|
||||
if (not work_queue_.empty()) {
|
||||
active_count_++;
|
||||
auto work = work_queue_.front();
|
||||
work_queue_.pop_front();
|
||||
work_notify_.notify_all();
|
||||
work_lock.unlock();
|
||||
|
||||
process_work_item(*work);
|
||||
|
||||
work_lock.lock();
|
||||
active_count_--;
|
||||
}
|
||||
}
|
||||
|
||||
work_notify_.notify_all();
|
||||
work_lock.unlock();
|
||||
}
|
||||
|
||||
work_lock.lock();
|
||||
work_notify_.notify_all();
|
||||
work_lock.unlock();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reader_pool::stop() {
|
||||
stop_requested_ = true;
|
||||
paused_ = false;
|
||||
restart();
|
||||
restart_active_ = true;
|
||||
|
||||
unique_mutex_lock work_lock(work_mutex_);
|
||||
work_notify_.notify_all();
|
||||
work_lock.unlock();
|
||||
|
||||
for (auto &work_thread : work_threads_) {
|
||||
work_thread.join();
|
||||
}
|
||||
work_threads_.clear();
|
||||
}
|
||||
|
||||
void reader_pool::wait_for_resume(unique_mutex_lock &lock) {
|
||||
while (paused_ && not stop_requested_) {
|
||||
work_notify_.notify_all();
|
||||
work_notify_.wait_for(lock, 2s);
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,450 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "download/ring_download.hpp"
|
||||
#include "app_config.hpp"
|
||||
#include "download/buffered_reader.hpp"
|
||||
#include "download/events.hpp"
|
||||
#include "download/utils.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
ring_download::ring_download(const app_config &config, filesystem_item fsi,
|
||||
const api_reader_callback &api_reader, const std::uint64_t &handle,
|
||||
const std::size_t &chunk_size, const std::size_t &ring_buffer_size)
|
||||
: config_(config),
|
||||
fsi_(std::move(fsi)),
|
||||
api_reader_(api_reader),
|
||||
handle_(handle),
|
||||
chunk_size_(chunk_size),
|
||||
ring_state_(ring_buffer_size / chunk_size) {
|
||||
if (not chunk_size) {
|
||||
error_ = api_error::empty_ring_buffer_chunk_size;
|
||||
} else if (not ring_buffer_size) {
|
||||
error_ = api_error::empty_ring_buffer_size;
|
||||
} else if (ring_buffer_size % chunk_size) {
|
||||
error_ = api_error::invalid_ring_buffer_multiple;
|
||||
} else if (ring_buffer_size < chunk_size) {
|
||||
error_ = api_error::invalid_ring_buffer_size;
|
||||
} else {
|
||||
*const_cast<std::size_t *>(&total_chunks_) =
|
||||
utils::divide_with_ceiling(static_cast<std::size_t>(fsi.size), chunk_size_);
|
||||
const auto buffer_directory = utils::path::combine(config_.get_data_directory(), {"buffer"});
|
||||
if (utils::file::create_full_directory_path(buffer_directory)) {
|
||||
buffer_file_path_ = utils::path::combine(buffer_directory, {utils::create_uuid_string()});
|
||||
if ((error_ = native_file::create_or_open(buffer_file_path_, buffer_file_)) ==
|
||||
api_error::success) {
|
||||
if (not buffer_file_->allocate(ring_buffer_size)) {
|
||||
error_ = api_error::os_error;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error_ = api_error::os_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ring_download::~ring_download() {
|
||||
stop();
|
||||
|
||||
if (buffer_file_) {
|
||||
buffer_file_->close();
|
||||
utils::file::delete_file(buffer_file_path_);
|
||||
}
|
||||
}
|
||||
|
||||
void ring_download::buffer_thread(std::size_t start_chunk_index) {
|
||||
buffered_reader reader(config_, fsi_, api_reader_, chunk_size_, total_chunks_, start_chunk_index);
|
||||
|
||||
unique_mutex_lock write_lock(write_mutex_);
|
||||
buffered_reader_ = &reader;
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
const auto ring_size = ring_state_.size();
|
||||
const auto half_ring_size = ring_size / 2;
|
||||
double progress = 0.0;
|
||||
|
||||
const auto reset_on_overflow = [&](const std::size_t &amt) -> bool {
|
||||
if (amt >= ring_size) {
|
||||
ring_state_.reset();
|
||||
head_chunk_index_ = read_chunk_;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto decrement_head_chunk = [&](std::size_t amt) {
|
||||
if (not reset_on_overflow(amt)) {
|
||||
while (amt-- && head_chunk_index_) {
|
||||
head_chunk_index_--;
|
||||
ring_state_[head_chunk_index_ % ring_size] = false;
|
||||
}
|
||||
}
|
||||
write_chunk_ = read_chunk_;
|
||||
};
|
||||
|
||||
const auto increment_head_chunk = [&](std::size_t amt) {
|
||||
if (not reset_on_overflow(amt)) {
|
||||
while (amt-- && (head_chunk_index_ < (total_chunks_ - 1u))) {
|
||||
ring_state_[head_chunk_index_ % ring_size] = false;
|
||||
head_chunk_index_++;
|
||||
}
|
||||
}
|
||||
write_chunk_ = read_chunk_;
|
||||
};
|
||||
|
||||
while (is_active()) {
|
||||
write_lock.lock();
|
||||
|
||||
const auto get_overflow_chunk = [&]() { return head_chunk_index_ + ring_size; };
|
||||
while ((write_chunk_ > read_chunk_) && (write_chunk_ >= get_overflow_chunk()) && is_active()) {
|
||||
// Buffer 50% for read-ahead/read-behind
|
||||
auto buffered = false;
|
||||
while ((write_chunk_ > read_chunk_) && ((write_chunk_ - read_chunk_) > half_ring_size) &&
|
||||
is_active()) {
|
||||
buffered = true;
|
||||
read_notify_.wait(write_lock);
|
||||
}
|
||||
|
||||
if (not buffered && is_active()) {
|
||||
read_notify_.wait(write_lock);
|
||||
}
|
||||
|
||||
if ((write_chunk_ > read_chunk_) && ((write_chunk_ - read_chunk_) <= half_ring_size)) {
|
||||
increment_head_chunk(1u);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_active()) {
|
||||
if (read_chunk_ >= get_overflow_chunk()) {
|
||||
increment_head_chunk(read_chunk_ - get_overflow_chunk() + 1u);
|
||||
} else if (read_chunk_ < head_chunk_index_) {
|
||||
decrement_head_chunk(head_chunk_index_ - read_chunk_);
|
||||
} else if ((write_chunk_ < head_chunk_index_) || (write_chunk_ >= get_overflow_chunk())) {
|
||||
write_chunk_ = read_chunk_;
|
||||
}
|
||||
|
||||
const auto write_position = write_chunk_ % ring_size;
|
||||
if (ring_state_[write_position]) {
|
||||
write_chunk_++;
|
||||
} else {
|
||||
const auto file_offset = write_chunk_ * chunk_size_;
|
||||
const auto write_chunk_index = write_chunk_;
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
const auto read_size = utils::calculate_read_size(fsi_.size, chunk_size_, file_offset);
|
||||
if (read_size > 0u) {
|
||||
if (((write_chunk_index == 0u) && reader.has_first_chunk()) ||
|
||||
((write_chunk_index == (total_chunks_ - 1ull)) && reader.has_last_chunk())) {
|
||||
write_lock.lock();
|
||||
write_chunk_++;
|
||||
} else {
|
||||
std::vector<char> data;
|
||||
auto error = reader.read_chunk(write_chunk_index, data);
|
||||
if (error == api_error::success) {
|
||||
std::mutex job_mutex;
|
||||
std::condition_variable job_notify;
|
||||
unique_mutex_lock job_lock(job_mutex);
|
||||
|
||||
if (queue_io_item(job_mutex, job_notify, false, [&]() {
|
||||
#ifdef _DEBUG
|
||||
write_lock.lock();
|
||||
if (write_chunk_index >= (head_chunk_index_ + ring_size)) {
|
||||
throw std::runtime_error("Invalid write: write chunk >= head");
|
||||
}
|
||||
if (write_chunk_index < head_chunk_index_) {
|
||||
throw std::runtime_error("Invalid write: write chunk < head");
|
||||
}
|
||||
#endif // _DEBUG
|
||||
std::size_t bytes_written = 0u;
|
||||
if (buffer_file_->write_bytes(&data[0u], data.size(),
|
||||
write_position * chunk_size_, bytes_written)) {
|
||||
buffer_file_->flush();
|
||||
utils::download::notify_progress<download_progress>(
|
||||
config_, fsi_.api_path, buffer_file_path_,
|
||||
static_cast<double>(write_chunk_index + 1ull),
|
||||
static_cast<double>(total_chunks_), progress);
|
||||
} else {
|
||||
error = api_error::os_error;
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
write_lock.unlock();
|
||||
#endif // _DEBUG
|
||||
})) {
|
||||
job_notify.wait(job_lock);
|
||||
}
|
||||
job_notify.notify_all();
|
||||
job_lock.unlock();
|
||||
}
|
||||
|
||||
set_api_error(error);
|
||||
|
||||
write_lock.lock();
|
||||
if (error == api_error::success) {
|
||||
ring_state_[write_position] = true;
|
||||
write_chunk_++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
write_lock.lock();
|
||||
while ((read_chunk_ <= write_chunk_) && (read_chunk_ >= head_chunk_index_) &&
|
||||
(((write_chunk_ * chunk_size_) >= fsi_.size)) && is_active()) {
|
||||
read_notify_.wait(write_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
}
|
||||
|
||||
write_lock.lock();
|
||||
buffered_reader_ = nullptr;
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
if (not disable_download_end_) {
|
||||
event_system::instance().raise<download_end>(fsi_.api_path, buffer_file_path_, handle_, error_);
|
||||
}
|
||||
}
|
||||
|
||||
void ring_download::io_thread() {
|
||||
const auto process_all = [&]() {
|
||||
unique_mutex_lock io_lock(io_mutex_);
|
||||
while (not io_queue_.empty()) {
|
||||
auto &action = io_queue_.front();
|
||||
io_lock.unlock();
|
||||
|
||||
unique_mutex_lock job_lock(action->mutex);
|
||||
action->action();
|
||||
action->notify.notify_all();
|
||||
job_lock.unlock();
|
||||
|
||||
io_lock.lock();
|
||||
utils::remove_element_from(io_queue_, action);
|
||||
}
|
||||
io_lock.unlock();
|
||||
};
|
||||
|
||||
while (not stop_requested_) {
|
||||
if (io_queue_.empty()) {
|
||||
unique_mutex_lock io_lock(io_mutex_);
|
||||
if (not stop_requested_ && io_queue_.empty()) {
|
||||
io_notify_.wait(io_lock);
|
||||
}
|
||||
} else {
|
||||
process_all();
|
||||
}
|
||||
}
|
||||
|
||||
process_all();
|
||||
}
|
||||
|
||||
void ring_download::notify_stop_requested() {
|
||||
set_api_error(api_error::download_stopped);
|
||||
stop_requested_ = true;
|
||||
|
||||
unique_mutex_lock write_lock(write_mutex_);
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
unique_mutex_lock io_lock(io_mutex_);
|
||||
io_notify_.notify_all();
|
||||
io_lock.unlock();
|
||||
}
|
||||
|
||||
bool ring_download::queue_io_item(std::mutex &m, std::condition_variable &cv, const bool &is_read,
|
||||
std::function<void()> action) {
|
||||
auto ret = false;
|
||||
|
||||
mutex_lock io_lock(io_mutex_);
|
||||
if (not stop_requested_) {
|
||||
ret = true;
|
||||
if (is_read) {
|
||||
io_queue_.insert(io_queue_.begin(), std::make_unique<io_action>(m, cv, action));
|
||||
} else {
|
||||
io_queue_.emplace_back(std::make_unique<io_action>(m, cv, action));
|
||||
}
|
||||
}
|
||||
io_notify_.notify_all();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ring_download::read(std::size_t read_chunk_index, std::size_t read_size,
|
||||
std::size_t read_offset, std::vector<char> &data) {
|
||||
while (is_active() && (read_size > 0u)) {
|
||||
unique_mutex_lock write_lock(write_mutex_);
|
||||
if ((read_chunk_index == (total_chunks_ - 1ull)) && buffered_reader_ &&
|
||||
buffered_reader_->has_last_chunk()) {
|
||||
std::vector<char> *buffer = nullptr;
|
||||
buffered_reader_->get_last_chunk(buffer);
|
||||
if (buffer) {
|
||||
data.insert(data.end(), buffer->begin() + read_offset,
|
||||
buffer->begin() + read_offset + read_size);
|
||||
}
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
read_size = 0u;
|
||||
} else {
|
||||
const auto notify_read_location = [&]() {
|
||||
if (read_chunk_index != read_chunk_) {
|
||||
write_lock.lock();
|
||||
read_chunk_ = read_chunk_index;
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
}
|
||||
};
|
||||
|
||||
if ((read_chunk_index == 0u) && buffered_reader_ && buffered_reader_->has_first_chunk()) {
|
||||
const auto to_read = std::min(chunk_size_ - read_offset, read_size);
|
||||
std::vector<char> *buffer = nullptr;
|
||||
buffered_reader_->get_first_chunk(buffer);
|
||||
if (buffer) {
|
||||
data.insert(data.end(), buffer->begin() + read_offset,
|
||||
buffer->begin() + read_offset + to_read);
|
||||
}
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
read_size -= to_read;
|
||||
read_offset = 0u;
|
||||
read_chunk_index++;
|
||||
notify_read_location();
|
||||
} else {
|
||||
const auto ring_size = ring_state_.size();
|
||||
while (((read_chunk_index > write_chunk_) || (read_chunk_index < head_chunk_index_) ||
|
||||
(read_chunk_index >= (head_chunk_index_ + ring_size))) &&
|
||||
is_active()) {
|
||||
read_chunk_ = read_chunk_index;
|
||||
read_notify_.notify_all();
|
||||
read_notify_.wait(write_lock);
|
||||
}
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
|
||||
if (is_active()) {
|
||||
const auto ringPos = read_chunk_index % ring_size;
|
||||
notify_read_location();
|
||||
if (ring_state_[ringPos]) {
|
||||
const auto to_read = std::min(chunk_size_ - read_offset, read_size);
|
||||
std::mutex job_mutex;
|
||||
std::condition_variable job_notify;
|
||||
unique_mutex_lock job_lock(job_mutex);
|
||||
|
||||
if (queue_io_item(job_mutex, job_notify, true, [&]() {
|
||||
std::vector<char> buffer(to_read);
|
||||
std::size_t bytes_read = 0u;
|
||||
if (buffer_file_->read_bytes(&buffer[0u], buffer.size(),
|
||||
(ringPos * chunk_size_) + read_offset, bytes_read)) {
|
||||
data.insert(data.end(), buffer.begin(), buffer.end());
|
||||
buffer.clear();
|
||||
|
||||
read_size -= to_read;
|
||||
read_offset = 0u;
|
||||
} else {
|
||||
set_api_error(api_error::os_error);
|
||||
}
|
||||
})) {
|
||||
job_notify.wait(job_lock);
|
||||
read_chunk_index++;
|
||||
}
|
||||
job_notify.notify_all();
|
||||
job_lock.unlock();
|
||||
} else {
|
||||
write_lock.lock();
|
||||
while (not ring_state_[ringPos] && is_active()) {
|
||||
read_notify_.wait(write_lock);
|
||||
}
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api_error ring_download::read_bytes(const std::uint64_t &, std::size_t read_size,
|
||||
const std::uint64_t &read_offset, std::vector<char> &data) {
|
||||
data.clear();
|
||||
|
||||
mutex_lock read_lock(read_mutex_);
|
||||
if (is_active()) {
|
||||
if (((read_size = utils::calculate_read_size(fsi_.size, read_size, read_offset)) > 0u)) {
|
||||
const auto read_chunk_index = static_cast<std::size_t>(read_offset / chunk_size_);
|
||||
if (not buffer_thread_) {
|
||||
start(read_chunk_index);
|
||||
}
|
||||
|
||||
read(read_chunk_index, read_size, read_offset % chunk_size_, data);
|
||||
set_api_error(((error_ == api_error::success) && stop_requested_)
|
||||
? api_error::download_stopped
|
||||
: error_);
|
||||
}
|
||||
}
|
||||
|
||||
return error_;
|
||||
}
|
||||
|
||||
void ring_download::set_api_error(const api_error &error) {
|
||||
if ((error_ == api_error::success) && (error_ != error)) {
|
||||
error_ = error;
|
||||
}
|
||||
}
|
||||
|
||||
void ring_download::start(const std::size_t &startChunk) {
|
||||
event_system::instance().raise<download_begin>(fsi_.api_path, buffer_file_path_);
|
||||
|
||||
error_ = api_error::success;
|
||||
stop_requested_ = false;
|
||||
head_chunk_index_ = read_chunk_ = write_chunk_ = startChunk;
|
||||
ring_state_.reset();
|
||||
|
||||
io_thread_ = std::make_unique<std::thread>([this] { this->io_thread(); });
|
||||
|
||||
unique_mutex_lock write_lock(write_mutex_);
|
||||
buffer_thread_ =
|
||||
std::make_unique<std::thread>([this, startChunk]() { this->buffer_thread(startChunk); });
|
||||
read_notify_.wait(write_lock);
|
||||
read_notify_.notify_all();
|
||||
write_lock.unlock();
|
||||
}
|
||||
|
||||
void ring_download::stop() {
|
||||
notify_stop_requested();
|
||||
|
||||
if (buffer_thread_) {
|
||||
buffer_thread_->join();
|
||||
buffer_thread_.reset();
|
||||
}
|
||||
|
||||
if (io_thread_) {
|
||||
io_thread_->join();
|
||||
io_thread_.reset();
|
||||
|
||||
io_queue_.clear();
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,63 +1,42 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "drives/directory_cache.hpp"
|
||||
|
||||
#include "drives/directory_iterator.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
|
||||
namespace repertory {
|
||||
bool directory_cache::execute_action(const std::string &api_path, const execute_callback &execute) {
|
||||
auto found = false;
|
||||
void directory_cache::execute_action(const std::string &api_path,
|
||||
const execute_callback &execute) {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
if ((found = (directory_lookup_.find(api_path) != directory_lookup_.end()))) {
|
||||
if ((directory_lookup_.find(api_path) != directory_lookup_.end())) {
|
||||
execute(*directory_lookup_[api_path].iterator);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void directory_cache::refresh_thread() {
|
||||
while (not is_shutdown_) {
|
||||
unique_recur_mutex_lock directory_lock(directory_mutex_);
|
||||
auto lookup = directory_lookup_;
|
||||
directory_lock.unlock();
|
||||
|
||||
for (const auto &kv : lookup) {
|
||||
if (std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() -
|
||||
kv.second.last_update) >= 120s) {
|
||||
directory_lock.lock();
|
||||
directory_lookup_.erase(kv.first);
|
||||
directory_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
if (not is_shutdown_) {
|
||||
unique_mutex_lock shutdown_lock(shutdown_mutex_);
|
||||
if (not is_shutdown_) {
|
||||
shutdown_notify_.wait_for(shutdown_lock, 15s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
directory_iterator *directory_cache::remove_directory(const std::string &api_path) {
|
||||
auto directory_cache::remove_directory(const std::string &api_path)
|
||||
-> directory_iterator * {
|
||||
directory_iterator *ret = nullptr;
|
||||
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
@@ -72,39 +51,42 @@ directory_iterator *directory_cache::remove_directory(const std::string &api_pat
|
||||
void directory_cache::remove_directory(directory_iterator *iterator) {
|
||||
if (iterator) {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
const auto it = std::find_if(
|
||||
directory_lookup_.begin(), directory_lookup_.end(),
|
||||
[&iterator](const auto &kv) -> bool { return kv.second.iterator == iterator; });
|
||||
const auto it =
|
||||
std::find_if(directory_lookup_.begin(), directory_lookup_.end(),
|
||||
[&iterator](const auto &kv) -> bool {
|
||||
return kv.second.iterator == iterator;
|
||||
});
|
||||
if (it != directory_lookup_.end()) {
|
||||
directory_lookup_.erase(it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void directory_cache::set_directory(const std::string &api_path, directory_iterator *iterator) {
|
||||
void directory_cache::service_function() {
|
||||
unique_recur_mutex_lock directory_lock(directory_mutex_);
|
||||
auto lookup = directory_lookup_;
|
||||
directory_lock.unlock();
|
||||
|
||||
for (const auto &kv : lookup) {
|
||||
if (std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now() - kv.second.last_update) >= 120s) {
|
||||
directory_lock.lock();
|
||||
directory_lookup_.erase(kv.first);
|
||||
directory_lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
if (not get_stop_requested()) {
|
||||
unique_mutex_lock shutdown_lock(get_mutex());
|
||||
if (not get_stop_requested()) {
|
||||
get_notify().wait_for(shutdown_lock, 15s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void directory_cache::set_directory(const std::string &api_path,
|
||||
directory_iterator *iterator) {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
directory_lookup_[api_path] = {iterator};
|
||||
}
|
||||
|
||||
void directory_cache::start() {
|
||||
unique_mutex_lock shutdown_lock(shutdown_mutex_);
|
||||
if (is_shutdown_) {
|
||||
is_shutdown_ = false;
|
||||
|
||||
refresh_thread_ = std::make_unique<std::thread>([this]() { this->refresh_thread(); });
|
||||
}
|
||||
}
|
||||
|
||||
void directory_cache::stop() {
|
||||
unique_mutex_lock shutdown_lock(shutdown_mutex_);
|
||||
if (not is_shutdown_) {
|
||||
event_system::instance().raise<service_shutdown>("directory_cache");
|
||||
is_shutdown_ = true;
|
||||
shutdown_notify_.notify_all();
|
||||
shutdown_lock.unlock();
|
||||
|
||||
refresh_thread_->join();
|
||||
refresh_thread_.reset();
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
@@ -1,53 +1,70 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "drives/directory_iterator.hpp"
|
||||
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
#ifndef _WIN32
|
||||
int directory_iterator::fill_buffer(const remote::file_offset &offset,
|
||||
fuse_fill_dir_t filler_function, void *buffer,
|
||||
populate_stat_callback populate_stat) {
|
||||
auto directory_iterator::fill_buffer(const remote::file_offset &offset,
|
||||
fuse_fill_dir_t filler_function,
|
||||
void *buffer,
|
||||
populate_stat_callback populate_stat)
|
||||
-> int {
|
||||
if (offset < items_.size()) {
|
||||
std::string item_name;
|
||||
struct stat st {};
|
||||
struct stat *pst = nullptr;
|
||||
switch (offset) {
|
||||
case 0: {
|
||||
item_name = ".";
|
||||
} break;
|
||||
try {
|
||||
std::string item_name;
|
||||
struct stat st {};
|
||||
struct stat *pst = nullptr;
|
||||
switch (offset) {
|
||||
case 0: {
|
||||
item_name = ".";
|
||||
} break;
|
||||
|
||||
case 1: {
|
||||
item_name = "..";
|
||||
} break;
|
||||
case 1: {
|
||||
item_name = "..";
|
||||
} break;
|
||||
|
||||
default: {
|
||||
const auto &item = items_[offset];
|
||||
item_name = utils::path::strip_to_file_name(item.api_path);
|
||||
populate_stat(item.api_path, item.size, item.meta, item.directory, &st);
|
||||
pst = &st;
|
||||
} break;
|
||||
}
|
||||
default: {
|
||||
const auto &item = items_[offset];
|
||||
item_name = utils::path::strip_to_file_name(item.api_path);
|
||||
populate_stat(item.api_path, item.size, item.meta, item.directory, &st);
|
||||
pst = &st;
|
||||
} break;
|
||||
}
|
||||
|
||||
if (filler_function(buffer, &item_name[0], pst, offset + 1) != 0) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
if (filler_function(buffer, &item_name[0], pst, offset + 1,
|
||||
FUSE_FILL_DIR_PLUS) != 0) {
|
||||
#else
|
||||
if (filler_function(buffer, &item_name[0], pst, offset + 1) != 0) {
|
||||
#endif
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e,
|
||||
"failed to fill fuse directory buffer");
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -58,7 +75,7 @@ int directory_iterator::fill_buffer(const remote::file_offset &offset,
|
||||
}
|
||||
#endif // !_WIN32
|
||||
|
||||
int directory_iterator::get(const std::size_t &offset, std::string &item) {
|
||||
auto directory_iterator::get(std::size_t offset, std::string &item) -> int {
|
||||
if (offset < items_.size()) {
|
||||
item = items_[offset].api_path;
|
||||
return 0;
|
||||
@@ -68,7 +85,8 @@ int directory_iterator::get(const std::size_t &offset, std::string &item) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
api_error directory_iterator::get_directory_item(const std::size_t &offset, directory_item &di) {
|
||||
auto directory_iterator::get_directory_item(std::size_t offset,
|
||||
directory_item &di) -> api_error {
|
||||
if (offset < items_.size()) {
|
||||
di = items_[offset];
|
||||
return api_error::success;
|
||||
@@ -77,9 +95,12 @@ api_error directory_iterator::get_directory_item(const std::size_t &offset, dire
|
||||
return api_error::directory_end_of_files;
|
||||
}
|
||||
|
||||
api_error directory_iterator::get_directory_item(const std::string &api_path, directory_item &di) {
|
||||
auto iter = std::find_if(items_.begin(), items_.end(),
|
||||
[&](const auto &item) -> bool { return api_path == item.api_path; });
|
||||
auto directory_iterator::get_directory_item(const std::string &api_path,
|
||||
directory_item &di) -> api_error {
|
||||
auto iter =
|
||||
std::find_if(items_.begin(), items_.end(), [&](const auto &item) -> bool {
|
||||
return api_path == item.api_path;
|
||||
});
|
||||
if (iter != items_.end()) {
|
||||
di = *iter;
|
||||
return api_error::success;
|
||||
@@ -88,7 +109,7 @@ api_error directory_iterator::get_directory_item(const std::string &api_path, di
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
int directory_iterator::get_json(const std::size_t &offset, json &item) {
|
||||
auto directory_iterator::get_json(std::size_t offset, json &item) -> int {
|
||||
if (offset < items_.size()) {
|
||||
item = items_[offset].to_json();
|
||||
return 0;
|
||||
@@ -98,29 +119,35 @@ int directory_iterator::get_json(const std::size_t &offset, json &item) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::size_t directory_iterator::get_next_directory_offset(const std::string &api_path) const {
|
||||
const auto it = std::find_if(items_.begin(), items_.end(), [&api_path](const auto &di) -> bool {
|
||||
return api_path == di.api_path;
|
||||
});
|
||||
auto directory_iterator::get_next_directory_offset(
|
||||
const std::string &api_path) const -> std::size_t {
|
||||
const auto it = std::find_if(
|
||||
items_.begin(), items_.end(),
|
||||
[&api_path](const auto &di) -> bool { return api_path == di.api_path; });
|
||||
|
||||
return (it == items_.end()) ? 0 : std::distance(items_.begin(), it) + std::size_t(1u);
|
||||
return (it == items_.end())
|
||||
? 0
|
||||
: std::distance(items_.begin(), it) + std::size_t(1u);
|
||||
}
|
||||
|
||||
directory_iterator &directory_iterator::operator=(const directory_iterator &iterator) noexcept {
|
||||
auto directory_iterator::operator=(const directory_iterator &iterator) noexcept
|
||||
-> directory_iterator & {
|
||||
if (this != &iterator) {
|
||||
items_ = iterator.items_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
directory_iterator &directory_iterator::operator=(directory_iterator &&iterator) noexcept {
|
||||
auto directory_iterator::operator=(directory_iterator &&iterator) noexcept
|
||||
-> directory_iterator & {
|
||||
if (this != &iterator) {
|
||||
items_ = std::move(iterator.items_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
directory_iterator &directory_iterator::operator=(directory_item_list list) noexcept {
|
||||
auto directory_iterator::operator=(directory_item_list list) noexcept
|
||||
-> directory_iterator & {
|
||||
if (&items_ != &list) {
|
||||
items_ = std::move(list);
|
||||
}
|
||||
|
||||
@@ -1,108 +1,62 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "drives/eviction.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "drives/i_open_file_table.hpp"
|
||||
#include "file_manager/i_file_manager.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/global_data.hpp"
|
||||
#include "utils/unix/unix_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
void eviction::check_items_thread() {
|
||||
while (not stop_requested_) {
|
||||
auto should_evict = true;
|
||||
|
||||
// Handle maximum cache size eviction
|
||||
auto used_bytes = global_data::instance().get_used_cache_space();
|
||||
if (config_.get_enable_max_cache_size()) {
|
||||
should_evict = (used_bytes > config_.get_max_cache_size_bytes());
|
||||
}
|
||||
|
||||
// Evict all items if minimum redundancy eviction is enabled; otherwise, evict
|
||||
// until required space is reclaimed.
|
||||
if (should_evict) {
|
||||
// Remove cached source files that don't meet minimum requirements
|
||||
auto cached_files_list = get_filtered_cached_files();
|
||||
if (not cached_files_list.empty()) {
|
||||
while (not stop_requested_ && should_evict && not cached_files_list.empty()) {
|
||||
std::string api_path;
|
||||
if (provider_.get_api_path_from_source(cached_files_list.front(), api_path) ==
|
||||
api_error::success) {
|
||||
std::string pinned;
|
||||
provider_.get_item_meta(api_path, META_PINNED, pinned);
|
||||
|
||||
api_file file{};
|
||||
filesystem_item fsi{};
|
||||
if ((pinned.empty() || not utils::string::to_bool(pinned)) &&
|
||||
provider_.get_filesystem_item_and_file(api_path, file, fsi) == api_error::success) {
|
||||
// Only evict files that match expected size
|
||||
std::uint64_t file_size = 0u;
|
||||
utils::file::get_file_size(cached_files_list.front(), file_size);
|
||||
if (file_size == fsi.size) {
|
||||
// Ensure minimum file redundancy has been met or source path is not being
|
||||
// used for local recovery
|
||||
const auto different_source = file.source_path != fsi.source_path;
|
||||
if (file.recoverable &&
|
||||
((file.redundancy >= config_.get_minimum_redundancy()) || different_source)) {
|
||||
// Try to evict file
|
||||
if (oft_.evict_file(fsi.api_path) && config_.get_enable_max_cache_size()) {
|
||||
// Restrict number of items evicted if maximum cache size is enabled
|
||||
used_bytes -= file_size;
|
||||
should_evict = (used_bytes > config_.get_max_cache_size_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cached_files_list.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (not stop_requested_) {
|
||||
unique_mutex_lock l(eviction_mutex_);
|
||||
if (not stop_requested_) {
|
||||
stop_notify_.wait_for(l, 30s);
|
||||
}
|
||||
}
|
||||
auto eviction::check_minimum_requirements(const std::string &file_path)
|
||||
-> bool {
|
||||
std::uint64_t file_size{};
|
||||
if (not utils::file::get_file_size(file_path, file_size)) {
|
||||
utils::error::raise_error(__FUNCTION__, utils::get_last_error_code(),
|
||||
file_path, "failed to get file size");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool eviction::check_minimum_requirements(const std::string &file_path) {
|
||||
auto ret = false;
|
||||
// Only evict cachedFileList that are > 0
|
||||
std::uint64_t file_size = 0u;
|
||||
utils::file::get_file_size(file_path, file_size);
|
||||
if (file_size) {
|
||||
// Check modified/accessed date/time
|
||||
std::uint64_t reference_time = 0u;
|
||||
if ((ret = config_.get_eviction_uses_accessed_time()
|
||||
? utils::file::get_accessed_time(file_path, reference_time)
|
||||
: utils::file::get_modified_time(file_path, reference_time))) {
|
||||
std::uint64_t reference_time{};
|
||||
if ((ret =
|
||||
config_.get_eviction_uses_accessed_time()
|
||||
? utils::file::get_accessed_time(file_path, reference_time)
|
||||
: utils::file::get_modified_time(file_path, reference_time))) {
|
||||
#ifdef _WIN32
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto delay = std::chrono::minutes(config_.get_eviction_delay_mins());
|
||||
ret = ((std::chrono::system_clock::from_time_t(reference_time) + delay) <= now);
|
||||
const auto delay =
|
||||
std::chrono::minutes(config_.get_eviction_delay_mins());
|
||||
ret = ((std::chrono::system_clock::from_time_t(reference_time) + delay) <=
|
||||
now);
|
||||
#else
|
||||
const auto now = utils::get_time_now();
|
||||
const auto delay = (config_.get_eviction_delay_mins() * 60L) * NANOS_PER_SECOND;
|
||||
const auto delay =
|
||||
(config_.get_eviction_delay_mins() * 60L) * NANOS_PER_SECOND;
|
||||
ret = ((reference_time + delay) <= now);
|
||||
#endif
|
||||
}
|
||||
@@ -111,8 +65,9 @@ bool eviction::check_minimum_requirements(const std::string &file_path) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::deque<std::string> eviction::get_filtered_cached_files() {
|
||||
auto list = utils::file::get_directory_files(config_.get_cache_directory(), true);
|
||||
auto eviction::get_filtered_cached_files() -> std::deque<std::string> {
|
||||
auto list =
|
||||
utils::file::get_directory_files(config_.get_cache_directory(), true);
|
||||
list.erase(std::remove_if(list.begin(), list.end(),
|
||||
[this](const std::string &path) -> bool {
|
||||
return not this->check_minimum_requirements(path);
|
||||
@@ -121,25 +76,66 @@ std::deque<std::string> eviction::get_filtered_cached_files() {
|
||||
return list;
|
||||
}
|
||||
|
||||
void eviction::start() {
|
||||
mutex_lock l(start_stop_mutex_);
|
||||
if (not eviction_thread_) {
|
||||
stop_requested_ = false;
|
||||
eviction_thread_ = std::make_unique<std::thread>([this] { this->check_items_thread(); });
|
||||
}
|
||||
}
|
||||
void eviction::service_function() {
|
||||
auto should_evict = true;
|
||||
|
||||
void eviction::stop() {
|
||||
mutex_lock l(start_stop_mutex_);
|
||||
if (eviction_thread_) {
|
||||
event_system::instance().raise<service_shutdown>("eviction");
|
||||
stop_requested_ = true;
|
||||
{
|
||||
mutex_lock l2(eviction_mutex_);
|
||||
stop_notify_.notify_all();
|
||||
// Handle maximum cache size eviction
|
||||
auto used_bytes =
|
||||
utils::file::calculate_used_space(config_.get_cache_directory(), false);
|
||||
if (config_.get_enable_max_cache_size()) {
|
||||
should_evict = (used_bytes > config_.get_max_cache_size_bytes());
|
||||
}
|
||||
|
||||
if (should_evict) {
|
||||
// Remove cached source files that don't meet minimum requirements
|
||||
auto cached_files_list = get_filtered_cached_files();
|
||||
while (not get_stop_requested() && should_evict &&
|
||||
not cached_files_list.empty()) {
|
||||
try {
|
||||
std::string api_path;
|
||||
if (provider_.get_api_path_from_source(
|
||||
cached_files_list.front(), api_path) == api_error::success) {
|
||||
api_file file{};
|
||||
filesystem_item fsi{};
|
||||
if (provider_.get_filesystem_item_and_file(api_path, file, fsi) ==
|
||||
api_error::success) {
|
||||
// Only evict files that match expected size
|
||||
std::uint64_t file_size{};
|
||||
if (utils::file::get_file_size(cached_files_list.front(),
|
||||
file_size)) {
|
||||
if (file_size == fsi.size) {
|
||||
// Try to evict file
|
||||
if (fm_.evict_file(fsi.api_path) &&
|
||||
config_.get_enable_max_cache_size()) {
|
||||
// Restrict number of items evicted if maximum cache size is
|
||||
// enabled
|
||||
used_bytes -= file_size;
|
||||
should_evict =
|
||||
(used_bytes > config_.get_max_cache_size_bytes());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
utils::error::raise_api_path_error(
|
||||
__FUNCTION__, file.api_path, file.source_path,
|
||||
utils::get_last_error_code(), "failed to get file size");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::raise_error(__FUNCTION__, ex,
|
||||
"failed to process cached file|sp|" +
|
||||
cached_files_list.front());
|
||||
}
|
||||
|
||||
cached_files_list.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
if (not get_stop_requested()) {
|
||||
unique_mutex_lock lock(get_mutex());
|
||||
if (not get_stop_requested()) {
|
||||
get_notify().wait_for(lock, 30s);
|
||||
}
|
||||
eviction_thread_->join();
|
||||
eviction_thread_.reset();
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
353
src/drives/fuse/fuse_drive_base.cpp
Normal file
353
src/drives/fuse/fuse_drive_base.cpp
Normal file
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 _WIN32
|
||||
|
||||
#include "drives/fuse/fuse_drive_base.hpp"
|
||||
|
||||
#include "providers/i_provider.hpp"
|
||||
|
||||
namespace repertory {
|
||||
auto fuse_drive_base::access_impl(std::string api_path, int mask) -> api_error {
|
||||
return check_access(api_path, mask);
|
||||
}
|
||||
|
||||
auto fuse_drive_base::check_access(const std::string &api_path, int mask) const
|
||||
-> api_error {
|
||||
api_meta_map meta;
|
||||
const auto res = get_item_meta(api_path, meta);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Always allow root
|
||||
auto current_uid = get_current_uid();
|
||||
if (current_uid != 0) {
|
||||
// Always allow forced user
|
||||
if (not forced_uid_.has_value() || (current_uid != get_effective_uid())) {
|
||||
// Always allow if checking file exists
|
||||
if (F_OK != mask) {
|
||||
const auto effective_uid =
|
||||
(forced_uid_.has_value() ? forced_uid_.value()
|
||||
: get_uid_from_meta(meta));
|
||||
const auto effective_gid =
|
||||
(forced_gid_.has_value() ? forced_gid_.value()
|
||||
: get_gid_from_meta(meta));
|
||||
|
||||
// Create file mode
|
||||
mode_t effective_mode =
|
||||
forced_umask_.has_value()
|
||||
? ((S_IRWXU | S_IRWXG | S_IRWXO) & (~forced_umask_.value()))
|
||||
: get_mode_from_meta(meta);
|
||||
|
||||
// Create access mask
|
||||
mode_t active_mask = S_IRWXO;
|
||||
if (current_uid == effective_uid) {
|
||||
active_mask |= S_IRWXU;
|
||||
}
|
||||
if (get_current_gid() == effective_gid) {
|
||||
active_mask |= S_IRWXG;
|
||||
}
|
||||
if (utils::is_uid_member_of_group(current_uid, effective_gid)) {
|
||||
active_mask |= S_IRWXG;
|
||||
}
|
||||
|
||||
// Calculate effective file mode
|
||||
effective_mode &= active_mask;
|
||||
|
||||
// Check allow execute
|
||||
if ((mask & X_OK) == X_OK) {
|
||||
if ((effective_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) {
|
||||
return api_error::permission_denied;
|
||||
}
|
||||
}
|
||||
|
||||
// Check allow write
|
||||
if ((mask & W_OK) == W_OK) {
|
||||
if ((effective_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0) {
|
||||
return api_error::access_denied;
|
||||
}
|
||||
}
|
||||
|
||||
// Check allow read
|
||||
if ((mask & R_OK) == R_OK) {
|
||||
if ((effective_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
|
||||
return api_error::access_denied;
|
||||
}
|
||||
}
|
||||
|
||||
if (effective_mode == 0) {
|
||||
// Deny access if effective mode is 0
|
||||
return api_error::access_denied;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto fuse_drive_base::check_and_perform(
|
||||
const std::string &api_path, int parent_mask,
|
||||
const std::function<api_error(api_meta_map &meta)> &action) -> api_error {
|
||||
api_meta_map meta;
|
||||
auto ret = get_item_meta(api_path, meta);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = check_parent_access(api_path, parent_mask)) !=
|
||||
api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((ret = check_owner(meta)) != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return action(meta);
|
||||
}
|
||||
|
||||
auto fuse_drive_base::check_open_flags(int flags, int mask,
|
||||
const api_error &fail_error)
|
||||
-> api_error {
|
||||
return ((flags & mask) ? fail_error : api_error::success);
|
||||
}
|
||||
|
||||
auto fuse_drive_base::check_owner(const std::string &api_path) const
|
||||
-> api_error {
|
||||
api_meta_map meta{};
|
||||
auto ret = get_item_meta(api_path, meta);
|
||||
if (ret == api_error::success) {
|
||||
ret = check_owner(meta);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto fuse_drive_base::check_owner(const api_meta_map &meta) const -> api_error {
|
||||
// Always allow root
|
||||
auto current_uid = get_current_uid();
|
||||
if ((current_uid != 0) &&
|
||||
// Always allow forced UID
|
||||
(not forced_uid_.has_value() || (current_uid != get_effective_uid())) &&
|
||||
// Check if current uid matches file uid
|
||||
(get_uid_from_meta(meta) != get_effective_uid())) {
|
||||
return api_error::permission_denied;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto fuse_drive_base::check_parent_access(const std::string &api_path,
|
||||
int mask) const -> api_error {
|
||||
auto ret = api_error::success;
|
||||
|
||||
// Ignore root
|
||||
if (api_path != "/") {
|
||||
if ((mask & X_OK) == X_OK) {
|
||||
for (auto parent = utils::path::get_parent_directory(api_path);
|
||||
(ret == api_error::success) && not parent.empty();
|
||||
parent = utils::path::get_parent_directory(parent)) {
|
||||
if (((ret = check_access(parent, X_OK)) == api_error::success) &&
|
||||
(parent == "/")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == api_error::success) {
|
||||
mask &= (~X_OK);
|
||||
if (mask != 0) {
|
||||
ret = check_access(utils::path::get_parent_directory(api_path), mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto fuse_drive_base::check_readable(int flags, const api_error &fail_error)
|
||||
-> api_error {
|
||||
const auto mode = (flags & O_ACCMODE);
|
||||
return ((mode == O_WRONLY) ? fail_error : api_error::success);
|
||||
}
|
||||
|
||||
auto fuse_drive_base::check_writeable(int flags, const api_error &fail_error)
|
||||
-> api_error {
|
||||
return ((flags & O_ACCMODE) ? api_error::success : fail_error);
|
||||
}
|
||||
|
||||
auto fuse_drive_base::get_current_gid() const -> gid_t {
|
||||
auto *ctx = fuse_get_context();
|
||||
return ctx ? ctx->gid : getgid();
|
||||
}
|
||||
|
||||
auto fuse_drive_base::get_current_uid() const -> uid_t {
|
||||
auto *ctx = fuse_get_context();
|
||||
return ctx ? ctx->uid : getuid();
|
||||
}
|
||||
|
||||
auto fuse_drive_base::get_effective_gid() const -> gid_t {
|
||||
return forced_gid_.has_value() ? forced_gid_.value() : get_current_gid();
|
||||
}
|
||||
|
||||
auto fuse_drive_base::get_effective_uid() const -> uid_t {
|
||||
return forced_uid_.has_value() ? forced_uid_.value() : get_current_uid();
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
auto fuse_drive_base::get_flags_from_meta(const api_meta_map &meta)
|
||||
-> __uint32_t {
|
||||
return utils::string::to_uint32(meta.at(META_OSXFLAGS));
|
||||
}
|
||||
#endif // __APPLE__
|
||||
|
||||
auto fuse_drive_base::get_gid_from_meta(const api_meta_map &meta) -> gid_t {
|
||||
return static_cast<gid_t>(utils::string::to_uint32(meta.at(META_GID)));
|
||||
}
|
||||
|
||||
auto fuse_drive_base::get_mode_from_meta(const api_meta_map &meta) -> mode_t {
|
||||
return static_cast<mode_t>(utils::string::to_uint32(meta.at(META_MODE)));
|
||||
}
|
||||
|
||||
void fuse_drive_base::get_timespec_from_meta(const api_meta_map &meta,
|
||||
const std::string &name,
|
||||
struct timespec &ts) {
|
||||
const auto t = utils::string::to_uint64(meta.at(name));
|
||||
ts.tv_nsec = t % NANOS_PER_SECOND;
|
||||
ts.tv_sec = t / NANOS_PER_SECOND;
|
||||
}
|
||||
|
||||
auto fuse_drive_base::get_uid_from_meta(const api_meta_map &meta) -> uid_t {
|
||||
return static_cast<uid_t>(utils::string::to_uint32(meta.at(META_UID)));
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
auto fuse_drive_base::parse_xattr_parameters(const char *name,
|
||||
const uint32_t &position,
|
||||
std::string &attribute_name,
|
||||
const std::string &api_path)
|
||||
-> api_error {
|
||||
#else
|
||||
auto fuse_drive_base::parse_xattr_parameters(const char *name,
|
||||
std::string &attribute_name,
|
||||
const std::string &api_path)
|
||||
-> api_error {
|
||||
#endif
|
||||
auto res = api_path.empty() ? api_error::bad_address : api_error::success;
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (not name) {
|
||||
return api_error::bad_address;
|
||||
}
|
||||
|
||||
attribute_name = std::string(name);
|
||||
#ifdef __APPLE__
|
||||
if (attribute_name == A_KAUTH_FILESEC_XATTR) {
|
||||
char new_name[MAXPATHLEN] = {0};
|
||||
memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR));
|
||||
memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1);
|
||||
attribute_name = new_name;
|
||||
} else if (attribute_name.empty() ||
|
||||
((attribute_name != XATTR_RESOURCEFORK_NAME) && (position != 0))) {
|
||||
return api_error::invalid_operation;
|
||||
}
|
||||
#endif
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
auto fuse_drive_base::parse_xattr_parameters(
|
||||
const char *name, const char *value, size_t size, const uint32_t &position,
|
||||
std::string &attribute_name, const std::string &api_path) -> api_error {
|
||||
auto res = parse_xattr_parameters(name, position, attribute_name, api_path);
|
||||
#else
|
||||
auto fuse_drive_base::parse_xattr_parameters(const char *name,
|
||||
const char *value, size_t size,
|
||||
std::string &attribute_name,
|
||||
const std::string &api_path)
|
||||
-> api_error {
|
||||
auto res = parse_xattr_parameters(name, attribute_name, api_path);
|
||||
#endif
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return (value ? api_error::success
|
||||
: (size ? api_error::bad_address : api_error::success));
|
||||
}
|
||||
|
||||
void fuse_drive_base::populate_stat(const std::string &api_path,
|
||||
std::uint64_t size_or_count,
|
||||
const api_meta_map &meta, bool directory,
|
||||
i_provider &provider, struct stat *st) {
|
||||
memset(st, 0, sizeof(struct stat));
|
||||
st->st_nlink =
|
||||
(directory
|
||||
? 2 + (size_or_count ? size_or_count
|
||||
: provider.get_directory_item_count(api_path))
|
||||
: 1);
|
||||
if (directory) {
|
||||
st->st_blocks = 0;
|
||||
} else {
|
||||
st->st_size = size_or_count;
|
||||
static const auto block_size_stat = static_cast<std::uint64_t>(512u);
|
||||
static const auto block_size = static_cast<std::uint64_t>(4096u);
|
||||
const auto size = utils::divide_with_ceiling(
|
||||
static_cast<std::uint64_t>(st->st_size), block_size) *
|
||||
block_size;
|
||||
st->st_blocks = std::max(block_size / block_size_stat,
|
||||
utils::divide_with_ceiling(size, block_size_stat));
|
||||
}
|
||||
st->st_gid = get_gid_from_meta(meta);
|
||||
st->st_mode = (directory ? S_IFDIR : S_IFREG) | get_mode_from_meta(meta);
|
||||
st->st_uid = get_uid_from_meta(meta);
|
||||
#ifdef __APPLE__
|
||||
st->st_blksize = 0;
|
||||
st->st_flags = get_flags_from_meta(meta);
|
||||
|
||||
set_timespec_from_meta(meta, META_MODIFIED, st->st_mtimespec);
|
||||
set_timespec_from_meta(meta, META_CREATION, st->st_birthtimespec);
|
||||
set_timespec_from_meta(meta, META_CHANGED, st->st_ctimespec);
|
||||
set_timespec_from_meta(meta, META_ACCESSED, st->st_atimespec);
|
||||
#else // __APPLE__
|
||||
st->st_blksize = 4096;
|
||||
|
||||
set_timespec_from_meta(meta, META_MODIFIED, st->st_mtim);
|
||||
set_timespec_from_meta(meta, META_CREATION, st->st_ctim);
|
||||
set_timespec_from_meta(meta, META_ACCESSED, st->st_atim);
|
||||
#endif // __APPLE__
|
||||
}
|
||||
|
||||
void fuse_drive_base::set_timespec_from_meta(const api_meta_map &meta,
|
||||
const std::string &name,
|
||||
struct timespec &ts) {
|
||||
const auto t = utils::string::to_uint64(meta.at(name));
|
||||
ts.tv_nsec = t % NANOS_PER_SECOND;
|
||||
ts.tv_sec = t / NANOS_PER_SECOND;
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif // _WIN32
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,52 +1,258 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 _WIN32
|
||||
|
||||
#include "drives/fuse/remotefuse/remote_fuse_drive.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "drives/fuse/events.hpp"
|
||||
#include "events/consumers/console_consumer.hpp"
|
||||
#include "events/consumers/logging_consumer.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "rpc/server/server.hpp"
|
||||
#include "types/remote.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory::remote_fuse {
|
||||
app_config *remote_fuse_drive::remote_fuse_impl::config_ = nullptr;
|
||||
lock_data *remote_fuse_drive::remote_fuse_impl::lock_ = nullptr;
|
||||
std::string *remote_fuse_drive::remote_fuse_impl::mount_location_ = nullptr;
|
||||
bool remote_fuse_drive::remote_fuse_impl::console_enabled_ = false;
|
||||
bool remote_fuse_drive::remote_fuse_impl::was_mounted_ = false;
|
||||
std::optional<gid_t> remote_fuse_drive::remote_fuse_impl::forced_gid_;
|
||||
std::optional<uid_t> remote_fuse_drive::remote_fuse_impl::forced_uid_;
|
||||
std::optional<mode_t> remote_fuse_drive::remote_fuse_impl::forced_umask_;
|
||||
remote_instance_factory *remote_fuse_drive::remote_fuse_impl::factory_ = nullptr;
|
||||
std::unique_ptr<logging_consumer> remote_fuse_drive::remote_fuse_impl::logging_consumer_;
|
||||
std::unique_ptr<console_consumer> remote_fuse_drive::remote_fuse_impl::console_consumer_;
|
||||
std::unique_ptr<i_remote_instance> remote_fuse_drive::remote_fuse_impl::remote_instance_;
|
||||
std::unique_ptr<server> remote_fuse_drive::remote_fuse_impl::server_;
|
||||
auto remote_fuse_drive::access_impl(std::string api_path, int mask)
|
||||
-> api_error {
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_access(api_path.c_str(), mask));
|
||||
}
|
||||
|
||||
void remote_fuse_drive::remote_fuse_impl::tear_down(const int &ret) {
|
||||
#ifdef __APPLE__
|
||||
api_error remote_fuse_drive::chflags_impl(std::string api_path,
|
||||
uint32_t flags) {
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_chflags(api_path.c_str(), flags));
|
||||
}
|
||||
#endif // __APPLE__
|
||||
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
auto remote_fuse_drive::chmod_impl(std::string api_path, mode_t mode,
|
||||
struct fuse_file_info * /*fi*/)
|
||||
-> api_error {
|
||||
#else
|
||||
auto remote_fuse_drive::chmod_impl(std::string api_path, mode_t mode)
|
||||
-> api_error {
|
||||
#endif
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_chmod(api_path.c_str(), mode));
|
||||
}
|
||||
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
auto remote_fuse_drive::chown_impl(std::string api_path, uid_t uid, gid_t gid,
|
||||
struct fuse_file_info * /*fi*/)
|
||||
-> api_error {
|
||||
#else
|
||||
auto remote_fuse_drive::chown_impl(std::string api_path, uid_t uid, gid_t gid)
|
||||
-> api_error {
|
||||
#endif
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_chown(api_path.c_str(), uid, gid));
|
||||
}
|
||||
|
||||
auto remote_fuse_drive::create_impl(std::string api_path, mode_t mode,
|
||||
struct fuse_file_info *fi) -> api_error {
|
||||
return utils::to_api_error(remote_instance_->fuse_create(
|
||||
api_path.c_str(), mode, remote::create_open_flags(fi->flags), fi->fh));
|
||||
}
|
||||
|
||||
void remote_fuse_drive::destroy_impl(void * /*ptr*/) {
|
||||
event_system::instance().raise<drive_unmount_pending>(get_mount_location());
|
||||
|
||||
if (server_) {
|
||||
server_->stop();
|
||||
server_.reset();
|
||||
}
|
||||
|
||||
if (remote_instance_) {
|
||||
const auto res = remote_instance_->fuse_destroy();
|
||||
if (res != 0) {
|
||||
utils::error::raise_error(__FUNCTION__,
|
||||
"remote fuse_destroy() failed|err|" +
|
||||
std::to_string(res));
|
||||
}
|
||||
remote_instance_.reset();
|
||||
}
|
||||
|
||||
if (not lock_data_.set_mount_state(false, "", -1)) {
|
||||
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
|
||||
}
|
||||
|
||||
event_system::instance().raise<drive_unmounted>(get_mount_location());
|
||||
}
|
||||
|
||||
auto remote_fuse_drive::fgetattr_impl(std::string api_path, struct stat *st,
|
||||
struct fuse_file_info *fi) -> api_error {
|
||||
remote::stat r{};
|
||||
auto directory = false;
|
||||
|
||||
const auto res =
|
||||
remote_instance_->fuse_fgetattr(api_path.c_str(), r, directory, fi->fh);
|
||||
if (res == 0) {
|
||||
populate_stat(r, directory, *st);
|
||||
}
|
||||
|
||||
return utils::to_api_error(res);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
api_error remote_fuse_drive::fsetattr_x_impl(std::string api_path,
|
||||
struct setattr_x *attr,
|
||||
struct fuse_file_info *fi) {
|
||||
remote::setattr_x attributes{};
|
||||
attributes.valid = attr->valid;
|
||||
attributes.mode = attr->mode;
|
||||
attributes.uid = attr->uid;
|
||||
attributes.gid = attr->gid;
|
||||
attributes.size = attr->size;
|
||||
attributes.acctime =
|
||||
((attr->acctime.tv_sec * NANOS_PER_SECOND) + attr->acctime.tv_nsec);
|
||||
attributes.modtime =
|
||||
((attr->modtime.tv_sec * NANOS_PER_SECOND) + attr->modtime.tv_nsec);
|
||||
attributes.crtime =
|
||||
((attr->crtime.tv_sec * NANOS_PER_SECOND) + attr->crtime.tv_nsec);
|
||||
attributes.chgtime =
|
||||
((attr->chgtime.tv_sec * NANOS_PER_SECOND) + attr->chgtime.tv_nsec);
|
||||
attributes.bkuptime =
|
||||
((attr->bkuptime.tv_sec * NANOS_PER_SECOND) + attr->bkuptime.tv_nsec);
|
||||
attributes.flags = attr->flags;
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_fsetattr_x(api_path.c_str(), attributes, fi->fh));
|
||||
}
|
||||
#endif
|
||||
|
||||
auto remote_fuse_drive::fsync_impl(std::string api_path, int datasync,
|
||||
struct fuse_file_info *fi) -> api_error {
|
||||
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_fsync(api_path.c_str(), datasync, fi->fh));
|
||||
}
|
||||
|
||||
#if FUSE_USE_VERSION < 30
|
||||
auto remote_fuse_drive::ftruncate_impl(std::string api_path, off_t size,
|
||||
struct fuse_file_info *fi) -> api_error {
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_ftruncate(api_path.c_str(), size, fi->fh));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
auto remote_fuse_drive::getattr_impl(std::string api_path, struct stat *st,
|
||||
struct fuse_file_info * /*fi*/)
|
||||
-> api_error {
|
||||
#else
|
||||
auto remote_fuse_drive::getattr_impl(std::string api_path, struct stat *st)
|
||||
-> api_error {
|
||||
#endif
|
||||
bool directory = false;
|
||||
remote::stat r{};
|
||||
|
||||
const auto res =
|
||||
remote_instance_->fuse_getattr(api_path.c_str(), r, directory);
|
||||
if (res == 0) {
|
||||
populate_stat(r, directory, *st);
|
||||
}
|
||||
|
||||
return utils::to_api_error(res);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
api_error remote_fuse_drive::getxtimes_impl(std::string api_path,
|
||||
struct timespec *bkuptime,
|
||||
struct timespec *crtime) {
|
||||
if (not(bkuptime && crtime)) {
|
||||
return utils::to_api_error(-EFAULT);
|
||||
}
|
||||
|
||||
remote::file_time repertory_bkuptime = 0u;
|
||||
remote::file_time repertory_crtime = 0u;
|
||||
const auto res = remote_instance_->fuse_getxtimes(
|
||||
api_path.c_str(), repertory_bkuptime, repertory_crtime);
|
||||
if (res == 0) {
|
||||
bkuptime->tv_nsec =
|
||||
static_cast<long>(repertory_bkuptime % NANOS_PER_SECOND);
|
||||
bkuptime->tv_sec = repertory_bkuptime / NANOS_PER_SECOND;
|
||||
crtime->tv_nsec = static_cast<long>(repertory_crtime % NANOS_PER_SECOND);
|
||||
crtime->tv_sec = repertory_crtime / NANOS_PER_SECOND;
|
||||
}
|
||||
|
||||
return utils::to_api_error(res);
|
||||
}
|
||||
#endif // __APPLE__
|
||||
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
auto remote_fuse_drive::init_impl(struct fuse_conn_info *conn,
|
||||
struct fuse_config *cfg) -> void * {
|
||||
#else
|
||||
auto remote_fuse_drive::init_impl(struct fuse_conn_info *conn) -> void * {
|
||||
#endif
|
||||
utils::file::change_to_process_directory();
|
||||
was_mounted_ = true;
|
||||
|
||||
if (console_enabled_) {
|
||||
console_consumer_ = std::make_shared<console_consumer>();
|
||||
}
|
||||
logging_consumer_ = std::make_shared<logging_consumer>(
|
||||
config_.get_log_directory(), config_.get_event_level());
|
||||
event_system::instance().start();
|
||||
|
||||
if (not lock_data_.set_mount_state(true, get_mount_location(), getpid())) {
|
||||
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
|
||||
}
|
||||
|
||||
remote_instance_ = factory_();
|
||||
remote_instance_->set_fuse_uid_gid(getuid(), getgid());
|
||||
|
||||
if (remote_instance_->fuse_init() != 0) {
|
||||
utils::error::raise_error(__FUNCTION__,
|
||||
"failed to connect to remote server");
|
||||
event_system::instance().raise<unmount_requested>();
|
||||
} else {
|
||||
server_ = std::make_shared<server>(config_);
|
||||
server_->start();
|
||||
event_system::instance().raise<drive_mounted>(get_mount_location());
|
||||
}
|
||||
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
return fuse_base::init_impl(conn, cfg);
|
||||
#else
|
||||
return fuse_base::init_impl(conn);
|
||||
#endif
|
||||
}
|
||||
|
||||
auto remote_fuse_drive::mkdir_impl(std::string api_path, mode_t mode)
|
||||
-> api_error {
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_mkdir(api_path.c_str(), mode));
|
||||
}
|
||||
|
||||
void remote_fuse_drive::notify_fuse_main_exit(int &ret) {
|
||||
if (was_mounted_) {
|
||||
event_system::instance().raise<drive_mount_result>(std::to_string(ret));
|
||||
event_system::instance().stop();
|
||||
@@ -55,8 +261,21 @@ void remote_fuse_drive::remote_fuse_impl::tear_down(const int &ret) {
|
||||
}
|
||||
}
|
||||
|
||||
void remote_fuse_drive::remote_fuse_impl::populate_stat(const remote::stat &r,
|
||||
const bool &directory, struct stat &st) {
|
||||
auto remote_fuse_drive::open_impl(std::string api_path,
|
||||
struct fuse_file_info *fi) -> api_error {
|
||||
return utils::to_api_error(remote_instance_->fuse_open(
|
||||
api_path.c_str(), remote::create_open_flags(fi->flags), fi->fh));
|
||||
}
|
||||
|
||||
auto remote_fuse_drive::opendir_impl(std::string api_path,
|
||||
struct fuse_file_info *fi) -> api_error {
|
||||
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_opendir(api_path.c_str(), fi->fh));
|
||||
}
|
||||
|
||||
void remote_fuse_drive::populate_stat(const remote::stat &r, bool directory,
|
||||
struct stat &st) {
|
||||
memset(&st, 0, sizeof(struct stat));
|
||||
|
||||
#ifdef __APPLE__
|
||||
@@ -88,12 +307,13 @@ void remote_fuse_drive::remote_fuse_impl::populate_stat(const remote::stat &r,
|
||||
st.st_mtim.tv_sec = r.st_mtimespec / NANOS_PER_SECOND;
|
||||
#endif
|
||||
if (not directory) {
|
||||
const auto blockSizeStat = static_cast<std::uint64_t>(512);
|
||||
const auto blockSize = static_cast<std::uint64_t>(4096);
|
||||
const auto size =
|
||||
utils::divide_with_ceiling(static_cast<std::uint64_t>(st.st_size), blockSize) * blockSize;
|
||||
st.st_blocks =
|
||||
std::max(blockSize / blockSizeStat, utils::divide_with_ceiling(size, blockSizeStat));
|
||||
const auto block_size_stat = static_cast<std::uint64_t>(512u);
|
||||
const auto block_size = static_cast<std::uint64_t>(4096u);
|
||||
const auto size = utils::divide_with_ceiling(
|
||||
static_cast<std::uint64_t>(st.st_size), block_size) *
|
||||
block_size;
|
||||
st.st_blocks = std::max(block_size / block_size_stat,
|
||||
utils::divide_with_ceiling(size, block_size_stat));
|
||||
}
|
||||
|
||||
st.st_gid = r.st_gid;
|
||||
@@ -104,316 +324,168 @@ void remote_fuse_drive::remote_fuse_impl::populate_stat(const remote::stat &r,
|
||||
st.st_uid = r.st_uid;
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_access(const char *path, int mask) {
|
||||
return remote_instance_->fuse_access(path, mask);
|
||||
auto remote_fuse_drive::read_impl(std::string api_path, char *buffer,
|
||||
size_t read_size, off_t read_offset,
|
||||
struct fuse_file_info *fi,
|
||||
std::size_t &bytes_read) -> api_error {
|
||||
auto res = remote_instance_->fuse_read(api_path.c_str(), buffer, read_size,
|
||||
read_offset, fi->fh);
|
||||
if (res >= 0) {
|
||||
bytes_read = res;
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
return utils::to_api_error(res);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_chflags(const char *path, uint32_t flags) {
|
||||
return remote_instance_->fuse_chflags(path, flags);
|
||||
}
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
auto remote_fuse_drive::readdir_impl(std::string api_path, void *buf,
|
||||
fuse_fill_dir_t fuse_fill_dir,
|
||||
off_t offset, struct fuse_file_info *fi,
|
||||
fuse_readdir_flags /*flags*/)
|
||||
-> api_error {
|
||||
#else
|
||||
auto remote_fuse_drive::readdir_impl(std::string api_path, void *buf,
|
||||
fuse_fill_dir_t fuse_fill_dir,
|
||||
off_t offset, struct fuse_file_info *fi)
|
||||
-> api_error {
|
||||
#endif
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_chmod(const char *path, mode_t mode) {
|
||||
return remote_instance_->fuse_chmod(path, mode);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_chown(const char *path, uid_t uid, gid_t gid) {
|
||||
return remote_instance_->fuse_chown(path, uid, gid);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_create(const char *path, mode_t mode,
|
||||
struct fuse_file_info *fi) {
|
||||
return remote_instance_->fuse_create(path, mode, remote::open_flags(fi->flags), fi->fh);
|
||||
}
|
||||
|
||||
void remote_fuse_drive::remote_fuse_impl::repertory_destroy(void * /*ptr*/) {
|
||||
event_system::instance().raise<drive_unmount_pending>(*mount_location_);
|
||||
if (server_) {
|
||||
server_->stop();
|
||||
server_.reset();
|
||||
}
|
||||
|
||||
remote_instance_->fuse_destroy();
|
||||
remote_instance_.reset();
|
||||
|
||||
lock_->set_mount_state(false, "", -1);
|
||||
event_system::instance().raise<drive_mounted>(*mount_location_);
|
||||
}
|
||||
|
||||
/*int remote_fuse_drive::remote_fuse_impl::repertory_fallocate(const char *path, int mode, off_t
|
||||
offset, off_t length, struct fuse_file_info *fi) { return remote_instance_->fuse_fallocate(path,
|
||||
mode, offset, length, fi->fh);
|
||||
}*/
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_fgetattr(const char *path, struct stat *st,
|
||||
struct fuse_file_info *fi) {
|
||||
remote::stat r{};
|
||||
|
||||
bool directory = false;
|
||||
auto ret = remote_instance_->fuse_fgetattr(path, r, directory, fi->fh);
|
||||
if (ret == 0) {
|
||||
populate_stat(r, directory, *st);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_fsetattr_x(const char *path,
|
||||
struct setattr_x *attr,
|
||||
struct fuse_file_info *fi) {
|
||||
remote::setattr_x attrRepertory{};
|
||||
attrRepertory.valid = attr->valid;
|
||||
attrRepertory.mode = attr->mode;
|
||||
attrRepertory.uid = attr->uid;
|
||||
attrRepertory.gid = attr->gid;
|
||||
attrRepertory.size = attr->size;
|
||||
attrRepertory.acctime = ((attr->acctime.tv_sec * NANOS_PER_SECOND) + attr->acctime.tv_nsec);
|
||||
attrRepertory.modtime = ((attr->modtime.tv_sec * NANOS_PER_SECOND) + attr->modtime.tv_nsec);
|
||||
attrRepertory.crtime = ((attr->crtime.tv_sec * NANOS_PER_SECOND) + attr->crtime.tv_nsec);
|
||||
attrRepertory.chgtime = ((attr->chgtime.tv_sec * NANOS_PER_SECOND) + attr->chgtime.tv_nsec);
|
||||
attrRepertory.bkuptime = ((attr->bkuptime.tv_sec * NANOS_PER_SECOND) + attr->bkuptime.tv_nsec);
|
||||
attrRepertory.flags = attr->flags;
|
||||
return remote_instance_->fuse_fsetattr_x(path, attrRepertory, fi->fh);
|
||||
}
|
||||
#endif
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_fsync(const char *path, int datasync,
|
||||
struct fuse_file_info *fi) {
|
||||
return remote_instance_->fuse_fsync(path, datasync, fi->fh);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_ftruncate(const char *path, off_t size,
|
||||
struct fuse_file_info *fi) {
|
||||
return remote_instance_->fuse_ftruncate(path, size, fi->fh);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_getattr(const char *path, struct stat *st) {
|
||||
bool directory = false;
|
||||
remote::stat r{};
|
||||
const auto ret = remote_instance_->fuse_getattr(path, r, directory);
|
||||
if (ret == 0) {
|
||||
populate_stat(r, directory, *st);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_getxtimes(const char *path,
|
||||
struct timespec *bkuptime,
|
||||
struct timespec *crtime) {
|
||||
if (not(bkuptime && crtime)) {
|
||||
return -EFAULT;
|
||||
} else {
|
||||
remote::file_time repertory_bkuptime = 0;
|
||||
remote::file_time repertory_crtime = 0;
|
||||
const auto ret = remote_instance_->fuse_getxtimes(path, repertory_bkuptime, repertory_crtime);
|
||||
if (ret == 0) {
|
||||
bkuptime->tv_nsec = static_cast<long>(repertory_bkuptime % NANOS_PER_SECOND);
|
||||
bkuptime->tv_sec = repertory_bkuptime / NANOS_PER_SECOND;
|
||||
crtime->tv_nsec = static_cast<long>(repertory_crtime % NANOS_PER_SECOND);
|
||||
crtime->tv_sec = repertory_crtime / NANOS_PER_SECOND;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void *remote_fuse_drive::remote_fuse_impl::repertory_init(struct fuse_conn_info *conn) {
|
||||
utils::file::change_to_process_directory();
|
||||
#ifdef __APPLE__
|
||||
conn->want |= FUSE_CAP_VOL_RENAME;
|
||||
conn->want |= FUSE_CAP_XTIMES;
|
||||
#endif
|
||||
conn->want |= FUSE_CAP_BIG_WRITES;
|
||||
|
||||
if (console_enabled_) {
|
||||
console_consumer_ = std::make_unique<console_consumer>();
|
||||
}
|
||||
logging_consumer_ =
|
||||
std::make_unique<logging_consumer>(config_->get_log_directory(), config_->get_event_level());
|
||||
event_system::instance().start();
|
||||
|
||||
was_mounted_ = true;
|
||||
lock_->set_mount_state(true, *mount_location_, getpid());
|
||||
|
||||
remote_instance_ = (*factory_)();
|
||||
remote_instance_->set_fuse_uid_gid(getuid(), getgid());
|
||||
|
||||
if (remote_instance_->fuse_init() != 0) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__,
|
||||
"Failed to connect to remote server");
|
||||
event_system::instance().raise<unmount_requested>();
|
||||
} else {
|
||||
server_ = std::make_unique<server>(*config_);
|
||||
server_->start();
|
||||
event_system::instance().raise<drive_mounted>(*mount_location_);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_mkdir(const char *path, mode_t mode) {
|
||||
return remote_instance_->fuse_mkdir(path, mode);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_open(const char *path,
|
||||
struct fuse_file_info *fi) {
|
||||
return remote_instance_->fuse_open(path, remote::open_flags(fi->flags), fi->fh);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_opendir(const char *path,
|
||||
struct fuse_file_info *fi) {
|
||||
return remote_instance_->fuse_opendir(path, fi->fh);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_read(const char *path, char *buffer,
|
||||
size_t readSize, off_t readOffset,
|
||||
struct fuse_file_info *fi) {
|
||||
return remote_instance_->fuse_read(path, buffer, readSize, readOffset, fi->fh);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_readdir(const char *path, void *buf,
|
||||
fuse_fill_dir_t fuseFillDir,
|
||||
off_t offset,
|
||||
struct fuse_file_info *fi) {
|
||||
std::string item_path;
|
||||
int ret = 0;
|
||||
while ((ret = remote_instance_->fuse_readdir(path, offset, fi->fh, item_path)) == 0) {
|
||||
int res = 0;
|
||||
while ((res = remote_instance_->fuse_readdir(api_path.c_str(), offset, fi->fh,
|
||||
item_path)) == 0) {
|
||||
if ((item_path != ".") && (item_path != "..")) {
|
||||
item_path = utils::path::strip_to_file_name(item_path);
|
||||
}
|
||||
if (fuseFillDir(buf, &item_path[0], nullptr, ++offset) != 0) {
|
||||
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
if (fuse_fill_dir(buf, item_path.c_str(), nullptr, ++offset,
|
||||
static_cast<fuse_fill_dir_flags>(0)) != 0) {
|
||||
#else
|
||||
if (fuse_fill_dir(buf, item_path.c_str(), nullptr, ++offset) != 0) {
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == -120) {
|
||||
ret = 0;
|
||||
if (res == -120) {
|
||||
res = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return utils::to_api_error(res);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_release(const char *path,
|
||||
struct fuse_file_info *fi) {
|
||||
return remote_instance_->fuse_release(path, fi->fh);
|
||||
auto remote_fuse_drive::release_impl(std::string api_path,
|
||||
struct fuse_file_info *fi) -> api_error {
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_release(api_path.c_str(), fi->fh));
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_releasedir(const char *path,
|
||||
struct fuse_file_info *fi) {
|
||||
return remote_instance_->fuse_releasedir(path, fi->fh);
|
||||
auto remote_fuse_drive::releasedir_impl(std::string api_path,
|
||||
struct fuse_file_info *fi)
|
||||
-> api_error {
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_releasedir(api_path.c_str(), fi->fh));
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_rename(const char *from, const char *to) {
|
||||
return remote_instance_->fuse_rename(from, to);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_rmdir(const char *path) {
|
||||
return remote_instance_->fuse_rmdir(path);
|
||||
}
|
||||
/*
|
||||
#ifdef HAS_SETXATTR
|
||||
#ifdef __APPLE__
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_getxattr(const char *path, const char *name,
|
||||
char *value, size_t size, uint32_t position) { return remote_instance_->fuse_getxattr_osx(path,
|
||||
name, value, size, position);
|
||||
}
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
auto remote_fuse_drive::rename_impl(std::string from_api_path,
|
||||
std::string to_api_path,
|
||||
unsigned int /*flags*/) -> api_error {
|
||||
#else
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_getxattr(const char *path, const char *name,
|
||||
char *value, size_t size) { return remote_instance_->fuse_getxattr(path, name, value, size);
|
||||
}
|
||||
auto remote_fuse_drive::rename_impl(std::string from_api_path,
|
||||
std::string to_api_path) -> api_error {
|
||||
#endif
|
||||
return utils::to_api_error(remote_instance_->fuse_rename(
|
||||
from_api_path.c_str(), to_api_path.c_str()));
|
||||
}
|
||||
|
||||
#endif
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_listxattr(const char *path, char *buffer,
|
||||
size_t size) { return remote_instance_->fuse_listxattr(path, buffer, size);
|
||||
}
|
||||
auto remote_fuse_drive::rmdir_impl(std::string api_path) -> api_error {
|
||||
return utils::to_api_error(remote_instance_->fuse_rmdir(api_path.c_str()));
|
||||
}
|
||||
|
||||
remote_fuse_drive::remote_fuse_impl::int repertory_removexattr(const char *path, const char
|
||||
*name) { return remote_instance_->fuse_removexattr(path, name);
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_setxattr(const char *path, const char *name,
|
||||
const char *value, size_t size, int flags, uint32_t position) { return
|
||||
remote_instance_->fuse_setxattr_osx(path, name, value, size, flags, position);
|
||||
}
|
||||
#else
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_setxattr(const char *path, const char *name,
|
||||
const char *value, size_t size, int flags) { return remote_instance_->fuse_setxattr(path, name,
|
||||
value, size, flags);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
*/
|
||||
#ifdef __APPLE__
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_setattr_x(const char *path,
|
||||
struct setattr_x *attr) {
|
||||
api_error remote_fuse_drive::setattr_x_impl(std::string api_path,
|
||||
struct setattr_x *attr) {
|
||||
remote::setattr_x attributes{};
|
||||
attributes.valid = attr->valid;
|
||||
attributes.mode = attr->mode;
|
||||
attributes.uid = attr->uid;
|
||||
attributes.gid = attr->gid;
|
||||
attributes.size = attr->size;
|
||||
attributes.acctime = ((attr->acctime.tv_sec * NANOS_PER_SECOND) + attr->acctime.tv_nsec);
|
||||
attributes.modtime = ((attr->modtime.tv_sec * NANOS_PER_SECOND) + attr->modtime.tv_nsec);
|
||||
attributes.crtime = ((attr->crtime.tv_sec * NANOS_PER_SECOND) + attr->crtime.tv_nsec);
|
||||
attributes.chgtime = ((attr->chgtime.tv_sec * NANOS_PER_SECOND) + attr->chgtime.tv_nsec);
|
||||
attributes.bkuptime = ((attr->bkuptime.tv_sec * NANOS_PER_SECOND) + attr->bkuptime.tv_nsec);
|
||||
attributes.acctime =
|
||||
((attr->acctime.tv_sec * NANOS_PER_SECOND) + attr->acctime.tv_nsec);
|
||||
attributes.modtime =
|
||||
((attr->modtime.tv_sec * NANOS_PER_SECOND) + attr->modtime.tv_nsec);
|
||||
attributes.crtime =
|
||||
((attr->crtime.tv_sec * NANOS_PER_SECOND) + attr->crtime.tv_nsec);
|
||||
attributes.chgtime =
|
||||
((attr->chgtime.tv_sec * NANOS_PER_SECOND) + attr->chgtime.tv_nsec);
|
||||
attributes.bkuptime =
|
||||
((attr->bkuptime.tv_sec * NANOS_PER_SECOND) + attr->bkuptime.tv_nsec);
|
||||
attributes.flags = attr->flags;
|
||||
return remote_instance_->fuse_setattr_x(path, attributes);
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_setattr_x(api_path.c_str(), attributes));
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_setbkuptime(const char *path,
|
||||
const struct timespec *bkuptime) {
|
||||
api_error remote_fuse_drive::setbkuptime_impl(std::string api_path,
|
||||
const struct timespec *bkuptime) {
|
||||
remote::file_time repertory_bkuptime =
|
||||
((bkuptime->tv_sec * NANOS_PER_SECOND) + bkuptime->tv_nsec);
|
||||
return remote_instance_->fuse_setbkuptime(path, repertory_bkuptime);
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_setbkuptime(api_path.c_str(), repertory_bkuptime));
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_setchgtime(const char *path,
|
||||
const struct timespec *chgtime) {
|
||||
remote::file_time repertory_chgtime = ((chgtime->tv_sec * NANOS_PER_SECOND) + chgtime->tv_nsec);
|
||||
return remote_instance_->fuse_setchgtime(path, repertory_chgtime);
|
||||
api_error remote_fuse_drive::setchgtime_impl(std::string api_path,
|
||||
const struct timespec *chgtime) {
|
||||
remote::file_time repertory_chgtime =
|
||||
((chgtime->tv_sec * NANOS_PER_SECOND) + chgtime->tv_nsec);
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_setchgtime(api_path.c_str(), repertory_chgtime));
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_setcrtime(const char *path,
|
||||
const struct timespec *crtime) {
|
||||
remote::file_time repertory_crtime = ((crtime->tv_sec * NANOS_PER_SECOND) + crtime->tv_nsec);
|
||||
return remote_instance_->fuse_setcrtime(path, repertory_crtime);
|
||||
api_error remote_fuse_drive::setcrtime_impl(std::string api_path,
|
||||
const struct timespec *crtime) {
|
||||
remote::file_time repertory_crtime =
|
||||
((crtime->tv_sec * NANOS_PER_SECOND) + crtime->tv_nsec);
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_setcrtime(api_path.c_str(), repertory_crtime));
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_setvolname(const char *volname) {
|
||||
return remote_instance_->fuse_setvolname(volname);
|
||||
api_error remote_fuse_drive::setvolname_impl(const char *volname) {
|
||||
return utils::to_api_error(remote_instance_->fuse_setvolname(volname));
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_statfs_x(const char *path,
|
||||
struct statfs *stbuf) {
|
||||
auto ret = statfs(config_->get_data_directory().c_str(), stbuf);
|
||||
if (ret == 0) {
|
||||
api_error remote_fuse_drive::statfs_x_impl(std::string api_path,
|
||||
struct statfs *stbuf) {
|
||||
auto res = statfs(config_.get_data_directory().c_str(), stbuf);
|
||||
if (res == 0) {
|
||||
remote::statfs_x r{};
|
||||
if ((ret = remote_instance_->fuse_statfs_x(path, stbuf->f_bsize, r)) == 0) {
|
||||
if ((res = remote_instance_->fuse_statfs_x(api_path.c_str(), stbuf->f_bsize,
|
||||
r)) == 0) {
|
||||
stbuf->f_blocks = r.f_blocks;
|
||||
stbuf->f_bavail = r.f_bavail;
|
||||
stbuf->f_bfree = r.f_bfree;
|
||||
stbuf->f_ffree = r.f_ffree;
|
||||
stbuf->f_files = r.f_files;
|
||||
stbuf->f_owner = getuid();
|
||||
strncpy(&stbuf->f_mntonname[0], mount_location_->c_str(), MNAMELEN);
|
||||
strncpy(&stbuf->f_mntfromname[0], &r.f_mntfromname[0], MNAMELEN);
|
||||
strncpy(&stbuf->f_mntonname[0u], get_mount_location().c_str(), MNAMELEN);
|
||||
strncpy(&stbuf->f_mntfromname[0u], &r.f_mntfromname[0], MNAMELEN);
|
||||
}
|
||||
} else {
|
||||
ret = -errno;
|
||||
res = -errno;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_statfs(const char *path, struct statvfs *stbuf) {
|
||||
auto ret = statvfs(&config_->get_data_directory()[0], stbuf);
|
||||
if (ret == 0) {
|
||||
return utils::to_api_error(res);
|
||||
}
|
||||
#else // __APPLE__
|
||||
auto remote_fuse_drive::statfs_impl(std::string api_path, struct statvfs *stbuf)
|
||||
-> api_error {
|
||||
auto res = statvfs(config_.get_data_directory().c_str(), stbuf);
|
||||
if (res == 0) {
|
||||
remote::statfs r{};
|
||||
if ((ret = remote_instance_->fuse_statfs(path, stbuf->f_frsize, r)) == 0) {
|
||||
if ((res = remote_instance_->fuse_statfs(api_path.c_str(), stbuf->f_frsize,
|
||||
r)) == 0) {
|
||||
stbuf->f_blocks = r.f_blocks;
|
||||
stbuf->f_bavail = r.f_bavail;
|
||||
stbuf->f_bfree = r.f_bfree;
|
||||
@@ -422,195 +494,61 @@ int remote_fuse_drive::remote_fuse_impl::repertory_statfs(const char *path, stru
|
||||
stbuf->f_files = r.f_files;
|
||||
}
|
||||
} else {
|
||||
ret = -errno;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_truncate(const char *path, off_t size) {
|
||||
return remote_instance_->fuse_truncate(path, size);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_unlink(const char *path) {
|
||||
return remote_instance_->fuse_unlink(path);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_utimens(const char *path,
|
||||
const struct timespec tv[2]) {
|
||||
remote::file_time rtv[2] = {0};
|
||||
if (tv) {
|
||||
rtv[0] = tv[0].tv_nsec + (tv[0].tv_sec * NANOS_PER_SECOND);
|
||||
rtv[1] = tv[1].tv_nsec + (tv[1].tv_sec * NANOS_PER_SECOND);
|
||||
}
|
||||
return remote_instance_->fuse_utimens(path, rtv, tv ? tv[0].tv_nsec : 0, tv ? tv[1].tv_nsec : 0);
|
||||
}
|
||||
|
||||
int remote_fuse_drive::remote_fuse_impl::repertory_write(const char *path, const char *buffer,
|
||||
size_t writeSize, off_t writeOffset,
|
||||
struct fuse_file_info *fi) {
|
||||
return remote_instance_->fuse_write(path, buffer, writeSize, writeOffset, fi->fh);
|
||||
}
|
||||
|
||||
remote_fuse_drive::remote_fuse_drive(app_config &config, lock_data &lock,
|
||||
remote_instance_factory factory)
|
||||
: config_(config), lock_(lock), factory_(std::move(factory)) {
|
||||
E_SUBSCRIBE_EXACT(unmount_requested, [this](const unmount_requested &) {
|
||||
std::thread([this]() { remote_fuse_drive::shutdown(mount_location_); }).detach();
|
||||
});
|
||||
}
|
||||
|
||||
int remote_fuse_drive::mount(std::vector<std::string> drive_args) {
|
||||
remote_fuse_impl::lock_ = &lock_;
|
||||
remote_fuse_impl::config_ = &config_;
|
||||
remote_fuse_impl::mount_location_ = &mount_location_;
|
||||
remote_fuse_impl::factory_ = &factory_;
|
||||
|
||||
#ifdef __APPLE__
|
||||
fuse_ops_.chflags = remote_fuse_impl::repertory_chflags;
|
||||
fuse_ops_.fsetattr_x = remote_fuse_impl::repertory_fsetattr_x;
|
||||
fuse_ops_.getxtimes = remote_fuse_impl::repertory_getxtimes;
|
||||
fuse_ops_.setattr_x = remote_fuse_impl::repertory_setattr_x;
|
||||
fuse_ops_.setbkuptime = remote_fuse_impl::repertory_setbkuptime;
|
||||
fuse_ops_.setchgtime = remote_fuse_impl::repertory_setchgtime;
|
||||
fuse_ops_.setcrtime = remote_fuse_impl::repertory_setcrtime;
|
||||
fuse_ops_.setvolname = remote_fuse_impl::repertory_setvolname;
|
||||
fuse_ops_.statfs_x = remote_fuse_impl::repertory_statfs_x;
|
||||
#endif
|
||||
auto force_no_console = false;
|
||||
for (std::size_t i = 1u; !force_no_console && (i < drive_args.size()); i++) {
|
||||
if (drive_args[i] == "-nc") {
|
||||
force_no_console = true;
|
||||
}
|
||||
}
|
||||
utils::remove_element_from(drive_args, "-nc");
|
||||
|
||||
for (std::size_t i = 1u; i < drive_args.size(); i++) {
|
||||
if (drive_args[i] == "-f") {
|
||||
remote_fuse_impl::console_enabled_ = not force_no_console;
|
||||
} else if (drive_args[i].find("-o") == 0) {
|
||||
std::string options;
|
||||
if (drive_args[i].size() == 2u) {
|
||||
if ((i + 1) < drive_args.size()) {
|
||||
options = drive_args[++i];
|
||||
}
|
||||
} else {
|
||||
options = drive_args[i].substr(2);
|
||||
}
|
||||
|
||||
const auto option_list = utils::string::split(options, ',');
|
||||
for (const auto &option : option_list) {
|
||||
if (option.find("gid") == 0) {
|
||||
const auto parts = utils::string::split(option, '=');
|
||||
if (parts.size() == 2u) {
|
||||
auto gid = getgrnam(parts[1u].c_str());
|
||||
if (not gid) {
|
||||
gid = getgrgid(utils::string::to_uint32(parts[1]));
|
||||
}
|
||||
if ((getgid() != 0) && (gid->gr_gid == 0)) {
|
||||
std::cerr << "'gid=0' requires running as root" << std::endl;
|
||||
return -1;
|
||||
} else {
|
||||
remote_fuse_impl::forced_gid_ = gid->gr_gid;
|
||||
}
|
||||
}
|
||||
} else if (option.find("uid") == 0) {
|
||||
const auto parts = utils::string::split(option, '=');
|
||||
if (parts.size() == 2u) {
|
||||
auto uid = getpwnam(parts[1u].c_str());
|
||||
if (not uid) {
|
||||
uid = getpwuid(utils::string::to_uint32(parts[1]));
|
||||
}
|
||||
if ((getuid() != 0) && (uid->pw_uid == 0)) {
|
||||
std::cerr << "'uid=0' requires running as root" << std::endl;
|
||||
return -1;
|
||||
} else {
|
||||
remote_fuse_impl::forced_uid_ = uid->pw_uid;
|
||||
}
|
||||
}
|
||||
} else if (option.find("umask") == 0) {
|
||||
const auto parts = utils::string::split(option, '=');
|
||||
if (parts.size() == 2u) {
|
||||
static const auto numeric_regex = std::regex("[0-9]+");
|
||||
try {
|
||||
if (not std::regex_match(parts[1u], numeric_regex)) {
|
||||
throw std::runtime_error("invalid syntax");
|
||||
} else {
|
||||
remote_fuse_impl::forced_umask_ = utils::string::to_uint32(parts[1]);
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << ("'" + option + "' invalid syntax") << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res = -errno;
|
||||
}
|
||||
|
||||
std::vector<const char *> fuse_argv(drive_args.size());
|
||||
for (std::size_t i = 0u; i < drive_args.size(); i++) {
|
||||
fuse_argv[i] = drive_args[i].c_str();
|
||||
}
|
||||
|
||||
struct fuse_args args =
|
||||
FUSE_ARGS_INIT(static_cast<int>(fuse_argv.size()), (char **)&fuse_argv[0]);
|
||||
char *mount_location = nullptr;
|
||||
fuse_parse_cmdline(&args, &mount_location, nullptr, nullptr);
|
||||
if (mount_location) {
|
||||
mount_location_ = mount_location;
|
||||
free(mount_location);
|
||||
}
|
||||
|
||||
std::string args_string;
|
||||
for (const auto *arg : fuse_argv) {
|
||||
if (args_string.empty()) {
|
||||
args_string += arg;
|
||||
} else {
|
||||
args_string += (" " + std::string(arg));
|
||||
}
|
||||
}
|
||||
|
||||
event_system::instance().raise<fuse_args_parsed>(args_string);
|
||||
|
||||
umask(0);
|
||||
const auto ret = fuse_main(static_cast<int>(fuse_argv.size()), (char **)&fuse_argv[0], &fuse_ops_,
|
||||
&mount_location);
|
||||
remote_fuse_impl::tear_down(ret);
|
||||
return ret;
|
||||
return utils::to_api_error(res);
|
||||
}
|
||||
#endif // __APPLE__
|
||||
|
||||
void remote_fuse_drive::display_options(int argc, char *argv[]) {
|
||||
struct fuse_operations fuse_ops {};
|
||||
fuse_main(argc, argv, &fuse_ops, nullptr);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void remote_fuse_drive::display_version_information(int argc, char *argv[]) {
|
||||
struct fuse_operations fuse_ops {};
|
||||
fuse_main(argc, argv, &fuse_ops, nullptr);
|
||||
}
|
||||
|
||||
void remote_fuse_drive::shutdown(std::string mount_location) {
|
||||
#if __APPLE__
|
||||
const auto unmount = "umount \"" + mount_location + "\" >/dev/null 2>&1";
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
auto remote_fuse_drive::truncate_impl(std::string api_path, off_t size,
|
||||
struct fuse_file_info * /*fi*/)
|
||||
-> api_error {
|
||||
#else
|
||||
const auto unmount = "fusermount -u \"" + mount_location + "\" >/dev/null 2>&1";
|
||||
auto remote_fuse_drive::truncate_impl(std::string api_path, off_t size)
|
||||
-> api_error {
|
||||
#endif
|
||||
int ret;
|
||||
for (std::uint8_t i = 0u; ((ret = system(unmount.c_str())) != 0) && (i < 10u); i++) {
|
||||
event_system::instance().raise<unmount_result>(mount_location, std::to_string(ret));
|
||||
if (i != 9u) {
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
return utils::to_api_error(
|
||||
remote_instance_->fuse_truncate(api_path.c_str(), size));
|
||||
}
|
||||
|
||||
auto remote_fuse_drive::unlink_impl(std::string api_path) -> api_error {
|
||||
return utils::to_api_error(remote_instance_->fuse_unlink(api_path.c_str()));
|
||||
}
|
||||
|
||||
#if FUSE_USE_VERSION >= 30
|
||||
auto remote_fuse_drive::utimens_impl(std::string api_path,
|
||||
const struct timespec tv[2],
|
||||
struct fuse_file_info * /*fi*/)
|
||||
-> api_error {
|
||||
#else
|
||||
auto remote_fuse_drive::utimens_impl(std::string api_path,
|
||||
const struct timespec tv[2]) -> api_error {
|
||||
#endif
|
||||
remote::file_time rtv[2u] = {0};
|
||||
if (tv) {
|
||||
rtv[0u] = tv[0u].tv_nsec + (tv[0u].tv_sec * NANOS_PER_SECOND);
|
||||
rtv[1u] = tv[1u].tv_nsec + (tv[1u].tv_sec * NANOS_PER_SECOND);
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
ret = kill(getpid(), SIGINT);
|
||||
return utils::to_api_error(remote_instance_->fuse_utimens(
|
||||
api_path.c_str(), rtv, tv ? tv[0u].tv_nsec : 0u,
|
||||
tv ? tv[1u].tv_nsec : 0u));
|
||||
}
|
||||
|
||||
auto remote_fuse_drive::write_impl(std::string api_path, const char *buffer,
|
||||
size_t write_size, off_t write_offset,
|
||||
struct fuse_file_info *fi,
|
||||
std::size_t &bytes_written) -> api_error {
|
||||
const auto res = remote_instance_->fuse_write(
|
||||
api_path.c_str(), buffer, write_size, write_offset, fi->fh);
|
||||
if (res >= 0) {
|
||||
bytes_written = res;
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
event_system::instance().raise<unmount_result>(mount_location, std::to_string(ret));
|
||||
return utils::to_api_error(res);
|
||||
}
|
||||
} // namespace repertory::remote_fuse
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 _WIN32
|
||||
#if 0
|
||||
|
||||
#include "drives/fuse/remotefuse/remote_fuse_drive2.hpp"
|
||||
#include "app_config.hpp"
|
||||
#include "events/consumers/console_consumer.hpp"
|
||||
#include "events/consumers/logging_consumer.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "rpc/server/server.hpp"
|
||||
#include "types/remote.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory::remote_fuse {
|
||||
api_error remote_fuse_drive2::access_impl(std::string api_path, int mask) {
|
||||
return utils::to_api_error(remote_instance_->fuse_access(api_path.c_str(), mask));
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
api_error remote_fuse_drive2::chflags_impl(std::string api_path, uint32_t flags) {
|
||||
return utils::to_api_error(remote_instance_->fuse_chflags(path, flags));
|
||||
}
|
||||
#endif // __APPLE__
|
||||
|
||||
api_error remote_fuse_drive2::chmod_impl(std::string api_path, mode_t mode) {
|
||||
return utils::to_api_error(remote_instance_->fuse_chmod(path, mode));
|
||||
}
|
||||
} // namespace repertory::remote_fuse
|
||||
|
||||
#endif // 0
|
||||
#endif // _WIN32
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,27 +1,34 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "drives/remote/remote_open_file_table.hpp"
|
||||
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
void remote_open_file_table::add_directory(const std::string &client_id, void *dir) {
|
||||
unique_mutex_lock directory_lock(directory_mutex_);
|
||||
void remote_open_file_table::add_directory(const std::string &client_id,
|
||||
void *dir) {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
auto &list = directory_lookup_[client_id];
|
||||
if (utils::collection_excludes(list, dir)) {
|
||||
directory_lookup_[client_id].emplace_back(dir);
|
||||
@@ -29,16 +36,16 @@ void remote_open_file_table::add_directory(const std::string &client_id, void *d
|
||||
}
|
||||
|
||||
void remote_open_file_table::close_all(const std::string &client_id) {
|
||||
std::vector<remote::file_handle> compatHandles;
|
||||
unique_mutex_lock compat_lock(compat_mutex_);
|
||||
std::vector<remote::file_handle> compat_handles;
|
||||
unique_recur_mutex_lock compat_lock(compat_mutex_);
|
||||
for (auto &kv : compat_lookup_) {
|
||||
if (kv.second.client_id == client_id) {
|
||||
compatHandles.emplace_back(kv.first);
|
||||
compat_handles.emplace_back(kv.first);
|
||||
}
|
||||
}
|
||||
compat_lock.unlock();
|
||||
|
||||
for (auto &handle : compatHandles) {
|
||||
for (auto &handle : compat_handles) {
|
||||
#ifdef _WIN32
|
||||
_close(static_cast<int>(handle));
|
||||
#else
|
||||
@@ -47,8 +54,8 @@ void remote_open_file_table::close_all(const std::string &client_id) {
|
||||
remove_compat_open_info(handle);
|
||||
}
|
||||
|
||||
std::vector<OSHandle> handles;
|
||||
unique_mutex_lock file_lock(file_mutex_);
|
||||
std::vector<native_handle> handles;
|
||||
unique_recur_mutex_lock file_lock(file_mutex_);
|
||||
for (auto &kv : file_lookup_) {
|
||||
if (kv.second.client_id == client_id) {
|
||||
handles.emplace_back(kv.first);
|
||||
@@ -66,7 +73,7 @@ void remote_open_file_table::close_all(const std::string &client_id) {
|
||||
}
|
||||
|
||||
std::vector<void *> dirs;
|
||||
unique_mutex_lock directory_lock(directory_mutex_);
|
||||
unique_recur_mutex_lock directory_lock(directory_mutex_);
|
||||
for (auto &kv : directory_lookup_) {
|
||||
if (kv.first == client_id) {
|
||||
dirs.insert(dirs.end(), kv.second.begin(), kv.second.end());
|
||||
@@ -80,8 +87,9 @@ void remote_open_file_table::close_all(const std::string &client_id) {
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool remote_open_file_table::get_directory_buffer(const OSHandle &handle, PVOID *&buffer) {
|
||||
mutex_lock file_lock(file_mutex_);
|
||||
auto remote_open_file_table::get_directory_buffer(const native_handle &handle,
|
||||
PVOID *&buffer) -> bool {
|
||||
recur_mutex_lock file_lock(file_mutex_);
|
||||
if (file_lookup_.find(handle) != file_lookup_.end()) {
|
||||
buffer = &file_lookup_[handle].directory_buffer;
|
||||
return true;
|
||||
@@ -90,8 +98,31 @@ bool remote_open_file_table::get_directory_buffer(const OSHandle &handle, PVOID
|
||||
}
|
||||
#endif
|
||||
|
||||
bool remote_open_file_table::get_open_info(const OSHandle &handle, open_info &oi) {
|
||||
mutex_lock file_lock(file_mutex_);
|
||||
auto remote_open_file_table::get_open_file_count(
|
||||
const std::string &file_path) const -> std::size_t {
|
||||
unique_recur_mutex_lock file_lock(file_mutex_);
|
||||
const auto count = std::accumulate(
|
||||
file_lookup_.cbegin(), file_lookup_.cend(), std::size_t(0U),
|
||||
[&file_path](std::size_t total, const auto &kv) -> std::size_t {
|
||||
if (kv.second.path == file_path) {
|
||||
return ++total;
|
||||
}
|
||||
return total;
|
||||
});
|
||||
|
||||
return std::accumulate(
|
||||
compat_lookup_.cbegin(), compat_lookup_.cend(), count,
|
||||
[&file_path](std::size_t total, const auto &kv) -> std::size_t {
|
||||
if (kv.second.path == file_path) {
|
||||
return ++total;
|
||||
}
|
||||
return total;
|
||||
});
|
||||
}
|
||||
|
||||
auto remote_open_file_table::get_open_info(const native_handle &handle,
|
||||
open_info &oi) -> bool {
|
||||
recur_mutex_lock file_lock(file_mutex_);
|
||||
if (file_lookup_.find(handle) != file_lookup_.end()) {
|
||||
oi = file_lookup_[handle];
|
||||
return true;
|
||||
@@ -99,8 +130,9 @@ bool remote_open_file_table::get_open_info(const OSHandle &handle, open_info &oi
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string remote_open_file_table::get_open_file_path(const OSHandle &handle) {
|
||||
mutex_lock file_lock(file_mutex_);
|
||||
auto remote_open_file_table::get_open_file_path(const native_handle &handle)
|
||||
-> std::string {
|
||||
recur_mutex_lock file_lock(file_mutex_);
|
||||
if (file_lookup_.find(handle) != file_lookup_.end()) {
|
||||
return file_lookup_[handle].path;
|
||||
}
|
||||
@@ -108,34 +140,72 @@ std::string remote_open_file_table::get_open_file_path(const OSHandle &handle) {
|
||||
return "";
|
||||
}
|
||||
|
||||
bool remote_open_file_table::has_open_directory(const std::string &client_id, void *dir) {
|
||||
unique_mutex_lock directory_lock(directory_mutex_);
|
||||
auto remote_open_file_table::has_open_directory(const std::string &client_id,
|
||||
void *dir) -> bool {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
auto &list = directory_lookup_[client_id];
|
||||
return (utils::collection_includes(list, dir));
|
||||
}
|
||||
|
||||
int remote_open_file_table::has_compat_open_info(const remote::file_handle &handle,
|
||||
const int &errorReturn) {
|
||||
mutex_lock compat_lock(compat_mutex_);
|
||||
const auto res = ((compat_lookup_.find(handle) == compat_lookup_.end()) ? -1 : 0);
|
||||
auto remote_open_file_table::has_compat_open_info(
|
||||
const remote::file_handle &handle, int error_return) -> int {
|
||||
recur_mutex_lock compat_lock(compat_mutex_);
|
||||
const auto res =
|
||||
((compat_lookup_.find(handle) == compat_lookup_.end()) ? -1 : 0);
|
||||
if (res == -1) {
|
||||
errno = errorReturn;
|
||||
errno = error_return;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void remote_open_file_table::remove_compat_open_info(const remote::file_handle &handle) {
|
||||
mutex_lock compat_lock(compat_mutex_);
|
||||
void remote_open_file_table::remove_all(const std::string &file_path) {
|
||||
unique_recur_mutex_lock file_lock(file_mutex_);
|
||||
const auto open_list = std::accumulate(
|
||||
file_lookup_.begin(), file_lookup_.end(), std::vector<native_handle>(),
|
||||
[&file_path](std::vector<native_handle> v,
|
||||
const auto &kv) -> std::vector<native_handle> {
|
||||
if (kv.second.path == file_path) {
|
||||
v.emplace_back(kv.first);
|
||||
}
|
||||
return v;
|
||||
});
|
||||
|
||||
const auto compat_open_list = std::accumulate(
|
||||
compat_lookup_.begin(), compat_lookup_.end(),
|
||||
std::vector<remote::file_handle>(),
|
||||
[&file_path](std::vector<remote::file_handle> v,
|
||||
const auto &kv) -> std::vector<remote::file_handle> {
|
||||
if (kv.second.path == file_path) {
|
||||
v.emplace_back(kv.first);
|
||||
}
|
||||
return v;
|
||||
});
|
||||
file_lock.unlock();
|
||||
|
||||
for (auto &handle : open_list) {
|
||||
remove_open_info(handle);
|
||||
}
|
||||
|
||||
for (auto &handle : compat_open_list) {
|
||||
remove_compat_open_info(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void remote_open_file_table::remove_compat_open_info(
|
||||
const remote::file_handle &handle) {
|
||||
recur_mutex_lock compat_lock(compat_mutex_);
|
||||
if (compat_lookup_[handle].count > 0) {
|
||||
compat_lookup_[handle].count--;
|
||||
}
|
||||
|
||||
if (not compat_lookup_[handle].count) {
|
||||
compat_lookup_.erase(handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool remote_open_file_table::remove_directory(const std::string &client_id, void *dir) {
|
||||
unique_mutex_lock directory_lock(directory_mutex_);
|
||||
auto remote_open_file_table::remove_directory(const std::string &client_id,
|
||||
void *dir) -> bool {
|
||||
recur_mutex_lock directory_lock(directory_mutex_);
|
||||
auto &list = directory_lookup_[client_id];
|
||||
if (utils::collection_includes(list, dir)) {
|
||||
utils::remove_element_from(list, dir);
|
||||
@@ -148,42 +218,46 @@ bool remote_open_file_table::remove_directory(const std::string &client_id, void
|
||||
return false;
|
||||
}
|
||||
|
||||
void remote_open_file_table::remove_open_info(const OSHandle &handle) {
|
||||
mutex_lock file_lock(file_mutex_);
|
||||
void remote_open_file_table::remove_open_info(const native_handle &handle) {
|
||||
recur_mutex_lock file_lock(file_mutex_);
|
||||
if (file_lookup_[handle].count > 0) {
|
||||
file_lookup_[handle].count--;
|
||||
}
|
||||
if (not file_lookup_[handle].count) {
|
||||
#ifdef _WIN32
|
||||
if (file_lookup_[handle].directory_buffer) {
|
||||
FspFileSystemDeleteDirectoryBuffer(&file_lookup_[handle].directory_buffer);
|
||||
FspFileSystemDeleteDirectoryBuffer(
|
||||
&file_lookup_[handle].directory_buffer);
|
||||
}
|
||||
#endif
|
||||
file_lookup_.erase(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void remote_open_file_table::set_compat_client_id(const remote::file_handle &handle,
|
||||
const std::string &client_id) {
|
||||
mutex_lock compat_lock(compat_mutex_);
|
||||
void remote_open_file_table::set_compat_client_id(
|
||||
const remote::file_handle &handle, const std::string &client_id) {
|
||||
recur_mutex_lock compat_lock(compat_mutex_);
|
||||
compat_lookup_[handle].client_id = client_id;
|
||||
}
|
||||
|
||||
void remote_open_file_table::set_client_id(const OSHandle &handle, const std::string &client_id) {
|
||||
mutex_lock file_lock(file_mutex_);
|
||||
void remote_open_file_table::set_client_id(const native_handle &handle,
|
||||
const std::string &client_id) {
|
||||
recur_mutex_lock file_lock(file_mutex_);
|
||||
file_lookup_[handle].client_id = client_id;
|
||||
}
|
||||
|
||||
void remote_open_file_table::set_compat_open_info(const remote::file_handle &handle) {
|
||||
mutex_lock compat_lock(compat_mutex_);
|
||||
void remote_open_file_table::set_compat_open_info(
|
||||
const remote::file_handle &handle, const std::string &file_path) {
|
||||
recur_mutex_lock compat_lock(compat_mutex_);
|
||||
if (compat_lookup_.find(handle) == compat_lookup_.end()) {
|
||||
compat_lookup_[handle] = {0, ""};
|
||||
compat_lookup_[handle] = {0, "", file_path};
|
||||
}
|
||||
compat_lookup_[handle].count++;
|
||||
}
|
||||
|
||||
void remote_open_file_table::set_open_info(const OSHandle &handle, open_info oi) {
|
||||
mutex_lock file_lock(file_mutex_);
|
||||
void remote_open_file_table::set_open_info(const native_handle &handle,
|
||||
open_info oi) {
|
||||
recur_mutex_lock file_lock(file_mutex_);
|
||||
if (file_lookup_.find(handle) == file_lookup_.end()) {
|
||||
file_lookup_[handle] = std::move(oi);
|
||||
}
|
||||
|
||||
@@ -1,52 +1,59 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "drives/winfsp/remotewinfsp/remote_client.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "drives/winfsp/remotewinfsp/i_remote_instance.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory::remote_winfsp {
|
||||
#define RAISE_REMOTE_WINFSP_CLIENT_EVENT(func, file, ret) \
|
||||
if (config_.get_enable_drive_events() && \
|
||||
(((config_.get_event_level() >= remote_winfsp_client_event::level) && \
|
||||
(ret != STATUS_SUCCESS)) || \
|
||||
(config_.get_event_level() >= event_level::verbose))) \
|
||||
#define RAISE_REMOTE_WINFSP_CLIENT_EVENT(func, file, ret) \
|
||||
if (config_.get_enable_drive_events() && \
|
||||
(((config_.get_event_level() >= remote_winfsp_client_event::level) && \
|
||||
(ret != STATUS_SUCCESS)) || \
|
||||
(config_.get_event_level() >= event_level::verbose))) \
|
||||
event_system::instance().raise<remote_winfsp_client_event>(func, file, ret)
|
||||
|
||||
// clang-format off
|
||||
E_SIMPLE3(remote_winfsp_client_event, debug, true,
|
||||
std::string, function, func, E_STRING,
|
||||
std::string, api_path, ap, E_STRING,
|
||||
E_SIMPLE3(remote_winfsp_client_event, debug, true,
|
||||
std::string, function, func, E_STRING,
|
||||
std::string, api_path, ap, E_STRING,
|
||||
packet::error_type, result, res, E_FROM_INT32
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
remote_client::remote_client(const app_config &config)
|
||||
: config_(config),
|
||||
packet_client_(config.get_remote_host_name_or_ip(), config.get_remote_max_connections(),
|
||||
config.get_remote_port(), config.get_remote_receive_timeout_secs(),
|
||||
config.get_remote_send_timeout_secs(), config.get_remote_token()) {}
|
||||
packet_client_(
|
||||
config.get_remote_host_name_or_ip(),
|
||||
config.get_remote_max_connections(), config.get_remote_port(),
|
||||
config.get_remote_receive_timeout_secs(),
|
||||
config.get_remote_send_timeout_secs(), config.get_remote_token()) {}
|
||||
|
||||
packet::error_type remote_client::winfsp_can_delete(PVOID file_desc, PWSTR file_name) {
|
||||
auto remote_client::winfsp_can_delete(PVOID file_desc, PWSTR file_name)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
request.encode(file_name);
|
||||
@@ -54,18 +61,21 @@ packet::error_type remote_client::winfsp_can_delete(PVOID file_desc, PWSTR file_
|
||||
std::uint32_t service_flags = 0u;
|
||||
const auto ret = packet_client_.send(__FUNCTION__, request, service_flags);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, utils::path::create_api_path(utils::string::to_utf8(file_name)), ret);
|
||||
__FUNCTION__,
|
||||
utils::path::create_api_path(utils::string::to_utf8(file_name)), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::json_create_directory_snapshot(const std::string &path,
|
||||
json &json_data) {
|
||||
auto remote_client::json_create_directory_snapshot(const std::string &path,
|
||||
json &json_data)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(path);
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
if (ret == 0) {
|
||||
ret = packet::decode_json(response, json_data);
|
||||
}
|
||||
@@ -74,10 +84,9 @@ packet::error_type remote_client::json_create_directory_snapshot(const std::stri
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::json_read_directory_snapshot(const std::string &path,
|
||||
const remote::file_handle &handle,
|
||||
const std::uint32_t &page,
|
||||
json &json_data) {
|
||||
auto remote_client::json_read_directory_snapshot(
|
||||
const std::string &path, const remote::file_handle &handle,
|
||||
std::uint32_t page, json &json_data) -> packet::error_type {
|
||||
packet request;
|
||||
request.encode(path);
|
||||
request.encode(handle);
|
||||
@@ -85,7 +94,8 @@ packet::error_type remote_client::json_read_directory_snapshot(const std::string
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
if (ret == 0) {
|
||||
ret = packet::decode_json(response, json_data);
|
||||
}
|
||||
@@ -94,9 +104,9 @@ packet::error_type remote_client::json_read_directory_snapshot(const std::string
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type
|
||||
remote_client::json_release_directory_snapshot(const std::string &path,
|
||||
const remote::file_handle &handle) {
|
||||
auto remote_client::json_release_directory_snapshot(
|
||||
const std::string &path, const remote::file_handle &handle)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(path);
|
||||
request.encode(handle);
|
||||
@@ -108,9 +118,11 @@ remote_client::json_release_directory_snapshot(const std::string &path,
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_cleanup(PVOID file_desc, PWSTR file_name, UINT32 flags,
|
||||
BOOLEAN &was_closed) {
|
||||
const auto file_path = get_open_file_path(to_handle(file_desc));
|
||||
auto remote_client::winfsp_cleanup(PVOID file_desc, PWSTR file_name,
|
||||
UINT32 flags, BOOLEAN &was_closed)
|
||||
-> packet::error_type {
|
||||
auto handle = to_handle(file_desc);
|
||||
const auto file_path = get_open_file_path(handle);
|
||||
was_closed = 0;
|
||||
|
||||
packet request;
|
||||
@@ -120,35 +132,41 @@ packet::error_type remote_client::winfsp_cleanup(PVOID file_desc, PWSTR file_nam
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
DECODE_OR_IGNORE(&response, was_closed);
|
||||
if (was_closed) {
|
||||
remove_open_info(to_handle(file_desc));
|
||||
remove_all(file_path);
|
||||
}
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, file_path, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_close(PVOID file_desc) {
|
||||
const auto file_path = get_open_file_path(to_handle(file_desc));
|
||||
auto remote_client::winfsp_close(PVOID file_desc) -> packet::error_type {
|
||||
auto handle = to_handle(file_desc);
|
||||
if (has_open_info(handle, STATUS_INVALID_HANDLE) == STATUS_SUCCESS) {
|
||||
const auto file_path = get_open_file_path(handle);
|
||||
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
|
||||
std::uint32_t service_flags = 0u;
|
||||
const auto ret = packet_client_.send(__FUNCTION__, request, service_flags);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
remove_open_info(to_handle(file_desc));
|
||||
std::uint32_t service_flags = 0u;
|
||||
const auto ret = packet_client_.send(__FUNCTION__, request, service_flags);
|
||||
if ((ret == STATUS_SUCCESS) || (ret == STATUS_INVALID_HANDLE)) {
|
||||
remove_open_info(handle);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, file_path, ret);
|
||||
}
|
||||
}
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, file_path, ret);
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_create(PWSTR file_name, UINT32 create_options,
|
||||
UINT32 granted_access, UINT32 attributes,
|
||||
UINT64 allocation_size, PVOID *file_desc,
|
||||
remote::file_info *file_info,
|
||||
std::string &normalized_name, BOOLEAN &exists) {
|
||||
auto remote_client::winfsp_create(PWSTR file_name, UINT32 create_options,
|
||||
UINT32 granted_access, UINT32 attributes,
|
||||
UINT64 allocation_size, PVOID *file_desc,
|
||||
remote::file_info *file_info,
|
||||
std::string &normalized_name, BOOLEAN &exists)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_name);
|
||||
request.encode(create_options);
|
||||
@@ -158,7 +176,8 @@ packet::error_type remote_client::winfsp_create(PWSTR file_name, UINT32 create_o
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
HANDLE handle;
|
||||
DECODE_OR_IGNORE(&response, handle);
|
||||
@@ -167,8 +186,9 @@ packet::error_type remote_client::winfsp_create(PWSTR file_name, UINT32 create_o
|
||||
DECODE_OR_IGNORE(&response, exists);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
*file_desc = reinterpret_cast<PVOID>(handle);
|
||||
set_open_info(to_handle(*file_desc),
|
||||
open_info{0, "", nullptr, utils::string::to_utf8(file_name)});
|
||||
set_open_info(
|
||||
to_handle(*file_desc),
|
||||
open_info{0, "", nullptr, utils::string::to_utf8(file_name)});
|
||||
#ifdef _WIN32
|
||||
if (exists) {
|
||||
::SetLastError(ERROR_ALREADY_EXISTS);
|
||||
@@ -177,57 +197,70 @@ packet::error_type remote_client::winfsp_create(PWSTR file_name, UINT32 create_o
|
||||
}
|
||||
}
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(*file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(*file_desc)), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_flush(PVOID file_desc, remote::file_info *file_info) {
|
||||
auto remote_client::winfsp_flush(PVOID file_desc, remote::file_info *file_info)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
DECODE_OR_IGNORE(&response, *file_info);
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_get_dir_buffer(PVOID file_desc, PVOID *&ptr) {
|
||||
auto remote_client::winfsp_get_dir_buffer([[maybe_unused]] PVOID file_desc,
|
||||
[[maybe_unused]] PVOID *&ptr)
|
||||
-> packet::error_type {
|
||||
#ifdef _WIN32
|
||||
if (get_directory_buffer(reinterpret_cast<OSHandle>(file_desc), ptr)) {
|
||||
if (get_directory_buffer(reinterpret_cast<native_handle>(file_desc), ptr)) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_get_file_info(PVOID file_desc,
|
||||
remote::file_info *file_info) {
|
||||
auto remote_client::winfsp_get_file_info(PVOID file_desc,
|
||||
remote::file_info *file_info)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
DECODE_OR_IGNORE(&response, *file_info);
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_get_security_by_name(PWSTR file_name, PUINT32 attributes,
|
||||
std::uint64_t *descriptor_size,
|
||||
std::wstring &string_descriptor) {
|
||||
auto remote_client::winfsp_get_security_by_name(PWSTR file_name,
|
||||
PUINT32 attributes,
|
||||
std::uint64_t *descriptor_size,
|
||||
std::wstring &string_descriptor)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_name);
|
||||
request.encode(static_cast<std::uint64_t>(descriptor_size ? *descriptor_size : 0));
|
||||
request.encode(
|
||||
static_cast<std::uint64_t>(descriptor_size ? *descriptor_size : 0));
|
||||
request.encode(static_cast<std::uint8_t>(attributes != nullptr));
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
|
||||
string_descriptor.clear();
|
||||
DECODE_OR_IGNORE(&response, string_descriptor);
|
||||
@@ -239,16 +272,20 @@ packet::error_type remote_client::winfsp_get_security_by_name(PWSTR file_name, P
|
||||
DECODE_OR_IGNORE(&response, *attributes);
|
||||
}
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, utils::string::to_utf8(file_name), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__,
|
||||
utils::string::to_utf8(file_name), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_get_volume_info(UINT64 &total_size, UINT64 &free_size,
|
||||
std::string &volume_label) {
|
||||
auto remote_client::winfsp_get_volume_info(UINT64 &total_size,
|
||||
UINT64 &free_size,
|
||||
std::string &volume_label)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
DECODE_OR_IGNORE(&response, total_size);
|
||||
DECODE_OR_IGNORE(&response, free_size);
|
||||
DECODE_OR_IGNORE(&response, volume_label);
|
||||
@@ -256,24 +293,26 @@ packet::error_type remote_client::winfsp_get_volume_info(UINT64 &total_size, UIN
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_mounted(const std::wstring &location) {
|
||||
auto remote_client::winfsp_mounted(const std::wstring &location)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(get_repertory_version());
|
||||
request.encode(location);
|
||||
|
||||
std::uint32_t service_flags = 0u;
|
||||
const auto ret = packet_client_.send(__FUNCTION__, request, service_flags);
|
||||
const auto mountLocation = utils::string::to_utf8(location);
|
||||
event_system::instance().raise<drive_mounted>(mountLocation);
|
||||
const auto mount_location = utils::string::to_utf8(location);
|
||||
event_system::instance().raise<drive_mounted>(mount_location);
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, mountLocation, ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, mount_location, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_open(PWSTR file_name, UINT32 create_options,
|
||||
UINT32 granted_access, PVOID *file_desc,
|
||||
remote::file_info *file_info,
|
||||
std::string &normalized_name) {
|
||||
auto remote_client::winfsp_open(PWSTR file_name, UINT32 create_options,
|
||||
UINT32 granted_access, PVOID *file_desc,
|
||||
remote::file_info *file_info,
|
||||
std::string &normalized_name)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_name);
|
||||
request.encode(create_options);
|
||||
@@ -281,7 +320,8 @@ packet::error_type remote_client::winfsp_open(PWSTR file_name, UINT32 create_opt
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
HANDLE handle;
|
||||
DECODE_OR_IGNORE(&response, handle);
|
||||
@@ -290,19 +330,22 @@ packet::error_type remote_client::winfsp_open(PWSTR file_name, UINT32 create_opt
|
||||
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
*file_desc = reinterpret_cast<PVOID>(handle);
|
||||
set_open_info(to_handle(*file_desc),
|
||||
open_info{0, "", nullptr, utils::string::to_utf8(file_name)});
|
||||
set_open_info(
|
||||
to_handle(*file_desc),
|
||||
open_info{0, "", nullptr, utils::string::to_utf8(file_name)});
|
||||
}
|
||||
}
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(*file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(*file_desc)), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_overwrite(PVOID file_desc, UINT32 attributes,
|
||||
BOOLEAN replace_attributes,
|
||||
UINT64 allocation_size,
|
||||
remote::file_info *file_info) {
|
||||
auto remote_client::winfsp_overwrite(PVOID file_desc, UINT32 attributes,
|
||||
BOOLEAN replace_attributes,
|
||||
UINT64 allocation_size,
|
||||
remote::file_info *file_info)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
request.encode(attributes);
|
||||
@@ -311,15 +354,18 @@ packet::error_type remote_client::winfsp_overwrite(PVOID file_desc, UINT32 attri
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
DECODE_OR_IGNORE(&response, *file_info);
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_read(PVOID file_desc, PVOID buffer, UINT64 offset,
|
||||
UINT32 length, PUINT32 bytes_transferred) {
|
||||
auto remote_client::winfsp_read(PVOID file_desc, PVOID buffer, UINT64 offset,
|
||||
UINT32 length, PUINT32 bytes_transferred)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
request.encode(offset);
|
||||
@@ -327,26 +373,30 @@ packet::error_type remote_client::winfsp_read(PVOID file_desc, PVOID buffer, UIN
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
DECODE_OR_IGNORE(&response, *bytes_transferred);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
ret = response.decode(buffer, *bytes_transferred);
|
||||
#ifdef _WIN32
|
||||
if ((ret == STATUS_SUCCESS) && (not *bytes_transferred || (*bytes_transferred != length))) {
|
||||
if ((ret == STATUS_SUCCESS) &&
|
||||
(not *bytes_transferred || (*bytes_transferred != length))) {
|
||||
::SetLastError(ERROR_HANDLE_EOF);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ret != STATUS_SUCCESS) {
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_read_directory(PVOID file_desc, PWSTR pattern,
|
||||
PWSTR marker, json &item_list) {
|
||||
auto remote_client::winfsp_read_directory(PVOID file_desc, PWSTR pattern,
|
||||
PWSTR marker, json &item_list)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
request.encode(pattern);
|
||||
@@ -354,17 +404,21 @@ packet::error_type remote_client::winfsp_read_directory(PVOID file_desc, PWSTR p
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
ret = packet::decode_json(response, item_list);
|
||||
}
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_rename(PVOID file_desc, PWSTR file_name,
|
||||
PWSTR new_file_name, BOOLEAN replace_if_exists) {
|
||||
auto remote_client::winfsp_rename(PVOID file_desc, PWSTR file_name,
|
||||
PWSTR new_file_name,
|
||||
BOOLEAN replace_if_exists)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
request.encode(file_name);
|
||||
@@ -381,11 +435,10 @@ packet::error_type remote_client::winfsp_rename(PVOID file_desc, PWSTR file_name
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_set_basic_info(PVOID file_desc, UINT32 attributes,
|
||||
UINT64 creation_time,
|
||||
UINT64 last_access_time,
|
||||
UINT64 last_write_time, UINT64 change_time,
|
||||
remote::file_info *file_info) {
|
||||
auto remote_client::winfsp_set_basic_info(
|
||||
PVOID file_desc, UINT32 attributes, UINT64 creation_time,
|
||||
UINT64 last_access_time, UINT64 last_write_time, UINT64 change_time,
|
||||
remote::file_info *file_info) -> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
request.encode(attributes);
|
||||
@@ -396,16 +449,19 @@ packet::error_type remote_client::winfsp_set_basic_info(PVOID file_desc, UINT32
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
DECODE_OR_IGNORE(&response, *file_info);
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_set_file_size(PVOID file_desc, UINT64 new_size,
|
||||
BOOLEAN set_allocation_size,
|
||||
remote::file_info *file_info) {
|
||||
auto remote_client::winfsp_set_file_size(PVOID file_desc, UINT64 new_size,
|
||||
BOOLEAN set_allocation_size,
|
||||
remote::file_info *file_info)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
request.encode(new_size);
|
||||
@@ -413,14 +469,17 @@ packet::error_type remote_client::winfsp_set_file_size(PVOID file_desc, UINT64 n
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
DECODE_OR_IGNORE(&response, *file_info);
|
||||
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_unmounted(const std::wstring &location) {
|
||||
auto remote_client::winfsp_unmounted(const std::wstring &location)
|
||||
-> packet::error_type {
|
||||
const auto mount_location = utils::string::to_utf8(location);
|
||||
event_system::instance().raise<drive_unmount_pending>(mount_location);
|
||||
packet request;
|
||||
@@ -434,10 +493,12 @@ packet::error_type remote_client::winfsp_unmounted(const std::wstring &location)
|
||||
return ret;
|
||||
}
|
||||
|
||||
packet::error_type remote_client::winfsp_write(PVOID file_desc, PVOID buffer, UINT64 offset,
|
||||
UINT32 length, BOOLEAN write_to_end,
|
||||
BOOLEAN constrained_io, PUINT32 bytes_transferred,
|
||||
remote::file_info *file_info) {
|
||||
auto remote_client::winfsp_write(PVOID file_desc, PVOID buffer, UINT64 offset,
|
||||
UINT32 length, BOOLEAN write_to_end,
|
||||
BOOLEAN constrained_io,
|
||||
PUINT32 bytes_transferred,
|
||||
remote::file_info *file_info)
|
||||
-> packet::error_type {
|
||||
packet request;
|
||||
request.encode(file_desc);
|
||||
request.encode(length);
|
||||
@@ -450,13 +511,21 @@ packet::error_type remote_client::winfsp_write(PVOID file_desc, PVOID buffer, UI
|
||||
|
||||
packet response;
|
||||
std::uint32_t service_flags = 0u;
|
||||
auto ret = packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
auto ret =
|
||||
packet_client_.send(__FUNCTION__, request, response, service_flags);
|
||||
DECODE_OR_IGNORE(&response, *bytes_transferred);
|
||||
DECODE_OR_IGNORE(&response, *file_info);
|
||||
|
||||
if (ret != STATUS_SUCCESS) {
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
RAISE_REMOTE_WINFSP_CLIENT_EVENT(
|
||||
__FUNCTION__, get_open_file_path(to_handle(file_desc)), ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
native_handle remote_client::to_handle(PVOID file_desc) {
|
||||
return static_cast<native_handle>(reinterpret_cast<std::uint64_t>(file_desc));
|
||||
}
|
||||
#endif
|
||||
} // namespace repertory::remote_winfsp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,144 +1,177 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "drives/winfsp/remotewinfsp/remote_winfsp_drive.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "events/consumers/console_consumer.hpp"
|
||||
#include "events/consumers/logging_consumer.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "rpc/server/server.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory::remote_winfsp {
|
||||
remote_winfsp_drive::winfsp_service::winfsp_service(lock_data &lock, remote_winfsp_drive &drive,
|
||||
std::vector<std::string> drive_args,
|
||||
app_config &config)
|
||||
: Service(&(L"RepertoryRemote_" + utils::string::from_utf8(lock.get_unique_id()))[0u]),
|
||||
remote_winfsp_drive::winfsp_service::winfsp_service(
|
||||
lock_data &lock, remote_winfsp_drive &drive,
|
||||
std::vector<std::string> drive_args, app_config &config)
|
||||
: Service(&(L"RepertoryRemote_" +
|
||||
utils::string::from_utf8(lock.get_unique_id()))[0u]),
|
||||
config_(config),
|
||||
lock_(lock),
|
||||
drive_(drive),
|
||||
drive_args_(std::move(drive_args)),
|
||||
host_(drive) {}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::winfsp_service::OnStart(ULONG, PWSTR *) {
|
||||
const auto mount_location =
|
||||
utils::path::absolute((drive_args_.size() > 1u) ? drive_args_[1u] : "");
|
||||
const auto drive_letter = ((mount_location.size() == 2u) ||
|
||||
((mount_location.size() == 3u) && (mount_location[2u] == '\\'))) &&
|
||||
(mount_location[1u] == ':');
|
||||
auto remote_winfsp_drive::winfsp_service::OnStart(ULONG, PWSTR *) -> NTSTATUS {
|
||||
const auto mount_location = utils::string::to_lower(
|
||||
utils::path::absolute((drive_args_.size() > 1u) ? drive_args_[1u] : ""));
|
||||
const auto drive_letter =
|
||||
((mount_location.size() == 2u) ||
|
||||
((mount_location.size() == 3u) && (mount_location[2u] == '\\'))) &&
|
||||
(mount_location[1u] == ':');
|
||||
|
||||
auto ret = drive_letter ? STATUS_DEVICE_BUSY : STATUS_NOT_SUPPORTED;
|
||||
if ((drive_letter && not utils::file::is_directory(mount_location))) {
|
||||
auto unicode_mount_location = utils::string::from_utf8(mount_location);
|
||||
host_.SetFileSystemName(&unicode_mount_location[0u]);
|
||||
if (config_.get_enable_mount_manager()) {
|
||||
unicode_mount_location =
|
||||
std::wstring(L"\\\\.\\") + unicode_mount_location[0u] + L":";
|
||||
}
|
||||
ret = host_.Mount(&unicode_mount_location[0u]);
|
||||
} else {
|
||||
std::cerr << (drive_letter ? "Mount location in use: " : "Mount location not supported: ")
|
||||
std::cerr << (drive_letter ? "Mount location in use: "
|
||||
: "Mount location not supported: ")
|
||||
<< mount_location << std::endl;
|
||||
}
|
||||
|
||||
if (ret != STATUS_SUCCESS) {
|
||||
event_system::instance().raise<drive_mount_failed>(mount_location, std::to_string(ret));
|
||||
lock_.set_mount_state(false, "", -1);
|
||||
event_system::instance().raise<drive_mount_failed>(mount_location,
|
||||
std::to_string(ret));
|
||||
if (not lock_.set_mount_state(false, "", -1)) {
|
||||
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::winfsp_service::OnStop() {
|
||||
auto remote_winfsp_drive::winfsp_service::OnStop() -> NTSTATUS {
|
||||
host_.Unmount();
|
||||
lock_.set_mount_state(false, "", -1);
|
||||
if (not lock_.set_mount_state(false, "", -1)) {
|
||||
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
remote_winfsp_drive::remote_winfsp_drive(app_config &config, lock_data &lockData,
|
||||
remote_instance_factory factory)
|
||||
: FileSystemBase(), config_(config), lock_(lockData), factory_(factory) {
|
||||
E_SUBSCRIBE_EXACT(unmount_requested, [this](const unmount_requested &e) {
|
||||
std::thread([this]() { this->shutdown(); }).detach();
|
||||
});
|
||||
remote_winfsp_drive::remote_winfsp_drive(app_config &config,
|
||||
remote_instance_factory factory,
|
||||
lock_data &lock)
|
||||
: FileSystemBase(),
|
||||
config_(config),
|
||||
lock_(lock),
|
||||
factory_(std::move(factory)) {
|
||||
E_SUBSCRIBE_EXACT(unmount_requested,
|
||||
[this](const unmount_requested & /* e */) {
|
||||
std::thread([this]() { this->shutdown(); }).detach();
|
||||
});
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::CanDelete(PVOID file_node, PVOID file_desc, PWSTR file_name) {
|
||||
auto remote_winfsp_drive::CanDelete(PVOID /*file_node*/, PVOID file_desc,
|
||||
PWSTR file_name) -> NTSTATUS {
|
||||
return remote_instance_->winfsp_can_delete(file_desc, file_name);
|
||||
}
|
||||
|
||||
VOID remote_winfsp_drive::Cleanup(PVOID file_node, PVOID file_desc, PWSTR file_name, ULONG flags) {
|
||||
VOID remote_winfsp_drive::Cleanup(PVOID /*file_node*/, PVOID file_desc,
|
||||
PWSTR file_name, ULONG flags) {
|
||||
BOOLEAN was_closed;
|
||||
remote_instance_->winfsp_cleanup(file_desc, file_name, flags, was_closed);
|
||||
}
|
||||
|
||||
VOID remote_winfsp_drive::Close(PVOID file_node, PVOID file_desc) {
|
||||
VOID remote_winfsp_drive::Close(PVOID /*file_node*/, PVOID file_desc) {
|
||||
remote_instance_->winfsp_close(file_desc);
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::Create(PWSTR file_name, UINT32 create_options, UINT32 granted_access,
|
||||
UINT32 attributes, PSECURITY_DESCRIPTOR descriptor,
|
||||
UINT64 allocation_size, PVOID *file_node, PVOID *file_desc,
|
||||
OpenFileInfo *ofi) {
|
||||
auto remote_winfsp_drive::Create(PWSTR file_name, UINT32 create_options,
|
||||
UINT32 granted_access, UINT32 attributes,
|
||||
PSECURITY_DESCRIPTOR /*descriptor*/,
|
||||
UINT64 allocation_size, PVOID * /*file_node*/,
|
||||
PVOID *file_desc, OpenFileInfo *ofi)
|
||||
-> NTSTATUS {
|
||||
remote::file_info fi{};
|
||||
std::string normalized_name;
|
||||
BOOLEAN exists = 0;
|
||||
const auto ret =
|
||||
remote_instance_->winfsp_create(file_name, create_options, granted_access, attributes,
|
||||
allocation_size, file_desc, &fi, normalized_name, exists);
|
||||
const auto ret = remote_instance_->winfsp_create(
|
||||
file_name, create_options, granted_access, attributes, allocation_size,
|
||||
file_desc, &fi, normalized_name, exists);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
SetFileInfo(ofi->FileInfo, fi);
|
||||
const auto filePath = utils::string::from_utf8(normalized_name);
|
||||
wcsncpy(ofi->NormalizedName, &filePath[0], wcslen(&filePath[0]));
|
||||
ofi->NormalizedNameSize = (UINT16)(wcslen(&filePath[0]) * sizeof(WCHAR));
|
||||
set_file_info(ofi->FileInfo, fi);
|
||||
const auto file_path = utils::string::from_utf8(normalized_name);
|
||||
wcsncpy(ofi->NormalizedName, &file_path[0], wcslen(&file_path[0]));
|
||||
ofi->NormalizedNameSize = (UINT16)(wcslen(&file_path[0]) * sizeof(WCHAR));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::Flush(PVOID file_node, PVOID file_desc, FileInfo *file_info) {
|
||||
auto remote_winfsp_drive::Flush(PVOID /*file_node*/, PVOID file_desc,
|
||||
FileInfo *file_info) -> NTSTATUS {
|
||||
remote::file_info fi{};
|
||||
const auto ret = remote_instance_->winfsp_flush(file_desc, &fi);
|
||||
SetFileInfo(*file_info, fi);
|
||||
set_file_info(*file_info, fi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::GetFileInfo(PVOID file_node, PVOID file_desc, FileInfo *file_info) {
|
||||
auto remote_winfsp_drive::GetFileInfo(PVOID /*file_node*/, PVOID file_desc,
|
||||
FileInfo *file_info) -> NTSTATUS {
|
||||
remote::file_info fi{};
|
||||
const auto ret = remote_instance_->winfsp_get_file_info(file_desc, &fi);
|
||||
SetFileInfo(*file_info, fi);
|
||||
set_file_info(*file_info, fi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::GetSecurityByName(PWSTR file_name, PUINT32 attributes,
|
||||
PSECURITY_DESCRIPTOR descriptor,
|
||||
SIZE_T *descriptor_size) {
|
||||
auto remote_winfsp_drive::GetSecurityByName(PWSTR file_name, PUINT32 attributes,
|
||||
PSECURITY_DESCRIPTOR descriptor,
|
||||
SIZE_T *descriptor_size)
|
||||
-> NTSTATUS {
|
||||
std::wstring string_descriptor;
|
||||
std::uint64_t sds = descriptor_size ? *descriptor_size : 0;
|
||||
auto ret = remote_instance_->winfsp_get_security_by_name(
|
||||
file_name, attributes, descriptor_size ? &sds : nullptr, string_descriptor);
|
||||
file_name, attributes, descriptor_size ? &sds : nullptr,
|
||||
string_descriptor);
|
||||
*descriptor_size = static_cast<SIZE_T>(sds);
|
||||
if ((ret == STATUS_SUCCESS) && *descriptor_size) {
|
||||
PSECURITY_DESCRIPTOR sd = nullptr;
|
||||
ULONG sz2 = 0u;
|
||||
if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(&string_descriptor[0u],
|
||||
SDDL_REVISION_1, &sd, &sz2)) {
|
||||
if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
||||
&string_descriptor[0u], SDDL_REVISION_1, &sd, &sz2)) {
|
||||
if (sz2 > *descriptor_size) {
|
||||
ret = STATUS_BUFFER_TOO_SMALL;
|
||||
} else {
|
||||
@@ -153,22 +186,28 @@ NTSTATUS remote_winfsp_drive::GetSecurityByName(PWSTR file_name, PUINT32 attribu
|
||||
return ret;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::GetVolumeInfo(VolumeInfo *volume_info) {
|
||||
auto remote_winfsp_drive::GetVolumeInfo(VolumeInfo *volume_info) -> NTSTATUS {
|
||||
std::string volume_label;
|
||||
const auto ret = remote_instance_->winfsp_get_volume_info(volume_info->TotalSize,
|
||||
volume_info->FreeSize, volume_label);
|
||||
const auto ret = remote_instance_->winfsp_get_volume_info(
|
||||
volume_info->TotalSize, volume_info->FreeSize, volume_label);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
const auto byte_size = static_cast<UINT16>(volume_label.size() * sizeof(WCHAR));
|
||||
wcscpy_s(&volume_info->VolumeLabel[0u], 32, &utils::string::from_utf8(volume_label)[0u]);
|
||||
volume_info->VolumeLabelLength = std::min(static_cast<UINT16>(64u), byte_size);
|
||||
const auto byte_size =
|
||||
static_cast<UINT16>(volume_label.size() * sizeof(WCHAR));
|
||||
wcscpy_s(&volume_info->VolumeLabel[0u], 32,
|
||||
&utils::string::from_utf8(volume_label)[0u]);
|
||||
volume_info->VolumeLabelLength =
|
||||
std::min(static_cast<UINT16>(64u), byte_size);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::Init(PVOID host) {
|
||||
auto remote_winfsp_drive::Init(PVOID host) -> NTSTATUS {
|
||||
auto *file_system_host = reinterpret_cast<FileSystemHost *>(host);
|
||||
file_system_host->SetPrefix(
|
||||
&(L"\\repertory\\" + std::wstring(file_system_host->FileSystemName()).substr(0, 1))[0]);
|
||||
if (not config_.get_enable_mount_manager()) {
|
||||
file_system_host->SetPrefix(
|
||||
&(L"\\repertory\\" +
|
||||
std::wstring(file_system_host->FileSystemName()).substr(0, 1))[0u]);
|
||||
}
|
||||
file_system_host->SetFileSystemName(REPERTORY_W);
|
||||
file_system_host->SetFlushAndPurgeOnCleanup(TRUE);
|
||||
file_system_host->SetReparsePoints(FALSE);
|
||||
@@ -188,7 +227,8 @@ NTSTATUS remote_winfsp_drive::Init(PVOID host) {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
int remote_winfsp_drive::mount(const std::vector<std::string> &drive_args) {
|
||||
auto remote_winfsp_drive::mount(const std::vector<std::string> &drive_args)
|
||||
-> int {
|
||||
std::vector<std::string> parsed_drive_args;
|
||||
|
||||
const auto force_no_console = utils::collection_includes(drive_args, "-nc");
|
||||
@@ -210,50 +250,61 @@ int remote_winfsp_drive::mount(const std::vector<std::string> &drive_args) {
|
||||
c = std::make_unique<console_consumer>();
|
||||
}
|
||||
event_system::instance().start();
|
||||
const auto ret = winfsp_service(lock_, *this, parsed_drive_args, config_).Run();
|
||||
const auto ret =
|
||||
winfsp_service(lock_, *this, parsed_drive_args, config_).Run();
|
||||
event_system::instance().raise<drive_mount_result>(std::to_string(ret));
|
||||
event_system::instance().stop();
|
||||
c.reset();
|
||||
return static_cast<int>(ret);
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::Mounted(PVOID host) {
|
||||
auto remote_winfsp_drive::Mounted(PVOID host) -> NTSTATUS {
|
||||
auto *file_system_host = reinterpret_cast<FileSystemHost *>(host);
|
||||
remote_instance_ = factory_();
|
||||
server_ = std::make_unique<server>(config_);
|
||||
server_->start();
|
||||
mount_location_ = utils::string::to_utf8(file_system_host->MountPoint());
|
||||
lock_.set_mount_state(true, mount_location_, ::GetCurrentProcessId());
|
||||
if (not lock_.set_mount_state(true, mount_location_,
|
||||
::GetCurrentProcessId())) {
|
||||
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
|
||||
}
|
||||
|
||||
return remote_instance_->winfsp_mounted(file_system_host->MountPoint());
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::Open(PWSTR file_name, UINT32 create_options, UINT32 granted_access,
|
||||
PVOID *file_node, PVOID *file_desc, OpenFileInfo *ofi) {
|
||||
auto remote_winfsp_drive::Open(PWSTR file_name, UINT32 create_options,
|
||||
UINT32 granted_access, PVOID * /*file_node*/,
|
||||
PVOID *file_desc, OpenFileInfo *ofi)
|
||||
-> NTSTATUS {
|
||||
remote::file_info fi{};
|
||||
std::string normalize_name;
|
||||
const auto ret = remote_instance_->winfsp_open(file_name, create_options, granted_access,
|
||||
file_desc, &fi, normalize_name);
|
||||
const auto ret =
|
||||
remote_instance_->winfsp_open(file_name, create_options, granted_access,
|
||||
file_desc, &fi, normalize_name);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
SetFileInfo(ofi->FileInfo, fi);
|
||||
const auto filePath = utils::string::from_utf8(normalize_name);
|
||||
wcsncpy(ofi->NormalizedName, &filePath[0], wcslen(&filePath[0]));
|
||||
ofi->NormalizedNameSize = (UINT16)(wcslen(&filePath[0]) * sizeof(WCHAR));
|
||||
set_file_info(ofi->FileInfo, fi);
|
||||
const auto file_path = utils::string::from_utf8(normalize_name);
|
||||
wcsncpy(ofi->NormalizedName, &file_path[0], wcslen(&file_path[0]));
|
||||
ofi->NormalizedNameSize = (UINT16)(wcslen(&file_path[0]) * sizeof(WCHAR));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::Overwrite(PVOID file_node, PVOID file_desc, UINT32 attributes,
|
||||
BOOLEAN replace_attributes, UINT64 allocation_size,
|
||||
FileInfo *file_info) {
|
||||
auto remote_winfsp_drive::Overwrite(PVOID /*file_node*/, PVOID file_desc,
|
||||
UINT32 attributes,
|
||||
BOOLEAN replace_attributes,
|
||||
UINT64 allocation_size, FileInfo *file_info)
|
||||
-> NTSTATUS {
|
||||
remote::file_info fi{};
|
||||
const auto ret = remote_instance_->winfsp_overwrite(file_desc, attributes, replace_attributes,
|
||||
allocation_size, &fi);
|
||||
SetFileInfo(*file_info, fi);
|
||||
const auto ret = remote_instance_->winfsp_overwrite(
|
||||
file_desc, attributes, replace_attributes, allocation_size, &fi);
|
||||
set_file_info(*file_info, fi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void remote_winfsp_drive::PopulateFileInfo(const json &item, FSP_FSCTL_FILE_INFO &file_info) {
|
||||
void remote_winfsp_drive::populate_file_info(const json &item,
|
||||
FSP_FSCTL_FILE_INFO &file_info) {
|
||||
const auto di = directory_item::from_json(item);
|
||||
file_info.FileSize = di.directory ? 0 : di.size;
|
||||
file_info.AllocationSize =
|
||||
@@ -270,28 +321,33 @@ void remote_winfsp_drive::PopulateFileInfo(const json &item, FSP_FSCTL_FILE_INFO
|
||||
file_info.EaSize = 0;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::Read(PVOID file_node, PVOID file_desc, PVOID buffer, UINT64 offset,
|
||||
ULONG length, PULONG bytes_transferred) {
|
||||
return remote_instance_->winfsp_read(file_desc, buffer, offset, length,
|
||||
reinterpret_cast<PUINT32>(bytes_transferred));
|
||||
auto remote_winfsp_drive::Read(PVOID /*file_node*/, PVOID file_desc,
|
||||
PVOID buffer, UINT64 offset, ULONG length,
|
||||
PULONG bytes_transferred) -> NTSTATUS {
|
||||
return remote_instance_->winfsp_read(
|
||||
file_desc, buffer, offset, length,
|
||||
reinterpret_cast<PUINT32>(bytes_transferred));
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::ReadDirectory(PVOID file_node, PVOID file_desc, PWSTR pattern,
|
||||
PWSTR marker, PVOID buffer, ULONG buffer_length,
|
||||
PULONG bytes_transferred) {
|
||||
json itemList;
|
||||
NTSTATUS ret = remote_instance_->winfsp_read_directory(file_desc, pattern, marker, itemList);
|
||||
auto remote_winfsp_drive::ReadDirectory(PVOID /*file_node*/, PVOID file_desc,
|
||||
PWSTR pattern, PWSTR marker,
|
||||
PVOID buffer, ULONG buffer_length,
|
||||
PULONG bytes_transferred) -> NTSTATUS {
|
||||
json item_list;
|
||||
NTSTATUS ret = remote_instance_->winfsp_read_directory(file_desc, pattern,
|
||||
marker, item_list);
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
PVOID *directory_buffer = nullptr;
|
||||
if ((ret = remote_instance_->winfsp_get_dir_buffer(file_desc, directory_buffer)) ==
|
||||
STATUS_SUCCESS) {
|
||||
if (FspFileSystemAcquireDirectoryBuffer(directory_buffer,
|
||||
static_cast<BOOLEAN>(nullptr == marker), &ret)) {
|
||||
if ((ret = remote_instance_->winfsp_get_dir_buffer(
|
||||
file_desc, directory_buffer)) == STATUS_SUCCESS) {
|
||||
if (FspFileSystemAcquireDirectoryBuffer(
|
||||
directory_buffer, static_cast<BOOLEAN>(nullptr == marker),
|
||||
&ret)) {
|
||||
auto item_found = false;
|
||||
for (const auto &item : itemList) {
|
||||
for (const auto &item : item_list) {
|
||||
const auto item_path = item["path"].get<std::string>();
|
||||
const auto display_name =
|
||||
utils::string::from_utf8(utils::path::strip_to_file_name(item_path));
|
||||
const auto display_name = utils::string::from_utf8(
|
||||
utils::path::strip_to_file_name(item_path));
|
||||
if (not marker || (marker && item_found)) {
|
||||
if (not utils::path::is_ads_file_path(item_path)) {
|
||||
union {
|
||||
@@ -304,15 +360,19 @@ NTSTATUS remote_winfsp_drive::ReadDirectory(PVOID file_node, PVOID file_desc, PW
|
||||
::ZeroMemory(directory_info, sizeof(*directory_info));
|
||||
directory_info->Size = static_cast<UINT16>(
|
||||
FIELD_OFFSET(FSP_FSCTL_DIR_INFO, FileNameBuf) +
|
||||
(std::min((size_t)MAX_PATH, display_name.size()) * sizeof(WCHAR)));
|
||||
(std::min((size_t)MAX_PATH, display_name.size()) *
|
||||
sizeof(WCHAR)));
|
||||
|
||||
if (not item["meta"].empty() || ((item_path != ".") && (item_path != ".."))) {
|
||||
PopulateFileInfo(item, directory_info->FileInfo);
|
||||
if (not item["meta"].empty() ||
|
||||
((item_path != ".") && (item_path != ".."))) {
|
||||
populate_file_info(item, directory_info->FileInfo);
|
||||
}
|
||||
if (ret == STATUS_SUCCESS) {
|
||||
::wcscpy_s(&directory_info->FileNameBuf[0], MAX_PATH, &display_name[0]);
|
||||
::wcscpy_s(&directory_info->FileNameBuf[0], MAX_PATH,
|
||||
&display_name[0]);
|
||||
|
||||
FspFileSystemFillDirectoryBuffer(directory_buffer, directory_info, &ret);
|
||||
FspFileSystemFillDirectoryBuffer(directory_buffer,
|
||||
directory_info, &ret);
|
||||
if (ret != STATUS_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
@@ -327,8 +387,9 @@ NTSTATUS remote_winfsp_drive::ReadDirectory(PVOID file_node, PVOID file_desc, PW
|
||||
}
|
||||
|
||||
if ((ret == STATUS_SUCCESS) || (ret == STATUS_NO_MORE_FILES)) {
|
||||
FspFileSystemReadDirectoryBuffer(directory_buffer, marker, buffer, buffer_length,
|
||||
reinterpret_cast<PULONG>(bytes_transferred));
|
||||
FspFileSystemReadDirectoryBuffer(
|
||||
directory_buffer, marker, buffer, buffer_length,
|
||||
reinterpret_cast<PULONG>(bytes_transferred));
|
||||
ret = STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -337,23 +398,29 @@ NTSTATUS remote_winfsp_drive::ReadDirectory(PVOID file_node, PVOID file_desc, PW
|
||||
return ret;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::Rename(PVOID file_node, PVOID file_desc, PWSTR file_name,
|
||||
PWSTR new_file_name, BOOLEAN replace_if_exists) {
|
||||
return remote_instance_->winfsp_rename(file_desc, file_name, new_file_name, replace_if_exists);
|
||||
auto remote_winfsp_drive::Rename(PVOID /*file_node*/, PVOID file_desc,
|
||||
PWSTR file_name, PWSTR new_file_name,
|
||||
BOOLEAN replace_if_exists) -> NTSTATUS {
|
||||
return remote_instance_->winfsp_rename(file_desc, file_name, new_file_name,
|
||||
replace_if_exists);
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::SetBasicInfo(PVOID file_node, PVOID file_desc, UINT32 attributes,
|
||||
UINT64 creation_time, UINT64 last_access_time,
|
||||
UINT64 last_write_time, UINT64 change_time,
|
||||
FileInfo *file_info) {
|
||||
auto remote_winfsp_drive::SetBasicInfo(PVOID /*file_node*/, PVOID file_desc,
|
||||
UINT32 attributes, UINT64 creation_time,
|
||||
UINT64 last_access_time,
|
||||
UINT64 last_write_time,
|
||||
UINT64 change_time, FileInfo *file_info)
|
||||
-> NTSTATUS {
|
||||
remote::file_info fi{};
|
||||
const auto ret = remote_instance_->winfsp_set_basic_info(
|
||||
file_desc, attributes, creation_time, last_access_time, last_write_time, change_time, &fi);
|
||||
SetFileInfo(*file_info, fi);
|
||||
file_desc, attributes, creation_time, last_access_time, last_write_time,
|
||||
change_time, &fi);
|
||||
set_file_info(*file_info, fi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void remote_winfsp_drive::SetFileInfo(FileInfo &dest, const remote::file_info &src) {
|
||||
void remote_winfsp_drive::set_file_info(FileInfo &dest,
|
||||
const remote::file_info &src) {
|
||||
dest.FileAttributes = src.FileAttributes;
|
||||
dest.ReparseTag = src.ReparseTag;
|
||||
dest.AllocationSize = src.AllocationSize;
|
||||
@@ -367,33 +434,39 @@ void remote_winfsp_drive::SetFileInfo(FileInfo &dest, const remote::file_info &s
|
||||
dest.EaSize = src.EaSize;
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::SetFileSize(PVOID file_node, PVOID file_desc, UINT64 new_size,
|
||||
BOOLEAN set_allocation_size, FileInfo *file_info) {
|
||||
auto remote_winfsp_drive::SetFileSize(PVOID /*file_node*/, PVOID file_desc,
|
||||
UINT64 new_size,
|
||||
BOOLEAN set_allocation_size,
|
||||
FileInfo *file_info) -> NTSTATUS {
|
||||
remote::file_info fi{};
|
||||
const auto ret =
|
||||
remote_instance_->winfsp_set_file_size(file_desc, new_size, set_allocation_size, &fi);
|
||||
SetFileInfo(*file_info, fi);
|
||||
const auto ret = remote_instance_->winfsp_set_file_size(
|
||||
file_desc, new_size, set_allocation_size, &fi);
|
||||
set_file_info(*file_info, fi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
VOID remote_winfsp_drive::Unmounted(PVOID host) {
|
||||
server_->stop();
|
||||
server_.reset();
|
||||
auto *fileSystemHost = reinterpret_cast<FileSystemHost *>(host);
|
||||
lock_.set_mount_state(false, "", -1);
|
||||
remote_instance_->winfsp_unmounted(fileSystemHost->MountPoint());
|
||||
auto *file_system_host = reinterpret_cast<FileSystemHost *>(host);
|
||||
if (not lock_.set_mount_state(false, "", -1)) {
|
||||
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
|
||||
}
|
||||
remote_instance_->winfsp_unmounted(file_system_host->MountPoint());
|
||||
remote_instance_.reset();
|
||||
mount_location_ = "";
|
||||
}
|
||||
|
||||
NTSTATUS remote_winfsp_drive::Write(PVOID file_node, PVOID file_desc, PVOID buffer, UINT64 offset,
|
||||
ULONG length, BOOLEAN write_to_end, BOOLEAN constrained_io,
|
||||
PULONG bytes_transferred, FileInfo *file_info) {
|
||||
auto remote_winfsp_drive::Write(PVOID /*file_node*/, PVOID file_desc,
|
||||
PVOID buffer, UINT64 offset, ULONG length,
|
||||
BOOLEAN write_to_end, BOOLEAN constrained_io,
|
||||
PULONG bytes_transferred, FileInfo *file_info)
|
||||
-> NTSTATUS {
|
||||
remote::file_info fi{};
|
||||
const auto ret = remote_instance_->winfsp_write(
|
||||
file_desc, buffer, offset, length, write_to_end, constrained_io,
|
||||
reinterpret_cast<PUINT32>(bytes_transferred), &fi);
|
||||
SetFileInfo(*file_info, fi);
|
||||
set_file_info(*file_info, fi);
|
||||
return ret;
|
||||
}
|
||||
} // namespace repertory::remote_winfsp
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,74 +1,106 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "events/consumers/logging_consumer.hpp"
|
||||
|
||||
#include "events/events.hpp"
|
||||
#include "types/startup_exception.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/unix/unix_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
logging_consumer::logging_consumer(const std::string &log_directory, const event_level &level)
|
||||
logging_consumer::logging_consumer(const std::string &log_directory,
|
||||
const event_level &level)
|
||||
: event_level_(level),
|
||||
log_directory_(utils::path::finalize(log_directory)),
|
||||
log_path_(utils::path::combine(log_directory, {"repertory.log"})) {
|
||||
utils::file::create_full_directory_path(log_directory_);
|
||||
if (not utils::file::create_full_directory_path(log_directory_)) {
|
||||
throw startup_exception("failed to create log directory|sp|" +
|
||||
log_directory_ + "|err|" +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
|
||||
check_log_roll(0);
|
||||
reopen_log_file();
|
||||
E_SUBSCRIBE_ALL(process_event);
|
||||
E_SUBSCRIBE_EXACT(event_level_changed, [this](const event_level_changed &e) {
|
||||
event_level_ = event_level_from_string(e.get_new_event_level().get<std::string>());
|
||||
});
|
||||
logging_thread_ = std::make_unique<std::thread>([this]() { this->logging_thread(); });
|
||||
E_SUBSCRIBE_EXACT(event_level_changed,
|
||||
[this](const event_level_changed &evt) {
|
||||
event_level_ = event_level_from_string(
|
||||
evt.get_new_event_level().get<std::string>());
|
||||
});
|
||||
logging_thread_ =
|
||||
std::make_unique<std::thread>([this] { logging_thread(false); });
|
||||
}
|
||||
|
||||
logging_consumer::~logging_consumer() {
|
||||
E_CONSUMER_RELEASE();
|
||||
{
|
||||
mutex_lock l(log_mutex_);
|
||||
logging_active_ = false;
|
||||
log_notify_.notify_all();
|
||||
}
|
||||
|
||||
unique_mutex_lock l(log_mutex_);
|
||||
logging_active_ = false;
|
||||
log_notify_.notify_all();
|
||||
l.unlock();
|
||||
|
||||
logging_thread_->join();
|
||||
logging_thread_.reset();
|
||||
|
||||
logging_thread(true);
|
||||
close_log_file();
|
||||
}
|
||||
|
||||
void logging_consumer::check_log_roll(const size_t &count) {
|
||||
void logging_consumer::check_log_roll(std::size_t count) {
|
||||
std::uint64_t file_size{};
|
||||
const auto success = utils::file::get_file_size(log_path_, file_size);
|
||||
if (success && (file_size + count) >= MAX_LOG_FILE_SIZE) {
|
||||
close_log_file();
|
||||
for (std::uint8_t i = MAX_LOG_FILES; i > 0u; i--) {
|
||||
const auto temp_log_path = utils::path::combine(
|
||||
log_directory_, {"repertory." + utils::string::from_uint8(i) + ".log"});
|
||||
log_directory_, {"repertory." + std::to_string(i) + ".log"});
|
||||
if (utils::file::is_file(temp_log_path)) {
|
||||
if (i == MAX_LOG_FILES) {
|
||||
utils::file::delete_file(temp_log_path);
|
||||
if (not utils::file::retry_delete_file(temp_log_path)) {
|
||||
}
|
||||
} else {
|
||||
const auto next_file_path = utils::path::combine(
|
||||
log_directory_,
|
||||
{"repertory." + utils::string::from_uint8(i + std::uint8_t(1)) + ".log"});
|
||||
utils::file::move_file(temp_log_path, next_file_path);
|
||||
{"repertory." + std::to_string(i + std::uint8_t(1)) + ".log"});
|
||||
if (not utils::file::move_file(temp_log_path, next_file_path)) {
|
||||
utils::error::raise_error(__FUNCTION__,
|
||||
utils::get_last_error_code(),
|
||||
temp_log_path + "|dest|" + next_file_path,
|
||||
"failed to move file");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
utils::file::move_file(log_path_, utils::path::combine(log_directory_, {"repertory.1.log"}));
|
||||
|
||||
auto backup_log_path =
|
||||
utils::path::combine(log_directory_, {"repertory.1.log"});
|
||||
if (not utils::file::move_file(log_path_, backup_log_path)) {
|
||||
utils::error::raise_error(__FUNCTION__, utils::get_last_error_code(),
|
||||
log_path_ + "|dest|" + backup_log_path,
|
||||
"failed to move file");
|
||||
}
|
||||
reopen_log_file();
|
||||
}
|
||||
}
|
||||
@@ -80,7 +112,7 @@ void logging_consumer::close_log_file() {
|
||||
}
|
||||
}
|
||||
|
||||
void logging_consumer::logging_thread(const bool &drain) {
|
||||
void logging_consumer::logging_thread(bool drain) {
|
||||
do {
|
||||
std::deque<std::shared_ptr<event>> events;
|
||||
{
|
||||
@@ -112,7 +144,8 @@ void logging_consumer::logging_thread(const bool &drain) {
|
||||
check_log_roll(msg.length());
|
||||
auto retry = true;
|
||||
for (int i = 0; retry && (i < 2); i++) {
|
||||
retry = (not log_file_ || (fwrite(&msg[0], 1, msg.length(), log_file_) != msg.length()));
|
||||
retry = (not log_file_ || (fwrite(&msg[0], 1, msg.length(),
|
||||
log_file_) != msg.length()));
|
||||
if (retry) {
|
||||
reopen_log_file();
|
||||
}
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "events/event.hpp"
|
||||
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
event_level event_level_from_string(std::string level) {
|
||||
auto event_level_from_string(std::string level) -> event_level {
|
||||
level = utils::string::to_lower(level);
|
||||
if (level == "debug" || level == "event_level::debug") {
|
||||
return event_level::debug;
|
||||
@@ -36,7 +40,7 @@ event_level event_level_from_string(std::string level) {
|
||||
return event_level::normal;
|
||||
}
|
||||
|
||||
std::string event_level_to_string(const event_level &level) {
|
||||
auto event_level_to_string(const event_level &level) -> std::string {
|
||||
switch (level) {
|
||||
case event_level::debug:
|
||||
return "debug";
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "events/t_event_system.hpp"
|
||||
|
||||
#include "events/event.hpp"
|
||||
|
||||
namespace repertory {
|
||||
template <typename event_type> t_event_system<event_type> &t_event_system<event_type>::instance() {
|
||||
template <typename event_type>
|
||||
auto t_event_system<event_type>::instance() -> t_event_system<event_type> & {
|
||||
return event_system_;
|
||||
}
|
||||
template <typename event_type> t_event_system<event_type> t_event_system<event_type>::event_system_;
|
||||
|
||||
template <typename event_type>
|
||||
t_event_system<event_type> t_event_system<event_type>::event_system_;
|
||||
|
||||
template class t_event_system<event>;
|
||||
} // namespace repertory
|
||||
|
||||
979
src/file_manager/file_manager.cpp
Normal file
979
src/file_manager/file_manager.cpp
Normal file
@@ -0,0 +1,979 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "file_manager/file_manager.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "file_manager/events.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/polling.hpp"
|
||||
#include "utils/rocksdb_utils.hpp"
|
||||
#include "utils/unix/unix_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
static auto create_resume_entry(const i_open_file &o) -> json {
|
||||
return {
|
||||
{"chunk_size", o.get_chunk_size()},
|
||||
{"path", o.get_api_path()},
|
||||
{"read_state", utils::string::from_dynamic_bitset(o.get_read_state())},
|
||||
{"source", o.get_source_path()},
|
||||
};
|
||||
}
|
||||
|
||||
static 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 = utils::string::to_dynamic_bitset(
|
||||
resume_entry["read_state"].get<std::string>());
|
||||
source_path = resume_entry["source"].get<std::string>();
|
||||
}
|
||||
|
||||
file_manager::file_manager(app_config &config, i_provider &provider)
|
||||
: config_(config), provider_(provider) {
|
||||
if (not provider_.is_direct_only()) {
|
||||
auto families = std::vector<rocksdb::ColumnFamilyDescriptor>();
|
||||
families.emplace_back(rocksdb::kDefaultColumnFamilyName,
|
||||
rocksdb::ColumnFamilyOptions());
|
||||
families.emplace_back("upload", rocksdb::ColumnFamilyOptions());
|
||||
families.emplace_back("upload_active", rocksdb::ColumnFamilyOptions());
|
||||
|
||||
auto handles = std::vector<rocksdb::ColumnFamilyHandle *>();
|
||||
utils::db::create_rocksdb(config, "upload_db", families, handles, db_);
|
||||
std::size_t idx{};
|
||||
default_family_ = handles[idx++];
|
||||
upload_family_ = handles[idx++];
|
||||
upload_active_family_ = handles[idx++];
|
||||
|
||||
E_SUBSCRIBE_EXACT(file_upload_completed,
|
||||
[this](const file_upload_completed &evt) {
|
||||
this->upload_completed(evt);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
file_manager::~file_manager() {
|
||||
stop();
|
||||
|
||||
E_CONSUMER_RELEASE();
|
||||
|
||||
db_.reset();
|
||||
}
|
||||
|
||||
void file_manager::close(std::uint64_t handle) {
|
||||
unique_recur_mutex_lock file_lock(open_file_mtx_);
|
||||
auto it = open_handle_lookup_.find(handle);
|
||||
if (it != open_handle_lookup_.end()) {
|
||||
auto *cur_file = it->second;
|
||||
open_handle_lookup_.erase(handle);
|
||||
cur_file->remove(handle);
|
||||
|
||||
if (cur_file->can_close()) {
|
||||
const auto api_path = cur_file->get_api_path();
|
||||
cur_file = nullptr;
|
||||
|
||||
auto closeable_file = open_file_lookup_.at(api_path);
|
||||
open_file_lookup_.erase(api_path);
|
||||
file_lock.unlock();
|
||||
|
||||
closeable_file->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::close_all(const std::string &api_path) {
|
||||
recur_mutex_lock file_lock(open_file_mtx_);
|
||||
std::vector<std::uint64_t> handles;
|
||||
auto it = open_file_lookup_.find(api_path);
|
||||
if (it != open_file_lookup_.end()) {
|
||||
handles = it->second->get_handles();
|
||||
}
|
||||
|
||||
for (auto &handle : handles) {
|
||||
open_file_lookup_[api_path]->remove(handle);
|
||||
open_handle_lookup_.erase(handle);
|
||||
}
|
||||
|
||||
open_file_lookup_.erase(api_path);
|
||||
}
|
||||
|
||||
void file_manager::close_timed_out_files() {
|
||||
unique_recur_mutex_lock file_lock(open_file_mtx_);
|
||||
auto closeable_list = std::accumulate(
|
||||
open_file_lookup_.begin(), open_file_lookup_.end(),
|
||||
std::vector<std::string>{}, [](auto items, const auto &kv) -> auto {
|
||||
if (kv.second->get_open_file_count() == 0U && kv.second->can_close()) {
|
||||
items.emplace_back(kv.first);
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
std::vector<std::shared_ptr<i_closeable_open_file>> open_files{};
|
||||
for (const auto &api_path : closeable_list) {
|
||||
auto closeable_file = open_file_lookup_.at(api_path);
|
||||
open_file_lookup_.erase(api_path);
|
||||
open_files.emplace_back(closeable_file);
|
||||
}
|
||||
closeable_list.clear();
|
||||
file_lock.unlock();
|
||||
|
||||
for (auto &closeable_file : open_files) {
|
||||
closeable_file->close();
|
||||
event_system::instance().raise<download_timeout>(
|
||||
closeable_file->get_api_path());
|
||||
}
|
||||
}
|
||||
|
||||
auto file_manager::create(const std::string &api_path, api_meta_map &meta,
|
||||
open_file_data ofd, std::uint64_t &handle,
|
||||
std::shared_ptr<i_open_file> &f) -> api_error {
|
||||
recur_mutex_lock file_lock(open_file_mtx_);
|
||||
auto res = provider_.create_file(api_path, meta);
|
||||
if (res != api_error::success && res != api_error::item_exists) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return open(api_path, false, ofd, handle, f);
|
||||
}
|
||||
|
||||
auto file_manager::evict_file(const std::string &api_path) -> bool {
|
||||
if (provider_.is_direct_only()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
if (is_processing(api_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_open_file_count(api_path) != 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string pinned;
|
||||
auto res = provider_.get_item_meta(api_path, META_PINNED, pinned);
|
||||
if (res != api_error::success && res != api_error::item_not_found) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, res,
|
||||
"failed to get pinned status");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not pinned.empty() && utils::string::to_bool(pinned)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string source_path{};
|
||||
res = provider_.get_item_meta(api_path, META_SOURCE, source_path);
|
||||
if (res != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, res,
|
||||
"failed to get source path");
|
||||
return false;
|
||||
}
|
||||
if (source_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto removed = utils::file::retry_delete_file(source_path);
|
||||
if (removed) {
|
||||
event_system::instance().raise<filesystem_item_evicted>(api_path,
|
||||
source_path);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
auto file_manager::get_directory_items(const std::string &api_path) const
|
||||
-> directory_item_list {
|
||||
directory_item_list list{};
|
||||
auto res = provider_.get_directory_items(api_path, list);
|
||||
if (res != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, res,
|
||||
"failed to get directory list");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
auto file_manager::get_next_handle() -> std::uint64_t {
|
||||
if (++next_handle_ == 0u) {
|
||||
next_handle_++;
|
||||
}
|
||||
|
||||
return next_handle_;
|
||||
}
|
||||
|
||||
auto file_manager::get_open_file_count(const std::string &api_path) const
|
||||
-> std::size_t {
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
auto it = open_file_lookup_.find(api_path);
|
||||
if (it != open_file_lookup_.end()) {
|
||||
return it->second->get_open_file_count();
|
||||
}
|
||||
|
||||
return 0u;
|
||||
}
|
||||
|
||||
auto file_manager::get_open_file(std::uint64_t handle, bool write_supported,
|
||||
std::shared_ptr<i_open_file> &f) -> bool {
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
auto it = open_handle_lookup_.find(handle);
|
||||
if (it == open_handle_lookup_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto of = open_file_lookup_.at(it->second->get_api_path());
|
||||
if (write_supported && not of->is_write_supported()) {
|
||||
auto new_f = std::make_shared<open_file>(
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size(),
|
||||
config_.get_enable_chunk_download_timeout()
|
||||
? config_.get_chunk_downloader_timeout_secs()
|
||||
: 0U,
|
||||
of->get_filesystem_item(), of->get_open_data(), provider_, *this);
|
||||
open_file_lookup_[of->get_api_path()] = new_f;
|
||||
f = new_f;
|
||||
return true;
|
||||
}
|
||||
|
||||
f = of;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto file_manager::get_open_file_count() const -> std::size_t {
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
return open_file_lookup_.size();
|
||||
}
|
||||
|
||||
auto file_manager::get_open_files() const
|
||||
-> std::unordered_map<std::string, std::size_t> {
|
||||
std::unordered_map<std::string, std::size_t> ret;
|
||||
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
for (const auto &kv : open_file_lookup_) {
|
||||
ret[kv.first] = kv.second->get_open_file_count();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto file_manager::get_open_handle_count() const -> std::size_t {
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
return open_handle_lookup_.size();
|
||||
}
|
||||
|
||||
auto file_manager::get_stored_downloads() const -> std::vector<json> {
|
||||
std::vector<json> ret;
|
||||
if (not provider_.is_direct_only()) {
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), default_family_));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
ret.emplace_back(json::parse(iterator->value().ToString()));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto file_manager::handle_file_rename(const std::string &from_api_path,
|
||||
const std::string &to_api_path)
|
||||
-> api_error {
|
||||
auto ret = api_error::file_in_use;
|
||||
if ((ret = provider_.rename_file(from_api_path, to_api_path)) ==
|
||||
api_error::success) {
|
||||
swap_renamed_items(from_api_path, to_api_path);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto file_manager::has_no_open_file_handles() const -> bool {
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
return open_handle_lookup_.empty();
|
||||
}
|
||||
|
||||
auto file_manager::is_processing(const std::string &api_path) const -> bool {
|
||||
if (provider_.is_direct_only()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
|
||||
mutex_lock upload_lock(upload_mtx_);
|
||||
if (upload_lookup_.find(api_path) != upload_lookup_.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
{
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), upload_family_));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
const auto parts = utils::string::split(iterator->key().ToString(), ':');
|
||||
if (parts[1u] == api_path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto it = open_file_lookup_.find(api_path);
|
||||
if (it != open_file_lookup_.end()) {
|
||||
return it->second->is_modified() || not it->second->is_complete();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto file_manager::open(const std::string &api_path, bool directory,
|
||||
const open_file_data &ofd, std::uint64_t &handle,
|
||||
std::shared_ptr<i_open_file> &f) -> api_error {
|
||||
return open(api_path, directory, ofd, handle, f, nullptr);
|
||||
}
|
||||
|
||||
auto file_manager::open(const std::string &api_path, bool directory,
|
||||
const open_file_data &ofd, std::uint64_t &handle,
|
||||
std::shared_ptr<i_open_file> &f,
|
||||
std::shared_ptr<i_closeable_open_file> of)
|
||||
-> api_error {
|
||||
const auto create_and_add_handle =
|
||||
[&](std::shared_ptr<i_closeable_open_file> cur_file) {
|
||||
handle = get_next_handle();
|
||||
cur_file->add(handle, ofd);
|
||||
open_handle_lookup_[handle] = cur_file.get();
|
||||
f = cur_file;
|
||||
};
|
||||
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
auto it = open_file_lookup_.find(api_path);
|
||||
if (it != open_file_lookup_.end()) {
|
||||
create_and_add_handle(it->second);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
filesystem_item fsi{};
|
||||
auto res = provider_.get_filesystem_item(api_path, directory, fsi);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (fsi.source_path.empty()) {
|
||||
fsi.source_path = utils::path::combine(config_.get_cache_directory(),
|
||||
{utils::create_uuid_string()});
|
||||
if ((res = provider_.set_item_meta(fsi.api_path, META_SOURCE,
|
||||
fsi.source_path)) !=
|
||||
api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
if (not of) {
|
||||
of = std::make_shared<open_file>(
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size(),
|
||||
config_.get_enable_chunk_download_timeout()
|
||||
? config_.get_chunk_downloader_timeout_secs()
|
||||
: 0U,
|
||||
fsi, provider_, *this);
|
||||
}
|
||||
open_file_lookup_[api_path] = of;
|
||||
create_and_add_handle(of);
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto file_manager::perform_locked_operation(
|
||||
locked_operation_callback locked_operation) -> bool {
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
return locked_operation(provider_);
|
||||
}
|
||||
|
||||
void file_manager::queue_upload(const i_open_file &o) {
|
||||
return queue_upload(o.get_api_path(), o.get_source_path(), false);
|
||||
}
|
||||
|
||||
void file_manager::queue_upload(const std::string &api_path,
|
||||
const std::string &source_path, bool no_lock) {
|
||||
if (provider_.is_direct_only()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<mutex_lock> l;
|
||||
if (not no_lock) {
|
||||
l = std::make_unique<mutex_lock>(upload_mtx_);
|
||||
}
|
||||
remove_upload(api_path, true);
|
||||
|
||||
auto res = db_->Put(
|
||||
rocksdb::WriteOptions(), upload_family_,
|
||||
std::to_string(utils::get_file_time_now()) + ":" + api_path, source_path);
|
||||
if (res.ok()) {
|
||||
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,
|
||||
res.ToString());
|
||||
}
|
||||
|
||||
if (not no_lock) {
|
||||
upload_notify_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
auto file_manager::remove_file(const std::string &api_path) -> api_error {
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
auto it = open_file_lookup_.find(api_path);
|
||||
if (it != open_file_lookup_.end() && it->second->is_modified()) {
|
||||
return api_error::file_in_use;
|
||||
}
|
||||
|
||||
filesystem_item fsi{};
|
||||
auto res = provider_.get_filesystem_item(api_path, false, fsi);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if ((res = provider_.remove_file(api_path)) != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
remove_upload(api_path);
|
||||
|
||||
close_all(api_path);
|
||||
|
||||
if (not utils::file::retry_delete_file(fsi.source_path)) {
|
||||
utils::error::raise_api_path_error(
|
||||
__FUNCTION__, fsi.api_path, fsi.source_path,
|
||||
utils::get_last_error_code(), "failed to delete source");
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
void file_manager::remove_resume(const std::string &api_path,
|
||||
const std::string &source_path) {
|
||||
auto res = db_->Delete(rocksdb::WriteOptions(), default_family_, api_path);
|
||||
if (res.ok()) {
|
||||
event_system::instance().raise<download_stored_removed>(api_path,
|
||||
source_path);
|
||||
} else {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, source_path,
|
||||
res.code(),
|
||||
"failed to remove resume entry");
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::remove_upload(const std::string &api_path) {
|
||||
remove_upload(api_path, false);
|
||||
}
|
||||
|
||||
void file_manager::remove_upload(const std::string &api_path, bool no_lock) {
|
||||
if (provider_.is_direct_only()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<mutex_lock> l;
|
||||
if (not no_lock) {
|
||||
l = std::make_unique<mutex_lock>(upload_mtx_);
|
||||
}
|
||||
|
||||
auto it = upload_lookup_.find(api_path);
|
||||
if (it == upload_lookup_.end()) {
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), upload_family_));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
const auto parts = utils::string::split(iterator->key().ToString(), ':');
|
||||
if (parts[1U] == api_path) {
|
||||
if (db_->Delete(rocksdb::WriteOptions(), upload_family_,
|
||||
iterator->key())
|
||||
.ok()) {
|
||||
db_->Delete(rocksdb::WriteOptions(), upload_active_family_,
|
||||
iterator->key());
|
||||
}
|
||||
event_system::instance().raise<file_upload_removed>(
|
||||
api_path, iterator->value().ToString());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), upload_active_family_));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
const auto parts = utils::string::split(iterator->key().ToString(), ':');
|
||||
if (parts[1U] == api_path) {
|
||||
db_->Delete(rocksdb::WriteOptions(), upload_active_family_,
|
||||
iterator->key());
|
||||
}
|
||||
}
|
||||
event_system::instance().raise<file_upload_removed>(
|
||||
api_path, it->second->get_source_path());
|
||||
|
||||
it->second->cancel();
|
||||
upload_lookup_.erase(api_path);
|
||||
}
|
||||
|
||||
if (not no_lock) {
|
||||
upload_notify_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::swap_renamed_items(std::string from_api_path,
|
||||
std::string to_api_path) {
|
||||
const auto it = open_file_lookup_.find(from_api_path);
|
||||
if (it != open_file_lookup_.end()) {
|
||||
open_file_lookup_[to_api_path] = open_file_lookup_[from_api_path];
|
||||
open_file_lookup_.erase(from_api_path);
|
||||
open_file_lookup_[to_api_path]->set_api_path(to_api_path);
|
||||
}
|
||||
}
|
||||
|
||||
auto file_manager::rename_directory(const std::string &from_api_path,
|
||||
const std::string &to_api_path)
|
||||
-> api_error {
|
||||
if (not provider_.is_rename_supported()) {
|
||||
return api_error::not_implemented;
|
||||
}
|
||||
|
||||
unique_recur_mutex_lock l(open_file_mtx_);
|
||||
// Ensure source directory exists
|
||||
bool exists{};
|
||||
auto res = provider_.is_directory(from_api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (not exists) {
|
||||
return api_error::directory_not_found;
|
||||
}
|
||||
|
||||
// Ensure destination directory does not exist
|
||||
res = provider_.is_directory(to_api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
return api_error::directory_exists;
|
||||
}
|
||||
|
||||
// Ensure destination is not a file
|
||||
res = provider_.is_file(from_api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
return api_error::item_exists;
|
||||
}
|
||||
|
||||
// Create destination directory
|
||||
res =
|
||||
provider_.create_directory_clone_source_meta(from_api_path, to_api_path);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
directory_item_list list{};
|
||||
res = provider_.get_directory_items(from_api_path, list);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Rename all items - directories MUST BE returned first
|
||||
for (std::size_t i = 0U; (res == api_error::success) && (i < list.size());
|
||||
i++) {
|
||||
const auto &api_path = list[i].api_path;
|
||||
if ((api_path != ".") && (api_path != "..")) {
|
||||
const auto old_api_path = api_path;
|
||||
const auto new_api_path =
|
||||
utils::path::create_api_path(utils::path::combine(
|
||||
to_api_path, {old_api_path.substr(from_api_path.size())}));
|
||||
res = list[i].directory ? rename_directory(old_api_path, new_api_path)
|
||||
: rename_file(old_api_path, new_api_path, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
res = provider_.remove_directory(from_api_path);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
swap_renamed_items(from_api_path, to_api_path);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto file_manager::rename_file(const std::string &from_api_path,
|
||||
const std::string &to_api_path, bool overwrite)
|
||||
-> api_error {
|
||||
if (not provider_.is_rename_supported()) {
|
||||
return api_error::not_implemented;
|
||||
}
|
||||
|
||||
// Don't rename if paths are the same
|
||||
if (from_api_path == to_api_path) {
|
||||
return api_error::item_exists;
|
||||
}
|
||||
|
||||
unique_recur_mutex_lock l(open_file_mtx_);
|
||||
|
||||
// Don't rename if source is directory
|
||||
bool exists{};
|
||||
auto res = provider_.is_directory(from_api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
return api_error::directory_exists;
|
||||
}
|
||||
|
||||
// Don't rename if source does not exist
|
||||
res = provider_.is_file(from_api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (not exists) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
// Don't rename if destination is directory
|
||||
res = provider_.is_directory(to_api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
res = api_error::directory_exists;
|
||||
}
|
||||
|
||||
// Check allow overwrite if file exists
|
||||
bool dest_exists{};
|
||||
res = provider_.is_file(to_api_path, dest_exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (not overwrite && dest_exists) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Don't rename if destination file is uploading or downloading
|
||||
if (is_processing(to_api_path)) {
|
||||
return api_error::file_in_use;
|
||||
}
|
||||
|
||||
// Handle destination file exists (should overwrite)
|
||||
if (dest_exists) {
|
||||
filesystem_item fsi{};
|
||||
res = provider_.get_filesystem_item(to_api_path, false, fsi);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
std::uint64_t file_size{};
|
||||
if (not utils::file::get_file_size(fsi.source_path, file_size)) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
|
||||
res = provider_.remove_file(to_api_path);
|
||||
if ((res == api_error::success) || (res == api_error::item_not_found)) {
|
||||
if (not utils::file::retry_delete_file(fsi.source_path)) {
|
||||
utils::error::raise_api_path_error(
|
||||
__FUNCTION__, fsi.api_path, fsi.source_path,
|
||||
utils::get_last_error_code(), "failed to delete source path");
|
||||
}
|
||||
return handle_file_rename(from_api_path, to_api_path);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Check destination parent directory exists
|
||||
res = provider_.is_directory(utils::path::get_parent_api_path(to_api_path),
|
||||
exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (not exists) {
|
||||
return api_error::directory_not_found;
|
||||
}
|
||||
|
||||
return handle_file_rename(from_api_path, to_api_path);
|
||||
}
|
||||
|
||||
void file_manager::start() {
|
||||
if (provider_.is_direct_only()) {
|
||||
stop_requested_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (not upload_thread_) {
|
||||
stop_requested_ = false;
|
||||
|
||||
struct active_item {
|
||||
std::string api_path;
|
||||
std::string source_path;
|
||||
};
|
||||
|
||||
std::vector<active_item> active_items{};
|
||||
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), upload_active_family_));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
const auto parts = utils::string::split(iterator->key().ToString(), ':');
|
||||
active_items.emplace_back(
|
||||
active_item{parts[1U], iterator->value().ToString()});
|
||||
}
|
||||
for (const auto &active_item : active_items) {
|
||||
queue_upload(active_item.api_path, active_item.source_path, false);
|
||||
}
|
||||
active_items.clear();
|
||||
|
||||
iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), default_family_));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
std::string api_path;
|
||||
std::string source_path;
|
||||
std::size_t chunk_size{};
|
||||
boost::dynamic_bitset<> read_state;
|
||||
restore_resume_entry(json::parse(iterator->value().ToString()), api_path,
|
||||
chunk_size, read_state, source_path);
|
||||
|
||||
filesystem_item fsi{};
|
||||
auto res = provider_.get_filesystem_item(api_path, false, fsi);
|
||||
if (res == api_error::success) {
|
||||
if (source_path == fsi.source_path) {
|
||||
std::uint64_t file_size{};
|
||||
if (utils::file::get_file_size(fsi.source_path, file_size)) {
|
||||
if (file_size == fsi.size) {
|
||||
auto f = 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] = f;
|
||||
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 {
|
||||
event_system::instance().raise<download_restore_failed>(
|
||||
api_path, source_path,
|
||||
"failed to get filesystem item|" + api_error_to_string(res));
|
||||
}
|
||||
}
|
||||
|
||||
upload_thread_ =
|
||||
std::make_unique<std::thread>([this] { upload_handler(); });
|
||||
polling::instance().set_callback(
|
||||
{"timed_out_close", polling::frequency::second,
|
||||
[this]() { this->close_timed_out_files(); }});
|
||||
event_system::instance().raise<service_started>("file_manager");
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::stop() {
|
||||
if (not stop_requested_) {
|
||||
event_system::instance().raise<service_shutdown_begin>("file_manager");
|
||||
polling::instance().remove_callback("timed_out_close");
|
||||
stop_requested_ = true;
|
||||
|
||||
unique_mutex_lock upload_lock(upload_mtx_);
|
||||
upload_notify_.notify_all();
|
||||
upload_lock.unlock();
|
||||
|
||||
if (upload_thread_) {
|
||||
upload_thread_->join();
|
||||
upload_thread_.reset();
|
||||
}
|
||||
|
||||
open_file_lookup_.clear();
|
||||
open_handle_lookup_.clear();
|
||||
|
||||
upload_lock.lock();
|
||||
for (auto &kv : upload_lookup_) {
|
||||
kv.second->stop();
|
||||
}
|
||||
upload_notify_.notify_all();
|
||||
upload_lock.unlock();
|
||||
|
||||
while (not upload_lookup_.empty()) {
|
||||
upload_lock.lock();
|
||||
if (not upload_lookup_.empty()) {
|
||||
upload_notify_.wait_for(upload_lock, 1s);
|
||||
}
|
||||
upload_notify_.notify_all();
|
||||
upload_lock.unlock();
|
||||
}
|
||||
|
||||
event_system::instance().raise<service_shutdown_end>("file_manager");
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::store_resume(const i_open_file &o) {
|
||||
if (provider_.is_direct_only()) {
|
||||
return;
|
||||
}
|
||||
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
const auto res = db_->Put(rocksdb::WriteOptions(), default_family_,
|
||||
o.get_api_path(), create_resume_entry(o).dump());
|
||||
if (res.ok()) {
|
||||
event_system::instance().raise<download_stored>(o.get_api_path(),
|
||||
o.get_source_path());
|
||||
} else {
|
||||
event_system::instance().raise<download_stored_failed>(
|
||||
o.get_api_path(), o.get_source_path(), res.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::upload_completed(const file_upload_completed &e) {
|
||||
unique_mutex_lock upload_lock(upload_mtx_);
|
||||
|
||||
if (not utils::string::to_bool(e.get_cancelled().get<std::string>())) {
|
||||
const auto err = api_error_from_string(e.get_result().get<std::string>());
|
||||
switch (err) {
|
||||
case api_error::success: {
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), upload_active_family_));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
const auto parts =
|
||||
utils::string::split(iterator->key().ToString(), ':');
|
||||
if (parts[1U] == e.get_api_path().get<std::string>()) {
|
||||
db_->Delete(rocksdb::WriteOptions(), upload_active_family_,
|
||||
iterator->key());
|
||||
break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case api_error::upload_stopped: {
|
||||
event_system::instance().raise<file_upload_retry>(e.get_api_path(),
|
||||
e.get_source(), err);
|
||||
queue_upload(e.get_api_path(), e.get_source(), true);
|
||||
upload_notify_.wait_for(upload_lock, 5s);
|
||||
} break;
|
||||
default: {
|
||||
bool exists{};
|
||||
auto res = provider_.is_file(e.get_api_path(), exists);
|
||||
if ((res == api_error::success && not exists) ||
|
||||
not utils::file::is_file(e.get_source())) {
|
||||
event_system::instance().raise<file_upload_not_found>(e.get_api_path(),
|
||||
e.get_source());
|
||||
remove_upload(e.get_api_path(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
event_system::instance().raise<file_upload_retry>(e.get_api_path(),
|
||||
e.get_source(), err);
|
||||
queue_upload(e.get_api_path(), e.get_source(), true);
|
||||
upload_notify_.wait_for(upload_lock, 5s);
|
||||
} break;
|
||||
}
|
||||
upload_lookup_.erase(e.get_api_path());
|
||||
}
|
||||
upload_notify_.notify_all();
|
||||
}
|
||||
|
||||
void file_manager::upload_handler() {
|
||||
while (not stop_requested_) {
|
||||
unique_mutex_lock upload_lock(upload_mtx_);
|
||||
if (not stop_requested_) {
|
||||
if (upload_lookup_.size() < config_.get_max_upload_count()) {
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), upload_family_));
|
||||
iterator->SeekToFirst();
|
||||
if (iterator->Valid()) {
|
||||
const auto parts =
|
||||
utils::string::split(iterator->key().ToString(), ':');
|
||||
const auto api_path = parts[1u];
|
||||
const auto source_path = iterator->value().ToString();
|
||||
|
||||
filesystem_item fsi{};
|
||||
auto res = provider_.get_filesystem_item(api_path, false, fsi);
|
||||
switch (res) {
|
||||
case api_error::item_not_found: {
|
||||
event_system::instance().raise<file_upload_not_found>(api_path,
|
||||
source_path);
|
||||
remove_upload(parts[1u], true);
|
||||
} break;
|
||||
case api_error::success: {
|
||||
upload_lookup_[fsi.api_path] =
|
||||
std::make_unique<upload>(fsi, provider_);
|
||||
if (db_->Delete(rocksdb::WriteOptions(), upload_family_,
|
||||
iterator->key())
|
||||
.ok()) {
|
||||
db_->Put(rocksdb::WriteOptions(), upload_active_family_,
|
||||
iterator->key(), iterator->value());
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
event_system::instance().raise<file_upload_retry>(api_path,
|
||||
source_path, res);
|
||||
queue_upload(api_path, source_path, true);
|
||||
upload_notify_.wait_for(upload_lock, 5s);
|
||||
} break;
|
||||
}
|
||||
} else {
|
||||
iterator.release();
|
||||
upload_notify_.wait(upload_lock);
|
||||
}
|
||||
} else {
|
||||
upload_notify_.wait(upload_lock);
|
||||
}
|
||||
}
|
||||
upload_notify_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::update_used_space(std::uint64_t &used_space) const {
|
||||
recur_mutex_lock open_lock(open_file_mtx_);
|
||||
for (const auto &of : open_file_lookup_) {
|
||||
std::uint64_t file_size{};
|
||||
auto res = provider_.get_file_size(of.second->get_api_path(), file_size);
|
||||
if ((res == api_error::success) &&
|
||||
(file_size != of.second->get_file_size()) &&
|
||||
(used_space >= file_size)) {
|
||||
used_space -= file_size;
|
||||
used_space += of.second->get_file_size();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
596
src/file_manager/file_manager_open_file.cpp
Normal file
596
src/file_manager/file_manager_open_file.cpp
Normal file
@@ -0,0 +1,596 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "file_manager/file_manager.hpp"
|
||||
|
||||
#include "file_manager/events.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "types/startup_exception.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/unix/unix_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
file_manager::open_file::open_file(std::uint64_t chunk_size,
|
||||
std::uint8_t chunk_timeout,
|
||||
filesystem_item fsi, i_provider &provider,
|
||||
i_upload_manager &um)
|
||||
: open_file(chunk_size, chunk_timeout, fsi, {}, provider, std::nullopt,
|
||||
um) {}
|
||||
|
||||
file_manager::open_file::open_file(
|
||||
std::uint64_t chunk_size, std::uint8_t chunk_timeout, filesystem_item fsi,
|
||||
std::map<std::uint64_t, open_file_data> open_data, i_provider &provider,
|
||||
i_upload_manager &um)
|
||||
: open_file(chunk_size, chunk_timeout, fsi, open_data, provider,
|
||||
std::nullopt, um) {}
|
||||
|
||||
file_manager::open_file::open_file(
|
||||
std::uint64_t chunk_size, std::uint8_t chunk_timeout, filesystem_item fsi,
|
||||
i_provider &provider, std::optional<boost::dynamic_bitset<>> read_state,
|
||||
i_upload_manager &um)
|
||||
: open_file(chunk_size, chunk_timeout, fsi, {}, provider, read_state, um) {}
|
||||
|
||||
file_manager::open_file::open_file(
|
||||
std::uint64_t chunk_size, std::uint8_t chunk_timeout, filesystem_item fsi,
|
||||
std::map<std::uint64_t, open_file_data> open_data, i_provider &provider,
|
||||
std::optional<boost::dynamic_bitset<>> read_state, i_upload_manager &um)
|
||||
: open_file_base(chunk_size, chunk_timeout, fsi, open_data, provider),
|
||||
um_(um) {
|
||||
if (fsi_.directory && read_state.has_value()) {
|
||||
throw startup_exception("cannot resume a directory|" + fsi.api_path);
|
||||
}
|
||||
|
||||
if (not fsi.directory) {
|
||||
set_api_error(native_file::create_or_open(
|
||||
fsi.source_path, not provider_.is_direct_only(), nf_));
|
||||
if (get_api_error() == api_error::success) {
|
||||
if (read_state.has_value()) {
|
||||
modified_ = true;
|
||||
um_.store_resume(*this);
|
||||
read_state_ = read_state.value();
|
||||
} else if (fsi_.size > 0u) {
|
||||
read_state_.resize(static_cast<std::size_t>(utils::divide_with_ceiling(
|
||||
fsi_.size, chunk_size)),
|
||||
false);
|
||||
|
||||
std::uint64_t file_size{};
|
||||
if (nf_->get_file_size(file_size)) {
|
||||
if (provider_.is_direct_only() || file_size == fsi.size) {
|
||||
read_state_.set(0u, read_state_.size(), true);
|
||||
} else if (not nf_->truncate(fsi.size)) {
|
||||
set_api_error(api_error::os_error);
|
||||
}
|
||||
} else {
|
||||
set_api_error(api_error::os_error);
|
||||
}
|
||||
}
|
||||
|
||||
if (get_api_error() != api_error::success && nf_) {
|
||||
nf_->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file_manager::open_file::~open_file() { close(); }
|
||||
|
||||
void file_manager::open_file::download_chunk(std::size_t chunk,
|
||||
bool skip_active,
|
||||
bool should_reset) {
|
||||
if (should_reset) {
|
||||
reset_timeout();
|
||||
}
|
||||
|
||||
unique_recur_mutex_lock file_lock(file_mtx_);
|
||||
if ((get_api_error() == api_error::success) && (chunk < read_state_.size()) &&
|
||||
not read_state_[chunk]) {
|
||||
if (active_downloads_.find(chunk) != active_downloads_.end()) {
|
||||
if (not skip_active) {
|
||||
auto active_download = active_downloads_.at(chunk);
|
||||
file_lock.unlock();
|
||||
|
||||
active_download->wait();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto data_offset = chunk * chunk_size_;
|
||||
const auto data_size =
|
||||
(chunk == read_state_.size() - 1u) ? last_chunk_size_ : chunk_size_;
|
||||
if (active_downloads_.empty() && (read_state_.count() == 0u)) {
|
||||
event_system::instance().raise<download_begin>(fsi_.api_path,
|
||||
fsi_.source_path);
|
||||
}
|
||||
event_system::instance().raise<download_chunk_begin>(
|
||||
fsi_.api_path, fsi_.source_path, chunk, read_state_.size(),
|
||||
read_state_.count());
|
||||
|
||||
active_downloads_[chunk] = std::make_shared<download>();
|
||||
file_lock.unlock();
|
||||
|
||||
if (should_reset) {
|
||||
reset_timeout();
|
||||
}
|
||||
|
||||
std::async(std::launch::async, [this, chunk, data_size, data_offset,
|
||||
should_reset]() {
|
||||
const auto notify_complete = [this, chunk, should_reset]() {
|
||||
unique_recur_mutex_lock file_lock(file_mtx_);
|
||||
auto active_download = active_downloads_.at(chunk);
|
||||
active_downloads_.erase(chunk);
|
||||
event_system::instance().raise<download_chunk_end>(
|
||||
fsi_.api_path, fsi_.source_path, chunk, read_state_.size(),
|
||||
read_state_.count(), get_api_error());
|
||||
if (get_api_error() == api_error::success) {
|
||||
auto progress = (static_cast<double>(read_state_.count()) /
|
||||
static_cast<double>(read_state_.size()) * 100.0);
|
||||
event_system::instance().raise<download_progress>(
|
||||
fsi_.api_path, fsi_.source_path, progress);
|
||||
if (read_state_.all() && not notified_) {
|
||||
notified_ = true;
|
||||
event_system::instance().raise<download_end>(
|
||||
fsi_.api_path, fsi_.source_path, get_api_error());
|
||||
}
|
||||
} else if (not notified_) {
|
||||
notified_ = true;
|
||||
event_system::instance().raise<download_end>(
|
||||
fsi_.api_path, fsi_.source_path, get_api_error());
|
||||
}
|
||||
file_lock.unlock();
|
||||
|
||||
active_download->notify(get_api_error());
|
||||
|
||||
if (should_reset) {
|
||||
reset_timeout();
|
||||
}
|
||||
};
|
||||
|
||||
data_buffer data;
|
||||
auto res = provider_.read_file_bytes(get_api_path(), data_size,
|
||||
data_offset, data, stop_requested_);
|
||||
if (res != api_error::success) {
|
||||
set_api_error(res);
|
||||
notify_complete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (should_reset) {
|
||||
reset_timeout();
|
||||
}
|
||||
|
||||
res = do_io([&]() -> api_error {
|
||||
std::size_t bytes_written{};
|
||||
if (not nf_->write_bytes(data.data(), data.size(), data_offset,
|
||||
bytes_written)) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
|
||||
if (should_reset) {
|
||||
reset_timeout();
|
||||
}
|
||||
return api_error::success;
|
||||
});
|
||||
if (res != api_error::success) {
|
||||
set_api_error(res);
|
||||
notify_complete();
|
||||
return;
|
||||
}
|
||||
|
||||
unique_recur_mutex_lock file_lock(file_mtx_);
|
||||
read_state_.set(chunk);
|
||||
file_lock.unlock();
|
||||
|
||||
notify_complete();
|
||||
}).wait();
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::open_file::download_range(
|
||||
std::size_t start_chunk_index, std::size_t end_chunk_index_inclusive,
|
||||
bool should_reset) {
|
||||
for (std::size_t chunk = start_chunk_index;
|
||||
chunk <= end_chunk_index_inclusive; chunk++) {
|
||||
download_chunk(chunk, false, should_reset);
|
||||
if (get_api_error() != api_error::success) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto file_manager::open_file::get_read_state() const
|
||||
-> boost::dynamic_bitset<> {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return read_state_;
|
||||
}
|
||||
|
||||
auto file_manager::open_file::get_read_state(std::size_t chunk) const -> bool {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return read_state_[chunk];
|
||||
}
|
||||
|
||||
auto file_manager::open_file::is_complete() const -> bool {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return read_state_.all();
|
||||
}
|
||||
|
||||
auto file_manager::open_file::native_operation(
|
||||
const i_open_file::native_operation_callback &operation) -> api_error {
|
||||
unique_recur_mutex_lock file_lock(file_mtx_);
|
||||
if (stop_requested_) {
|
||||
return api_error::download_stopped;
|
||||
}
|
||||
file_lock.unlock();
|
||||
|
||||
return do_io([&]() -> api_error { return operation(nf_->get_handle()); });
|
||||
}
|
||||
|
||||
auto file_manager::open_file::native_operation(
|
||||
std::uint64_t new_file_size,
|
||||
const i_open_file::native_operation_callback &operation) -> api_error {
|
||||
if (fsi_.directory) {
|
||||
return api_error::invalid_operation;
|
||||
}
|
||||
|
||||
unique_recur_mutex_lock file_lock(file_mtx_);
|
||||
if (stop_requested_) {
|
||||
return api_error::download_stopped;
|
||||
}
|
||||
file_lock.unlock();
|
||||
|
||||
const auto is_empty_file = new_file_size == 0u;
|
||||
const auto last_chunk =
|
||||
is_empty_file ? std::size_t(0u)
|
||||
: static_cast<std::size_t>(utils::divide_with_ceiling(
|
||||
new_file_size, chunk_size_)) -
|
||||
1u;
|
||||
|
||||
file_lock.lock();
|
||||
if (not is_empty_file && (last_chunk < read_state_.size())) {
|
||||
file_lock.unlock();
|
||||
update_background_reader(0u);
|
||||
|
||||
download_chunk(last_chunk, false, true);
|
||||
if (get_api_error() != api_error::success) {
|
||||
return get_api_error();
|
||||
}
|
||||
file_lock.lock();
|
||||
}
|
||||
|
||||
const auto original_file_size = get_file_size();
|
||||
|
||||
auto res = do_io([&]() -> api_error { return operation(nf_->get_handle()); });
|
||||
if (res != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, get_api_path(),
|
||||
utils::get_last_error_code(),
|
||||
"failed to allocate file");
|
||||
return res;
|
||||
}
|
||||
|
||||
{
|
||||
std::uint64_t file_size{};
|
||||
if (not nf_->get_file_size(file_size)) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, get_api_path(),
|
||||
utils::get_last_error_code(),
|
||||
"failed to get file size");
|
||||
return set_api_error(api_error::os_error);
|
||||
}
|
||||
|
||||
if (file_size != new_file_size) {
|
||||
utils::error::raise_api_path_error(
|
||||
__FUNCTION__, get_api_path(), api_error::file_size_mismatch,
|
||||
"allocated file size mismatch|expected|" +
|
||||
std::to_string(new_file_size) + "|actual|" +
|
||||
std::to_string(file_size));
|
||||
return set_api_error(api_error::error);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_empty_file || (read_state_.size() != (last_chunk + 1u))) {
|
||||
read_state_.resize(is_empty_file ? 0u : last_chunk + 1u);
|
||||
|
||||
if (not is_empty_file) {
|
||||
read_state_[last_chunk] = true;
|
||||
}
|
||||
|
||||
last_chunk_size_ = static_cast<std::size_t>(
|
||||
new_file_size <= chunk_size_ ? new_file_size
|
||||
: new_file_size % chunk_size_ ? new_file_size % chunk_size_
|
||||
: chunk_size_);
|
||||
}
|
||||
|
||||
if (original_file_size != new_file_size) {
|
||||
if (not modified_) {
|
||||
um_.store_resume(*this);
|
||||
}
|
||||
modified_ = true;
|
||||
um_.remove_upload(get_api_path());
|
||||
|
||||
fsi_.size = new_file_size;
|
||||
const auto now = std::to_string(utils::get_file_time_now());
|
||||
res = provider_.set_item_meta(
|
||||
fsi_.api_path, {
|
||||
{META_CHANGED, now},
|
||||
{META_MODIFIED, now},
|
||||
{META_SIZE, std::to_string(new_file_size)},
|
||||
{META_WRITTEN, now},
|
||||
});
|
||||
if (res != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, get_api_path(), res,
|
||||
"failed to set file meta");
|
||||
return set_api_error(res);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
auto file_manager::open_file::read(std::size_t read_size,
|
||||
std::uint64_t read_offset, data_buffer &data)
|
||||
-> api_error {
|
||||
if (fsi_.directory) {
|
||||
return api_error::invalid_operation;
|
||||
}
|
||||
|
||||
read_size =
|
||||
utils::calculate_read_size(get_file_size(), read_size, read_offset);
|
||||
if (read_size == 0u) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
const auto read_from_source = [this, &data, &read_offset,
|
||||
&read_size]() -> api_error {
|
||||
return do_io([this, &data, &read_offset, &read_size]() -> api_error {
|
||||
if (provider_.is_direct_only()) {
|
||||
return provider_.read_file_bytes(fsi_.api_path, read_size, read_offset,
|
||||
data, stop_requested_);
|
||||
}
|
||||
|
||||
data.resize(read_size);
|
||||
std::size_t bytes_read{};
|
||||
return nf_->read_bytes(data.data(), read_size, read_offset, bytes_read)
|
||||
? api_error::success
|
||||
: api_error::os_error;
|
||||
});
|
||||
};
|
||||
|
||||
unique_recur_mutex_lock file_lock(file_mtx_);
|
||||
if (read_state_.all()) {
|
||||
reset_timeout();
|
||||
return read_from_source();
|
||||
}
|
||||
file_lock.unlock();
|
||||
|
||||
const auto start_chunk_index =
|
||||
static_cast<std::size_t>(read_offset / chunk_size_);
|
||||
const auto end_chunk_index =
|
||||
static_cast<std::size_t>((read_size + read_offset) / chunk_size_);
|
||||
|
||||
update_background_reader(start_chunk_index);
|
||||
|
||||
download_range(start_chunk_index, end_chunk_index, true);
|
||||
if (get_api_error() != api_error::success) {
|
||||
return get_api_error();
|
||||
}
|
||||
|
||||
file_lock.lock();
|
||||
return get_api_error() == api_error::success ? read_from_source()
|
||||
: get_api_error();
|
||||
}
|
||||
|
||||
void file_manager::open_file::remove(std::uint64_t handle) {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
open_file_base::remove(handle);
|
||||
if (modified_ && read_state_.all() &&
|
||||
(get_api_error() == api_error::success)) {
|
||||
um_.queue_upload(*this);
|
||||
modified_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto file_manager::open_file::resize(std::uint64_t new_file_size) -> api_error {
|
||||
if (fsi_.directory) {
|
||||
return api_error::invalid_operation;
|
||||
}
|
||||
|
||||
return native_operation(
|
||||
new_file_size, [this, &new_file_size](native_handle) -> api_error {
|
||||
return nf_->truncate(new_file_size) ? api_error::success
|
||||
: api_error::os_error;
|
||||
});
|
||||
}
|
||||
|
||||
auto file_manager::open_file::close() -> bool {
|
||||
if (not fsi_.directory && not stop_requested_) {
|
||||
stop_requested_ = true;
|
||||
|
||||
unique_mutex_lock reader_lock(io_thread_mtx_);
|
||||
io_thread_notify_.notify_all();
|
||||
reader_lock.unlock();
|
||||
|
||||
if (reader_thread_) {
|
||||
reader_thread_->join();
|
||||
reader_thread_.reset();
|
||||
}
|
||||
|
||||
if (open_file_base::close()) {
|
||||
{
|
||||
const auto err = get_api_error();
|
||||
if (err == api_error::success ||
|
||||
err == api_error::download_incomplete ||
|
||||
err == api_error::download_stopped) {
|
||||
if (modified_ && not read_state_.all()) {
|
||||
set_api_error(api_error::download_incomplete);
|
||||
} else if (not modified_ && (fsi_.size > 0u) &&
|
||||
not read_state_.all()) {
|
||||
set_api_error(api_error::download_stopped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nf_->close();
|
||||
nf_.reset();
|
||||
|
||||
if (modified_ && (get_api_error() == api_error::success)) {
|
||||
um_.queue_upload(*this);
|
||||
} else if (modified_ &&
|
||||
(get_api_error() == api_error::download_incomplete)) {
|
||||
um_.store_resume(*this);
|
||||
} else if (get_api_error() != api_error::success) {
|
||||
um_.remove_resume(get_api_path(), get_source_path());
|
||||
if (not utils::file::retry_delete_file(fsi_.source_path)) {
|
||||
utils::error::raise_api_path_error(
|
||||
__FUNCTION__, get_api_path(), fsi_.source_path,
|
||||
utils::get_last_error_code(), "failed to delete file");
|
||||
}
|
||||
|
||||
auto parent = utils::path::remove_file_name(fsi_.source_path);
|
||||
fsi_.source_path =
|
||||
utils::path::combine(parent, {utils::create_uuid_string()});
|
||||
const auto res = provider_.set_item_meta(fsi_.api_path, META_SOURCE,
|
||||
fsi_.source_path);
|
||||
if (res != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, get_api_path(),
|
||||
fsi_.source_path, res,
|
||||
"failed to set file meta");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void file_manager::open_file::update_background_reader(std::size_t read_chunk) {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
read_chunk_index_ = read_chunk;
|
||||
|
||||
if (not reader_thread_ && not stop_requested_) {
|
||||
reader_thread_ = std::make_unique<std::thread>([this]() {
|
||||
auto next_chunk = 0u;
|
||||
while (not stop_requested_) {
|
||||
unique_recur_mutex_lock file_lock(file_mtx_);
|
||||
if ((fsi_.size == 0u) || read_state_.all()) {
|
||||
file_lock.unlock();
|
||||
|
||||
unique_mutex_lock io_lock(io_thread_mtx_);
|
||||
if (not stop_requested_ && io_thread_queue_.empty()) {
|
||||
io_thread_notify_.wait(io_lock);
|
||||
}
|
||||
io_thread_notify_.notify_all();
|
||||
io_lock.unlock();
|
||||
} else {
|
||||
do {
|
||||
next_chunk = read_chunk_index_ =
|
||||
((read_chunk_index_ + 1u) >= read_state_.size())
|
||||
? 0u
|
||||
: read_chunk_index_ + 1u;
|
||||
} while ((next_chunk != 0u) && (active_downloads_.find(next_chunk) !=
|
||||
active_downloads_.end()));
|
||||
|
||||
file_lock.unlock();
|
||||
download_chunk(next_chunk, true, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
auto file_manager::open_file::write(std::uint64_t write_offset,
|
||||
const data_buffer &data,
|
||||
std::size_t &bytes_written) -> api_error {
|
||||
bytes_written = 0u;
|
||||
|
||||
if (fsi_.directory || provider_.is_direct_only()) {
|
||||
return api_error::invalid_operation;
|
||||
}
|
||||
|
||||
if (data.empty()) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
unique_recur_mutex_lock file_lock(file_mtx_);
|
||||
if (stop_requested_) {
|
||||
return api_error::download_stopped;
|
||||
}
|
||||
file_lock.unlock();
|
||||
|
||||
const auto start_chunk_index =
|
||||
static_cast<std::size_t>(write_offset / chunk_size_);
|
||||
const auto end_chunk_index =
|
||||
static_cast<std::size_t>((write_offset + data.size()) / chunk_size_);
|
||||
|
||||
update_background_reader(start_chunk_index);
|
||||
|
||||
download_range(start_chunk_index,
|
||||
std::min(read_state_.size() - 1u, end_chunk_index), true);
|
||||
if (get_api_error() != api_error::success) {
|
||||
return get_api_error();
|
||||
}
|
||||
|
||||
file_lock.lock();
|
||||
if ((write_offset + data.size()) > fsi_.size) {
|
||||
auto res = resize(write_offset + data.size());
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
auto res = do_io([&]() -> api_error {
|
||||
if (not nf_->write_bytes(data.data(), data.size(), write_offset,
|
||||
bytes_written)) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
|
||||
reset_timeout();
|
||||
return api_error::success;
|
||||
});
|
||||
if (res != api_error::success) {
|
||||
return set_api_error(res);
|
||||
}
|
||||
|
||||
const auto now = std::to_string(utils::get_file_time_now());
|
||||
res = provider_.set_item_meta(fsi_.api_path, {
|
||||
{META_CHANGED, now},
|
||||
{META_MODIFIED, now},
|
||||
{META_WRITTEN, now},
|
||||
});
|
||||
if (res != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, get_api_path(), res,
|
||||
"failed to set file meta");
|
||||
return set_api_error(res);
|
||||
}
|
||||
|
||||
if (not modified_) {
|
||||
um_.store_resume(*this);
|
||||
}
|
||||
modified_ = true;
|
||||
um_.remove_upload(get_api_path());
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
} // namespace repertory
|
||||
242
src/file_manager/file_manager_open_file_base.cpp
Normal file
242
src/file_manager/file_manager_open_file_base.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "file_manager/file_manager.hpp"
|
||||
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
file_manager::open_file_base::open_file_base(std::uint64_t chunk_size,
|
||||
std::uint8_t chunk_timeout,
|
||||
filesystem_item fsi,
|
||||
i_provider &provider)
|
||||
: open_file_base(chunk_size, chunk_timeout, fsi, {}, provider) {}
|
||||
|
||||
file_manager::open_file_base::open_file_base(
|
||||
std::uint64_t chunk_size, std::uint8_t chunk_timeout, filesystem_item fsi,
|
||||
std::map<std::uint64_t, open_file_data> open_data, i_provider &provider)
|
||||
: chunk_size_(chunk_size),
|
||||
chunk_timeout_(chunk_timeout),
|
||||
fsi_(std::move(fsi)),
|
||||
last_chunk_size_(static_cast<std::size_t>(
|
||||
fsi.size <= chunk_size ? fsi.size
|
||||
: fsi.size % chunk_size ? fsi.size % chunk_size
|
||||
: chunk_size)),
|
||||
open_data_(std::move(open_data)),
|
||||
provider_(provider) {
|
||||
if (not fsi.directory) {
|
||||
io_thread_ = std::make_unique<std::thread>([this] { file_io_thread(); });
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::open_file_base::add(std::uint64_t handle,
|
||||
open_file_data ofd) {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
open_data_[handle] = ofd;
|
||||
if (open_data_.size() == 1u) {
|
||||
event_system::instance().raise<filesystem_item_opened>(
|
||||
fsi_.api_path, fsi_.source_path, fsi_.directory);
|
||||
}
|
||||
|
||||
event_system::instance().raise<filesystem_item_handle_opened>(
|
||||
fsi_.api_path, handle, fsi_.source_path, fsi_.directory);
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::can_close() const -> bool {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
if (fsi_.directory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (not open_data_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (modified_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_api_error() != api_error::success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_download_complete()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::chrono::system_clock::time_point last_access = last_access_;
|
||||
const auto duration = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now() - last_access);
|
||||
return (duration.count() >= chunk_timeout_);
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::do_io(std::function<api_error()> action)
|
||||
-> api_error {
|
||||
unique_mutex_lock io_lock(io_thread_mtx_);
|
||||
auto item = std::make_shared<io_item>(action);
|
||||
io_thread_queue_.emplace_back(item);
|
||||
io_thread_notify_.notify_all();
|
||||
io_lock.unlock();
|
||||
|
||||
return item->get_result();
|
||||
}
|
||||
|
||||
void file_manager::open_file_base::file_io_thread() {
|
||||
unique_mutex_lock io_lock(io_thread_mtx_);
|
||||
io_thread_notify_.notify_all();
|
||||
io_lock.unlock();
|
||||
|
||||
const auto process_queue = [&]() {
|
||||
io_lock.lock();
|
||||
if (not io_stop_requested_ && io_thread_queue_.empty()) {
|
||||
io_thread_notify_.wait(io_lock);
|
||||
}
|
||||
|
||||
while (not io_thread_queue_.empty()) {
|
||||
auto *item = io_thread_queue_.front().get();
|
||||
io_thread_notify_.notify_all();
|
||||
io_lock.unlock();
|
||||
|
||||
item->action();
|
||||
|
||||
io_lock.lock();
|
||||
io_thread_queue_.pop_front();
|
||||
}
|
||||
|
||||
io_thread_notify_.notify_all();
|
||||
io_lock.unlock();
|
||||
};
|
||||
|
||||
while (not io_stop_requested_) {
|
||||
process_queue();
|
||||
}
|
||||
|
||||
process_queue();
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::get_api_error() const -> api_error {
|
||||
mutex_lock error_lock(error_mtx_);
|
||||
return error_;
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::get_api_path() const -> std::string {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return fsi_.api_path;
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::get_file_size() const -> std::uint64_t {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return fsi_.size;
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::get_filesystem_item() const
|
||||
-> filesystem_item {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return fsi_;
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::get_handles() const
|
||||
-> std::vector<std::uint64_t> {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
std::vector<std::uint64_t> ret;
|
||||
for (const auto &kv : open_data_) {
|
||||
ret.emplace_back(kv.first);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::get_open_data() const
|
||||
-> std::map<std::uint64_t, open_file_data> {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return open_data_;
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::get_open_data(std::uint64_t handle) const
|
||||
-> open_file_data {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return open_data_.at(handle);
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::get_open_file_count() const -> std::size_t {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return open_data_.size();
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::is_modified() const -> bool {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return modified_;
|
||||
}
|
||||
|
||||
void file_manager::open_file_base::remove(std::uint64_t handle) {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
open_data_.erase(handle);
|
||||
event_system::instance().raise<filesystem_item_handle_closed>(
|
||||
fsi_.api_path, handle, fsi_.source_path, fsi_.directory, modified_);
|
||||
if (open_data_.empty()) {
|
||||
event_system::instance().raise<filesystem_item_closed>(
|
||||
fsi_.api_path, fsi_.source_path, fsi_.directory, modified_);
|
||||
}
|
||||
}
|
||||
|
||||
void file_manager::open_file_base::reset_timeout() {
|
||||
last_access_ = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::set_api_error(const api_error &e)
|
||||
-> api_error {
|
||||
mutex_lock error_lock(error_mtx_);
|
||||
if (error_ != e) {
|
||||
return ((error_ = (error_ == api_error::success ||
|
||||
error_ == api_error::download_incomplete ||
|
||||
error_ == api_error::download_stopped
|
||||
? e
|
||||
: error_)));
|
||||
}
|
||||
|
||||
return error_;
|
||||
}
|
||||
|
||||
void file_manager::open_file_base::set_api_path(const std::string &api_path) {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
fsi_.api_path = api_path;
|
||||
fsi_.api_parent = utils::path::get_parent_api_path(api_path);
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::close() -> bool {
|
||||
unique_mutex_lock io_lock(io_thread_mtx_);
|
||||
if (not fsi_.directory && not io_stop_requested_) {
|
||||
io_stop_requested_ = true;
|
||||
io_thread_notify_.notify_all();
|
||||
io_lock.unlock();
|
||||
|
||||
if (io_thread_) {
|
||||
io_thread_->join();
|
||||
io_thread_.reset();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
io_thread_notify_.notify_all();
|
||||
io_lock.unlock();
|
||||
return false;
|
||||
}
|
||||
} // namespace repertory
|
||||
43
src/file_manager/file_manager_open_file_base_download.cpp
Normal file
43
src/file_manager/file_manager_open_file_base_download.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "file_manager/file_manager.hpp"
|
||||
|
||||
namespace repertory {
|
||||
void file_manager::open_file_base::download::notify(const api_error &e) {
|
||||
complete_ = true;
|
||||
error_ = e;
|
||||
unique_mutex_lock lock(mtx_);
|
||||
notify_.notify_all();
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::download::wait() -> api_error {
|
||||
if (not complete_) {
|
||||
unique_mutex_lock lock(mtx_);
|
||||
if (not complete_) {
|
||||
notify_.wait(lock);
|
||||
}
|
||||
notify_.notify_all();
|
||||
}
|
||||
|
||||
return error_;
|
||||
}
|
||||
} // namespace repertory
|
||||
41
src/file_manager/file_manager_open_file_base_io_item.cpp
Normal file
41
src/file_manager/file_manager_open_file_base_io_item.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "file_manager/file_manager.hpp"
|
||||
|
||||
namespace repertory {
|
||||
void file_manager::open_file_base::io_item::action() {
|
||||
result_ = action_();
|
||||
|
||||
mutex_lock lock(mtx_);
|
||||
notify_.notify_all();
|
||||
}
|
||||
|
||||
auto file_manager::open_file_base::io_item::get_result() -> api_error {
|
||||
unique_mutex_lock lock(mtx_);
|
||||
if (result_.has_value()) {
|
||||
return result_.value();
|
||||
}
|
||||
|
||||
notify_.wait(lock);
|
||||
return result_.value();
|
||||
}
|
||||
} // namespace repertory
|
||||
317
src/file_manager/file_manager_ring_buffer_open_file.cpp
Normal file
317
src/file_manager/file_manager_ring_buffer_open_file.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "file_manager/file_manager.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "file_manager/events.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/unix/unix_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
file_manager::ring_buffer_open_file::ring_buffer_open_file(
|
||||
std::string buffer_directory, std::uint64_t chunk_size,
|
||||
std::uint8_t chunk_timeout, filesystem_item fsi, i_provider &provider)
|
||||
: ring_buffer_open_file(std::move(buffer_directory), chunk_size,
|
||||
chunk_timeout, std::move(fsi), provider,
|
||||
(1024ull * 1024ull * 1024ull) / chunk_size) {}
|
||||
|
||||
file_manager::ring_buffer_open_file::ring_buffer_open_file(
|
||||
std::string buffer_directory, std::uint64_t chunk_size,
|
||||
std::uint8_t chunk_timeout, filesystem_item fsi, i_provider &provider,
|
||||
std::size_t ring_size)
|
||||
: open_file_base(chunk_size, chunk_timeout, fsi, provider),
|
||||
ring_state_(ring_size),
|
||||
total_chunks_(static_cast<std::size_t>(
|
||||
utils::divide_with_ceiling(fsi.size, chunk_size_))) {
|
||||
if (ring_size % 2u) {
|
||||
throw std::runtime_error("ring size must be a multiple of 2");
|
||||
}
|
||||
|
||||
if (ring_size < 4u) {
|
||||
throw std::runtime_error("ring size must be greater than or equal to 4");
|
||||
}
|
||||
|
||||
if (fsi.size < (ring_state_.size() * chunk_size)) {
|
||||
throw std::runtime_error("file size is less than ring buffer size");
|
||||
}
|
||||
|
||||
last_chunk_ = ring_state_.size() - 1u;
|
||||
ring_state_.set(0u, ring_state_.size(), true);
|
||||
|
||||
buffer_directory = utils::path::absolute(buffer_directory);
|
||||
if (not utils::file::create_full_directory_path(buffer_directory)) {
|
||||
throw std::runtime_error("failed to create buffer directory|path|" +
|
||||
buffer_directory + "|err|" +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
|
||||
fsi_.source_path =
|
||||
utils::path::combine(buffer_directory, {utils::create_uuid_string()});
|
||||
auto res = native_file::create_or_open(fsi_.source_path, nf_);
|
||||
if (res != api_error::success) {
|
||||
throw std::runtime_error("failed to create buffer file|err|" +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
|
||||
if (not nf_->truncate(ring_state_.size() * chunk_size)) {
|
||||
nf_->close();
|
||||
throw std::runtime_error("failed to resize buffer file|err|" +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
}
|
||||
|
||||
file_manager::ring_buffer_open_file::~ring_buffer_open_file() {
|
||||
close();
|
||||
|
||||
nf_->close();
|
||||
if (not utils::file::retry_delete_file(fsi_.source_path)) {
|
||||
utils::error::raise_api_path_error(
|
||||
__FUNCTION__, fsi_.api_path, fsi_.source_path,
|
||||
utils::get_last_error_code(), "failed to delete file");
|
||||
}
|
||||
}
|
||||
|
||||
auto file_manager::file_manager::ring_buffer_open_file::download_chunk(
|
||||
std::size_t chunk) -> api_error {
|
||||
unique_mutex_lock chunk_lock(chunk_mtx_);
|
||||
if (active_downloads_.find(chunk) != active_downloads_.end()) {
|
||||
auto active_download = active_downloads_.at(chunk);
|
||||
chunk_notify_.notify_all();
|
||||
chunk_lock.unlock();
|
||||
|
||||
return active_download->wait();
|
||||
}
|
||||
|
||||
if (ring_state_[chunk % ring_state_.size()]) {
|
||||
auto active_download = std::make_shared<download>();
|
||||
active_downloads_[chunk] = active_download;
|
||||
ring_state_[chunk % ring_state_.size()] = false;
|
||||
chunk_notify_.notify_all();
|
||||
chunk_lock.unlock();
|
||||
|
||||
data_buffer buffer((chunk == (total_chunks_ - 1u)) ? last_chunk_size_
|
||||
: chunk_size_);
|
||||
|
||||
stop_type stop_requested = !!ring_state_[chunk % ring_state_.size()];
|
||||
auto res =
|
||||
provider_.read_file_bytes(fsi_.api_path, buffer.size(),
|
||||
chunk * chunk_size_, buffer, stop_requested);
|
||||
if (res == api_error::success) {
|
||||
res = do_io([&]() -> api_error {
|
||||
std::size_t bytes_written{};
|
||||
if (not nf_->write_bytes(buffer.data(), buffer.size(),
|
||||
(chunk % ring_state_.size()) * chunk_size_,
|
||||
bytes_written)) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
});
|
||||
}
|
||||
|
||||
active_download->notify(res);
|
||||
|
||||
chunk_lock.lock();
|
||||
active_downloads_.erase(chunk);
|
||||
chunk_notify_.notify_all();
|
||||
return res;
|
||||
}
|
||||
|
||||
chunk_notify_.notify_all();
|
||||
chunk_lock.unlock();
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
void file_manager::ring_buffer_open_file::forward(std::size_t count) {
|
||||
mutex_lock chunk_lock(chunk_mtx_);
|
||||
if ((current_chunk_ + count) > (total_chunks_ - 1u)) {
|
||||
count = (total_chunks_ - 1u) - current_chunk_;
|
||||
}
|
||||
|
||||
if ((current_chunk_ + count) <= last_chunk_) {
|
||||
current_chunk_ += count;
|
||||
} else {
|
||||
const auto added = count - (last_chunk_ - current_chunk_);
|
||||
if (added >= ring_state_.size()) {
|
||||
ring_state_.set(0u, ring_state_.size(), true);
|
||||
current_chunk_ += count;
|
||||
first_chunk_ += added;
|
||||
last_chunk_ =
|
||||
std::min(total_chunks_ - 1u, first_chunk_ + ring_state_.size() - 1u);
|
||||
} else {
|
||||
for (std::size_t i = 0u; i < added; i++) {
|
||||
ring_state_[(first_chunk_ + i) % ring_state_.size()] = true;
|
||||
}
|
||||
first_chunk_ += added;
|
||||
current_chunk_ += count;
|
||||
last_chunk_ =
|
||||
std::min(total_chunks_ - 1u, first_chunk_ + ring_state_.size() - 1u);
|
||||
}
|
||||
}
|
||||
|
||||
chunk_notify_.notify_all();
|
||||
}
|
||||
|
||||
auto file_manager::ring_buffer_open_file::get_read_state() const
|
||||
-> boost::dynamic_bitset<> {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
auto read_state = ring_state_;
|
||||
return read_state.flip();
|
||||
}
|
||||
|
||||
auto file_manager::ring_buffer_open_file::get_read_state(
|
||||
std::size_t chunk) const -> bool {
|
||||
recur_mutex_lock file_lock(file_mtx_);
|
||||
return not ring_state_[chunk % ring_state_.size()];
|
||||
}
|
||||
|
||||
auto file_manager::ring_buffer_open_file::is_download_complete() const -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto file_manager::ring_buffer_open_file::native_operation(
|
||||
const i_open_file::native_operation_callback &operation) -> api_error {
|
||||
return do_io([&]() -> api_error { return operation(nf_->get_handle()); });
|
||||
}
|
||||
|
||||
void file_manager::ring_buffer_open_file::reverse(std::size_t count) {
|
||||
mutex_lock chunk_lock(chunk_mtx_);
|
||||
if (current_chunk_ < count) {
|
||||
count = current_chunk_;
|
||||
}
|
||||
|
||||
if ((current_chunk_ - count) >= first_chunk_) {
|
||||
current_chunk_ -= count;
|
||||
} else {
|
||||
const auto removed = count - (current_chunk_ - first_chunk_);
|
||||
if (removed >= ring_state_.size()) {
|
||||
ring_state_.set(0u, ring_state_.size(), true);
|
||||
current_chunk_ -= count;
|
||||
first_chunk_ = current_chunk_;
|
||||
last_chunk_ =
|
||||
std::min(total_chunks_ - 1u, first_chunk_ + ring_state_.size() - 1u);
|
||||
} else {
|
||||
for (std::size_t i = 0u; i < removed; i++) {
|
||||
ring_state_[(last_chunk_ - i) % ring_state_.size()] = true;
|
||||
}
|
||||
first_chunk_ -= removed;
|
||||
current_chunk_ -= count;
|
||||
last_chunk_ =
|
||||
std::min(total_chunks_ - 1u, first_chunk_ + ring_state_.size() - 1u);
|
||||
}
|
||||
}
|
||||
|
||||
chunk_notify_.notify_all();
|
||||
}
|
||||
|
||||
auto file_manager::ring_buffer_open_file::read(std::size_t read_size,
|
||||
std::uint64_t read_offset,
|
||||
data_buffer &data) -> api_error {
|
||||
if (fsi_.directory) {
|
||||
return api_error::invalid_operation;
|
||||
}
|
||||
|
||||
reset_timeout();
|
||||
|
||||
read_size = utils::calculate_read_size(fsi_.size, read_size, read_offset);
|
||||
if (read_size == 0u) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
const auto start_chunk_index =
|
||||
static_cast<std::size_t>(read_offset / chunk_size_);
|
||||
read_offset = read_offset - (start_chunk_index * chunk_size_);
|
||||
data_buffer buffer(chunk_size_);
|
||||
|
||||
auto res = api_error::success;
|
||||
for (std::size_t chunk = start_chunk_index;
|
||||
(res == api_error::success) && (read_size > 0u); chunk++) {
|
||||
if (chunk > current_chunk_) {
|
||||
forward(chunk - current_chunk_);
|
||||
} else if (chunk < current_chunk_) {
|
||||
reverse(current_chunk_ - chunk);
|
||||
}
|
||||
|
||||
reset_timeout();
|
||||
if ((res = download_chunk(chunk)) == api_error::success) {
|
||||
const auto to_read = std::min(
|
||||
static_cast<std::size_t>(chunk_size_ - read_offset), read_size);
|
||||
res = do_io([this, &buffer, &chunk, &data, read_offset,
|
||||
&to_read]() -> api_error {
|
||||
std::size_t bytes_read{};
|
||||
auto res = nf_->read_bytes(buffer.data(), buffer.size(),
|
||||
((chunk % ring_state_.size()) * chunk_size_),
|
||||
bytes_read)
|
||||
? api_error::success
|
||||
: api_error::os_error;
|
||||
if (res == api_error::success) {
|
||||
data.insert(data.end(), buffer.begin() + read_offset,
|
||||
buffer.begin() + read_offset + to_read);
|
||||
reset_timeout();
|
||||
}
|
||||
|
||||
return res;
|
||||
});
|
||||
read_offset = 0u;
|
||||
read_size -= to_read;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void file_manager::ring_buffer_open_file::set(std::size_t first_chunk,
|
||||
std::size_t current_chunk) {
|
||||
mutex_lock chunk_lock(chunk_mtx_);
|
||||
if (first_chunk >= total_chunks_) {
|
||||
chunk_notify_.notify_all();
|
||||
throw std::runtime_error("first chunk must be less than total chunks");
|
||||
}
|
||||
|
||||
first_chunk_ = first_chunk;
|
||||
last_chunk_ = first_chunk_ + ring_state_.size() - 1u;
|
||||
|
||||
if (current_chunk > last_chunk_) {
|
||||
chunk_notify_.notify_all();
|
||||
throw std::runtime_error(
|
||||
"current chunk must be less than or equal to last chunk");
|
||||
}
|
||||
|
||||
current_chunk_ = current_chunk;
|
||||
ring_state_.set(0u, ring_state_.size(), false);
|
||||
|
||||
chunk_notify_.notify_all();
|
||||
}
|
||||
|
||||
void file_manager::ring_buffer_open_file::set_api_path(
|
||||
const std::string &api_path) {
|
||||
mutex_lock chunk_lock(chunk_mtx_);
|
||||
open_file_base::set_api_path(api_path);
|
||||
chunk_notify_.notify_all();
|
||||
}
|
||||
} // namespace repertory
|
||||
62
src/file_manager/file_manager_upload.cpp
Normal file
62
src/file_manager/file_manager_upload.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "file_manager/file_manager.hpp"
|
||||
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/unix/unix_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
file_manager::upload::upload(filesystem_item fsi, i_provider &provider)
|
||||
: fsi_(fsi), provider_(provider) {
|
||||
thread_ =
|
||||
std::make_unique<std::thread>(std::bind(&upload::upload_thread, this));
|
||||
}
|
||||
|
||||
file_manager::upload::~upload() {
|
||||
stop();
|
||||
|
||||
thread_->join();
|
||||
thread_.reset();
|
||||
}
|
||||
|
||||
void file_manager::upload::cancel() {
|
||||
cancelled_ = true;
|
||||
stop();
|
||||
}
|
||||
|
||||
void file_manager::upload::stop() { stop_requested_ = true; }
|
||||
|
||||
void file_manager::upload::upload_thread() {
|
||||
error_ = provider_.upload_file(fsi_.api_path, fsi_.source_path,
|
||||
fsi_.encryption_token, stop_requested_);
|
||||
if (not utils::file::reset_modified_time(fsi_.source_path)) {
|
||||
utils::error::raise_api_path_error(
|
||||
__FUNCTION__, fsi_.api_path, fsi_.source_path,
|
||||
utils::get_last_error_code(), "failed to reset modified time");
|
||||
}
|
||||
|
||||
event_system::instance().raise<file_upload_completed>(
|
||||
get_api_path(), get_source_path(), get_api_error(), cancelled_);
|
||||
}
|
||||
} // namespace repertory
|
||||
166
src/main.cpp
166
src/main.cpp
@@ -1,135 +1,165 @@
|
||||
#include "common.hpp"
|
||||
#include "utils/cli_utils.hpp"
|
||||
#include "utils/global_data.hpp"
|
||||
#include "utils/polling.hpp"
|
||||
/*
|
||||
Copyright <2018-2023> <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.
|
||||
*/
|
||||
#ifdef REPERTORY_TESTING
|
||||
#include "test_common.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "test_common.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
#else
|
||||
#include "cli/actions.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#endif
|
||||
#include "utils/cli_utils.hpp"
|
||||
#include "utils/polling.hpp"
|
||||
|
||||
using namespace repertory;
|
||||
|
||||
#ifdef _WIN32
|
||||
std::size_t PROVIDER_INDEX = 0u;
|
||||
#endif // _WIN32
|
||||
#if defined(REPERTORY_TESTING) && defined(_WIN32)
|
||||
std::size_t PROVIDER_INDEX{0U};
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
auto main(int argc, char **argv) -> int {
|
||||
repertory_init();
|
||||
|
||||
#ifdef REPERTORY_TESTING
|
||||
#ifdef _WIN32
|
||||
if (utils::cli::has_option(argc, argv, "--provider_index")) {
|
||||
PROVIDER_INDEX = static_cast<std::size_t>(
|
||||
utils::string::to_uint64(utils::cli::parse_option(argc, argv, "--provider_index", 1u)[0u]) +
|
||||
1u);
|
||||
utils::string::to_uint64(
|
||||
utils::cli::parse_option(argc, argv, "--provider_index", 1U)[0U]) +
|
||||
1U);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
InitGoogleTest(&argc, argv);
|
||||
const auto ret = RUN_ALL_TESTS();
|
||||
|
||||
curl_global_cleanup();
|
||||
return ret;
|
||||
}
|
||||
#else // REPERTORY_TESTING
|
||||
#include "cli/actions.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
|
||||
using namespace repertory;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
auto ret = RUN_ALL_TESTS();
|
||||
delete_generated_files();
|
||||
#else
|
||||
if (argc == 1) {
|
||||
argc++;
|
||||
static std::string cmd(argv[0u]);
|
||||
static std::vector<const char *> v({&cmd[0u], "-h"});
|
||||
argv = (char **)&v[0u];
|
||||
static std::string cmd(argv[0U]);
|
||||
static std::vector<const char *> v({&cmd[0U], "-h"});
|
||||
argv = (char **)&v[0U];
|
||||
}
|
||||
|
||||
auto pt = utils::cli::get_provider_type_from_args(argc, argv);
|
||||
|
||||
std::string data_directory;
|
||||
auto ret = utils::cli::parse_string_option(argc, argv, utils::cli::options::data_directory_option,
|
||||
data_directory);
|
||||
auto res = utils::cli::parse_string_option(
|
||||
argc, argv, utils::cli::options::data_directory_option, data_directory);
|
||||
|
||||
std::string password;
|
||||
ret = (ret == exit_code::success)
|
||||
? utils::cli::parse_string_option(argc, argv, utils::cli::options::password_option,
|
||||
password)
|
||||
: ret;
|
||||
res = (res == exit_code::success)
|
||||
? utils::cli::parse_string_option(
|
||||
argc, argv, utils::cli::options::password_option, password)
|
||||
: res;
|
||||
|
||||
std::string user;
|
||||
ret = (ret == exit_code::success)
|
||||
? utils::cli::parse_string_option(argc, argv, utils::cli::options::user_option, user)
|
||||
: ret;
|
||||
res = (res == exit_code::success)
|
||||
? utils::cli::parse_string_option(
|
||||
argc, argv, utils::cli::options::user_option, user)
|
||||
: res;
|
||||
|
||||
std::string remote_host;
|
||||
std::uint16_t remote_port = 0u;
|
||||
std::uint16_t remote_port{};
|
||||
std::string unique_id;
|
||||
if ((ret == exit_code::success) && (pt == provider_type::remote)) {
|
||||
if ((res == exit_code::success) && (pt == provider_type::remote)) {
|
||||
std::string data;
|
||||
if ((ret = utils::cli::parse_string_option(argc, argv, utils::cli::options::remote_mount_option,
|
||||
data)) == exit_code::success) {
|
||||
if ((res = utils::cli::parse_string_option(
|
||||
argc, argv, utils::cli::options::remote_mount_option, data)) ==
|
||||
exit_code::success) {
|
||||
const auto parts = utils::string::split(data, ':');
|
||||
if (parts.size() != 2) {
|
||||
std::cerr << "Invalid syntax for host/port '-rm host:port,--remote_mount host:port'"
|
||||
std::cerr << "Invalid syntax for host/port '-rm "
|
||||
"host:port,--remote_mount host:port'"
|
||||
<< std::endl;
|
||||
ret = exit_code::invalid_syntax;
|
||||
res = exit_code::invalid_syntax;
|
||||
} else {
|
||||
unique_id = parts[0u] + ':' + parts[1u];
|
||||
remote_host = parts[0u];
|
||||
unique_id = parts[0U] + ':' + parts[1U];
|
||||
remote_host = parts[0U];
|
||||
try {
|
||||
remote_port = utils::string::to_uint16(parts[1u]);
|
||||
remote_port = utils::string::to_uint16(parts[1U]);
|
||||
data_directory = utils::path::combine(
|
||||
data_directory.empty() ? app_config::default_data_directory(pt) : data_directory,
|
||||
data_directory.empty() ? app_config::default_data_directory(pt)
|
||||
: data_directory,
|
||||
{utils::string::replace_copy(unique_id, ':', '_')});
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << (e.what() ? e.what() : "Unable to parse port") << std::endl;
|
||||
ret = exit_code::invalid_syntax;
|
||||
std::cerr << (e.what() ? e.what() : "Unable to parse port")
|
||||
<< std::endl;
|
||||
res = exit_code::invalid_syntax;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef REPERTORY_ENABLE_S3
|
||||
if ((ret == exit_code::success) && (pt == provider_type::s3)) {
|
||||
if ((res == exit_code::success) && (pt == provider_type::s3)) {
|
||||
std::string data;
|
||||
if ((ret = utils::cli::parse_string_option(argc, argv, utils::cli::options::name_option,
|
||||
if ((res = utils::cli::parse_string_option(argc, argv,
|
||||
utils::cli::options::name_option,
|
||||
data)) == exit_code::success) {
|
||||
unique_id = utils::string::trim(data);
|
||||
if (unique_id.empty()) {
|
||||
std::cerr << "Name of S3 instance not provided" << std::endl;
|
||||
ret = exit_code::invalid_syntax;
|
||||
res = exit_code::invalid_syntax;
|
||||
} else {
|
||||
data_directory = utils::path::combine(
|
||||
data_directory.empty() ? app_config::default_data_directory(pt) : data_directory,
|
||||
data_directory.empty() ? app_config::default_data_directory(pt)
|
||||
: data_directory,
|
||||
{unique_id});
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // REPERTORY_ENABLE_S3
|
||||
|
||||
int mount_result = 0;
|
||||
if (ret == exit_code::success) {
|
||||
int mount_result{};
|
||||
if (res == exit_code::success) {
|
||||
if (utils::cli::has_option(argc, argv, utils::cli::options::help_option)) {
|
||||
cli::actions::help<repertory_drive>(argc, argv);
|
||||
} else if (utils::cli::has_option(argc, argv, utils::cli::options::version_option)) {
|
||||
} else if (utils::cli::has_option(argc, argv,
|
||||
utils::cli::options::version_option)) {
|
||||
cli::actions::version<repertory_drive>(argc, argv);
|
||||
} else {
|
||||
ret = exit_code::option_not_found;
|
||||
for (std::size_t i = 0;
|
||||
(ret == exit_code::option_not_found) && (i < utils::cli::options::option_list.size());
|
||||
i++) {
|
||||
ret = cli::actions::perform_action(utils::cli::options::option_list[i], argc, argv,
|
||||
data_directory, pt, unique_id, user, password);
|
||||
res = exit_code::option_not_found;
|
||||
for (std::size_t idx = 0U;
|
||||
(res == exit_code::option_not_found) &&
|
||||
(idx < utils::cli::options::option_list.size());
|
||||
idx++) {
|
||||
res = cli::actions::perform_action(
|
||||
utils::cli::options::option_list[idx], argc, argv, data_directory,
|
||||
pt, unique_id, user, password);
|
||||
}
|
||||
if (ret == exit_code::option_not_found) {
|
||||
ret = cli::actions::mount(argc, argv, data_directory, mount_result, pt, remote_host,
|
||||
remote_port, unique_id);
|
||||
if (res == exit_code::option_not_found) {
|
||||
res = cli::actions::mount(argc, argv, data_directory, mount_result, pt,
|
||||
remote_host, remote_port, unique_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ((ret == exit_code::mount_result) ? mount_result : static_cast<int>(ret));
|
||||
}
|
||||
auto ret =
|
||||
((res == exit_code::mount_result) ? mount_result
|
||||
: static_cast<std::int32_t>(res));
|
||||
|
||||
#endif // REPERTORY_TESTING
|
||||
#endif
|
||||
|
||||
repertory_shutdown();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,36 +1,47 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 _WIN32
|
||||
|
||||
#include "platform/unix_platform.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "types/startup_exception.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/unix/unix_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
lock_data::lock_data(const provider_type &pt, std::string unique_id /*= ""*/)
|
||||
: pt_(pt),
|
||||
unique_id_(std::move(unique_id)),
|
||||
mutex_id_("repertory2_" + app_config::get_provider_name(pt) + "_" + unique_id_) {
|
||||
mutex_id_("repertory2_" + app_config::get_provider_name(pt) + "_" +
|
||||
unique_id_) {
|
||||
lock_fd_ = open(get_lock_file().c_str(), O_CREAT | O_RDWR, S_IWUSR | S_IRUSR);
|
||||
}
|
||||
|
||||
lock_data::lock_data() : pt_(provider_type::sia), unique_id_(""), mutex_id_(""), lock_fd_(-1) {}
|
||||
lock_data::lock_data()
|
||||
: pt_(provider_type::sia), unique_id_(""), mutex_id_(""), lock_fd_(-1) {}
|
||||
|
||||
lock_data::~lock_data() {
|
||||
if (lock_fd_ != -1) {
|
||||
@@ -43,21 +54,31 @@ lock_data::~lock_data() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string lock_data::get_lock_data_file() {
|
||||
auto lock_data::get_lock_data_file() -> std::string {
|
||||
const auto dir = get_state_directory();
|
||||
utils::file::create_full_directory_path(dir);
|
||||
return utils::path::combine(dir, {"mountstate_" + std::to_string(getuid()) + ".json"});
|
||||
if (not utils::file::create_full_directory_path(dir)) {
|
||||
throw startup_exception("failed to create directory|sp|" + dir + "|err|" +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
return utils::path::combine(
|
||||
dir, {"mountstate_" + std::to_string(getuid()) + ".json"});
|
||||
}
|
||||
|
||||
std::string lock_data::get_lock_file() {
|
||||
auto lock_data::get_lock_file() -> std::string {
|
||||
const auto dir = get_state_directory();
|
||||
utils::file::create_full_directory_path(dir);
|
||||
return utils::path::combine(dir, {mutex_id_ + "_" + std::to_string(getuid())});
|
||||
if (not utils::file::create_full_directory_path(dir)) {
|
||||
throw startup_exception("failed to create directory|sp|" + dir + "|err|" +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
|
||||
return utils::path::combine(dir,
|
||||
{mutex_id_ + "_" + std::to_string(getuid())});
|
||||
}
|
||||
|
||||
bool lock_data::get_mount_state(json &mount_state) {
|
||||
auto lock_data::get_mount_state(json &mount_state) -> bool {
|
||||
auto ret = false;
|
||||
auto fd = open(get_lock_data_file().c_str(), O_CREAT | O_RDWR, S_IWUSR | S_IRUSR);
|
||||
auto fd =
|
||||
open(get_lock_data_file().c_str(), O_CREAT | O_RDWR, S_IWUSR | S_IRUSR);
|
||||
if (fd != -1) {
|
||||
if (wait_for_lock(fd) == 0) {
|
||||
ret = utils::file::read_json_file(get_lock_data_file(), mount_state);
|
||||
@@ -69,16 +90,17 @@ bool lock_data::get_mount_state(json &mount_state) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string lock_data::get_state_directory() {
|
||||
auto lock_data::get_state_directory() -> std::string {
|
||||
#ifdef __APPLE__
|
||||
return utils::path::resolve("~/Library/Application Support/" + std::string(REPERTORY_DATA_NAME) +
|
||||
"/state");
|
||||
return utils::path::resolve("~/Library/Application Support/" +
|
||||
std::string(REPERTORY_DATA_NAME) + "/state");
|
||||
#else
|
||||
return utils::path::resolve("~/.local/" + std::string(REPERTORY_DATA_NAME) + "/state");
|
||||
return utils::path::resolve("~/.local/" + std::string(REPERTORY_DATA_NAME) +
|
||||
"/state");
|
||||
#endif
|
||||
}
|
||||
|
||||
lock_result lock_data::grab_lock(const std::uint8_t &retry_count) {
|
||||
auto lock_data::grab_lock(std::uint8_t retry_count) -> lock_result {
|
||||
if (lock_fd_ == -1) {
|
||||
return lock_result::failure;
|
||||
}
|
||||
@@ -86,7 +108,9 @@ lock_result lock_data::grab_lock(const std::uint8_t &retry_count) {
|
||||
lock_status_ = wait_for_lock(lock_fd_, retry_count);
|
||||
switch (lock_status_) {
|
||||
case 0:
|
||||
set_mount_state(false, "", -1);
|
||||
if (not set_mount_state(false, "", -1)) {
|
||||
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
|
||||
}
|
||||
return lock_result::success;
|
||||
case EWOULDBLOCK:
|
||||
return lock_result::locked;
|
||||
@@ -95,30 +119,44 @@ lock_result lock_data::grab_lock(const std::uint8_t &retry_count) {
|
||||
}
|
||||
}
|
||||
|
||||
bool lock_data::set_mount_state(const bool &active, const std::string &mount_location,
|
||||
const int &pid) {
|
||||
auto lock_data::set_mount_state(bool active, const std::string &mount_location,
|
||||
int pid) -> bool {
|
||||
auto ret = false;
|
||||
auto fd = open(get_lock_data_file().c_str(), O_CREAT | O_RDWR, S_IWUSR | S_IRUSR);
|
||||
auto fd =
|
||||
open(get_lock_data_file().c_str(), O_CREAT | O_RDWR, S_IWUSR | S_IRUSR);
|
||||
if (fd != -1) {
|
||||
if (wait_for_lock(fd) == 0) {
|
||||
const auto mount_id = app_config::get_provider_display_name(pt_) + unique_id_;
|
||||
const auto mount_id =
|
||||
app_config::get_provider_display_name(pt_) + unique_id_;
|
||||
json mount_state;
|
||||
utils::file::read_json_file(get_lock_data_file(), mount_state);
|
||||
if (not utils::file::read_json_file(get_lock_data_file(), mount_state)) {
|
||||
utils::error::raise_error(__FUNCTION__,
|
||||
"failed to read mount state file|sp|" +
|
||||
get_lock_file());
|
||||
}
|
||||
if ((mount_state.find(mount_id) == mount_state.end()) ||
|
||||
(mount_state[mount_id].find("Active") == mount_state[mount_id].end()) ||
|
||||
(mount_state[mount_id].find("Active") ==
|
||||
mount_state[mount_id].end()) ||
|
||||
(mount_state[mount_id]["Active"].get<bool>() != active) ||
|
||||
(active && ((mount_state[mount_id].find("Location") == mount_state[mount_id].end()) ||
|
||||
(mount_state[mount_id]["Location"].get<std::string>() != mount_location)))) {
|
||||
(active && ((mount_state[mount_id].find("Location") ==
|
||||
mount_state[mount_id].end()) ||
|
||||
(mount_state[mount_id]["Location"].get<std::string>() !=
|
||||
mount_location)))) {
|
||||
const auto lines = utils::file::read_file_lines(get_lock_data_file());
|
||||
const auto txt =
|
||||
std::accumulate(lines.begin(), lines.end(), std::string(),
|
||||
[](std::string s, const std::string &s2) { return std::move(s) + s2; });
|
||||
[](std::string s, const std::string &s2) {
|
||||
return std::move(s) + s2;
|
||||
});
|
||||
auto json = json::parse(txt.empty() ? "{}" : txt);
|
||||
json[mount_id] = {{"Active", active},
|
||||
{"Location", active ? mount_location : ""},
|
||||
{"PID", active ? pid : -1}};
|
||||
ret = utils::file::write_json_file(get_lock_data_file(), json);
|
||||
} else {
|
||||
ret = true;
|
||||
}
|
||||
|
||||
flock(fd, LOCK_UN);
|
||||
}
|
||||
|
||||
@@ -127,14 +165,14 @@ bool lock_data::set_mount_state(const bool &active, const std::string &mount_loc
|
||||
return ret;
|
||||
}
|
||||
|
||||
int lock_data::wait_for_lock(const int &fd, const std::uint8_t &retry_count) {
|
||||
auto lock_data::wait_for_lock(int fd, std::uint8_t retry_count) -> int {
|
||||
auto lock_status = EWOULDBLOCK;
|
||||
std::int16_t remain = retry_count * 100u;
|
||||
while ((remain > 0) && (lock_status == EWOULDBLOCK)) {
|
||||
if ((lock_status = flock(fd, LOCK_EX | LOCK_NB)) == -1) {
|
||||
if ((lock_status = errno) == EWOULDBLOCK) {
|
||||
const auto sleep_ms =
|
||||
utils::random_between(std::int16_t(1), std::min(remain, std::int16_t(100)));
|
||||
const auto sleep_ms = utils::random_between(
|
||||
std::int16_t(1), std::min(remain, std::int16_t(100)));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
|
||||
remain -= sleep_ms;
|
||||
}
|
||||
@@ -143,6 +181,56 @@ int lock_data::wait_for_lock(const int &fd, const std::uint8_t &retry_count) {
|
||||
|
||||
return lock_status;
|
||||
}
|
||||
|
||||
auto create_meta_attributes(
|
||||
std::uint64_t accessed_date, std::uint32_t attributes,
|
||||
std::uint64_t changed_date, std::uint64_t creation_date, bool directory,
|
||||
const std::string &encryption_token, std::uint32_t gid,
|
||||
const std::string &key, std::uint32_t mode, std::uint64_t modified_date,
|
||||
std::uint32_t osx_backup, std::uint32_t osx_flags, std::uint64_t size,
|
||||
const std::string &source_path, std::uint32_t uid,
|
||||
std::uint64_t written_date) -> api_meta_map {
|
||||
return {
|
||||
{META_ACCESSED, std::to_string(accessed_date)},
|
||||
{META_ATTRIBUTES, std::to_string(attributes)},
|
||||
{META_BACKUP, std::to_string(osx_backup)},
|
||||
{META_CHANGED, std::to_string(changed_date)},
|
||||
{META_CREATION, std::to_string(creation_date)},
|
||||
{META_DIRECTORY, utils::string::from_bool(directory)},
|
||||
{META_ENCRYPTION_TOKEN, encryption_token},
|
||||
{META_GID, std::to_string(gid)},
|
||||
{META_KEY, key},
|
||||
{META_MODE, std::to_string(mode)},
|
||||
{META_MODIFIED, std::to_string(modified_date)},
|
||||
{META_OSXFLAGS, std::to_string(osx_flags)},
|
||||
{META_PINNED, "0"},
|
||||
{META_SOURCE, source_path},
|
||||
{META_SIZE, std::to_string(size)},
|
||||
{META_UID, std::to_string(uid)},
|
||||
{META_WRITTEN, std::to_string(written_date)},
|
||||
};
|
||||
}
|
||||
|
||||
auto provider_meta_handler(i_provider &provider, bool directory,
|
||||
const api_file &file) -> api_error {
|
||||
const auto meta = create_meta_attributes(
|
||||
file.accessed_date,
|
||||
directory ? FILE_ATTRIBUTE_DIRECTORY
|
||||
: FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_ARCHIVE,
|
||||
file.changed_date, file.creation_date, directory, file.encryption_token,
|
||||
getgid(), file.key,
|
||||
directory ? S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR
|
||||
: S_IFREG | S_IRUSR | S_IWUSR,
|
||||
file.modified_date, 0u, 0u, file.file_size, file.source_path, getuid(),
|
||||
file.modified_date);
|
||||
auto res = provider.set_item_meta(file.api_path, meta);
|
||||
if (res == api_error::success) {
|
||||
event_system::instance().raise<filesystem_item_added>(
|
||||
file.api_path, file.api_parent, directory);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif //_WIN32
|
||||
|
||||
@@ -1,29 +1,38 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "platform/win32_platform.hpp"
|
||||
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
bool lock_data::get_mount_state(const provider_type &pt, json &mount_state) {
|
||||
auto lock_data::get_mount_state(const provider_type & /*pt*/, json &mount_state)
|
||||
-> bool {
|
||||
const auto ret = get_mount_state(mount_state);
|
||||
if (ret) {
|
||||
const auto mount_id = app_config::get_provider_display_name(pt_) + unique_id_;
|
||||
const auto mount_id =
|
||||
app_config::get_provider_display_name(pt_) + unique_id_;
|
||||
mount_state = mount_state[mount_id].empty()
|
||||
? json({{"Active", false}, {"Location", ""}, {"PID", -1}})
|
||||
: mount_state[mount_id];
|
||||
@@ -32,10 +41,11 @@ bool lock_data::get_mount_state(const provider_type &pt, json &mount_state) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool lock_data::get_mount_state(json &mount_state) {
|
||||
auto lock_data::get_mount_state(json &mount_state) -> bool {
|
||||
HKEY key;
|
||||
auto ret = !::RegCreateKeyEx(
|
||||
HKEY_CURRENT_USER, ("SOFTWARE\\" + std::string(REPERTORY_DATA_NAME) + "\\Mounts").c_str(), 0,
|
||||
HKEY_CURRENT_USER,
|
||||
("SOFTWARE\\" + std::string(REPERTORY_DATA_NAME) + "\\Mounts").c_str(), 0,
|
||||
nullptr, 0, KEY_ALL_ACCESS, nullptr, &key, nullptr);
|
||||
if (ret) {
|
||||
DWORD i = 0u;
|
||||
@@ -43,13 +53,15 @@ bool lock_data::get_mount_state(json &mount_state) {
|
||||
std::string name;
|
||||
name.resize(32767u);
|
||||
auto name_size = static_cast<DWORD>(name.size());
|
||||
while (ret && (::RegEnumValue(key, i, &name[0], &name_size, nullptr, nullptr, nullptr,
|
||||
&data_size) == ERROR_SUCCESS)) {
|
||||
while (ret &&
|
||||
(::RegEnumValue(key, i, &name[0], &name_size, nullptr, nullptr,
|
||||
nullptr, &data_size) == ERROR_SUCCESS)) {
|
||||
std::string data;
|
||||
data.resize(data_size);
|
||||
name_size++;
|
||||
if ((ret = !::RegEnumValue(key, i++, &name[0], &name_size, nullptr, nullptr,
|
||||
reinterpret_cast<LPBYTE>(&data[0]), &data_size))) {
|
||||
if ((ret = !::RegEnumValue(key, i++, &name[0], &name_size, nullptr,
|
||||
nullptr, reinterpret_cast<LPBYTE>(&data[0]),
|
||||
&data_size))) {
|
||||
mount_state[name.c_str()] = json::parse(data);
|
||||
name_size = static_cast<DWORD>(name.size());
|
||||
data_size = 0u;
|
||||
@@ -60,13 +72,14 @@ bool lock_data::get_mount_state(json &mount_state) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
lock_result lock_data::grab_lock(const std::uint8_t &retryCount) {
|
||||
auto lock_data::grab_lock(std::uint8_t retry_count) -> lock_result {
|
||||
auto ret = lock_result::success;
|
||||
if (mutex_handle_ == INVALID_HANDLE_VALUE) {
|
||||
ret = lock_result::failure;
|
||||
} else {
|
||||
for (auto i = 0; (i <= retryCount) &&
|
||||
((mutex_state_ = ::WaitForSingleObject(mutex_handle_, 100)) == WAIT_TIMEOUT);
|
||||
for (auto i = 0;
|
||||
(i <= retry_count) && ((mutex_state_ = ::WaitForSingleObject(
|
||||
mutex_handle_, 100)) == WAIT_TIMEOUT);
|
||||
i++) {
|
||||
}
|
||||
|
||||
@@ -76,13 +89,16 @@ lock_result lock_data::grab_lock(const std::uint8_t &retryCount) {
|
||||
auto should_reset = true;
|
||||
json mount_state;
|
||||
if (get_mount_state(pt_, mount_state)) {
|
||||
if (mount_state["Active"].get<bool>() && mount_state["Location"] == "elevating") {
|
||||
if (mount_state["Active"].get<bool>() &&
|
||||
mount_state["Location"] == "elevating") {
|
||||
should_reset = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (should_reset) {
|
||||
set_mount_state(false, "", -1);
|
||||
if (not set_mount_state(false, "", -1)) {
|
||||
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -109,37 +125,90 @@ void lock_data::release() {
|
||||
}
|
||||
}
|
||||
|
||||
bool lock_data::set_mount_state(const bool &active, const std::string &mountLocation,
|
||||
const std::int64_t &pid) {
|
||||
auto lock_data::set_mount_state(bool active, const std::string &mount_location,
|
||||
const std::int64_t &pid) -> bool {
|
||||
auto ret = false;
|
||||
if (mutex_handle_ != INVALID_HANDLE_VALUE) {
|
||||
const auto mount_id = app_config::get_provider_display_name(pt_) + unique_id_;
|
||||
const auto mount_id =
|
||||
app_config::get_provider_display_name(pt_) + unique_id_;
|
||||
json mount_state;
|
||||
get_mount_state(mount_state);
|
||||
[[maybe_unused]] auto success = get_mount_state(mount_state);
|
||||
if ((mount_state.find(mount_id) == mount_state.end()) ||
|
||||
(mount_state[mount_id].find("Active") == mount_state[mount_id].end()) ||
|
||||
(mount_state[mount_id]["Active"].get<bool>() != active) ||
|
||||
(active && ((mount_state[mount_id].find("Location") == mount_state[mount_id].end()) ||
|
||||
(mount_state[mount_id]["Location"].get<std::string>() != mountLocation)))) {
|
||||
(active && ((mount_state[mount_id].find("Location") ==
|
||||
mount_state[mount_id].end()) ||
|
||||
(mount_state[mount_id]["Location"].get<std::string>() !=
|
||||
mount_location)))) {
|
||||
HKEY key;
|
||||
if ((ret = !::RegCreateKeyEx(
|
||||
HKEY_CURRENT_USER,
|
||||
("SOFTWARE\\" + std::string(REPERTORY_DATA_NAME) + "\\Mounts").c_str(), 0, nullptr,
|
||||
0, KEY_ALL_ACCESS, nullptr, &key, nullptr))) {
|
||||
("SOFTWARE\\" + std::string(REPERTORY_DATA_NAME) + "\\Mounts")
|
||||
.c_str(),
|
||||
0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &key, nullptr))) {
|
||||
const auto str = json({{"Active", active},
|
||||
{"Location", active ? mountLocation : ""},
|
||||
{"Location", active ? mount_location : ""},
|
||||
{"PID", active ? pid : -1}})
|
||||
.dump(0);
|
||||
ret =
|
||||
!::RegSetValueEx(key, &mount_id[0], 0, REG_SZ, reinterpret_cast<const BYTE *>(&str[0]),
|
||||
static_cast<DWORD>(str.size()));
|
||||
ret = !::RegSetValueEx(key, &mount_id[0], 0, REG_SZ,
|
||||
reinterpret_cast<const BYTE *>(&str[0]),
|
||||
static_cast<DWORD>(str.size()));
|
||||
::RegCloseKey(key);
|
||||
}
|
||||
} else {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto create_meta_attributes(
|
||||
std::uint64_t accessed_date, std::uint32_t attributes,
|
||||
std::uint64_t changed_date, std::uint64_t creation_date, bool directory,
|
||||
const std::string &encryption_token, std::uint32_t gid,
|
||||
const std::string &key, std::uint32_t mode, std::uint64_t modified_date,
|
||||
std::uint32_t osx_backup, std::uint32_t osx_flags, std::uint64_t size,
|
||||
const std::string &source_path, std::uint32_t uid,
|
||||
std::uint64_t written_date) -> api_meta_map {
|
||||
return {
|
||||
{META_ACCESSED, std::to_string(accessed_date)},
|
||||
{META_ATTRIBUTES, std::to_string(attributes)},
|
||||
{META_BACKUP, std::to_string(osx_backup)},
|
||||
{META_CHANGED, std::to_string(changed_date)},
|
||||
{META_CREATION, std::to_string(creation_date)},
|
||||
{META_DIRECTORY, utils::string::from_bool(directory)},
|
||||
{META_ENCRYPTION_TOKEN, encryption_token},
|
||||
{META_GID, std::to_string(gid)},
|
||||
{META_KEY, key},
|
||||
{META_MODE, std::to_string(mode)},
|
||||
{META_MODIFIED, std::to_string(modified_date)},
|
||||
{META_OSXFLAGS, std::to_string(osx_flags)},
|
||||
{META_PINNED, "0"},
|
||||
{META_SIZE, std::to_string(size)},
|
||||
{META_SOURCE, source_path},
|
||||
{META_UID, std::to_string(uid)},
|
||||
{META_WRITTEN, std::to_string(written_date)},
|
||||
};
|
||||
}
|
||||
|
||||
auto provider_meta_handler(i_provider &provider, bool directory,
|
||||
const api_file &file) -> api_error {
|
||||
const auto meta = create_meta_attributes(
|
||||
file.accessed_date,
|
||||
directory ? FILE_ATTRIBUTE_DIRECTORY
|
||||
: FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_ARCHIVE,
|
||||
file.changed_date, file.creation_date, directory, file.encryption_token,
|
||||
0u, file.key, directory ? S_IFDIR : S_IFREG, file.modified_date, 0u, 0u,
|
||||
file.file_size, file.source_path, 0u, file.modified_date);
|
||||
auto res = provider.set_item_meta(file.api_path, meta);
|
||||
if (res == api_error::success) {
|
||||
event_system::instance().raise<filesystem_item_added>(
|
||||
file.api_path, file.api_parent, directory);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif //_WIN32
|
||||
|
||||
@@ -1,105 +1,196 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "providers/base_provider.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "file_manager/i_file_manager.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "types/startup_exception.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/global_data.hpp"
|
||||
#include "utils/native_file.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
base_provider::base_provider(app_config &config) : config_(config), meta_db_(config) {}
|
||||
base_provider::base_provider(app_config &config) : config_(config) {}
|
||||
|
||||
void base_provider::notify_directory_added(const std::string &api_path,
|
||||
const std::string &api_parent) {
|
||||
recur_mutex_lock l(notify_added_mutex_);
|
||||
|
||||
const auto now = utils::get_file_time_now();
|
||||
api_item_added_(api_path, api_parent, "", true, now, now, now, now);
|
||||
}
|
||||
|
||||
void base_provider::update_filesystem_item(const bool &directory, const api_error &error,
|
||||
const std::string &api_path,
|
||||
filesystem_item &fsi) const {
|
||||
if (error == api_error::success) {
|
||||
fsi.directory = directory;
|
||||
fsi.lock = fsi.lock ? fsi.lock : std::make_shared<std::recursive_mutex>();
|
||||
fsi.api_path = api_path;
|
||||
fsi.api_parent = utils::path::get_parent_api_path(api_path);
|
||||
} else {
|
||||
event_system::instance().raise<filesystem_item_get_failed>(
|
||||
api_path, std::to_string(static_cast<int>(error)));
|
||||
void base_provider::calculate_used_drive_space(bool add_missing) {
|
||||
api_file_list list{};
|
||||
if (get_file_list(list) != api_error::success) {
|
||||
return;
|
||||
}
|
||||
|
||||
used_space_ = std::accumulate(
|
||||
list.begin(), list.end(), std::uint64_t(0U),
|
||||
[this, &add_missing](std::uint64_t total_size, const auto &file) {
|
||||
if (add_missing && not meta_db_->get_item_meta_exists(file.api_path)) {
|
||||
[[maybe_unused]] auto res = this->notify_file_added(
|
||||
file.api_path, utils::path::get_parent_api_path(file.api_path),
|
||||
0);
|
||||
}
|
||||
|
||||
return total_size + file.file_size;
|
||||
});
|
||||
}
|
||||
|
||||
api_error base_provider::create_directory_clone_source_meta(const std::string &source_api_path,
|
||||
const std::string &api_path) {
|
||||
void base_provider::cleanup() {
|
||||
remove_deleted_files();
|
||||
remove_unknown_source_files();
|
||||
remove_expired_orphaned_files();
|
||||
}
|
||||
|
||||
auto base_provider::create_directory_clone_source_meta(
|
||||
const std::string &source_api_path, const std::string &api_path)
|
||||
-> api_error {
|
||||
api_meta_map meta{};
|
||||
auto ret = get_item_meta(source_api_path, meta);
|
||||
if (ret == api_error::success) {
|
||||
ret = create_directory(api_path, meta);
|
||||
}
|
||||
return ret;
|
||||
return ret == api_error::item_not_found ? api_error::directory_not_found
|
||||
: ret;
|
||||
}
|
||||
|
||||
api_error base_provider::create_file(const std::string &api_path, api_meta_map &meta) {
|
||||
const auto isDir = is_directory(api_path);
|
||||
const auto isFile = is_file(api_path);
|
||||
auto ret = isDir ? api_error::directory_exists
|
||||
: isFile ? api_error::file_exists
|
||||
: api_error::success;
|
||||
if (ret == api_error::success) {
|
||||
const auto source =
|
||||
utils::path::combine(get_config().get_cache_directory(), {utils::create_uuid_string()});
|
||||
auto base_provider::create_file(const std::string &api_path, api_meta_map &meta)
|
||||
-> api_error {
|
||||
bool exists{};
|
||||
auto res = is_directory(api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
return api_error::directory_exists;
|
||||
}
|
||||
|
||||
res = is_file(api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
return api_error::item_exists;
|
||||
}
|
||||
|
||||
if ((res = meta_db_->set_item_meta(api_path, meta)) != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
{
|
||||
native_file_ptr nf;
|
||||
if ((ret = native_file::create_or_open(source, nf)) == api_error::success) {
|
||||
nf->close();
|
||||
res = native_file::create_or_open(meta[META_SOURCE], nf);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
nf->close();
|
||||
}
|
||||
|
||||
stop_type stop_requested = false;
|
||||
return upload_file(api_path, meta[META_SOURCE], meta[META_ENCRYPTION_TOKEN],
|
||||
stop_requested);
|
||||
}
|
||||
|
||||
auto base_provider::get_api_path_from_source(const std::string &source_path,
|
||||
std::string &api_path) const
|
||||
-> api_error {
|
||||
return meta_db_->get_api_path_from_source(source_path, api_path);
|
||||
}
|
||||
|
||||
auto base_provider::get_directory_items(const std::string &api_path,
|
||||
directory_item_list &list) const
|
||||
-> api_error {
|
||||
auto res = populate_directory_items(api_path, list);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
std::sort(list.begin(), list.end(), [](const auto &a, const auto &b) -> bool {
|
||||
return (a.directory && not b.directory) ||
|
||||
(not(b.directory && not a.directory) &&
|
||||
(a.api_path.compare(b.api_path) < 0));
|
||||
});
|
||||
|
||||
list.insert(list.begin(), directory_item{
|
||||
"..",
|
||||
"",
|
||||
true,
|
||||
});
|
||||
list.insert(list.begin(), directory_item{
|
||||
".",
|
||||
"",
|
||||
true,
|
||||
});
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto base_provider::get_file(const std::string &api_path, api_file &file) const
|
||||
-> api_error {
|
||||
auto ret = api_error::success;
|
||||
try {
|
||||
if ((ret = populate_file(api_path, file)) != api_error::success) {
|
||||
event_system::instance().raise<file_get_failed>(api_path,
|
||||
api_error_to_string(ret));
|
||||
}
|
||||
|
||||
if (ret == api_error::success) {
|
||||
if (((ret = set_item_meta(api_path, meta)) != api_error::success) ||
|
||||
((ret = set_source_path(api_path, source)) != api_error::success) ||
|
||||
((ret = upload_file(api_path, source, meta[META_ENCRYPTION_TOKEN])) !=
|
||||
api_error::success)) {
|
||||
meta_db_.remove_item_meta(format_api_path(api_path));
|
||||
utils::file::delete_file(source);
|
||||
}
|
||||
std::string sz;
|
||||
if ((ret = get_item_meta(api_path, META_SIZE, sz)) != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
file.file_size = utils::string::to_uint64(sz);
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<file_get_failed>(
|
||||
api_path, e.what() ? e.what() : "failed to get file");
|
||||
}
|
||||
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
auto base_provider::get_file_size(const std::string &api_path,
|
||||
std::uint64_t &file_size) const -> api_error {
|
||||
api_file file{};
|
||||
const auto ret = get_file(api_path, file);
|
||||
if (ret == api_error::success) {
|
||||
file_size = file.file_size;
|
||||
} else {
|
||||
event_system::instance().raise<file_get_size_failed>(
|
||||
api_path, api_error_to_string(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error base_provider::get_api_path_from_source(const std::string &source_path,
|
||||
std::string &api_path) const {
|
||||
const auto ret = meta_db_.get_api_path_from_source(source_path, api_path);
|
||||
restore_api_path(api_path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error base_provider::get_filesystem_item(const std::string &api_path, const bool &directory,
|
||||
filesystem_item &fsi) const {
|
||||
auto base_provider::get_filesystem_item(const std::string &api_path,
|
||||
bool directory,
|
||||
filesystem_item &fsi) const
|
||||
-> api_error {
|
||||
auto ret = api_error::error;
|
||||
if (directory) {
|
||||
ret = is_directory(api_path) ? api_error::success : api_error::item_not_found;
|
||||
bool exists{};
|
||||
ret = is_directory(api_path, exists);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
ret = exists ? api_error::success : api_error::item_not_found;
|
||||
update_filesystem_item(true, ret, api_path, fsi);
|
||||
} else {
|
||||
api_file file{};
|
||||
@@ -109,16 +200,25 @@ api_error base_provider::get_filesystem_item(const std::string &api_path, const
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error base_provider::get_filesystem_item_and_file(const std::string &api_path, api_file &file,
|
||||
filesystem_item &fsi) const {
|
||||
auto base_provider::get_filesystem_item_and_file(const std::string &api_path,
|
||||
api_file &file,
|
||||
filesystem_item &fsi) const
|
||||
-> api_error {
|
||||
auto ret = get_item_meta(api_path, META_SOURCE, fsi.source_path);
|
||||
if (ret == api_error::success) {
|
||||
ret = get_file(api_path, file);
|
||||
if (ret == api_error::success) {
|
||||
fsi.encryption_token = file.encryption_token;
|
||||
fsi.size = file.file_size;
|
||||
} else if (not is_file(api_path)) {
|
||||
ret = api_error::item_not_found;
|
||||
} else {
|
||||
bool exists{};
|
||||
ret = is_file(api_path, exists);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
if (not exists) {
|
||||
ret = api_error::item_not_found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,12 +226,13 @@ api_error base_provider::get_filesystem_item_and_file(const std::string &api_pat
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error base_provider::get_filesystem_item_from_source_path(const std::string &source_path,
|
||||
filesystem_item &fsi) const {
|
||||
auto base_provider::get_filesystem_item_from_source_path(
|
||||
const std::string &source_path, filesystem_item &fsi) const -> api_error {
|
||||
auto ret = api_error::item_not_found;
|
||||
if (not source_path.empty()) {
|
||||
std::string api_path;
|
||||
if ((ret = get_api_path_from_source(source_path, api_path)) == api_error::success) {
|
||||
if ((ret = get_api_path_from_source(source_path, api_path)) ==
|
||||
api_error::success) {
|
||||
ret = get_filesystem_item(api_path, false, fsi);
|
||||
}
|
||||
}
|
||||
@@ -139,71 +240,308 @@ api_error base_provider::get_filesystem_item_from_source_path(const std::string
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error base_provider::get_item_meta(const std::string &api_path, api_meta_map &meta) const {
|
||||
auto ret = meta_db_.get_item_meta(format_api_path(api_path), meta);
|
||||
auto base_provider::get_item_meta(const std::string &api_path,
|
||||
api_meta_map &meta) const -> api_error {
|
||||
auto ret = meta_db_->get_item_meta(api_path, meta);
|
||||
if (ret == api_error::item_not_found) {
|
||||
auto get_meta = false;
|
||||
if (is_directory(api_path)) {
|
||||
notify_directory_added(api_path, utils::path::get_parent_api_path(api_path));
|
||||
get_meta = true;
|
||||
} else if (is_file(api_path)) {
|
||||
std::uint64_t file_size = 0u;
|
||||
if ((ret = get_file_size(api_path, file_size)) == api_error::success) {
|
||||
get_meta = ((ret = notify_file_added(api_path, utils::path::get_parent_api_path(api_path),
|
||||
file_size)) == api_error::success);
|
||||
bool exists{};
|
||||
ret = is_directory(api_path, exists);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
ret = notify_directory_added(api_path,
|
||||
utils::path::get_parent_api_path(api_path));
|
||||
if (ret == api_error::success) {
|
||||
get_meta = true;
|
||||
}
|
||||
} else {
|
||||
ret = is_file(api_path, exists);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
if (exists) {
|
||||
std::uint64_t file_size{};
|
||||
if ((ret = get_file_size(api_path, file_size)) == api_error::success) {
|
||||
get_meta = ((ret = notify_file_added(
|
||||
api_path, utils::path::get_parent_api_path(api_path),
|
||||
file_size)) == api_error::success);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (get_meta) {
|
||||
ret = meta_db_.get_item_meta(format_api_path(api_path), meta);
|
||||
}
|
||||
|
||||
ret = get_meta ? meta_db_->get_item_meta(api_path, meta)
|
||||
: api_error::item_not_found;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error base_provider::get_item_meta(const std::string &api_path, const std::string &key,
|
||||
std::string &value) const {
|
||||
auto ret = meta_db_.get_item_meta(format_api_path(api_path), key, value);
|
||||
auto base_provider::get_item_meta(const std::string &api_path,
|
||||
const std::string &key,
|
||||
std::string &value) const -> api_error {
|
||||
auto ret = meta_db_->get_item_meta(api_path, key, value);
|
||||
if (ret == api_error::item_not_found) {
|
||||
auto get_meta = false;
|
||||
if (is_directory(api_path)) {
|
||||
notify_directory_added(api_path, utils::path::get_parent_api_path(api_path));
|
||||
get_meta = true;
|
||||
} else if (is_file(api_path)) {
|
||||
std::uint64_t file_size = 0u;
|
||||
if ((ret = get_file_size(api_path, file_size)) == api_error::success) {
|
||||
get_meta = ((ret = notify_file_added(api_path, utils::path::get_parent_api_path(api_path),
|
||||
file_size)) == api_error::success);
|
||||
bool exists{};
|
||||
ret = is_directory(api_path, exists);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
if (exists) {
|
||||
ret = notify_directory_added(api_path,
|
||||
utils::path::get_parent_api_path(api_path));
|
||||
if (ret == api_error::success) {
|
||||
get_meta = true;
|
||||
}
|
||||
} else {
|
||||
ret = is_file(api_path, exists);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
if (exists) {
|
||||
std::uint64_t file_size{};
|
||||
if ((ret = get_file_size(api_path, file_size)) == api_error::success) {
|
||||
get_meta = ((ret = notify_file_added(
|
||||
api_path, utils::path::get_parent_api_path(api_path),
|
||||
file_size)) == api_error::success);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (get_meta) {
|
||||
ret = meta_db_.get_item_meta(format_api_path(api_path), key, value);
|
||||
}
|
||||
|
||||
ret = get_meta ? meta_db_->get_item_meta(api_path, key, value)
|
||||
: api_error::item_not_found;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::uint64_t base_provider::get_used_drive_space() const {
|
||||
return global_data::instance().get_used_drive_space();
|
||||
auto base_provider::get_used_drive_space() const -> std::uint64_t {
|
||||
std::uint64_t used_space = used_space_;
|
||||
fm_->update_used_space(used_space);
|
||||
return used_space;
|
||||
}
|
||||
|
||||
bool base_provider::start(api_item_added_callback api_item_added, i_open_file_table *oft) {
|
||||
auto base_provider::notify_directory_added(const std::string &api_path,
|
||||
const std::string &api_parent)
|
||||
-> api_error {
|
||||
recur_mutex_lock l(notify_added_mutex_);
|
||||
|
||||
const auto now = utils::get_file_time_now();
|
||||
api_file file{};
|
||||
file.api_path = api_path;
|
||||
file.api_parent = api_parent;
|
||||
file.accessed_date = now;
|
||||
file.changed_date = now;
|
||||
file.creation_date = now;
|
||||
file.file_size = 0U;
|
||||
file.modified_date = now;
|
||||
return api_item_added_(true, file);
|
||||
}
|
||||
|
||||
auto base_provider::processed_orphaned_file(const std::string &source_path,
|
||||
const std::string &api_path) const
|
||||
-> bool {
|
||||
const auto orphaned_directory =
|
||||
utils::path::combine(get_config().get_data_directory(), {"orphaned"});
|
||||
if (utils::file::create_full_directory_path(orphaned_directory)) {
|
||||
event_system::instance().raise<orphaned_file_detected>(source_path);
|
||||
const auto parts = utils::string::split(api_path, '/', false);
|
||||
const auto orphaned_file = utils::path::combine(
|
||||
orphaned_directory, {utils::path::strip_to_file_name(source_path) +
|
||||
'_' + parts[parts.size() - 1U]});
|
||||
|
||||
if (utils::file::reset_modified_time(source_path) &&
|
||||
utils::file::move_file(source_path, orphaned_file)) {
|
||||
event_system::instance().raise<orphaned_file_processed>(source_path,
|
||||
orphaned_file);
|
||||
return true;
|
||||
}
|
||||
|
||||
event_system::instance().raise<orphaned_file_processing_failed>(
|
||||
source_path, orphaned_file,
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
return false;
|
||||
}
|
||||
|
||||
utils::error::raise_error(
|
||||
__FUNCTION__, std::to_string(utils::get_last_error_code()),
|
||||
"failed to create orphaned director|sp|" + orphaned_directory);
|
||||
return false;
|
||||
}
|
||||
|
||||
void base_provider::remove_deleted_files() {
|
||||
std::vector<std::string> removed_files{};
|
||||
|
||||
api_file_list list{};
|
||||
if (get_file_list(list) == api_error::success) {
|
||||
if (not list.empty()) {
|
||||
auto iterator = meta_db_->create_iterator(false);
|
||||
for (iterator->SeekToFirst(); not stop_requested_ && iterator->Valid();
|
||||
iterator->Next()) {
|
||||
const auto meta_api_path = iterator->key().ToString();
|
||||
if (meta_api_path.empty()) {
|
||||
const auto res = meta_db_->remove_item_meta(meta_api_path);
|
||||
if (res != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, meta_api_path, res,
|
||||
"failed to remove item meta");
|
||||
}
|
||||
} else {
|
||||
auto api_path = meta_api_path;
|
||||
const auto it = std::find_if(list.begin(), list.end(),
|
||||
[&api_path](const auto &file) -> bool {
|
||||
return file.api_path == api_path;
|
||||
});
|
||||
if (it == list.end()) {
|
||||
removed_files.emplace_back(api_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (not stop_requested_ && not removed_files.empty()) {
|
||||
const auto api_path = removed_files.back();
|
||||
removed_files.pop_back();
|
||||
|
||||
bool exists{};
|
||||
auto res = is_directory(api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string source_path;
|
||||
if (not exists &&
|
||||
(check_file_exists(api_path) == api_error::item_not_found) &&
|
||||
(meta_db_->get_item_meta(api_path, META_SOURCE, source_path) ==
|
||||
api_error::success)) {
|
||||
if (not source_path.empty()) {
|
||||
fm_->perform_locked_operation(
|
||||
[this, &api_path, &source_path](i_provider &) -> bool {
|
||||
if (fm_->has_no_open_file_handles()) {
|
||||
const auto res = meta_db_->remove_item_meta(api_path);
|
||||
if (res == api_error::success) {
|
||||
event_system::instance().raise<file_removed_externally>(
|
||||
api_path, source_path);
|
||||
processed_orphaned_file(source_path, api_path);
|
||||
} else {
|
||||
utils::error::raise_api_path_error(
|
||||
__FUNCTION__, api_path, source_path, res,
|
||||
"failed to remove item meta for externally removed file");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void base_provider::remove_expired_orphaned_files() {
|
||||
const auto orphaned_directory =
|
||||
utils::path::combine(get_config().get_data_directory(), {"orphaned"});
|
||||
const auto files = utils::file::get_directory_files(orphaned_directory, true);
|
||||
for (const auto &file : files) {
|
||||
if (utils::file::is_modified_date_older_than(
|
||||
file, std::chrono::hours(
|
||||
get_config().get_orphaned_file_retention_days() * 24))) {
|
||||
if (utils::file::retry_delete_file(file)) {
|
||||
event_system::instance().raise<orphaned_file_deleted>(file);
|
||||
}
|
||||
}
|
||||
if (stop_requested_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void base_provider::remove_unknown_source_files() {
|
||||
auto files = utils::file::get_directory_files(
|
||||
get_config().get_cache_directory(), true);
|
||||
while (not stop_requested_ && not files.empty()) {
|
||||
const auto file = files.front();
|
||||
files.pop_front();
|
||||
|
||||
std::string api_path;
|
||||
if (not meta_db_->get_source_path_exists(file)) {
|
||||
processed_orphaned_file(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto base_provider::rename_file(const std::string &from_api_path,
|
||||
const std::string &to_api_path) -> api_error {
|
||||
std::string source_path;
|
||||
auto ret = get_item_meta(from_api_path, META_SOURCE, source_path);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string encryption_token;
|
||||
ret = get_item_meta(from_api_path, META_ENCRYPTION_TOKEN, encryption_token);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = handle_rename_file(from_api_path, to_api_path, source_path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto base_provider::start(api_item_added_callback api_item_added,
|
||||
i_file_manager *fm) -> bool {
|
||||
meta_db_ = std::make_unique<meta_db>(config_);
|
||||
|
||||
api_item_added_ = api_item_added;
|
||||
oft_ = oft;
|
||||
fm_ = fm;
|
||||
|
||||
auto unmount_requested = false;
|
||||
{
|
||||
repertory::event_consumer ec("unmount_requested",
|
||||
[&unmount_requested](const event &) { unmount_requested = true; });
|
||||
for (std::uint16_t i = 0u; not unmount_requested && not is_online() &&
|
||||
repertory::event_consumer ec(
|
||||
"unmount_requested",
|
||||
[&unmount_requested](const event &) { unmount_requested = true; });
|
||||
for (std::uint16_t i = 0U; not unmount_requested && not is_online() &&
|
||||
(i < get_config().get_online_check_retry_secs());
|
||||
i++) {
|
||||
event_system::instance().raise<provider_offline>(
|
||||
get_config().get_host_config().host_name_or_ip, get_config().get_host_config().api_port);
|
||||
get_config().get_host_config().host_name_or_ip,
|
||||
get_config().get_host_config().api_port);
|
||||
std::this_thread::sleep_for(1s);
|
||||
}
|
||||
}
|
||||
return unmount_requested;
|
||||
|
||||
auto ret = not unmount_requested && is_online();
|
||||
if (ret) {
|
||||
// Force root creation
|
||||
api_meta_map meta{};
|
||||
auto res = get_item_meta("/", meta);
|
||||
if (res != api_error::success) {
|
||||
throw startup_exception("failed to create root|err|" +
|
||||
api_error_to_string(res));
|
||||
}
|
||||
|
||||
calculate_used_drive_space(false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void base_provider::stop() { meta_db_.reset(); }
|
||||
|
||||
void base_provider::update_filesystem_item(bool directory,
|
||||
const api_error &error,
|
||||
const std::string &api_path,
|
||||
filesystem_item &fsi) const {
|
||||
if (error == api_error::success) {
|
||||
fsi.directory = directory;
|
||||
fsi.api_path = api_path;
|
||||
fsi.api_parent = utils::path::get_parent_api_path(api_path);
|
||||
} else {
|
||||
event_system::instance().raise<filesystem_item_get_failed>(
|
||||
api_path, std::to_string(static_cast<int>(error)));
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
813
src/providers/encrypt/encrypt_provider.cpp
Normal file
813
src/providers/encrypt/encrypt_provider.cpp
Normal file
@@ -0,0 +1,813 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "providers/encrypt/encrypt_provider.hpp"
|
||||
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "platform/win32_platform.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/polling.hpp"
|
||||
#include "utils/rocksdb_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
encrypt_provider::encrypt_provider(app_config &config) : config_(config) {}
|
||||
|
||||
auto encrypt_provider::create_api_file(const std::string api_path,
|
||||
bool directory,
|
||||
const std::string &source_path)
|
||||
-> api_file {
|
||||
#ifdef _WIN32
|
||||
struct _stat64 buf {};
|
||||
_stat64(source_path.c_str(), &buf);
|
||||
#else
|
||||
struct stat buf {};
|
||||
stat(source_path.c_str(), &buf);
|
||||
#endif
|
||||
|
||||
api_file file{};
|
||||
file.api_path = api_path;
|
||||
file.api_parent = utils::path::get_parent_api_path(api_path);
|
||||
file.file_size =
|
||||
directory
|
||||
? 0U
|
||||
: utils::encryption::encrypting_reader::calculate_encrypted_size(
|
||||
source_path);
|
||||
file.source_path = source_path;
|
||||
#ifdef __APPLE__
|
||||
file.changed_date =
|
||||
buf.st_ctimespec.tv_nsec + (buf.st_ctimespec.tv_sec * NANOS_PER_SECOND);
|
||||
file.accessed_date =
|
||||
buf.st_atimespec.tv_nsec + (buf.st_atimespec.tv_sec * NANOS_PER_SECOND);
|
||||
file.creation_date = buf.st_birthtimespec.tv_nsec +
|
||||
(buf.st_birthtimespec.tv_sec * NANOS_PER_SECOND);
|
||||
file.modified_date =
|
||||
buf.st_mtimespec.tv_nsec + (buf.st_mtimespec.tv_sec * NANOS_PER_SECOND);
|
||||
#elif _WIN32
|
||||
FILETIME ft{};
|
||||
utils::unix_time_to_filetime(utils::time64_to_unix_time(buf.st_atime), ft);
|
||||
file.accessed_date =
|
||||
(static_cast<std::uint64_t>(ft.dwHighDateTime) << 32U) | ft.dwLowDateTime;
|
||||
|
||||
utils::unix_time_to_filetime(utils::time64_to_unix_time(buf.st_mtime), ft);
|
||||
file.changed_date =
|
||||
(static_cast<std::uint64_t>(ft.dwHighDateTime) << 32U) | ft.dwLowDateTime;
|
||||
|
||||
utils::unix_time_to_filetime(utils::time64_to_unix_time(buf.st_ctime), ft);
|
||||
file.creation_date =
|
||||
(static_cast<std::uint64_t>(ft.dwHighDateTime) << 32U) | ft.dwLowDateTime;
|
||||
|
||||
utils::unix_time_to_filetime(utils::time64_to_unix_time(buf.st_mtime), ft);
|
||||
file.modified_date =
|
||||
(static_cast<std::uint64_t>(ft.dwHighDateTime) << 32U) | ft.dwLowDateTime;
|
||||
#else
|
||||
file.changed_date =
|
||||
buf.st_mtim.tv_nsec + (buf.st_mtim.tv_sec * NANOS_PER_SECOND);
|
||||
file.accessed_date =
|
||||
buf.st_atim.tv_nsec + (buf.st_atim.tv_sec * NANOS_PER_SECOND);
|
||||
file.creation_date =
|
||||
buf.st_ctim.tv_nsec + (buf.st_ctim.tv_sec * NANOS_PER_SECOND);
|
||||
file.modified_date =
|
||||
buf.st_mtim.tv_nsec + (buf.st_mtim.tv_sec * NANOS_PER_SECOND);
|
||||
#endif
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
void encrypt_provider::create_item_meta(api_meta_map &meta, bool directory,
|
||||
const api_file &file) {
|
||||
#ifdef _WIN32
|
||||
struct _stat64 buf {};
|
||||
_stat64(file.source_path.c_str(), &buf);
|
||||
#else
|
||||
struct stat buf {};
|
||||
stat(file.source_path.c_str(), &buf);
|
||||
#endif
|
||||
|
||||
meta[META_ACCESSED] = std::to_string(file.accessed_date);
|
||||
#ifdef _WIN32
|
||||
meta[META_ATTRIBUTES] =
|
||||
std::to_string(::GetFileAttributesA(file.source_path.c_str()));
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
meta[META_BACKUP];
|
||||
#endif
|
||||
meta[META_CHANGED] = std::to_string(file.changed_date);
|
||||
meta[META_CREATION] = std::to_string(file.creation_date);
|
||||
meta[META_DIRECTORY] = utils::string::from_bool(directory);
|
||||
meta[META_GID] = std::to_string(buf.st_gid);
|
||||
meta[META_MODE] = std::to_string(buf.st_mode);
|
||||
meta[META_MODIFIED] = std::to_string(file.modified_date);
|
||||
#ifdef __APPLE__
|
||||
meta[META_OSXFLAGS];
|
||||
#endif
|
||||
meta[META_SIZE] = std::to_string(file.file_size);
|
||||
meta[META_SOURCE] = file.source_path;
|
||||
meta[META_UID] = std::to_string(buf.st_uid);
|
||||
meta[META_WRITTEN] = std::to_string(file.modified_date);
|
||||
}
|
||||
|
||||
auto encrypt_provider::create_directory(const std::string &api_path,
|
||||
api_meta_map & /*meta*/) -> api_error {
|
||||
if (api_path == "/") {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
return api_error::not_implemented;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_api_path_from_source(const std::string &source_path,
|
||||
std::string &api_path) const
|
||||
-> api_error {
|
||||
try {
|
||||
std::string api_path_data{};
|
||||
db_->Get(rocksdb::ReadOptions(), file_family_, source_path, &api_path_data);
|
||||
if (not api_path_data.empty()) {
|
||||
api_path = json::parse(api_path_data).at("api_path").get<std::string>();
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
std::string dir_api_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), dir_family_, source_path, &dir_api_path);
|
||||
if (dir_api_path.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
api_path = dir_api_path;
|
||||
return api_error::success;
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::raise_error(__FUNCTION__, ex, source_path,
|
||||
"failed to get api path from source path");
|
||||
}
|
||||
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_directory_item_count(
|
||||
const std::string &api_path) const -> std::uint64_t {
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, api_path, &source_path);
|
||||
if (source_path.empty()) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
std::string dir_api_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), dir_family_, source_path, &dir_api_path);
|
||||
if (dir_api_path.empty()) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
const auto cfg = config_.get_encrypt_config();
|
||||
|
||||
std::uint64_t count{};
|
||||
try {
|
||||
for ([[maybe_unused]] const auto &dir_entry :
|
||||
std::filesystem::directory_iterator(source_path)) {
|
||||
count++;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::raise_error(__FUNCTION__, ex, cfg.path,
|
||||
"failed to get directory item count");
|
||||
return 0U;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_directory_items(const std::string &api_path,
|
||||
directory_item_list &list) const
|
||||
-> api_error {
|
||||
bool exists{};
|
||||
auto res = is_file(api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
return api_error::item_exists;
|
||||
}
|
||||
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, api_path, &source_path);
|
||||
if (source_path.empty()) {
|
||||
return api_error::directory_not_found;
|
||||
}
|
||||
|
||||
std::string dir_api_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), dir_family_, source_path, &dir_api_path);
|
||||
if (dir_api_path.empty()) {
|
||||
return api_error::directory_not_found;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const auto &dir_entry :
|
||||
std::filesystem::directory_iterator(source_path)) {
|
||||
try {
|
||||
std::string api_path{};
|
||||
if (dir_entry.is_directory()) {
|
||||
db_->Get(rocksdb::ReadOptions(), dir_family_,
|
||||
dir_entry.path().string(), &api_path);
|
||||
if (api_path.empty()) {
|
||||
const auto cfg = config_.get_encrypt_config();
|
||||
for (const auto &child_dir_entry :
|
||||
std::filesystem::directory_iterator(dir_entry.path())) {
|
||||
if (process_directory_entry(child_dir_entry, cfg, api_path)) {
|
||||
api_path = utils::path::get_parent_api_path(api_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (api_path.empty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::string api_path_data{};
|
||||
db_->Get(rocksdb::ReadOptions(), file_family_,
|
||||
dir_entry.path().string(), &api_path_data);
|
||||
if (api_path_data.empty()) {
|
||||
const auto cfg = config_.get_encrypt_config();
|
||||
if (not process_directory_entry(dir_entry, cfg, api_path)) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
api_path =
|
||||
json::parse(api_path_data).at("api_path").get<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
auto file = create_api_file(api_path, dir_entry.is_directory(),
|
||||
dir_entry.path().string());
|
||||
|
||||
directory_item di{};
|
||||
di.api_parent = file.api_parent;
|
||||
di.api_path = file.api_path;
|
||||
di.directory = dir_entry.is_directory();
|
||||
di.resolved = true;
|
||||
di.size = file.file_size;
|
||||
create_item_meta(di.meta, di.directory, file);
|
||||
|
||||
list.emplace_back(std::move(di));
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::raise_error(__FUNCTION__, ex, dir_entry.path().string(),
|
||||
"failed to process directory item");
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::raise_error(__FUNCTION__, ex, source_path,
|
||||
"failed to get directory items");
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
std::sort(list.begin(), list.end(), [](const auto &a, const auto &b) -> bool {
|
||||
return (a.directory && not b.directory) ||
|
||||
(not(b.directory && not a.directory) &&
|
||||
(a.api_path.compare(b.api_path) < 0));
|
||||
});
|
||||
|
||||
list.insert(list.begin(), directory_item{
|
||||
"..",
|
||||
"",
|
||||
true,
|
||||
});
|
||||
list.insert(list.begin(), directory_item{
|
||||
".",
|
||||
"",
|
||||
true,
|
||||
});
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_file(const std::string &api_path,
|
||||
api_file &file) const -> api_error {
|
||||
bool exists{};
|
||||
auto res = is_directory(api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
return api_error::directory_exists;
|
||||
}
|
||||
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, api_path, &source_path);
|
||||
if (source_path.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
file = create_api_file(api_path, false, source_path);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto encrypt_provider::process_directory_entry(
|
||||
const std::filesystem::directory_entry &dir_entry,
|
||||
const encrypt_config &cfg, std::string &api_path) const -> bool {
|
||||
if (dir_entry.is_regular_file() && not dir_entry.is_symlink() &&
|
||||
not dir_entry.is_directory()) {
|
||||
const auto relative_path = dir_entry.path().lexically_relative(cfg.path);
|
||||
|
||||
std::string api_path_data{};
|
||||
db_->Get(rocksdb::ReadOptions(), file_family_, dir_entry.path().string(),
|
||||
&api_path_data);
|
||||
|
||||
std::string api_parent{};
|
||||
db_->Get(rocksdb::ReadOptions(), dir_family_,
|
||||
dir_entry.path().parent_path().string(), &api_parent);
|
||||
|
||||
if (api_path_data.empty() || api_parent.empty()) {
|
||||
stop_type stop_requested = false;
|
||||
utils::encryption::encrypting_reader reader(
|
||||
relative_path.filename().string(), dir_entry.path().string(),
|
||||
stop_requested, cfg.encryption_token,
|
||||
relative_path.parent_path().string());
|
||||
if (api_parent.empty()) {
|
||||
auto encrypted_parts =
|
||||
utils::string::split(reader.get_encrypted_file_path(), '/', false);
|
||||
|
||||
std::size_t idx{1U};
|
||||
|
||||
std::string current_source_path{cfg.path};
|
||||
std::string current_encrypted_path{};
|
||||
for (const auto &part : relative_path.parent_path()) {
|
||||
if (part.string() == "/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
current_source_path =
|
||||
utils::path::combine(current_source_path, {part.string()});
|
||||
|
||||
std::string parent_api_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), dir_family_, current_source_path,
|
||||
&parent_api_path);
|
||||
if (parent_api_path.empty()) {
|
||||
parent_api_path = utils::path::create_api_path(
|
||||
current_encrypted_path + '/' + encrypted_parts[idx]);
|
||||
db_->Put(rocksdb::WriteOptions(), dir_family_, current_source_path,
|
||||
parent_api_path);
|
||||
db_->Put(rocksdb::WriteOptions(), source_family_, parent_api_path,
|
||||
current_source_path);
|
||||
event_system::instance().raise<filesystem_item_added>(
|
||||
parent_api_path,
|
||||
utils::path::get_parent_api_path(parent_api_path), true);
|
||||
} else {
|
||||
encrypted_parts[idx] =
|
||||
utils::string::split(parent_api_path, '/', false)[idx];
|
||||
}
|
||||
|
||||
current_encrypted_path = utils::path::create_api_path(
|
||||
current_encrypted_path + '/' + encrypted_parts[idx++]);
|
||||
}
|
||||
|
||||
api_parent = current_encrypted_path;
|
||||
}
|
||||
|
||||
if (api_path_data.empty()) {
|
||||
api_path = utils::path::create_api_path(
|
||||
api_parent + "/" + reader.get_encrypted_file_name());
|
||||
|
||||
auto iv_list = reader.get_iv_list();
|
||||
json data = {
|
||||
{"api_path", api_path},
|
||||
{"iv_list", iv_list},
|
||||
{"original_file_size", dir_entry.file_size()},
|
||||
};
|
||||
db_->Put(rocksdb::WriteOptions(), file_family_,
|
||||
dir_entry.path().string(), data.dump());
|
||||
db_->Put(rocksdb::WriteOptions(), source_family_, api_path,
|
||||
dir_entry.path().string());
|
||||
event_system::instance().raise<filesystem_item_added>(
|
||||
api_path, api_parent, false);
|
||||
} else {
|
||||
api_path = json::parse(api_path_data)["api_path"].get<std::string>();
|
||||
}
|
||||
} else {
|
||||
api_path = json::parse(api_path_data)["api_path"].get<std::string>();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_file_list(api_file_list &list) const -> api_error {
|
||||
const auto cfg = config_.get_encrypt_config();
|
||||
|
||||
try {
|
||||
for (const auto &dir_entry :
|
||||
std::filesystem::recursive_directory_iterator(cfg.path)) {
|
||||
std::string api_path{};
|
||||
if (process_directory_entry(dir_entry, cfg, api_path)) {
|
||||
list.emplace_back(create_api_file(api_path, dir_entry.is_directory(),
|
||||
dir_entry.path().string()));
|
||||
}
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::raise_error(__FUNCTION__, ex, cfg.path,
|
||||
"failed to get file list");
|
||||
}
|
||||
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_file_size(const std::string &api_path,
|
||||
std::uint64_t &file_size) const
|
||||
-> api_error {
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, api_path, &source_path);
|
||||
if (source_path.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
try {
|
||||
file_size = utils::encryption::encrypting_reader::calculate_encrypted_size(
|
||||
source_path);
|
||||
return api_error::success;
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::raise_error(__FUNCTION__, ex, api_path,
|
||||
"failed to get file size");
|
||||
}
|
||||
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_filesystem_item(const std::string &api_path,
|
||||
bool directory,
|
||||
filesystem_item &fsi) const
|
||||
-> api_error {
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, api_path, &source_path);
|
||||
if (source_path.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
if (directory) {
|
||||
std::string api_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), dir_family_, source_path, &api_path);
|
||||
if (api_path.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
fsi.api_parent = utils::path::get_parent_api_path(api_path);
|
||||
fsi.api_path = api_path;
|
||||
fsi.directory = true;
|
||||
fsi.size = 0U;
|
||||
fsi.source_path = source_path;
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
std::string api_path_data{};
|
||||
db_->Get(rocksdb::ReadOptions(), file_family_, source_path, &api_path_data);
|
||||
if (api_path_data.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
auto data = json::parse(api_path_data);
|
||||
fsi.api_path = data["api_path"].get<std::string>();
|
||||
fsi.api_parent = utils::path::get_parent_api_path(fsi.api_path);
|
||||
fsi.directory = false;
|
||||
fsi.size = utils::encryption::encrypting_reader::calculate_encrypted_size(
|
||||
source_path);
|
||||
fsi.source_path = source_path;
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_filesystem_item_from_source_path(
|
||||
const std::string &source_path, filesystem_item &fsi) const -> api_error {
|
||||
std::string api_path{};
|
||||
auto res = get_api_path_from_source(source_path, api_path);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
bool exists{};
|
||||
res = is_directory(api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
return api_error::directory_exists;
|
||||
}
|
||||
|
||||
return get_filesystem_item(api_path, false, fsi);
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_filesystem_item_and_file(const std::string &api_path,
|
||||
api_file &file,
|
||||
filesystem_item &fsi) const
|
||||
-> api_error {
|
||||
bool exists{};
|
||||
auto res = is_directory(api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
if (exists) {
|
||||
return api_error::directory_exists;
|
||||
}
|
||||
|
||||
auto ret = get_filesystem_item(api_path, exists, fsi);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
file = create_api_file(api_path, false, fsi.source_path);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_pinned_files() const -> std::vector<std::string> {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_item_meta(const std::string &api_path,
|
||||
api_meta_map &meta) const -> api_error {
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, api_path, &source_path);
|
||||
if (source_path.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
bool exists{};
|
||||
auto res = is_directory(api_path, exists);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
auto file = create_api_file(api_path, exists, source_path);
|
||||
create_item_meta(meta, exists, file);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_item_meta(const std::string &api_path,
|
||||
const std::string &key,
|
||||
std::string &value) const -> api_error {
|
||||
api_meta_map meta{};
|
||||
auto ret = get_item_meta(api_path, meta);
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
value = meta[key];
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_total_drive_space() const -> std::uint64_t {
|
||||
const auto cfg = config_.get_encrypt_config();
|
||||
return utils::file::get_total_drive_space(cfg.path);
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_total_item_count() const -> std::uint64_t {
|
||||
std::uint64_t ret{};
|
||||
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), source_family_));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
ret++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto encrypt_provider::get_used_drive_space() const -> std::uint64_t {
|
||||
const auto cfg = config_.get_encrypt_config();
|
||||
return get_total_drive_space() - utils::file::get_free_drive_space(cfg.path);
|
||||
}
|
||||
|
||||
auto encrypt_provider::is_directory(const std::string &api_path,
|
||||
bool &exists) const -> api_error {
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, api_path, &source_path);
|
||||
if (source_path.empty()) {
|
||||
exists = false;
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
exists = utils::file::is_directory(source_path);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto encrypt_provider::is_file(const std::string &api_path, bool &exists) const
|
||||
-> api_error {
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, api_path, &source_path);
|
||||
if (source_path.empty()) {
|
||||
exists = false;
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
exists = utils::file::is_file(source_path);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto encrypt_provider::is_file_writeable(const std::string & /*api_path*/) const
|
||||
-> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto encrypt_provider::is_online() const -> bool {
|
||||
return std::filesystem::exists(config_.get_encrypt_config().path);
|
||||
}
|
||||
|
||||
auto encrypt_provider::is_rename_supported() const -> bool { return false; }
|
||||
|
||||
auto encrypt_provider::read_file_bytes(const std::string &api_path,
|
||||
std::size_t size, std::uint64_t offset,
|
||||
data_buffer &data,
|
||||
stop_type &stop_requested) -> api_error {
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, api_path, &source_path);
|
||||
if (source_path.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
std::string api_path_data{};
|
||||
db_->Get(rocksdb::ReadOptions(), file_family_, source_path, &api_path_data);
|
||||
if (api_path_data.empty()) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
std::uint64_t file_size{};
|
||||
if (not utils::file::get_file_size(source_path, file_size)) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list{};
|
||||
|
||||
const auto cfg = config_.get_encrypt_config();
|
||||
|
||||
unique_recur_mutex_lock reader_lookup_lock(reader_lookup_mtx_);
|
||||
|
||||
auto file_data = json::parse(api_path_data);
|
||||
if (file_data.at("original_file_size").get<std::uint64_t>() != file_size) {
|
||||
const auto relative_path =
|
||||
std::filesystem::path(source_path).lexically_relative(cfg.path);
|
||||
|
||||
auto ri = std::make_shared<reader_info>();
|
||||
ri->reader = std::make_unique<utils::encryption::encrypting_reader>(
|
||||
relative_path.filename().string(), source_path, stop_requested,
|
||||
cfg.encryption_token, relative_path.parent_path().string());
|
||||
reader_lookup_[source_path] = ri;
|
||||
iv_list = ri->reader->get_iv_list();
|
||||
|
||||
file_data["original_file_size"] = file_size;
|
||||
file_data["iv_list"] = iv_list;
|
||||
auto res = db_->Put(rocksdb::WriteOptions(), file_family_, source_path,
|
||||
file_data.dump());
|
||||
if (not res.ok()) {
|
||||
utils::error::raise_error(__FUNCTION__, res.code(), source_path,
|
||||
"failed to update meta db");
|
||||
return api_error::error;
|
||||
}
|
||||
} else {
|
||||
iv_list =
|
||||
file_data["iv_list"]
|
||||
.get<std::vector<
|
||||
std::array<unsigned char,
|
||||
crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>>();
|
||||
if (reader_lookup_.find(source_path) == reader_lookup_.end()) {
|
||||
auto ri = std::make_shared<reader_info>();
|
||||
ri->reader = std::make_unique<utils::encryption::encrypting_reader>(
|
||||
api_path, source_path, stop_requested, cfg.encryption_token,
|
||||
std::move(iv_list));
|
||||
reader_lookup_[source_path] = ri;
|
||||
}
|
||||
}
|
||||
|
||||
if (file_size == 0U || size == 0U) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto ri = reader_lookup_.at(source_path);
|
||||
ri->last_access_time = std::chrono::system_clock::now();
|
||||
reader_lookup_lock.unlock();
|
||||
|
||||
mutex_lock reader_lock(ri->reader_mtx);
|
||||
ri->reader->set_read_position(offset);
|
||||
data.resize(size);
|
||||
|
||||
const auto res = ri->reader->reader_function(data.data(), 1u, data.size(),
|
||||
ri->reader.get());
|
||||
if (res == 0) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
void encrypt_provider::remove_deleted_files() {
|
||||
struct removed_item {
|
||||
std::string api_path{};
|
||||
bool directory{};
|
||||
std::string source_path{};
|
||||
};
|
||||
|
||||
std::vector<removed_item> removed_list{};
|
||||
auto iterator = std::unique_ptr<rocksdb::Iterator>(
|
||||
db_->NewIterator(rocksdb::ReadOptions(), source_family_));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
auto source_path = iterator->value().ToString();
|
||||
if (not std::filesystem::exists(source_path)) {
|
||||
auto api_path =
|
||||
utils::string::split(iterator->key().ToString(), '|', false)[1U];
|
||||
|
||||
std::string value{};
|
||||
db_->Get(rocksdb::ReadOptions(), file_family_, source_path, &value);
|
||||
|
||||
removed_list.emplace_back(
|
||||
removed_item{api_path, value.empty(), source_path});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &item : removed_list) {
|
||||
if (not item.directory) {
|
||||
db_->Delete(rocksdb::WriteOptions(), source_family_, item.api_path);
|
||||
db_->Delete(rocksdb::WriteOptions(), file_family_, item.source_path);
|
||||
event_system::instance().raise<file_removed_externally>(item.api_path,
|
||||
item.source_path);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &item : removed_list) {
|
||||
if (item.directory) {
|
||||
db_->Delete(rocksdb::WriteOptions(), source_family_, item.api_path);
|
||||
db_->Delete(rocksdb::WriteOptions(), dir_family_, item.source_path);
|
||||
event_system::instance().raise<directory_removed_externally>(
|
||||
item.api_path, item.source_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto encrypt_provider::start(api_item_added_callback /*api_item_added*/,
|
||||
i_file_manager * /*fm*/) -> bool {
|
||||
if (not is_online()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto families = std::vector<rocksdb::ColumnFamilyDescriptor>();
|
||||
families.emplace_back(rocksdb::kDefaultColumnFamilyName,
|
||||
rocksdb::ColumnFamilyOptions());
|
||||
families.emplace_back("dir", rocksdb::ColumnFamilyOptions());
|
||||
families.emplace_back("file", rocksdb::ColumnFamilyOptions());
|
||||
families.emplace_back("source", rocksdb::ColumnFamilyOptions());
|
||||
|
||||
auto handles = std::vector<rocksdb::ColumnFamilyHandle *>();
|
||||
|
||||
utils::db::create_rocksdb(config_, DB_NAME, families, handles, db_);
|
||||
|
||||
std::size_t idx{};
|
||||
dir_family_ = handles[idx++];
|
||||
file_family_ = handles[idx++];
|
||||
source_family_ = handles[idx++];
|
||||
|
||||
const auto cfg = config_.get_encrypt_config();
|
||||
|
||||
std::string source_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), source_family_, "/", &source_path);
|
||||
if (source_path.empty()) {
|
||||
db_->Put(rocksdb::WriteOptions(), source_family_, "/", cfg.path);
|
||||
source_path = cfg.path;
|
||||
}
|
||||
|
||||
std::string dir_api_path{};
|
||||
db_->Get(rocksdb::ReadOptions(), dir_family_, source_path, &dir_api_path);
|
||||
if (dir_api_path.empty()) {
|
||||
db_->Put(rocksdb::WriteOptions(), dir_family_, source_path, "/");
|
||||
}
|
||||
|
||||
polling::instance().set_callback({"check_deleted", polling::frequency::low,
|
||||
[this]() { remove_deleted_files(); }});
|
||||
|
||||
event_system::instance().raise<service_started>("encrypt_provider");
|
||||
return true;
|
||||
}
|
||||
|
||||
void encrypt_provider::stop() {
|
||||
event_system::instance().raise<service_shutdown_begin>("encrypt_provider");
|
||||
polling::instance().remove_callback("check_deleted");
|
||||
db_.reset();
|
||||
event_system::instance().raise<service_shutdown_end>("encrypt_provider");
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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.
|
||||
*/
|
||||
#if defined(REPERTORY_TESTING_NEW)
|
||||
#include "providers/passthrough/passthroughprovider.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
std::string CPassthroughProvider::ConstructFullPath(const std::string &api_path) const {
|
||||
return utils::path::combine(passthroughLocation_, {api_path});
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::CreateDirectory(const std::string &api_path,
|
||||
const api_meta_map &meta) {
|
||||
const auto fullPath = ConstructFullPath(api_path);
|
||||
auto ret = utils::file::create_full_directory_path(fullPath) ? api_error::OSErrorCode
|
||||
: api_error::Success;
|
||||
if (ret == api_error::Success) {
|
||||
ret = set_item_meta(api_path, meta);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::get_file_list(ApiFileList &fileList) const {
|
||||
const auto fullPath = ConstructFullPath("/");
|
||||
const auto fl = utils::file::get_directory_files(fullPath, false, true);
|
||||
for (const auto &file : fl) {
|
||||
const auto api_path = utils::path::create_api_path(file.substr(fullPath.length()));
|
||||
/*
|
||||
struct ApiFile {
|
||||
std::string ApiFilePath{};
|
||||
std::string ApiParent{};
|
||||
std::uint64_t AccessedDate = 0;
|
||||
std::uint64_t ChangedDate = 0;
|
||||
std::uint64_t CreationDate = 0;
|
||||
std::string EncryptionToken{};
|
||||
std::uint64_t FileSize = 0;
|
||||
std::uint64_t ModifiedDate = 0;
|
||||
bool Recoverable = false;
|
||||
double Redundancy = 0.0;
|
||||
std::string SourceFilePath{};
|
||||
};
|
||||
*/
|
||||
ApiFile apiFile{
|
||||
api_path,
|
||||
utils::path::get_parent_api_path(api_path),
|
||||
};
|
||||
// apiFile.Recoverable = not IsProcessing(api_path);
|
||||
apiFile.Redundancy = 3.0;
|
||||
apiFile.SourceFilePath = file;
|
||||
// utils::file::UpdateApiFileInfo(apiFile);
|
||||
fileList.emplace_back(apiFile);
|
||||
}
|
||||
|
||||
return api_error::Success;
|
||||
}
|
||||
|
||||
std::uint64_t CPassthroughProvider::get_directory_item_count(const std::string &api_path) const {
|
||||
const auto fullPath = ConstructFullPath(api_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::get_directory_items(const std::string &api_path,
|
||||
directory_item_list &list) const {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::GetFile(const std::string &api_path, ApiFile &file) const {
|
||||
return api_error::Error;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::GetFileSize(const std::string &api_path,
|
||||
std::uint64_t &fileSize) const {
|
||||
return api_error::Error;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::get_filesystem_item(const std::string &api_path,
|
||||
const bool &directory,
|
||||
FileSystemItem &fileSystemItem) const {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error
|
||||
CPassthroughProvider::get_filesystem_item_from_source_path(const std::string &sourceFilePath,
|
||||
FileSystemItem &fileSystemItem) const {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::get_item_meta(const std::string &api_path,
|
||||
api_meta_map &meta) const {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::get_item_meta(const std::string &api_path,
|
||||
const std::string &key, std::string &value) const {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
std::uint64_t CPassthroughProvider::get_total_drive_space() const { return 0; }
|
||||
|
||||
std::uint64_t CPassthroughProvider::get_total_item_count() const { return 0; }
|
||||
|
||||
std::uint64_t CPassthroughProvider::get_used_drive_space() const { return 0; }
|
||||
|
||||
bool CPassthroughProvider::IsDirectory(const std::string &api_path) const {
|
||||
const auto fullPath = ConstructFullPath(api_path);
|
||||
return utils::file::is_directory(fullPath);
|
||||
}
|
||||
|
||||
bool CPassthroughProvider::IsFile(const std::string &api_path) const {
|
||||
const auto fullPath = ConstructFullPath(api_path);
|
||||
return utils::file::is_file(fullPath);
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::notify_file_added(const std::string &api_path,
|
||||
const std::string &api_parent,
|
||||
const std::uint64_t &size) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::read_file_bytes(const std::string &apiFilepath,
|
||||
const std::size_t &size,
|
||||
const std::uint64_t &offset,
|
||||
std::vector<char> &data,
|
||||
const bool &stop_requested) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::RemoveDirectory(const std::string &api_path) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::RemoveFile(const std::string &api_path) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::RenameFile(const std::string &fromApiPath,
|
||||
const std::string &toApiPath) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::remove_item_meta(const std::string &api_path,
|
||||
const std::string &key) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::set_item_meta(const std::string &api_path,
|
||||
const std::string &key, const std::string &value) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::set_item_meta(const std::string &api_path,
|
||||
const api_meta_map &meta) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
api_error CPassthroughProvider::set_source_path(const std::string &api_path,
|
||||
const std::string &sourcePath) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
|
||||
bool CPassthroughProvider::Start(ApiItemAdded apiItemAdded, i_open_file_table *openFileTable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void CPassthroughProvider::Stop() {}
|
||||
|
||||
api_error CPassthroughProvider::upload_file(const std::string &api_path,
|
||||
const std::string &sourcePath,
|
||||
const std::string &encryptionToken) {
|
||||
return api_error::NotImplemented;
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif // defined(REPERTORY_TESTING_NEW)
|
||||
@@ -1,76 +1,82 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "providers/provider.hpp"
|
||||
#include "comm/aws_s3/aws_s3_comm.hpp"
|
||||
#include "comm/curl/curl_comm.hpp"
|
||||
#include "comm/i_comm.hpp"
|
||||
#include "comm/i_s3_comm.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "comm/curl/curl_comm.hpp"
|
||||
#include "comm/i_http_comm.hpp"
|
||||
#include "comm/i_s3_comm.hpp"
|
||||
#include "comm/s3/s3_comm.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "providers/passthrough/passthroughprovider.hpp"
|
||||
#include "providers/encrypt/encrypt_provider.hpp"
|
||||
#include "providers/s3/s3_provider.hpp"
|
||||
#include "providers/sia/sia_provider.hpp"
|
||||
#include "providers/skynet/skynet_provider.hpp"
|
||||
#include "types/startup_exception.hpp"
|
||||
|
||||
namespace repertory {
|
||||
template <typename i, typename t>
|
||||
static void create_comm(std::unique_ptr<i> &comm, app_config &config) {
|
||||
namespace {
|
||||
template <typename intf_t, typename comm_t, typename config_t>
|
||||
inline void create_comm(std::unique_ptr<intf_t> &comm, const config_t &config) {
|
||||
if (comm) {
|
||||
throw startup_exception("'create_provider' should only be called once");
|
||||
throw repertory::startup_exception(
|
||||
"'create_provider' should only be called once");
|
||||
}
|
||||
comm = std::make_unique<t>(config);
|
||||
}
|
||||
|
||||
std::unique_ptr<i_provider> create_provider(const provider_type &pt, app_config &config) {
|
||||
comm = std::make_unique<comm_t>(config);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace repertory {
|
||||
auto create_provider(const provider_type &pt, app_config &config)
|
||||
-> std::unique_ptr<i_provider> {
|
||||
static std::mutex mutex;
|
||||
mutex_lock lock(mutex);
|
||||
|
||||
static std::unique_ptr<i_comm> comm;
|
||||
static std::unique_ptr<i_http_comm> comm;
|
||||
#if defined(REPERTORY_ENABLE_S3)
|
||||
static std::unique_ptr<i_s3_comm> s3_comm;
|
||||
static std::unique_ptr<i_s3_comm> s3_comm_;
|
||||
#endif // defined(REPERTORY_ENABLE_S3)
|
||||
|
||||
switch (pt) {
|
||||
case provider_type::sia:
|
||||
create_comm<i_comm, curl_comm>(comm, config);
|
||||
return std::unique_ptr<i_provider>(dynamic_cast<i_provider *>(new sia_provider(config, *comm)));
|
||||
case provider_type::sia: {
|
||||
create_comm<i_http_comm, curl_comm, host_config>(comm,
|
||||
config.get_host_config());
|
||||
return std::unique_ptr<i_provider>(
|
||||
dynamic_cast<i_provider *>(new sia_provider(config, *comm)));
|
||||
}
|
||||
#if defined(REPERTORY_ENABLE_S3)
|
||||
case provider_type::s3:
|
||||
create_comm<i_s3_comm, aws_s3_comm>(s3_comm, config);
|
||||
case provider_type::s3: {
|
||||
create_comm<i_s3_comm, s3_comm, app_config>(s3_comm_, config);
|
||||
return std::unique_ptr<i_provider>(
|
||||
dynamic_cast<i_provider *>(new s3_provider(config, *s3_comm)));
|
||||
dynamic_cast<i_provider *>(new s3_provider(config, *s3_comm_)));
|
||||
}
|
||||
#endif // defined(REPERTORY_ENABLE_S3)
|
||||
#if defined(REPERTORY_ENABLE_SKYNET)
|
||||
case provider_type::skynet:
|
||||
create_comm<i_comm, curl_comm>(comm, config);
|
||||
case provider_type::encrypt: {
|
||||
return std::unique_ptr<i_provider>(
|
||||
dynamic_cast<i_provider *>(new skynet_provider(config, *comm)));
|
||||
#endif // defined(REPERTORY_ENABLE_SKYNET)
|
||||
#if defined(REPERTORY_TESTING_NEW)
|
||||
case provider_type::passthrough:
|
||||
return std::unique_ptr<i_provider>(
|
||||
dynamic_cast<i_provider *>(new CPassthroughProvider(config)));
|
||||
#endif // defined(REPERTORY_TESTING_NEW)
|
||||
dynamic_cast<i_provider *>(new encrypt_provider(config)));
|
||||
}
|
||||
case provider_type::unknown:
|
||||
default:
|
||||
throw startup_exception("provider not supported: " + app_config::get_provider_display_name(pt));
|
||||
throw startup_exception("provider not supported: " +
|
||||
app_config::get_provider_display_name(pt));
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,649 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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.
|
||||
*/
|
||||
#if defined(REPERTORY_ENABLE_SKYNET)
|
||||
|
||||
#include "providers/skynet/skynet_provider.hpp"
|
||||
#include "comm/i_comm.hpp"
|
||||
#include "app_config.hpp"
|
||||
#include "drives/i_open_file_table.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "types/skynet.hpp"
|
||||
#include "types/startup_exception.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/global_data.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
skynet_provider::skynet_provider(app_config &config, i_comm &comm)
|
||||
: base_provider(config),
|
||||
comm_(comm),
|
||||
directory_db_(config),
|
||||
upload_manager_(
|
||||
config, [this](const std::string &api_path) -> bool { return this->is_file(api_path); },
|
||||
[this](const upload_manager::upload &upload, json &data, json &error) -> api_error {
|
||||
return this->upload_handler(upload, data, error);
|
||||
},
|
||||
[this](const std::string &api_path, const std::string &source_path, const json &data) {
|
||||
return this->upload_completed(api_path, source_path, data);
|
||||
}) {
|
||||
next_download_index_ = 0u;
|
||||
next_upload_index_ = 0u;
|
||||
update_portal_list();
|
||||
E_SUBSCRIBE_EXACT(skynet_portal_list_changed,
|
||||
[this](const skynet_portal_list_changed &) { this->update_portal_list(); });
|
||||
|
||||
// Remove legacy encrypted files
|
||||
api_file_list list;
|
||||
get_file_list(list);
|
||||
for (const auto &file : list) {
|
||||
std::string token;
|
||||
get_item_meta(file.api_path, "token", token);
|
||||
if (not token.empty()) {
|
||||
remove_file(file.api_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api_error skynet_provider::create_directory(const std::string &api_path, const api_meta_map &meta) {
|
||||
auto ret = api_error::success;
|
||||
if (utils::path::is_trash_directory(api_path)) {
|
||||
ret = api_error::access_denied;
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
ret = is_directory(api_path) ? api_error::directory_exists
|
||||
: is_file(api_path) ? api_error::file_exists
|
||||
: api_error::success;
|
||||
if (ret != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
if ((ret = directory_db_.create_directory(api_path)) != api_error::success) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
set_item_meta(api_path, meta);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error skynet_provider::create_file(const std::string &api_path, api_meta_map &meta) {
|
||||
if (meta[META_SIZE].empty()) {
|
||||
meta[META_SIZE] = "0";
|
||||
}
|
||||
|
||||
// When META_ID is present, an external import is occurring.
|
||||
// Need to skip the encryption token in this scenario.
|
||||
if (meta[META_ID].empty() && meta[META_ENCRYPTION_TOKEN].empty()) {
|
||||
meta[META_ENCRYPTION_TOKEN] = get_config().get_skynet_config().encryption_token;
|
||||
}
|
||||
|
||||
auto ret = base_provider::create_file(api_path, meta);
|
||||
if (ret == api_error::success) {
|
||||
ret = directory_db_.create_file(api_path);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
json skynet_provider::export_all() const {
|
||||
json ret = {{"success", std::vector<json>()}, {"failed", std::vector<std::string>()}};
|
||||
|
||||
api_file_list list;
|
||||
get_file_list(list);
|
||||
for (const auto &file : list) {
|
||||
process_export(ret, file.api_path);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
json skynet_provider::export_list(const std::vector<std::string> &api_path_list) const {
|
||||
json ret = {{"success", std::vector<json>()}, {"failed", std::vector<std::string>()}};
|
||||
|
||||
for (const auto &api_path : api_path_list) {
|
||||
process_export(ret, api_path);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::uint64_t skynet_provider::get_directory_item_count(const std::string &api_path) const {
|
||||
return is_directory(api_path) ? directory_db_.get_directory_item_count(api_path) : 0u;
|
||||
}
|
||||
|
||||
api_error skynet_provider::get_directory_items(const std::string &api_path,
|
||||
directory_item_list &list) const {
|
||||
if (is_file(api_path)) {
|
||||
return api_error::item_is_file;
|
||||
}
|
||||
|
||||
if (not is_directory(api_path)) {
|
||||
const_cast<skynet_provider *>(this)->remove_item_meta(api_path);
|
||||
return api_error::directory_not_found;
|
||||
}
|
||||
|
||||
directory_db_.populate_sub_directories(
|
||||
api_path,
|
||||
[this](directory_item &di, const bool &) {
|
||||
this->get_item_meta(di.api_path, di.meta);
|
||||
this->oft_->update_directory_item(di);
|
||||
},
|
||||
list);
|
||||
|
||||
directory_db_.populate_directory_files(
|
||||
api_path,
|
||||
[this](directory_item &di, const bool &) {
|
||||
di.api_parent = utils::path::get_parent_api_path(di.api_path);
|
||||
this->get_item_meta(di.api_path, di.meta);
|
||||
this->oft_->update_directory_item(di);
|
||||
},
|
||||
list);
|
||||
|
||||
std::sort(list.begin(), list.end(), [](const auto &a, const auto &b) -> bool {
|
||||
return (a.directory && not b.directory) ? true
|
||||
: (b.directory && not a.directory) ? false
|
||||
: (a.api_path.compare(b.api_path) < 0);
|
||||
});
|
||||
|
||||
list.insert(list.begin(), directory_item{
|
||||
"..",
|
||||
"",
|
||||
true,
|
||||
});
|
||||
list.insert(list.begin(), directory_item{
|
||||
".",
|
||||
"",
|
||||
true,
|
||||
});
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
api_error skynet_provider::get_file(const std::string &api_path, api_file &file) const {
|
||||
const auto ret =
|
||||
directory_db_.get_file(api_path, file, [this](api_file &file) { populate_api_file(file); });
|
||||
|
||||
if (ret != api_error::success) {
|
||||
if (not is_directory(api_path)) {
|
||||
const_cast<skynet_provider *>(this)->remove_item_meta(api_path);
|
||||
}
|
||||
event_system::instance().raise<file_get_failed>(api_path,
|
||||
std::to_string(static_cast<int>(ret)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error skynet_provider::get_file_list(api_file_list &list) const {
|
||||
const auto ret =
|
||||
directory_db_.get_file_list(list, [this](api_file &file) { populate_api_file(file); });
|
||||
if (ret != api_error::success) {
|
||||
event_system::instance().raise<file_get_api_list_failed>(std::to_string(static_cast<int>(ret)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error skynet_provider::get_file_size(const std::string &api_path,
|
||||
std::uint64_t &file_size) const {
|
||||
api_file file{};
|
||||
const auto ret = get_file(api_path, file);
|
||||
if (ret == api_error::success) {
|
||||
file_size = file.file_size;
|
||||
} else {
|
||||
event_system::instance().raise<file_get_size_failed>(api_path,
|
||||
std::to_string(static_cast<int>(ret)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
host_config skynet_provider::get_host_config(const bool &upload) {
|
||||
const auto format_host_config = [&](host_config hc) {
|
||||
hc.path = upload ? "/skynet/skyfile" : "/";
|
||||
return hc;
|
||||
};
|
||||
|
||||
unique_mutex_lock portal_lock(portal_mutex_);
|
||||
const auto portal_list = upload ? upload_list_ : download_list_;
|
||||
portal_lock.unlock();
|
||||
|
||||
auto &next_upload_index = upload ? next_upload_index_ : next_download_index_;
|
||||
auto idx = next_upload_index++;
|
||||
if (idx >= portal_list->size()) {
|
||||
idx = next_upload_index = 0u;
|
||||
}
|
||||
|
||||
return format_host_config((*portal_list)[idx]);
|
||||
}
|
||||
|
||||
std::size_t skynet_provider::get_retry_count() const {
|
||||
mutex_lock portal_lock(portal_mutex_);
|
||||
return std::max(download_list_->size(),
|
||||
static_cast<std::size_t>(get_config().get_retry_read_count()));
|
||||
}
|
||||
|
||||
api_error skynet_provider::get_skynet_metadata(const std::string &skylink, json &json_meta) {
|
||||
auto ret = api_error::error;
|
||||
|
||||
http_headers headers;
|
||||
const auto retry_count = get_retry_count();
|
||||
|
||||
for (std::size_t i = 0u; (ret != api_error::success) && (i < retry_count); i++) {
|
||||
json data, error;
|
||||
const auto hc = get_host_config(false);
|
||||
if (comm_.get(hc, "/skynet/metadata/" + skylink, data, error) == api_error::success) {
|
||||
headers["skynet-file-metadata"] = data.dump();
|
||||
ret = api_error::success;
|
||||
} else {
|
||||
std::vector<char> buffer;
|
||||
if (comm_.get_range_and_headers(hc, utils::path::create_api_path(skylink), 0u,
|
||||
{{"format", "concat"}}, "", buffer, {{0, 0}}, error, headers,
|
||||
stop_requested_) == api_error::success) {
|
||||
ret = api_error::success;
|
||||
} else if (not error.empty()) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, error.dump(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == api_error::success) {
|
||||
json_meta = json::parse(headers["skynet-file-metadata"]);
|
||||
if (json_meta["subfiles"].empty()) {
|
||||
auto sub_file = json_meta;
|
||||
sub_file["len"] =
|
||||
utils::string::to_uint64(utils::string::split(headers["content-range"], '/')[1]);
|
||||
json sub_files = {{json_meta["filename"], sub_file}};
|
||||
json_meta["subfiles"] = sub_files;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error skynet_provider::import_skylink(const skylink_import &si) {
|
||||
json json_meta;
|
||||
auto ret = get_skynet_metadata(si.skylink, json_meta);
|
||||
if (ret == api_error::success) {
|
||||
const auto encrypted = not si.token.empty();
|
||||
for (auto sub_file : json_meta["subfiles"]) {
|
||||
const auto meta_file_name = sub_file["filename"].get<std::string>();
|
||||
auto file_name = meta_file_name;
|
||||
if (encrypted) {
|
||||
if ((ret = utils::encryption::decrypt_file_name(si.token, file_name)) !=
|
||||
api_error::success) {
|
||||
event_system::instance().raise<skynet_import_decryption_failed>(
|
||||
si.skylink, sub_file["filename"], ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == api_error::success) {
|
||||
const auto api_path =
|
||||
utils::path::create_api_path(utils::path::combine(si.directory, {file_name}));
|
||||
const auto api_parent = utils::path::get_parent_api_path(api_path);
|
||||
const auto parts = utils::string::split(api_parent, '/', false);
|
||||
|
||||
std::string sub_directory = "/";
|
||||
for (std::size_t i = 0u; (ret == api_error::success) && (i < parts.size()); i++) {
|
||||
sub_directory =
|
||||
utils::path::create_api_path(utils::path::combine(sub_directory, {parts[i]}));
|
||||
if (not is_directory(sub_directory)) {
|
||||
if ((ret = directory_db_.create_directory(sub_directory)) == api_error::success) {
|
||||
base_provider::notify_directory_added(
|
||||
sub_directory, utils::path::get_parent_api_path(sub_directory));
|
||||
} else {
|
||||
event_system::instance().raise<skynet_import_directory_failed>(si.skylink,
|
||||
sub_directory, ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == api_error::success) {
|
||||
auto file_size = sub_file["len"].get<std::uint64_t>();
|
||||
if (encrypted) {
|
||||
file_size = utils::encryption::encrypting_reader::calculate_decrypted_size(file_size);
|
||||
}
|
||||
|
||||
const auto skylink =
|
||||
si.skylink + ((json_meta["filename"].get<std::string>() == meta_file_name)
|
||||
? ""
|
||||
: "/" + meta_file_name);
|
||||
api_meta_map meta{};
|
||||
meta[META_ID] = json({{"skylink", skylink}}).dump();
|
||||
meta[META_ENCRYPTION_TOKEN] = si.token;
|
||||
if ((ret = create_file(api_path, meta)) == api_error::success) {
|
||||
const auto now = utils::get_file_time_now();
|
||||
api_item_added_(api_path, api_parent, "", false, now, now, now, now);
|
||||
|
||||
if (file_size > 0u) {
|
||||
set_item_meta(api_path, META_SIZE, std::to_string(file_size));
|
||||
global_data::instance().increment_used_drive_space(file_size);
|
||||
}
|
||||
} else {
|
||||
event_system::instance().raise<skynet_import_file_failed>(si.skylink, api_path, ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool skynet_provider::is_directory(const std::string &api_path) const {
|
||||
return (api_path == "/") || directory_db_.is_directory(api_path);
|
||||
}
|
||||
|
||||
bool skynet_provider::is_file(const std::string &api_path) const {
|
||||
return (api_path != "/") && directory_db_.is_file(api_path);
|
||||
}
|
||||
|
||||
bool skynet_provider::is_file_writeable(const std::string &api_path) const {
|
||||
auto ret = true;
|
||||
std::string id;
|
||||
get_item_meta(api_path, META_ID, id);
|
||||
if (not id.empty()) {
|
||||
try {
|
||||
const auto skynet_data = json::parse(id);
|
||||
const auto skylink = skynet_data["skylink"].get<std::string>();
|
||||
ret = not utils::string::contains(skylink, "/");
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
__FUNCTION__, e.what() ? e.what() : "exception occurred");
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool skynet_provider::is_processing(const std::string &api_path) const {
|
||||
return upload_manager_.is_processing(api_path);
|
||||
}
|
||||
|
||||
void skynet_provider::notify_directory_added(const std::string &api_path,
|
||||
const std::string &api_parent) {
|
||||
if (api_path == "/") {
|
||||
if (directory_db_.create_directory("/") == api_error::success) {
|
||||
base_provider::notify_directory_added(api_path, api_parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api_error skynet_provider::notify_file_added(const std::string &, const std::string &,
|
||||
const std::uint64_t &) {
|
||||
return api_error::not_implemented;
|
||||
}
|
||||
|
||||
void skynet_provider::populate_api_file(api_file &file) const {
|
||||
api_meta_map meta{};
|
||||
this->get_item_meta(file.api_path, meta);
|
||||
|
||||
file.api_parent = utils::path::get_parent_api_path(file.api_path);
|
||||
file.accessed_date = utils::string::to_uint64(meta[META_ACCESSED]);
|
||||
file.changed_date = utils::string::to_uint64(meta[META_MODIFIED]);
|
||||
file.created_date = utils::string::to_uint64(meta[META_CREATION]);
|
||||
file.encryption_token = meta[META_ENCRYPTION_TOKEN].empty() ? "" : meta[META_ENCRYPTION_TOKEN];
|
||||
file.file_size = utils::string::to_uint64(meta[META_SIZE]);
|
||||
file.modified_date = utils::string::to_uint64(meta[META_MODIFIED]);
|
||||
file.recoverable = not is_processing(file.api_path);
|
||||
file.redundancy = 3.0;
|
||||
file.source_path = meta[META_SOURCE];
|
||||
}
|
||||
|
||||
void skynet_provider::process_export(json &result, const std::string &api_path) const {
|
||||
try {
|
||||
std::string id;
|
||||
std::string token;
|
||||
if (is_file(api_path) && (get_item_meta(api_path, META_ID, id) == api_error::success) &&
|
||||
(get_item_meta(api_path, META_ENCRYPTION_TOKEN, token) == api_error::success)) {
|
||||
auto directory = utils::path::get_parent_api_path(api_path);
|
||||
|
||||
const auto skylink = json::parse(id)["skylink"].get<std::string>();
|
||||
if (utils::string::contains(skylink, "/")) {
|
||||
const auto pos = skylink.find('/');
|
||||
const auto path =
|
||||
utils::path::create_api_path(utils::path::remove_file_name(skylink.substr(pos)));
|
||||
if (path != "/") {
|
||||
directory =
|
||||
utils::path::create_api_path(directory.substr(0, directory.length() - path.length()));
|
||||
}
|
||||
}
|
||||
|
||||
result["success"].emplace_back(
|
||||
json({{"skylink", skylink},
|
||||
{"token", token},
|
||||
{"directory", directory},
|
||||
{"filename", utils::path::strip_to_file_name(api_path)}}));
|
||||
} else {
|
||||
result["failed"].emplace_back(api_path);
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
result["failed"].emplace_back(api_path);
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
__FUNCTION__, e.what() ? e.what() : "export failed: " + api_path);
|
||||
}
|
||||
}
|
||||
|
||||
api_error skynet_provider::read_file_bytes(const std::string &api_path, const std::size_t &size,
|
||||
const std::uint64_t &offset, std::vector<char> &data,
|
||||
const bool &stop_requested) {
|
||||
if (size == 0u) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
std::string id;
|
||||
auto ret = get_item_meta(api_path, META_ID, id);
|
||||
if (ret == api_error::success) {
|
||||
ret = api_error::download_failed;
|
||||
|
||||
const auto skynet_data = json::parse(id);
|
||||
const auto path = utils::path::create_api_path(skynet_data["skylink"].get<std::string>());
|
||||
const auto ranges = http_ranges({{offset, offset + size - 1}});
|
||||
|
||||
std::string encryption_token;
|
||||
get_item_meta(api_path, META_ENCRYPTION_TOKEN, encryption_token);
|
||||
|
||||
std::uint64_t file_size{};
|
||||
{
|
||||
std::string temp;
|
||||
get_item_meta(api_path, META_SIZE, temp);
|
||||
file_size = utils::string::to_uint64(temp);
|
||||
}
|
||||
|
||||
const auto retry_count = get_retry_count();
|
||||
for (std::size_t i = 0u; not stop_requested && (ret != api_error::success) && (i < retry_count);
|
||||
i++) {
|
||||
json error;
|
||||
ret = (comm_.get_range(get_host_config(false), path, file_size, {{"format", "concat"}},
|
||||
encryption_token, data, ranges, error,
|
||||
stop_requested) == api_error::success)
|
||||
? api_error::success
|
||||
: api_error::download_failed;
|
||||
if (ret != api_error::success) {
|
||||
event_system::instance().raise<file_read_bytes_failed>(api_path, error.dump(2), i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error skynet_provider::remove_directory(const std::string &api_path) {
|
||||
const auto ret = directory_db_.remove_directory(api_path);
|
||||
if (ret == api_error::success) {
|
||||
remove_item_meta(api_path);
|
||||
event_system::instance().raise<directory_removed>(api_path);
|
||||
} else {
|
||||
event_system::instance().raise<directory_remove_failed>(api_path,
|
||||
std::to_string(static_cast<int>(ret)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error skynet_provider::remove_file(const std::string &api_path) {
|
||||
upload_manager_.remove_upload(api_path);
|
||||
const auto ret =
|
||||
directory_db_.remove_file(api_path) ? api_error::success : api_error::item_not_found;
|
||||
if (ret == api_error::success) {
|
||||
remove_item_meta(api_path);
|
||||
event_system::instance().raise<file_removed>(api_path);
|
||||
} else {
|
||||
event_system::instance().raise<file_remove_failed>(api_path,
|
||||
std::to_string(static_cast<int>(ret)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error skynet_provider::rename_file(const std::string &from_api_path,
|
||||
const std::string &to_api_path) {
|
||||
std::string id;
|
||||
auto ret = get_item_meta(from_api_path, META_ID, id);
|
||||
if (ret == api_error::success) {
|
||||
ret = api_error::access_denied;
|
||||
|
||||
const auto skynet_data = json::parse(id.empty() ? R"({"skylink":""})" : id);
|
||||
const auto skylink = skynet_data["skylink"].get<std::string>();
|
||||
if (utils::string::contains(skylink, "/")) {
|
||||
const auto pos = skylink.find('/');
|
||||
const auto len = skylink.size() - pos;
|
||||
if (to_api_path.size() >= len) {
|
||||
const auto comp1 = to_api_path.substr(to_api_path.size() - len);
|
||||
const auto comp2 = skylink.substr(pos);
|
||||
ret = (comp1 == comp2) ? api_error::success : ret;
|
||||
}
|
||||
} else {
|
||||
ret = (skylink.empty() || (utils::path::strip_to_file_name(from_api_path) ==
|
||||
utils::path::strip_to_file_name(to_api_path)))
|
||||
? api_error::success
|
||||
: ret;
|
||||
}
|
||||
|
||||
if (ret == api_error::success) {
|
||||
std::string current_source;
|
||||
if ((ret = get_item_meta(from_api_path, META_SOURCE, current_source)) == api_error::success) {
|
||||
if (not upload_manager_.execute_if_not_processing({from_api_path, to_api_path}, [&]() {
|
||||
if ((ret = directory_db_.rename_file(from_api_path, to_api_path)) ==
|
||||
api_error::success) {
|
||||
meta_db_.rename_item_meta(current_source, from_api_path, to_api_path);
|
||||
}
|
||||
})) {
|
||||
ret = api_error::file_in_use;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool skynet_provider::start(api_item_added_callback api_item_added, i_open_file_table *oft) {
|
||||
const auto ret = base_provider::start(api_item_added, oft);
|
||||
if (not ret) {
|
||||
api_file_list list;
|
||||
if (get_file_list(list) != api_error::success) {
|
||||
throw startup_exception("failed to determine used space");
|
||||
}
|
||||
|
||||
const auto total_size =
|
||||
std::accumulate(list.begin(), list.end(), std::uint64_t(0),
|
||||
[](std::uint64_t t, const api_file &file) { return t + file.file_size; });
|
||||
global_data::instance().initialize_used_drive_space(total_size);
|
||||
upload_manager_.start();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void skynet_provider::stop() {
|
||||
stop_requested_ = true;
|
||||
upload_manager_.stop();
|
||||
}
|
||||
|
||||
bool skynet_provider::update_portal_list() {
|
||||
auto portal_list = get_config().get_skynet_config().portal_list;
|
||||
auto download_portal_list = std::make_shared<std::vector<host_config>>();
|
||||
auto upload_portal_list = std::make_shared<std::vector<host_config>>();
|
||||
|
||||
std::copy_if(portal_list.begin(), portal_list.end(), std::back_inserter(*upload_portal_list),
|
||||
[](const auto &portal) -> bool {
|
||||
return not portal.auth_url.empty() && not portal.auth_user.empty();
|
||||
});
|
||||
for (const auto &portal : portal_list) {
|
||||
if (upload_portal_list->empty()) {
|
||||
upload_portal_list->emplace_back(portal);
|
||||
}
|
||||
download_portal_list->emplace_back(portal);
|
||||
}
|
||||
|
||||
unique_mutex_lock portal_lock(portal_mutex_);
|
||||
download_list_ = std::move(download_portal_list);
|
||||
upload_list_ = std::move(upload_portal_list);
|
||||
portal_lock.unlock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void skynet_provider::upload_completed(const std::string &api_path, const std::string &,
|
||||
const json &data) {
|
||||
set_item_meta(api_path, META_ID, data.dump());
|
||||
}
|
||||
|
||||
api_error skynet_provider::upload_file(const std::string &api_path, const std::string &source_path,
|
||||
const std::string &encryption_token) {
|
||||
std::uint64_t file_size = 0u;
|
||||
utils::file::get_file_size(source_path, file_size);
|
||||
auto ret = set_source_path(api_path, source_path);
|
||||
if (ret == api_error::success) {
|
||||
if (((ret = set_item_meta(api_path, META_SIZE, std::to_string(file_size))) ==
|
||||
api_error::success) &&
|
||||
((ret = set_item_meta(api_path, META_ENCRYPTION_TOKEN, encryption_token)) ==
|
||||
api_error::success) &&
|
||||
(file_size != 0u)) {
|
||||
ret = upload_manager_.queue_upload(api_path, source_path, encryption_token);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error skynet_provider::upload_handler(const upload_manager::upload &upload, json &data,
|
||||
json &error) {
|
||||
const auto file_name = utils::path::strip_to_file_name(upload.api_path);
|
||||
|
||||
auto ret = api_error::upload_failed;
|
||||
if (not upload.cancel) {
|
||||
event_system::instance().raise<file_upload_begin>(upload.api_path, upload.source_path);
|
||||
ret = (comm_.post_multipart_file(get_host_config(true), "", file_name, upload.source_path,
|
||||
upload.encryption_token, data, error,
|
||||
upload.cancel) == api_error::success)
|
||||
? api_error::success
|
||||
: api_error::upload_failed;
|
||||
}
|
||||
|
||||
event_system::instance().raise<file_upload_end>(upload.api_path, upload.source_path, ret);
|
||||
return ret;
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif // defined(REPERTORY_ENABLE_SKYNET)
|
||||
@@ -1,240 +1,267 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "rpc/client/client.hpp"
|
||||
#include "comm/curl/curl_resolver.hpp"
|
||||
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/Base64.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
client::client(rpc_host_info host_info) : host_info_(std::move(host_info)) { request_id_ = 0u; }
|
||||
|
||||
rpc_response client::export_list(const std::vector<std::string> &paths) {
|
||||
auto ret = make_request(rpc_method::export_links, {paths});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
}
|
||||
|
||||
return ret;
|
||||
client::client(rpc_host_info host_info) : host_info_(std::move(host_info)) {
|
||||
request_id_ = 0u;
|
||||
}
|
||||
|
||||
rpc_response client::export_all() {
|
||||
auto ret = make_request(rpc_method::export_links, {}, 60000u);
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
auto client::get_drive_information() -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp = cli.Get("/api/v1/" + rpc_method::get_drive_information);
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, json::parse(resp->body)};
|
||||
}
|
||||
|
||||
rpc_response client::get_drive_information() {
|
||||
auto ret = make_request(rpc_method::get_drive_information, {});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
auto client::get_config() -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp = cli.Get("/api/v1/" + rpc_method::get_config);
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, json::parse(resp->body)};
|
||||
}
|
||||
|
||||
rpc_response client::get_config() {
|
||||
auto ret = make_request(rpc_method::get_config, {});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
auto client::get_config_value_by_name(const std::string &name) -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Params params{{"name", name}};
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp =
|
||||
cli.Get("/api/v1/" + rpc_method::get_config_value_by_name, params, {});
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, json::parse(resp->body)};
|
||||
}
|
||||
|
||||
rpc_response client::get_config_value_by_name(const std::string &name) {
|
||||
auto ret = make_request(rpc_method::get_config_value_by_name, {name});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
if (ret.data["result"]["value"].get<std::string>().empty()) {
|
||||
ret.response_type = rpc_response_type::config_value_not_found;
|
||||
} else {
|
||||
ret.data = ret.data["result"];
|
||||
}
|
||||
auto client::get_directory_items(const std::string &api_path) -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Params params{{"api_path", api_path}};
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp = cli.Get("/api/v1/" + rpc_method::get_directory_items, params, {});
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, json::parse(resp->body)};
|
||||
}
|
||||
|
||||
rpc_response client::get_directory_items(const std::string &api_path) {
|
||||
auto ret = make_request(rpc_method::get_directory_items, {api_path});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
auto client::get_open_files() -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp = cli.Get("/api/v1/" + rpc_method::get_open_files);
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, json::parse(resp->body)};
|
||||
}
|
||||
|
||||
rpc_response client::get_open_files() {
|
||||
auto ret = make_request(rpc_method::get_open_files, {});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
auto client::get_pinned_files() -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp = cli.Get("/api/v1/" + rpc_method::get_pinned_files);
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, json::parse(resp->body)};
|
||||
}
|
||||
|
||||
rpc_response client::get_pinned_files() {
|
||||
auto ret = make_request(rpc_method::get_pinned_files, {});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
auto client::pin_file(const std::string &api_path) -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Params params{{"api_path", api_path}};
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp = cli.Post("/api/v1/" + rpc_method::pin_file, params);
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, {}};
|
||||
}
|
||||
|
||||
rpc_response client::import_skylink(const skylink_import_list &list) {
|
||||
std::vector<json> json_list;
|
||||
for (const auto &skynet_import : list) {
|
||||
json_list.emplace_back(skynet_import.to_json());
|
||||
auto client::pinned_status(const std::string &api_path) -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Params params{{"api_path", api_path}};
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp = cli.Get("/api/v1/" + rpc_method::pinned_status, params, {});
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
auto ret = make_request(rpc_method::import, {json(json_list)}, 60000u);
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, json::parse(resp->body)};
|
||||
}
|
||||
|
||||
rpc_response client::make_request(const std::string &command, const std::vector<json> &args,
|
||||
std::uint32_t timeout_ms) {
|
||||
auto error = rpc_response_type::success;
|
||||
auto client::set_config_value_by_name(const std::string &name,
|
||||
const std::string &value)
|
||||
-> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
auto *curl_handle = utils::create_curl();
|
||||
httplib::Params params{
|
||||
{"name", name},
|
||||
{"value", value},
|
||||
};
|
||||
|
||||
const auto port = static_cast<std::uint16_t>(host_info_.port);
|
||||
const auto url = "http://" + host_info_.host + ":" + std::to_string(port) + "/api";
|
||||
const auto request = json({{"jsonrpc", "2.0"},
|
||||
{"id", std::to_string(++request_id_)},
|
||||
{"method", command},
|
||||
{"params", args}})
|
||||
.dump();
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
struct curl_slist *hs = nullptr;
|
||||
hs = curl_slist_append(hs, "Content-Type: application/json;");
|
||||
if (not(host_info_.password.empty() && host_info_.user.empty())) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_USERNAME, &host_info_.user[0]);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_PASSWORD, &host_info_.password[0]);
|
||||
auto resp =
|
||||
cli.Post("/api/v1/" + rpc_method::set_config_value_by_name, params);
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
#ifndef __APPLE__
|
||||
curl_resolver resolver(curl_handle,
|
||||
{"localhost:" + std::to_string(host_info_.port) + ":127.0.0.1"}, true);
|
||||
#endif
|
||||
if (timeout_ms > 0) {
|
||||
curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT_MS, timeout_ms);
|
||||
}
|
||||
curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, hs);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, request.c_str());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, request.size());
|
||||
curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 5L);
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION,
|
||||
static_cast<size_t (*)(char *, size_t, size_t, void *)>(
|
||||
[](char *buffer, size_t size, size_t nitems, void *outstream) -> size_t {
|
||||
(*reinterpret_cast<std::string *>(outstream)) +=
|
||||
std::string(buffer, size * nitems);
|
||||
return size * nitems;
|
||||
}));
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
};
|
||||
|
||||
std::string response;
|
||||
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &response);
|
||||
|
||||
json json_data;
|
||||
const auto res = curl_easy_perform(curl_handle);
|
||||
if (res == CURLE_OK) {
|
||||
long httpErrorCode;
|
||||
curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &httpErrorCode);
|
||||
if (httpErrorCode == 200) {
|
||||
json_data = json::parse(response.begin(), response.end());
|
||||
} else {
|
||||
json parsed;
|
||||
try {
|
||||
parsed = json::parse(response.begin(), response.end());
|
||||
} catch (...) {
|
||||
}
|
||||
error = rpc_response_type::http_error;
|
||||
json_data = {{"error", {{"code", std::to_string(httpErrorCode)}}}};
|
||||
if (parsed.empty()) {
|
||||
json_data["error"]["response"] = response;
|
||||
} else {
|
||||
json_data["error"]["response"] = parsed;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error = rpc_response_type::curl_error;
|
||||
json_data = {{"error", {{"message", curl_easy_strerror(res)}}}};
|
||||
}
|
||||
curl_easy_cleanup(curl_handle);
|
||||
curl_slist_free_all(hs);
|
||||
|
||||
return rpc_response({error, json_data});
|
||||
return rpc_response{rpc_response_type::success,
|
||||
nlohmann::json::parse(resp->body)};
|
||||
}
|
||||
|
||||
rpc_response client::pin_file(const std::string &api_path) {
|
||||
auto ret = make_request(rpc_method::pin_file, {api_path});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
auto client::unmount() -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp = cli.Post("/api/v1/" + rpc_method::unmount);
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, {}};
|
||||
}
|
||||
|
||||
rpc_response client::pinned_status(const std::string &api_path) {
|
||||
auto ret = make_request(rpc_method::pinned_status, {api_path});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
auto client::unpin_file(const std::string &api_path) -> rpc_response {
|
||||
const auto base_url =
|
||||
"http://" + host_info_.host + ":" + std::to_string(host_info_.port);
|
||||
|
||||
httplib::Params params{{"api_path", api_path}};
|
||||
httplib::Client cli{base_url};
|
||||
cli.set_basic_auth(host_info_.user, host_info_.password);
|
||||
|
||||
auto resp = cli.Post("/api/v1/" + rpc_method::unpin_file, params);
|
||||
if (resp.error() != httplib::Error::Success) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", httplib::to_string(resp.error())}}};
|
||||
}
|
||||
if (resp->status != 200) {
|
||||
return rpc_response{rpc_response_type::http_error,
|
||||
{{"error", std::to_string(resp->status)}}};
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
rpc_response client::set_config_value_by_name(const std::string &name, const std::string &value) {
|
||||
auto ret = make_request(rpc_method::set_config_value_by_name, {name, value});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
if (ret.data["result"]["value"].get<std::string>().empty()) {
|
||||
ret.response_type = rpc_response_type::config_value_not_found;
|
||||
} else {
|
||||
ret.data = ret.data["result"];
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
rpc_response client::unmount() {
|
||||
auto ret = make_request(rpc_method::unmount, {});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
rpc_response client::unpin_file(const std::string &api_path) {
|
||||
auto ret = make_request(rpc_method::unpin_file, {api_path});
|
||||
if (ret.response_type == rpc_response_type::success) {
|
||||
ret.data = ret.data["result"];
|
||||
}
|
||||
|
||||
return ret;
|
||||
return rpc_response{rpc_response_type::success, {}};
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
@@ -1,165 +1,204 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "rpc/server/full_server.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "drives/directory_iterator.hpp"
|
||||
#include "drives/i_open_file_table.hpp"
|
||||
#include "file_manager/i_file_manager.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "providers/skynet/skynet_provider.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "types/rpc.hpp"
|
||||
#include "types/skynet.hpp"
|
||||
#include "utils/global_data.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
full_server::full_server(app_config &config, i_provider &provider, i_open_file_table &oft)
|
||||
: server(config), provider_(provider), oft_(oft) {}
|
||||
full_server::full_server(app_config &config, i_provider &provider,
|
||||
i_file_manager &fm)
|
||||
: server(config), provider_(provider), fm_(fm) {}
|
||||
|
||||
bool full_server::handle_request(jsonrpcpp::request_ptr &request,
|
||||
std::unique_ptr<jsonrpcpp::Response> &response) {
|
||||
auto handled = true;
|
||||
if (request->method == rpc_method::get_drive_information) {
|
||||
if (request->params.param_array.empty()) {
|
||||
response = std::make_unique<jsonrpcpp::Response>(
|
||||
*request, json({{"cache_space_used", global_data::instance().get_used_cache_space()},
|
||||
{"drive_space_total", provider_.get_total_drive_space()},
|
||||
{"drive_space_used", global_data::instance().get_used_drive_space()},
|
||||
{"item_count", provider_.get_total_item_count()}}));
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::get_pinned_files) {
|
||||
if (request->params.param_array.empty()) {
|
||||
response = std::make_unique<jsonrpcpp::Response>(*request, provider_.get_pinned_files());
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::get_directory_items) {
|
||||
if (request->params.param_array.size() == 1u) {
|
||||
const auto api_path = utils::path::create_api_path(request->params.param_array[0]);
|
||||
auto directoryItems = oft_.get_directory_items(api_path);
|
||||
std::vector<json> items;
|
||||
for (const auto &item : directoryItems) {
|
||||
items.emplace_back(item.to_json());
|
||||
}
|
||||
void full_server::handle_get_directory_items(const httplib::Request &req,
|
||||
httplib::Response &res) {
|
||||
const auto api_path =
|
||||
utils::path::create_api_path(req.get_param_value("api_path"));
|
||||
const auto list = fm_.get_directory_items(api_path);
|
||||
|
||||
response = std::make_unique<jsonrpcpp::Response>(*request, json({{"items", items}}));
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::pin_file) {
|
||||
if (request->params.param_array.size() == 1u) {
|
||||
const auto api_path = utils::path::create_api_path(request->params.param_array[0]);
|
||||
auto success = provider_.is_file(api_path);
|
||||
if (success) {
|
||||
success = api_error::success ==
|
||||
provider_.set_item_meta(api_path, META_PINNED, utils::string::from_bool(true));
|
||||
}
|
||||
|
||||
if (success) {
|
||||
event_system::instance().raise<file_pinned>(api_path);
|
||||
}
|
||||
|
||||
response = std::make_unique<jsonrpcpp::Response>(*request, json({{"success", success}}));
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::pinned_status) {
|
||||
if (request->params.param_array.size() == 1u) {
|
||||
const auto api_path = utils::path::create_api_path(request->params.param_array[0]);
|
||||
std::string pinned;
|
||||
const auto success =
|
||||
api_error::success == provider_.get_item_meta(api_path, META_PINNED, pinned);
|
||||
|
||||
response = std::make_unique<jsonrpcpp::Response>(
|
||||
*request, json({{"success", success},
|
||||
{"pinned", pinned.empty() ? false : utils::string::to_bool(pinned)}}));
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::unpin_file) {
|
||||
if (request->params.param_array.size() == 1u) {
|
||||
const auto api_path = utils::path::create_api_path(request->params.param_array[0]);
|
||||
auto success = provider_.is_file(api_path);
|
||||
if (success) {
|
||||
success = api_error::success ==
|
||||
provider_.set_item_meta(api_path, META_PINNED, utils::string::from_bool(false));
|
||||
}
|
||||
|
||||
if (success) {
|
||||
event_system::instance().raise<file_unpinned>(api_path);
|
||||
}
|
||||
|
||||
response = std::make_unique<jsonrpcpp::Response>(*request, json({{"success", success}}));
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::get_open_files) {
|
||||
if (request->params.param_array.empty()) {
|
||||
const auto list = oft_.get_open_files();
|
||||
json open_files = {{"file_list", std::vector<json>()}};
|
||||
for (const auto &kv : list) {
|
||||
open_files["file_list"].emplace_back(json({{"path", kv.first}, {"count", kv.second}}));
|
||||
}
|
||||
response = std::make_unique<jsonrpcpp::Response>(*request, open_files);
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
#if defined(REPERTORY_ENABLE_SKYNET)
|
||||
} else if (get_config().get_provider_type() == provider_type::skynet) {
|
||||
if (request->method == rpc_method::import) {
|
||||
if (request->params.param_array.size() == 1) {
|
||||
json results = {{"success", std::vector<json>()}, {"failed", std::vector<std::string>()}};
|
||||
for (const auto &link : request->params.param_array[0]) {
|
||||
const auto si = skylink_import::from_json(link);
|
||||
auto &provider = dynamic_cast<skynet_provider &>(provider_);
|
||||
const auto res = provider.import_skylink(si);
|
||||
if (res == api_error::success) {
|
||||
results["success"].emplace_back(link);
|
||||
} else {
|
||||
results["failed"].emplace_back(link["skylink"].get<std::string>());
|
||||
}
|
||||
}
|
||||
response = std::make_unique<jsonrpcpp::Response>(*request, results);
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::export_links) {
|
||||
if (request->params.param_array.empty()) {
|
||||
auto &provider = dynamic_cast<skynet_provider &>(provider_);
|
||||
response = std::make_unique<jsonrpcpp::Response>(*request, provider.export_all());
|
||||
} else if (request->params.param_array.size() == 1) {
|
||||
auto &provider = dynamic_cast<skynet_provider &>(provider_);
|
||||
response = std::make_unique<jsonrpcpp::Response>(
|
||||
*request,
|
||||
provider.export_list(request->params.param_array[0].get<std::vector<std::string>>()));
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else {
|
||||
handled = server::handle_request(request, response);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
handled = server::handle_request(request, response);
|
||||
json items = {{"items", std::vector<json>()}};
|
||||
for (const auto &item : list) {
|
||||
items["items"].emplace_back(item.to_json());
|
||||
}
|
||||
return handled;
|
||||
res.set_content(items.dump(), "application/json");
|
||||
res.status = 200;
|
||||
}
|
||||
|
||||
void full_server::handle_get_drive_information(const httplib::Request & /*req*/,
|
||||
httplib::Response &res) {
|
||||
res.set_content(
|
||||
json({
|
||||
{"cache_space_used",
|
||||
utils::file::calculate_used_space(
|
||||
get_config().get_cache_directory(), false)},
|
||||
{"drive_space_total", provider_.get_total_drive_space()},
|
||||
{"drive_space_used", provider_.get_used_drive_space()},
|
||||
{"item_count", provider_.get_total_item_count()},
|
||||
})
|
||||
.dump(),
|
||||
"application/json");
|
||||
res.status = 200;
|
||||
}
|
||||
|
||||
void full_server::handle_get_open_files(const httplib::Request & /*req*/,
|
||||
httplib::Response &res) {
|
||||
const auto list = fm_.get_open_files();
|
||||
|
||||
json open_files = {{"items", std::vector<json>()}};
|
||||
for (const auto &kv : list) {
|
||||
open_files["items"].emplace_back(json({
|
||||
{"path", kv.first},
|
||||
{"count", kv.second},
|
||||
}));
|
||||
}
|
||||
res.set_content(open_files.dump(), "application/json");
|
||||
res.status = 200;
|
||||
}
|
||||
|
||||
void full_server::handle_get_pinned_files(const httplib::Request & /*req*/,
|
||||
httplib::Response &res) {
|
||||
res.set_content(json({{"items", provider_.get_pinned_files()}}).dump(),
|
||||
"application/json");
|
||||
res.status = 200;
|
||||
}
|
||||
|
||||
void full_server::handle_get_pinned_status(const httplib::Request &req,
|
||||
httplib::Response &res) {
|
||||
const auto api_path =
|
||||
utils::path::create_api_path(req.get_param_value("api_path"));
|
||||
|
||||
std::string pinned;
|
||||
const auto result = provider_.get_item_meta(api_path, META_PINNED, pinned);
|
||||
if (result != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, result,
|
||||
"failed to get pinned status");
|
||||
res.status = 500;
|
||||
return;
|
||||
}
|
||||
|
||||
res.set_content(
|
||||
json(
|
||||
{{"pinned", pinned.empty() ? false : utils::string::to_bool(pinned)}})
|
||||
.dump(),
|
||||
"application/json");
|
||||
res.status = 200;
|
||||
}
|
||||
|
||||
void full_server::handle_pin_file(const httplib::Request &req,
|
||||
httplib::Response &res) {
|
||||
const auto api_path =
|
||||
utils::path::create_api_path(req.get_param_value("api_path"));
|
||||
|
||||
bool exists{};
|
||||
auto result = provider_.is_file(api_path, exists);
|
||||
if (result != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, result,
|
||||
"failed to pin file");
|
||||
res.status = 500;
|
||||
return;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
exists = api_error::success ==
|
||||
provider_.set_item_meta(api_path, META_PINNED,
|
||||
utils::string::from_bool(true));
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
event_system::instance().raise<file_pinned>(api_path);
|
||||
}
|
||||
|
||||
res.status = exists ? 200 : 404;
|
||||
}
|
||||
|
||||
void full_server::handle_unpin_file(const httplib::Request &req,
|
||||
httplib::Response &res) {
|
||||
const auto api_path =
|
||||
utils::path::create_api_path(req.get_param_value("api_path"));
|
||||
|
||||
bool exists{};
|
||||
auto result = provider_.is_file(api_path, exists);
|
||||
if (result != api_error::success) {
|
||||
utils::error::raise_api_path_error(__FUNCTION__, api_path, result,
|
||||
"failed to unpin file");
|
||||
res.status = 500;
|
||||
return;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
exists = api_error::success ==
|
||||
provider_.set_item_meta(api_path, META_PINNED,
|
||||
utils::string::from_bool(false));
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
event_system::instance().raise<file_unpinned>(api_path);
|
||||
}
|
||||
|
||||
res.status = exists ? 200 : 404;
|
||||
}
|
||||
|
||||
void full_server::initialize(httplib::Server &inst) {
|
||||
server::initialize(inst);
|
||||
|
||||
inst.Get("/api/v1/" + rpc_method::get_directory_items,
|
||||
[this](auto &&req, auto &&res) {
|
||||
handle_get_directory_items(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
inst.Get("/api/v1/" + rpc_method::get_drive_information,
|
||||
[this](auto &&req, auto &&res) {
|
||||
handle_get_drive_information(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
inst.Get("/api/v1/" + rpc_method::get_open_files,
|
||||
[this](auto &&req, auto &&res) {
|
||||
handle_get_open_files(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
inst.Get("/api/v1/" + rpc_method::get_pinned_files,
|
||||
[this](auto &&req, auto &&res) {
|
||||
handle_get_pinned_files(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
inst.Get("/api/v1/" + rpc_method::pinned_status,
|
||||
[this](auto &&req, auto &&res) {
|
||||
handle_get_pinned_status(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
inst.Post("/api/v1/" + rpc_method::pin_file, [this](auto &&req, auto &&res) {
|
||||
handle_pin_file(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
inst.Post("/api/v1/" + rpc_method::unpin_file,
|
||||
[this](auto &&req, auto &&res) {
|
||||
handle_unpin_file(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
@@ -1,163 +1,181 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "rpc/server/server.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "utils/Base64.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
server::rpc_resource::rpc_resource(server &owner) : httpserver::http_resource(), owner_(owner) {
|
||||
disallow_all();
|
||||
set_allowing("POST", true);
|
||||
}
|
||||
server::server(app_config &config) : config_(config) {}
|
||||
|
||||
const std::shared_ptr<httpserver::http_response>
|
||||
server::rpc_resource::render(const httpserver::http_request &request) {
|
||||
std::shared_ptr<json_response> ret;
|
||||
|
||||
try {
|
||||
if (owner_.check_authorization(request)) {
|
||||
std::unique_ptr<jsonrpcpp::Response> response;
|
||||
auto entity = jsonrpcpp::Parser::do_parse(utils::string::to_utf8(request.get_content()));
|
||||
if (entity->is_request()) {
|
||||
auto request = std::dynamic_pointer_cast<jsonrpcpp::Request>(entity);
|
||||
if (not owner_.handle_request(request, response)) {
|
||||
throw jsonrpcpp::MethodNotFoundException(*request);
|
||||
}
|
||||
ret = std::make_shared<json_response>(response->to_json());
|
||||
}
|
||||
} else {
|
||||
ret = std::make_shared<json_response>(json({{"error", "unauthorized"}}),
|
||||
httpserver::http::http_utils::http_unauthorized);
|
||||
}
|
||||
} catch (const jsonrpcpp::RequestException &e) {
|
||||
ret = std::make_shared<json_response>(e.to_json(),
|
||||
httpserver::http::http_utils::http_bad_request);
|
||||
event_system::instance().raise<rpc_server_exception>(e.to_json().dump());
|
||||
} catch (const std::exception &e2) {
|
||||
ret = std::make_shared<json_response>(json({{"exception", e2.what()}}),
|
||||
httpserver::http::http_utils::http_internal_server_error);
|
||||
event_system::instance().raise<rpc_server_exception>(e2.what());
|
||||
auto server::check_authorization(const httplib::Request &req) -> bool {
|
||||
if (config_.get_api_auth().empty() || config_.get_api_user().empty()) {
|
||||
utils::error::raise_error(__FUNCTION__,
|
||||
"authorization user or password is not set");
|
||||
return false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
server::server(app_config &config) : config_(config), resource_(*this) {}
|
||||
|
||||
bool server::check_authorization(const httpserver::http_request &request) {
|
||||
auto ret = (config_.get_api_auth().empty() && config_.get_api_user().empty());
|
||||
if (not ret) {
|
||||
const auto authorization = request.get_header("Authorization");
|
||||
if (not authorization.empty()) {
|
||||
const auto auth_parts = utils::string::split(authorization, ' ');
|
||||
if (not auth_parts.empty()) {
|
||||
const auto auth_type = auth_parts[0];
|
||||
if (auth_type == "Basic") {
|
||||
const auto data = macaron::Base64::Decode(authorization.substr(6));
|
||||
const auto auth = utils::string::split(std::string(data.begin(), data.end()), ':');
|
||||
if (auth.size() == 2) {
|
||||
const auto &user = auth[0];
|
||||
const auto &pwd = auth[1];
|
||||
ret = (user == config_.get_api_user()) && (pwd == config_.get_api_auth());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto authorization = req.get_header_value("Authorization");
|
||||
if (authorization.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
const auto auth_parts = utils::string::split(authorization, ' ');
|
||||
if (auth_parts.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto auth_type = auth_parts[0U];
|
||||
if (auth_type != "Basic") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto data = macaron::Base64::Decode(authorization.substr(6U));
|
||||
const auto auth =
|
||||
utils::string::split(std::string(data.begin(), data.end()), ':');
|
||||
if (auth.size() != 2U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &user = auth[0U];
|
||||
const auto &pwd = auth[1U];
|
||||
return (user == config_.get_api_user()) && (pwd == config_.get_api_auth());
|
||||
}
|
||||
|
||||
bool server::handle_request(jsonrpcpp::request_ptr &request,
|
||||
std::unique_ptr<jsonrpcpp::Response> &response) {
|
||||
auto handled = true;
|
||||
if (request->method == rpc_method::get_config) {
|
||||
if (request->params.param_array.empty()) {
|
||||
response = std::make_unique<jsonrpcpp::Response>(*request, config_.get_json());
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::get_config_value_by_name) {
|
||||
if (request->params.param_array.size() == 1) {
|
||||
response = std::make_unique<jsonrpcpp::Response>(
|
||||
*request, json({{"value", config_.get_value_by_name(request->params.param_array[0])}}));
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::set_config_value_by_name) {
|
||||
if (request->params.param_array.size() == 2) {
|
||||
response = std::make_unique<jsonrpcpp::Response>(
|
||||
*request, json({{"value", config_.set_value_by_name(request->params.param_array[0],
|
||||
request->params.param_array[1])}}));
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else if (request->method == rpc_method::unmount) {
|
||||
if (request->params.param_array.empty()) {
|
||||
event_system::instance().raise<unmount_requested>();
|
||||
response = std::make_unique<jsonrpcpp::Response>(*request, json({{"success", true}}));
|
||||
} else {
|
||||
throw jsonrpcpp::InvalidParamsException(*request);
|
||||
}
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
return handled;
|
||||
void server::handle_get_config(const httplib::Request & /*req*/,
|
||||
httplib::Response &res) {
|
||||
auto data = config_.get_json();
|
||||
res.set_content(data.dump(), "application/json");
|
||||
res.status = 200;
|
||||
}
|
||||
|
||||
/*void server::Start() {
|
||||
mutex_lock l(startStopMutex_);
|
||||
if (not started_) {
|
||||
struct sockaddr_in localHost{};
|
||||
inet_pton(AF_INET, "127.0.0.1", &localHost.sin_addr);
|
||||
void server::handle_get_config_value_by_name(const httplib::Request &req,
|
||||
httplib::Response &res) {
|
||||
auto name = req.get_param_value("name");
|
||||
auto data = json({{"value", config_.get_value_by_name(name)}});
|
||||
res.set_content(data.dump(), "application/json");
|
||||
res.status = 200;
|
||||
}
|
||||
|
||||
ws_ = std::make_unique<httpserver::webserver>(
|
||||
httpserver::create_webserver(config_.GetAPIPort())
|
||||
.bind_address((sockaddr*)&localHost)
|
||||
.default_policy(httpserver::http::http_utils::REJECT));
|
||||
ws_->allow_ip("127.0.0.1");
|
||||
ws_->register_resource("/api", &rpcResource_);
|
||||
ws_->start(false);
|
||||
started_ = true;
|
||||
}
|
||||
}*/
|
||||
void server::handle_set_config_value_by_name(const httplib::Request &req,
|
||||
httplib::Response &res) {
|
||||
auto name = req.get_param_value("name");
|
||||
auto value = req.get_param_value("value");
|
||||
|
||||
json data = {{"value", config_.set_value_by_name(name, value)}};
|
||||
res.set_content(data.dump(), "application/json");
|
||||
res.status = 200;
|
||||
}
|
||||
|
||||
void server::handle_unmount(const httplib::Request & /*req*/,
|
||||
httplib::Response &res) {
|
||||
event_system::instance().raise<unmount_requested>();
|
||||
res.status = 200;
|
||||
}
|
||||
|
||||
void server::initialize(httplib::Server &inst) {
|
||||
inst.Get("/api/v1/" + rpc_method::get_config, [this](auto &&req, auto &&res) {
|
||||
handle_get_config(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
|
||||
inst.Get("/api/v1/" + rpc_method::get_config_value_by_name,
|
||||
[this](auto &&req, auto &&res) {
|
||||
handle_get_config_value_by_name(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
|
||||
inst.Post("/api/v1/" + rpc_method::set_config_value_by_name,
|
||||
[this](auto &&req, auto &&res) {
|
||||
handle_set_config_value_by_name(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
|
||||
inst.Post("/api/v1/" + rpc_method::unmount, [this](auto &&req, auto &&res) {
|
||||
handle_unmount(std::forward<decltype(req)>(req),
|
||||
std::forward<decltype(res)>(res));
|
||||
});
|
||||
}
|
||||
|
||||
void server::start() {
|
||||
mutex_lock l(start_stop_mutex_);
|
||||
if (not started_) {
|
||||
ws_ = std::make_unique<httpserver::webserver>(
|
||||
httpserver::create_webserver(config_.get_api_port())
|
||||
.default_policy(httpserver::http::http_utils::REJECT));
|
||||
ws_->allow_ip("127.0.0.1");
|
||||
ws_->register_resource("/api", &resource_);
|
||||
ws_->start(false);
|
||||
event_system::instance().raise<service_started>("server");
|
||||
server_ = std::make_unique<httplib::Server>();
|
||||
|
||||
server_->set_exception_handler([](const httplib::Request &req,
|
||||
httplib::Response &res,
|
||||
std::exception_ptr ep) {
|
||||
json data = {{"path", req.path}};
|
||||
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (std::exception &e) {
|
||||
data["error"] = e.what() ? e.what() : "unknown error";
|
||||
utils::error::raise_error(__FUNCTION__, e,
|
||||
"failed request: " + req.path);
|
||||
} catch (...) {
|
||||
data["error"] = "unknown error";
|
||||
utils::error::raise_error(__FUNCTION__, "unknown error",
|
||||
"failed request: " + req.path);
|
||||
}
|
||||
|
||||
res.set_content(data.dump(), "application/json");
|
||||
res.status = 500;
|
||||
});
|
||||
|
||||
server_->set_pre_routing_handler(
|
||||
[this](auto &&req, auto &&res) -> httplib::Server::HandlerResponse {
|
||||
if (check_authorization(req)) {
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
}
|
||||
|
||||
res.status = 401;
|
||||
return httplib::Server::HandlerResponse::Handled;
|
||||
});
|
||||
|
||||
initialize(*server_);
|
||||
|
||||
server_thread_ = std::make_unique<std::thread>(
|
||||
[this]() { server_->listen("127.0.0.1", config_.get_api_port()); });
|
||||
|
||||
started_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void server::stop() {
|
||||
mutex_lock l(start_stop_mutex_);
|
||||
if (started_) {
|
||||
event_system::instance().raise<service_shutdown>("server");
|
||||
ws_->stop();
|
||||
ws_.reset();
|
||||
started_ = false;
|
||||
mutex_lock l(start_stop_mutex_);
|
||||
if (started_) {
|
||||
event_system::instance().raise<service_shutdown_begin>("server");
|
||||
|
||||
server_->stop();
|
||||
server_thread_->join();
|
||||
server_thread_.reset();
|
||||
|
||||
started_ = false;
|
||||
event_system::instance().raise<service_shutdown_end>("server");
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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:
|
||||
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 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.
|
||||
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 "types/remote.hpp"
|
||||
|
||||
namespace repertory::remote {
|
||||
#ifndef _WIN32
|
||||
open_flags create_open_flags(const std::uint32_t &flags) {
|
||||
auto create_open_flags(std::uint32_t flags) -> open_flags {
|
||||
open_flags ret{};
|
||||
{
|
||||
const auto f = (flags & 3u);
|
||||
@@ -99,7 +102,7 @@ open_flags create_open_flags(const std::uint32_t &flags) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::uint32_t create_os_open_flags(const open_flags &flags) {
|
||||
auto create_os_open_flags(const open_flags &flags) -> std::uint32_t {
|
||||
std::uint32_t ret = 0u;
|
||||
if ((flags & open_flags::read_write) == open_flags::read_write) {
|
||||
ret |= static_cast<std::uint32_t>(O_RDWR);
|
||||
|
||||
@@ -1,69 +1,85 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "types/repertory.hpp"
|
||||
|
||||
#include "types/startup_exception.hpp"
|
||||
|
||||
namespace repertory {
|
||||
const std::string &api_error_to_string(const api_error &error) {
|
||||
static const std::unordered_map<api_error, std::string> LOOKUP = {
|
||||
{api_error::success, "success"},
|
||||
{api_error::access_denied, "access_denied"},
|
||||
{api_error::bad_address, "bad_address"},
|
||||
{api_error::buffer_overflow, "buffer_overflow"},
|
||||
{api_error::buffer_too_small, "buffer_too_small"},
|
||||
{api_error::comm_error, "comm_error"},
|
||||
{api_error::decryption_error, "decryption_error"},
|
||||
{api_error::directory_end_of_files, "directory_end_of_files"},
|
||||
{api_error::directory_exists, "directory_exists"},
|
||||
{api_error::directory_not_empty, "directory_not_empty"},
|
||||
{api_error::directory_not_found, "directory_not_found"},
|
||||
{api_error::download_failed, "download_failed"},
|
||||
{api_error::download_incomplete, "download_incomplete"},
|
||||
{api_error::download_stopped, "download_stopped"},
|
||||
{api_error::download_timeout, "download_timeout"},
|
||||
{api_error::empty_ring_buffer_chunk_size, "empty_ring_buffer_chunk_size"},
|
||||
{api_error::empty_ring_buffer_size, "empty_ring_buffer_size"},
|
||||
{api_error::error, "error"},
|
||||
{api_error::file_exists, "file_exists"},
|
||||
{api_error::file_in_use, "file_in_use"},
|
||||
{api_error::incompatible_version, "incompatible_version"},
|
||||
{api_error::invalid_handle, "invalid_handle"},
|
||||
{api_error::invalid_operation, "invalid_operation"},
|
||||
{api_error::invalid_ring_buffer_multiple, "invalid_ring_buffer_multiple"},
|
||||
{api_error::invalid_ring_buffer_size, "invalid_ring_buffer_size"},
|
||||
{api_error::invalid_version, "invalid_version"},
|
||||
{api_error::item_is_file, "item_is_file"},
|
||||
{api_error::item_not_found, "item_not_found"},
|
||||
{api_error::not_implemented, "not_implemented"},
|
||||
{api_error::not_supported, "not_supported"},
|
||||
{api_error::os_error, "os_error"},
|
||||
{api_error::permission_denied, "permission_denied"},
|
||||
{api_error::upload_failed, "upload_failed"},
|
||||
{api_error::upload_stopped, "upload_stopped"},
|
||||
{api_error::xattr_buffer_small, "xattr_buffer_small"},
|
||||
{api_error::xattr_exists, "xattr_exists"},
|
||||
{api_error::xattr_invalid_namespace, "xattr_invalid_namespace"},
|
||||
{api_error::xattr_not_found, "xattr_not_found"},
|
||||
{api_error::xattr_osx_invalid, "xattr_osx_invalid"},
|
||||
{api_error::xattr_too_big, "xattr_too_big"},
|
||||
};
|
||||
static const std::unordered_map<api_error, std::string> LOOKUP = {
|
||||
{api_error::success, "success"},
|
||||
{api_error::access_denied, "access_denied"},
|
||||
{api_error::bad_address, "bad_address"},
|
||||
{api_error::buffer_overflow, "buffer_overflow"},
|
||||
{api_error::buffer_too_small, "buffer_too_small"},
|
||||
{api_error::comm_error, "comm_error"},
|
||||
{api_error::decryption_error, "decryption_error"},
|
||||
{api_error::directory_end_of_files, "directory_end_of_files"},
|
||||
{api_error::directory_exists, "directory_exists"},
|
||||
{api_error::directory_not_empty, "directory_not_empty"},
|
||||
{api_error::directory_not_found, "directory_not_found"},
|
||||
{api_error::download_failed, "download_failed"},
|
||||
{api_error::download_incomplete, "download_incomplete"},
|
||||
{api_error::download_stopped, "download_stopped"},
|
||||
{api_error::empty_ring_buffer_chunk_size, "empty_ring_buffer_chunk_size"},
|
||||
{api_error::empty_ring_buffer_size, "empty_ring_buffer_size"},
|
||||
{api_error::error, "error"},
|
||||
{api_error::file_in_use, "file_in_use"},
|
||||
{api_error::file_size_mismatch, "file_size_mismatch"},
|
||||
{api_error::incompatible_version, "incompatible_version"},
|
||||
{api_error::invalid_handle, "invalid_handle"},
|
||||
{api_error::invalid_operation, "invalid_operation"},
|
||||
{api_error::invalid_ring_buffer_multiple, "invalid_ring_buffer_multiple"},
|
||||
{api_error::invalid_ring_buffer_size, "invalid_ring_buffer_size"},
|
||||
{api_error::invalid_version, "invalid_version"},
|
||||
{api_error::item_exists, "item_exists"},
|
||||
{api_error::item_not_found, "item_not_found"},
|
||||
{api_error::no_disk_space, "no_disk_space"},
|
||||
{api_error::not_implemented, "not_implemented"},
|
||||
{api_error::not_supported, "not_supported"},
|
||||
{api_error::os_error, "os_error"},
|
||||
{api_error::out_of_memory, "out_of_memory"},
|
||||
{api_error::permission_denied, "permission_denied"},
|
||||
{api_error::upload_failed, "upload_failed"},
|
||||
{api_error::upload_stopped, "upload_stopped"},
|
||||
{api_error::xattr_buffer_small, "xattr_buffer_small"},
|
||||
{api_error::xattr_exists, "xattr_exists"},
|
||||
{api_error::xattr_not_found, "xattr_not_found"},
|
||||
{api_error::xattr_too_big, "xattr_too_big"},
|
||||
};
|
||||
|
||||
auto api_error_from_string(std::string_view s) -> api_error {
|
||||
if (LOOKUP.size() != static_cast<std::size_t>(api_error::ERROR_COUNT)) {
|
||||
throw startup_exception("undefined api_error strings");
|
||||
}
|
||||
|
||||
const auto it =
|
||||
std::find_if(LOOKUP.begin(), LOOKUP.end(),
|
||||
[&s](const std::pair<api_error, std::string> &kv) -> bool {
|
||||
return kv.second == s;
|
||||
});
|
||||
return it == LOOKUP.end() ? api_error::error : it->first;
|
||||
}
|
||||
|
||||
auto api_error_to_string(const api_error &error) -> const std::string & {
|
||||
if (LOOKUP.size() != static_cast<std::size_t>(api_error::ERROR_COUNT)) {
|
||||
throw startup_exception("undefined api_error strings");
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "types/skynet.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
std::vector<host_config> skynet_config::from_string(const std::string &list) {
|
||||
const auto obj = json::parse(list);
|
||||
std::vector<host_config> ret;
|
||||
std::transform(obj.begin(), obj.end(), std::back_inserter(ret),
|
||||
[](const json &portal) -> host_config {
|
||||
host_config hc{};
|
||||
hc.agent_string = portal["AgentString"].get<std::string>();
|
||||
hc.api_password = portal["ApiPassword"].get<std::string>();
|
||||
hc.api_port = portal["ApiPort"].get<std::uint16_t>();
|
||||
hc.host_name_or_ip = portal["HostNameOrIp"].get<std::string>();
|
||||
hc.path = portal["Path"].get<std::string>();
|
||||
hc.protocol = portal["Protocol"].get<std::string>();
|
||||
hc.timeout_ms = portal["TimeoutMs"].get<std::uint32_t>();
|
||||
hc.auth_url = portal["AuthURL"].get<std::string>();
|
||||
hc.auth_user = portal["AuthUser"].get<std::string>();
|
||||
hc.auth_password = portal["AuthPassword"].get<std::string>();
|
||||
return hc;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string skynet_config::to_string(const std::vector<host_config> &list) {
|
||||
json j;
|
||||
std::transform(list.begin(), list.end(), std::back_inserter(j), [](const auto &hc) -> json {
|
||||
return {
|
||||
{"AgentString", hc.agent_string},
|
||||
{"ApiPassword", hc.api_password},
|
||||
{"ApiPort", hc.api_port},
|
||||
{"HostNameOrIp", hc.host_name_or_ip},
|
||||
{"Path", hc.path},
|
||||
{"Protocol", hc.protocol},
|
||||
{"TimeoutMs", hc.timeout_ms},
|
||||
{"AuthURL", hc.auth_url},
|
||||
{"AuthUser", hc.auth_user},
|
||||
{"AuthPassword", hc.auth_password},
|
||||
};
|
||||
});
|
||||
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
skylink_import skylink_import::from_json(const json &j) {
|
||||
return skylink_import{
|
||||
j.contains("directory") ? j["directory"].get<std::string>() : "/",
|
||||
j.contains("filename") ? j["filename"].get<std::string>() : "",
|
||||
j["skylink"].get<std::string>(),
|
||||
j.contains("token") ? j["token"].get<std::string>() : "",
|
||||
};
|
||||
}
|
||||
|
||||
skylink_import skylink_import::from_string(const std::string &str) {
|
||||
auto parts = utils::string::split(str, ',');
|
||||
if (parts.empty() || (parts.size() > 4)) {
|
||||
throw std::runtime_error("unable to parse skylink from string: " + str);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> linkData;
|
||||
for (auto &part : parts) {
|
||||
utils::string::replace(part, "@comma@", ",");
|
||||
auto parts2 = utils::string::split(part, '=', false);
|
||||
if (parts2.size() != 2) {
|
||||
throw std::runtime_error("unable to parse skylink from string: " + str);
|
||||
}
|
||||
|
||||
for (auto &part2 : parts2) {
|
||||
utils::string::replace(part2, "@equal@", "=");
|
||||
}
|
||||
|
||||
linkData[parts2[0]] = parts2[1];
|
||||
}
|
||||
|
||||
return skylink_import{
|
||||
linkData["directory"],
|
||||
linkData["filename"],
|
||||
linkData["skylink"],
|
||||
linkData["token"],
|
||||
};
|
||||
}
|
||||
|
||||
json skylink_import::to_json() const {
|
||||
return {
|
||||
{"directory", directory},
|
||||
{"filename", file_name},
|
||||
{"skylink", skylink},
|
||||
{"token", token},
|
||||
};
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "upload/upload_manager.hpp"
|
||||
#include "comm/i_comm.hpp"
|
||||
#include "app_config.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/rocksdb_utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
upload_manager::upload_manager(const app_config &config, api_file_exists_callback api_file_exists,
|
||||
upload_handler_callback upload_handler,
|
||||
upload_completed_callback upload_completed)
|
||||
: config_(config),
|
||||
api_file_exists_(std::move(api_file_exists)),
|
||||
upload_handler_(std::move(upload_handler)),
|
||||
upload_completed_(std::move(upload_completed)) {
|
||||
utils::db::create_rocksdb(config, ROCKS_DB_NAME, upload_db_);
|
||||
auto iterator =
|
||||
std::unique_ptr<rocksdb::Iterator>(upload_db_->NewIterator(rocksdb::ReadOptions()));
|
||||
for (iterator->SeekToFirst(); iterator->Valid(); iterator->Next()) {
|
||||
auto data = json::parse(iterator->value().ToString());
|
||||
auto u = std::make_shared<upload>(upload{
|
||||
data["path"].get<std::string>(),
|
||||
false,
|
||||
data["token"].get<std::string>(),
|
||||
data["source"].get<std::string>(),
|
||||
});
|
||||
|
||||
upload_lookup_[iterator->key().ToString()] = u;
|
||||
upload_queue_.emplace_back(u.get());
|
||||
}
|
||||
}
|
||||
|
||||
upload_manager::~upload_manager() {
|
||||
stop();
|
||||
upload_db_.reset();
|
||||
}
|
||||
|
||||
bool upload_manager::execute_if_not_processing(const std::vector<std::string> &api_paths,
|
||||
const std::function<void()> &action) const {
|
||||
mutex_lock upload_lock(upload_mutex_);
|
||||
const auto ret =
|
||||
(std::find_if(api_paths.begin(), api_paths.end(), [this](const auto &path) -> bool {
|
||||
return upload_lookup_.find(path) != upload_lookup_.end();
|
||||
}) == api_paths.end());
|
||||
if (ret) {
|
||||
action();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool upload_manager::is_processing(const std::string &api_path) const {
|
||||
mutex_lock upload_lock(upload_mutex_);
|
||||
return upload_lookup_.find(api_path) != upload_lookup_.end();
|
||||
}
|
||||
|
||||
api_error upload_manager::queue_upload(const std::string &api_path, const std::string &source_path,
|
||||
const std::string &encryption_token) {
|
||||
auto ret = remove_upload(api_path);
|
||||
if ((ret == api_error::success) || (ret == api_error::item_not_found)) {
|
||||
ret = api_error::success;
|
||||
|
||||
auto u = std::make_shared<upload>(upload{api_path, false, encryption_token, source_path});
|
||||
|
||||
unique_mutex_lock upload_lock(upload_mutex_);
|
||||
upload_db_->Put(rocksdb::WriteOptions(), api_path,
|
||||
json({
|
||||
{"path", api_path},
|
||||
{"source", source_path},
|
||||
{"token", encryption_token},
|
||||
})
|
||||
.dump());
|
||||
if (not stop_requested_) {
|
||||
upload_lookup_[api_path] = u;
|
||||
upload_queue_.emplace_back(u.get());
|
||||
}
|
||||
upload_notify_.notify_all();
|
||||
event_system::instance().raise<file_upload_queued>(api_path, source_path);
|
||||
upload_lock.unlock();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error upload_manager::remove_upload(const std::string &api_path) {
|
||||
auto ret = api_error::item_not_found;
|
||||
|
||||
unique_mutex_lock upload_lock(upload_mutex_);
|
||||
if (upload_lookup_.find(api_path) != upload_lookup_.end()) {
|
||||
auto upload = upload_lookup_[api_path];
|
||||
utils::remove_element_from(upload_queue_, upload.get());
|
||||
upload_lookup_.erase(api_path);
|
||||
upload_db_->Delete(rocksdb::WriteOptions(), api_path);
|
||||
upload->cancel = true;
|
||||
upload_lock.unlock();
|
||||
|
||||
upload->wait();
|
||||
|
||||
event_system::instance().raise<file_upload_removed>(api_path, upload->source_path);
|
||||
ret = api_error::success;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void upload_manager::start() {
|
||||
mutex_lock upload_lock(upload_mutex_);
|
||||
if (not upload_thread_) {
|
||||
stop_requested_ = false;
|
||||
upload_thread_ = std::make_unique<std::thread>([this]() { this->upload_thread(); });
|
||||
}
|
||||
}
|
||||
|
||||
void upload_manager::stop() {
|
||||
unique_mutex_lock upload_lock(upload_mutex_);
|
||||
if (upload_thread_) {
|
||||
stop_requested_ = true;
|
||||
upload_notify_.notify_all();
|
||||
upload_lock.unlock();
|
||||
|
||||
upload_thread_->join();
|
||||
upload_thread_.reset();
|
||||
stop_requested_ = false;
|
||||
|
||||
event_system::instance().raise<service_shutdown>("upload_manager");
|
||||
}
|
||||
}
|
||||
|
||||
void upload_manager::upload_thread() {
|
||||
unique_mutex_lock upload_lock(upload_mutex_);
|
||||
upload_lock.unlock();
|
||||
|
||||
std::deque<std::shared_ptr<upload>> completed;
|
||||
const auto process_completed = [&]() {
|
||||
while (not completed.empty()) {
|
||||
upload_lock.lock();
|
||||
auto u = completed.front();
|
||||
completed.pop_front();
|
||||
upload_lock.unlock();
|
||||
|
||||
u->thread->join();
|
||||
u->thread.reset();
|
||||
u->completed = true;
|
||||
}
|
||||
};
|
||||
|
||||
const auto process_next = [this, &completed]() {
|
||||
if (not upload_queue_.empty()) {
|
||||
auto *next_upload = upload_queue_.front();
|
||||
if (not next_upload->thread) {
|
||||
upload_queue_.pop_front();
|
||||
next_upload->completed = false;
|
||||
active_uploads_.emplace_back(next_upload);
|
||||
auto u = upload_lookup_[next_upload->api_path];
|
||||
auto upload_handler = [this, &completed, u]() {
|
||||
json data, err;
|
||||
const auto ret = this->upload_handler_(*u, data, err);
|
||||
|
||||
unique_mutex_lock upload_lock(upload_mutex_);
|
||||
if (ret == api_error::success) {
|
||||
upload_lookup_.erase(u->api_path);
|
||||
upload_db_->Delete(rocksdb::WriteOptions(), u->api_path);
|
||||
upload_completed_(u->api_path, u->source_path, data);
|
||||
event_system::instance().raise<file_upload_completed>(u->api_path, u->source_path);
|
||||
} else {
|
||||
event_system::instance().raise<file_upload_failed>(u->api_path, u->source_path,
|
||||
err.dump(2));
|
||||
if (u->cancel) {
|
||||
upload_lookup_.erase(u->api_path);
|
||||
} else if (not api_file_exists_(u->api_path) ||
|
||||
not utils::file::is_file(u->source_path)) {
|
||||
upload_lookup_.erase(u->api_path);
|
||||
upload_db_->Delete(rocksdb::WriteOptions(), u->api_path);
|
||||
event_system::instance().raise<file_upload_not_found>(u->api_path, u->source_path);
|
||||
} else {
|
||||
upload_queue_.emplace_back(u.get());
|
||||
u->retry = true;
|
||||
}
|
||||
}
|
||||
utils::remove_element_from(active_uploads_, u.get());
|
||||
|
||||
completed.emplace_back(u);
|
||||
upload_notify_.notify_all();
|
||||
upload_lock.unlock();
|
||||
};
|
||||
|
||||
u->thread = std::make_unique<std::thread>(std::move(upload_handler));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const auto has_only_retries = [this]() -> bool {
|
||||
return std::find_if(upload_queue_.begin(), upload_queue_.end(), [](const auto &upload) -> bool {
|
||||
return not upload->retry;
|
||||
}) == upload_queue_.end();
|
||||
};
|
||||
|
||||
const auto cancel_all_active = [this, &process_completed, &upload_lock]() {
|
||||
upload_lock.lock();
|
||||
for (auto &upload : upload_lookup_) {
|
||||
upload.second->cancel = true;
|
||||
}
|
||||
upload_lock.unlock();
|
||||
|
||||
process_completed();
|
||||
};
|
||||
|
||||
const auto drain_queue = [this, &process_completed, &process_next, &upload_lock]() {
|
||||
while (not upload_lookup_.empty()) {
|
||||
upload_lock.lock();
|
||||
process_next();
|
||||
upload_lock.unlock();
|
||||
|
||||
process_completed();
|
||||
}
|
||||
};
|
||||
|
||||
const auto run_queue_loop = [this, &has_only_retries, &process_completed, &process_next,
|
||||
&upload_lock]() {
|
||||
while (not stop_requested_) {
|
||||
upload_lock.lock();
|
||||
if ((active_uploads_.size() >= config_.get_max_upload_count()) || upload_queue_.empty()) {
|
||||
upload_notify_.wait_for(upload_lock, 5s);
|
||||
if (not stop_requested_ && has_only_retries()) {
|
||||
upload_notify_.wait_for(upload_lock, 5s);
|
||||
}
|
||||
} else {
|
||||
process_next();
|
||||
}
|
||||
upload_lock.unlock();
|
||||
|
||||
process_completed();
|
||||
}
|
||||
};
|
||||
|
||||
run_queue_loop();
|
||||
cancel_all_active();
|
||||
drain_queue();
|
||||
}
|
||||
} // namespace repertory
|
||||
21
src/utils/action_queue.cpp
Normal file
21
src/utils/action_queue.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "utils/action_queue.hpp"
|
||||
|
||||
#include "types/repertory.hpp"
|
||||
|
||||
namespace repertory::utils::action_queue {
|
||||
action_queue::action_queue(const std::string &id,
|
||||
std::uint8_t max_concurrent_actions)
|
||||
: single_thread_service_base("action_queue_" + id),
|
||||
id_(id),
|
||||
max_concurrent_actions_(max_concurrent_actions) {}
|
||||
|
||||
void action_queue::service_function() {
|
||||
//
|
||||
}
|
||||
|
||||
void action_queue::push(std::function<void()> action) {
|
||||
unique_mutex_lock queue_lock(queue_mtx_);
|
||||
queue_.emplace_back(action);
|
||||
queue_notify_.notify_all();
|
||||
}
|
||||
} // namespace repertory::utils::action_queue
|
||||
@@ -1,22 +1,26 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/cli_utils.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "utils/file_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
@@ -24,18 +28,18 @@
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory::utils::cli {
|
||||
void get_api_authentication_data(std::string &user, std::string &password, std::uint16_t &port,
|
||||
const provider_type &pt, const std::string &data_directory) {
|
||||
void get_api_authentication_data(std::string &user, std::string &password,
|
||||
std::uint16_t &port, const provider_type &pt,
|
||||
const std::string &data_directory) {
|
||||
const auto cfg_file_path = utils::path::combine(
|
||||
data_directory.empty() ? app_config::default_data_directory(pt) : data_directory,
|
||||
data_directory.empty() ? app_config::default_data_directory(pt)
|
||||
: data_directory,
|
||||
{"config.json"});
|
||||
|
||||
json data;
|
||||
auto success = false;
|
||||
for (auto i = 0; not(success = utils::file::read_json_file(cfg_file_path, data)) && (i < 20);
|
||||
i++) {
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
const auto success = utils::retryable_action([&]() -> bool {
|
||||
return utils::file::read_json_file(cfg_file_path, data);
|
||||
});
|
||||
|
||||
if (success) {
|
||||
if (user.empty() && password.empty()) {
|
||||
@@ -46,29 +50,24 @@ void get_api_authentication_data(std::string &user, std::string &password, std::
|
||||
}
|
||||
}
|
||||
|
||||
provider_type get_provider_type_from_args(const int &argc, char *argv[]) {
|
||||
auto pt = provider_type::unknown;
|
||||
auto get_provider_type_from_args(int argc, char *argv[]) -> provider_type {
|
||||
#if defined(REPERTORY_ENABLE_S3)
|
||||
if ((pt == provider_type::unknown) && (has_option(argc, argv, options::s3_option))) {
|
||||
pt = provider_type::s3;
|
||||
if (has_option(argc, argv, options::s3_option)) {
|
||||
return provider_type::s3;
|
||||
}
|
||||
#endif // defined(REPERTORY_ENABLE_S3)
|
||||
#if defined(REPERTORY_ENABLE_SKYNET)
|
||||
if ((pt == provider_type::unknown) && (has_option(argc, argv, options::skynet_option))) {
|
||||
pt = provider_type::skynet;
|
||||
if (has_option(argc, argv, options::remote_mount_option)) {
|
||||
return provider_type::remote;
|
||||
}
|
||||
#endif // defined(REPERTORY_ENABLE_SKYNET)
|
||||
if ((pt == provider_type::unknown) && (has_option(argc, argv, options::remote_mount_option))) {
|
||||
pt = provider_type::remote;
|
||||
if (has_option(argc, argv, options::encrypt_option)) {
|
||||
return provider_type::encrypt;
|
||||
}
|
||||
|
||||
if (pt == provider_type::unknown) {
|
||||
pt = provider_type::sia;
|
||||
}
|
||||
|
||||
return pt;
|
||||
return provider_type::sia;
|
||||
}
|
||||
bool has_option(const int &argc, char *argv[], const std::string &option_name) {
|
||||
|
||||
auto has_option(int argc, char *argv[], const std::string &option_name)
|
||||
-> bool {
|
||||
auto ret = false;
|
||||
for (int i = 0; not ret && (i < argc); i++) {
|
||||
ret = (option_name == argv[i]);
|
||||
@@ -76,12 +75,12 @@ bool has_option(const int &argc, char *argv[], const std::string &option_name) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool has_option(const int &argc, char *argv[], const option &opt) {
|
||||
auto has_option(int argc, char *argv[], const option &opt) -> bool {
|
||||
return has_option(argc, argv, opt[0u]) || has_option(argc, argv, opt[1u]);
|
||||
}
|
||||
|
||||
std::vector<std::string> parse_option(const int &argc, char *argv[], const std::string &option_name,
|
||||
std::uint8_t count) {
|
||||
auto parse_option(int argc, char *argv[], const std::string &option_name,
|
||||
std::uint8_t count) -> std::vector<std::string> {
|
||||
std::vector<std::string> ret;
|
||||
auto found = false;
|
||||
for (auto i = 0; not found && (i < argc); i++) {
|
||||
@@ -97,14 +96,16 @@ std::vector<std::string> parse_option(const int &argc, char *argv[], const std::
|
||||
return ret;
|
||||
}
|
||||
|
||||
exit_code parse_string_option(const int &argc, char **argv, const option &opt, std::string &value) {
|
||||
auto parse_string_option(int argc, char **argv, const option &opt,
|
||||
std::string &value) -> exit_code {
|
||||
auto ret = exit_code::success;
|
||||
if (has_option(argc, argv, opt[0u]) || has_option(argc, argv, opt[1u])) {
|
||||
auto data = parse_option(argc, argv, opt[0u], 1u);
|
||||
if (data.empty()) {
|
||||
data = parse_option(argc, argv, opt[1u], 1u);
|
||||
if (data.empty()) {
|
||||
std::cerr << "Invalid syntax for '" << opt[0u] << "," << opt[1u] << '\'' << std::endl;
|
||||
std::cerr << "Invalid syntax for '" << opt[0u] << "," << opt[1u] << '\''
|
||||
<< std::endl;
|
||||
ret = exit_code::invalid_syntax;
|
||||
}
|
||||
}
|
||||
@@ -117,16 +118,19 @@ exit_code parse_string_option(const int &argc, char **argv, const option &opt, s
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::string> parse_drive_options(const int &argc, char **argv, provider_type &pt,
|
||||
std::string &data_directory) {
|
||||
auto parse_drive_options(int argc, char **argv,
|
||||
[[maybe_unused]] provider_type &pt,
|
||||
[[maybe_unused]] std::string &data_directory)
|
||||
-> std::vector<std::string> {
|
||||
// Strip out options from command line
|
||||
const auto &option_list = options::option_list;
|
||||
std::vector<std::string> drive_args;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
const auto &a = argv[i];
|
||||
if (std::find_if(option_list.begin(), option_list.end(), [&a](const auto &pair) -> bool {
|
||||
return ((pair[0] == a) || (pair[1] == a));
|
||||
}) == option_list.end()) {
|
||||
if (std::find_if(option_list.begin(), option_list.end(),
|
||||
[&a](const auto &pair) -> bool {
|
||||
return ((pair[0] == a) || (pair[1] == a));
|
||||
}) == option_list.end()) {
|
||||
drive_args.emplace_back(argv[i]);
|
||||
} else if ((std::string(argv[i]) == options::remote_mount_option[0]) ||
|
||||
(std::string(argv[i]) == options::remote_mount_option[1])) {
|
||||
@@ -160,17 +164,14 @@ std::vector<std::string> parse_drive_options(const int &argc, char **argv, provi
|
||||
pt = provider_type::s3;
|
||||
}
|
||||
#endif // defined(REPERTORY_ENABLE_S3)
|
||||
#if defined(REPERTORY_ENABLE_SKYNET)
|
||||
if ((fuse_option.find("sk") == 0) || (fuse_option.find("skynet") == 0)) {
|
||||
pt = provider_type::skynet;
|
||||
}
|
||||
#endif // defined(REPERTORY_ENABLE_SKYNET)
|
||||
if ((fuse_option.find("dd") == 0) || (fuse_option.find("data_directory") == 0)) {
|
||||
if ((fuse_option.find("dd") == 0) ||
|
||||
(fuse_option.find("data_directory") == 0)) {
|
||||
const auto data = utils::string::split(fuse_option, '=');
|
||||
if (data.size() == 2) {
|
||||
data_directory = data[1];
|
||||
} else {
|
||||
std::cerr << "Invalid syntax for '-dd,--data_directory'" << std::endl;
|
||||
std::cerr << "Invalid syntax for '-dd,--data_directory'"
|
||||
<< std::endl;
|
||||
exit(3);
|
||||
}
|
||||
} else {
|
||||
@@ -197,25 +198,44 @@ std::vector<std::string> parse_drive_options(const int &argc, char **argv, provi
|
||||
}
|
||||
}
|
||||
|
||||
#if FUSE_USE_VERSION < 30
|
||||
{
|
||||
auto it = std::remove_if(
|
||||
fuse_flags_list.begin(), fuse_flags_list.end(),
|
||||
[](const auto &opt) -> bool { return opt.find("hard_remove") == 0; });
|
||||
if (it == fuse_flags_list.end()) {
|
||||
fuse_flags_list.emplace_back("hard_remove");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
auto it = std::remove_if(fuse_flags_list.begin(), fuse_flags_list.end(),
|
||||
[](const auto &opt) -> bool { return opt.find("volname") == 0; });
|
||||
if (it != fuse_flags_list.end()) {
|
||||
fuse_flags_list.erase(it, fuse_flags_list.end());
|
||||
}
|
||||
fuse_flags_list.emplace_back("volname=Repertory" + app_config::get_provider_display_name(pt));
|
||||
{
|
||||
auto it = std::remove_if(
|
||||
fuse_flags_list.begin(), fuse_flags_list.end(),
|
||||
[](const auto &opt) -> bool { return opt.find("volname") == 0; });
|
||||
if (it != fuse_flags_list.end()) {
|
||||
fuse_flags_list.erase(it, fuse_flags_list.end());
|
||||
}
|
||||
fuse_flags_list.emplace_back("volname=Repertory" +
|
||||
app_config::get_provider_display_name(pt));
|
||||
|
||||
it = std::remove_if(fuse_flags_list.begin(), fuse_flags_list.end(),
|
||||
[](const auto &opt) -> bool { return opt.find("daemon_timeout") == 0; });
|
||||
if (it != fuse_flags_list.end()) {
|
||||
fuse_flags_list.erase(it, fuse_flags_list.end());
|
||||
}
|
||||
fuse_flags_list.emplace_back("daemon_timeout=300");
|
||||
it = std::remove_if(fuse_flags_list.begin(), fuse_flags_list.end(),
|
||||
[](const auto &opt) -> bool {
|
||||
return opt.find("daemon_timeout") == 0;
|
||||
});
|
||||
if (it != fuse_flags_list.end()) {
|
||||
fuse_flags_list.erase(it, fuse_flags_list.end());
|
||||
}
|
||||
fuse_flags_list.emplace_back("daemon_timeout=300");
|
||||
|
||||
it = std::remove_if(fuse_flags_list.begin(), fuse_flags_list.end(),
|
||||
[](const auto &opt) -> bool { return opt.find("noappledouble") == 0; });
|
||||
if (it == fuse_flags_list.end()) {
|
||||
fuse_flags_list.emplace_back("noappledouble");
|
||||
it = std::remove_if(fuse_flags_list.begin(), fuse_flags_list.end(),
|
||||
[](const auto &opt) -> bool {
|
||||
return opt.find("noappledouble") == 0;
|
||||
});
|
||||
if (it == fuse_flags_list.end()) {
|
||||
fuse_flags_list.emplace_back("noappledouble");
|
||||
}
|
||||
}
|
||||
#endif // __APPLE__
|
||||
|
||||
|
||||
@@ -1,31 +1,38 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/encrypting_reader.hpp"
|
||||
#include "events/events.hpp"
|
||||
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory::utils::encryption {
|
||||
class encrypting_streambuf final : public encrypting_reader::streambuf {
|
||||
public:
|
||||
explicit encrypting_streambuf(const encrypting_reader &reader) : reader_(reader) {
|
||||
explicit encrypting_streambuf(const encrypting_reader &reader)
|
||||
: reader_(reader) {
|
||||
setg(reinterpret_cast<char *>(0), reinterpret_cast<char *>(0),
|
||||
reinterpret_cast<char *>(reader_.get_total_size()));
|
||||
}
|
||||
@@ -36,9 +43,10 @@ private:
|
||||
encrypting_reader reader_;
|
||||
|
||||
protected:
|
||||
pos_type seekoff(off_type off, std::ios_base::seekdir dir,
|
||||
std::ios_base::openmode which = std::ios_base::out |
|
||||
std::ios_base::in) override {
|
||||
auto seekoff(off_type off, std::ios_base::seekdir dir,
|
||||
std::ios_base::openmode which = std::ios_base::out |
|
||||
std::ios_base::in)
|
||||
-> pos_type override {
|
||||
if ((which & std::ios_base::in) != std::ios_base::in) {
|
||||
throw std::runtime_error("output is not supported");
|
||||
}
|
||||
@@ -46,9 +54,11 @@ protected:
|
||||
const auto set_position = [this](char *next) -> pos_type {
|
||||
if ((next <= egptr()) && (next >= eback())) {
|
||||
setg(eback(), next, reinterpret_cast<char *>(reader_.get_total_size()));
|
||||
return pos_type(reinterpret_cast<std::uintptr_t>(gptr()));
|
||||
return static_cast<std::streamoff>(
|
||||
reinterpret_cast<std::uintptr_t>(gptr()));
|
||||
}
|
||||
return pos_type(traits_type::eof());
|
||||
|
||||
return {traits_type::eof()};
|
||||
};
|
||||
|
||||
switch (dir) {
|
||||
@@ -65,12 +75,14 @@ protected:
|
||||
return encrypting_reader::streambuf::seekoff(off, dir, which);
|
||||
}
|
||||
|
||||
pos_type seekpos(pos_type pos, std::ios_base::openmode which = std::ios_base::out |
|
||||
std::ios_base::in) override {
|
||||
auto seekpos(pos_type pos,
|
||||
std::ios_base::openmode which = std::ios_base::out |
|
||||
std::ios_base::in)
|
||||
-> pos_type override {
|
||||
return seekoff(pos, std::ios_base::beg, which);
|
||||
}
|
||||
|
||||
int_type uflow() override {
|
||||
auto uflow() -> int_type override {
|
||||
auto ret = underflow();
|
||||
if (ret == traits_type::eof()) {
|
||||
return ret;
|
||||
@@ -81,12 +93,13 @@ protected:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int_type underflow() override {
|
||||
auto underflow() -> int_type override {
|
||||
if (gptr() == egptr()) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
reader_.set_read_position(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(gptr())));
|
||||
reader_.set_read_position(
|
||||
static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(gptr())));
|
||||
|
||||
char c{};
|
||||
const auto res = reader_.reader_function(&c, 1u, 1u, &reader_);
|
||||
@@ -97,12 +110,13 @@ protected:
|
||||
return c;
|
||||
}
|
||||
|
||||
std::streamsize xsgetn(char *ptr, std::streamsize count) override {
|
||||
auto xsgetn(char *ptr, std::streamsize count) -> std::streamsize override {
|
||||
if (gptr() == egptr()) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
reader_.set_read_position(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(gptr())));
|
||||
reader_.set_read_position(
|
||||
static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(gptr())));
|
||||
|
||||
const auto res = reader_.reader_function(ptr, 1u, count, &reader_);
|
||||
if ((res == reader_.get_error_return()) ||
|
||||
@@ -110,14 +124,16 @@ protected:
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
setg(eback(), gptr() + res, reinterpret_cast<char *>(reader_.get_total_size()));
|
||||
setg(eback(), gptr() + res,
|
||||
reinterpret_cast<char *>(reader_.get_total_size()));
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
class encrypting_reader_iostream final : public encrypting_reader::iostream {
|
||||
public:
|
||||
explicit encrypting_reader_iostream(std::unique_ptr<encrypting_streambuf> buffer)
|
||||
explicit encrypting_reader_iostream(
|
||||
std::unique_ptr<encrypting_streambuf> buffer)
|
||||
: encrypting_reader::iostream(buffer.get()), buffer_(std::move(buffer)) {}
|
||||
|
||||
~encrypting_reader_iostream() override = default;
|
||||
@@ -127,42 +143,101 @@ private:
|
||||
};
|
||||
|
||||
const std::size_t encrypting_reader::header_size_ = ([]() {
|
||||
CryptoPP::XChaCha20Poly1305::Encryption enc;
|
||||
return enc.IVSize() + enc.DigestSize();
|
||||
return crypto_aead_xchacha20poly1305_IETF_NPUBBYTES +
|
||||
crypto_aead_xchacha20poly1305_IETF_ABYTES;
|
||||
})();
|
||||
const std::size_t encrypting_reader::data_chunk_size_ = (8u * 1024u * 1024u);
|
||||
const std::size_t encrypting_reader::encrypted_chunk_size_ = data_chunk_size_ + header_size_;
|
||||
const std::size_t encrypting_reader::encrypted_chunk_size_ =
|
||||
data_chunk_size_ + header_size_;
|
||||
|
||||
encrypting_reader::encrypting_reader(const std::string &file_name, const std::string &source_path,
|
||||
const bool &stop_requested, const std::string &token,
|
||||
const size_t error_return)
|
||||
encrypting_reader::encrypting_reader(
|
||||
const std::string &file_name, const std::string &source_path,
|
||||
stop_type &stop_requested, const std::string &token,
|
||||
std::optional<std::string> relative_parent_path, const size_t error_return)
|
||||
: key_(utils::encryption::generate_key(token)),
|
||||
stop_requested_(stop_requested),
|
||||
error_return_(error_return) {
|
||||
const auto res = native_file::open(source_path, source_file_);
|
||||
const auto res = native_file::open(
|
||||
source_path, not relative_parent_path.has_value(), source_file_);
|
||||
if (res != api_error::success) {
|
||||
throw std::runtime_error("file open failed: " + source_path + ':' + api_error_to_string(res));
|
||||
throw std::runtime_error("file open failed|src|" + source_path + '|' +
|
||||
api_error_to_string(res));
|
||||
}
|
||||
|
||||
std::vector<char> result;
|
||||
data_buffer result;
|
||||
utils::encryption::encrypt_data(key_, file_name.c_str(),
|
||||
strnlen(file_name.c_str(), file_name.size()), result);
|
||||
strnlen(file_name.c_str(), file_name.size()),
|
||||
result);
|
||||
encrypted_file_name_ = utils::to_hex_string(result);
|
||||
|
||||
if (relative_parent_path.has_value()) {
|
||||
for (const auto &part :
|
||||
std::filesystem::path(relative_parent_path.value())) {
|
||||
utils::encryption::encrypt_data(
|
||||
key_, part.string().c_str(),
|
||||
strnlen(part.string().c_str(), part.string().size()), result);
|
||||
encrypted_file_path_ += '/' + utils::to_hex_string(result);
|
||||
}
|
||||
encrypted_file_path_ += '/' + encrypted_file_name_;
|
||||
}
|
||||
|
||||
std::uint64_t file_size{};
|
||||
if (not utils::file::get_file_size(source_path, file_size)) {
|
||||
throw std::runtime_error("get file size failed: " + source_path + ':' +
|
||||
throw std::runtime_error("get file size failed|src|" + source_path + '|' +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
|
||||
const auto total_chunks = static_cast<std::size_t>(
|
||||
utils::divide_with_ceiling(file_size, static_cast<std::uint64_t>(data_chunk_size_)));
|
||||
total_size_ = file_size + (total_chunks * encrypting_reader::get_header_size());
|
||||
const auto total_chunks = static_cast<std::size_t>(utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_)));
|
||||
total_size_ =
|
||||
file_size + (total_chunks * encrypting_reader::get_header_size());
|
||||
last_data_chunk_ = total_chunks - 1u;
|
||||
last_data_chunk_size_ =
|
||||
static_cast<std::size_t>((file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) ? file_size % data_chunk_size_
|
||||
: data_chunk_size_);
|
||||
last_data_chunk_size_ = static_cast<std::size_t>(
|
||||
(file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) ? file_size % data_chunk_size_
|
||||
: data_chunk_size_);
|
||||
iv_list_.resize(total_chunks);
|
||||
for (auto &iv : iv_list_) {
|
||||
randombytes_buf(iv.data(), iv.size());
|
||||
}
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
const std::string &encrypted_file_path, const std::string &source_path,
|
||||
stop_type &stop_requested, const std::string &token,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
const size_t error_return)
|
||||
: key_(utils::encryption::generate_key(token)),
|
||||
stop_requested_(stop_requested),
|
||||
error_return_(error_return) {
|
||||
const auto res = native_file::open(source_path, false, source_file_);
|
||||
if (res != api_error::success) {
|
||||
throw std::runtime_error("file open failed|src|" + source_path + '|' +
|
||||
api_error_to_string(res));
|
||||
}
|
||||
|
||||
encrypted_file_path_ = encrypted_file_path;
|
||||
encrypted_file_name_ =
|
||||
std::filesystem::path(encrypted_file_path_).filename().string();
|
||||
|
||||
std::uint64_t file_size{};
|
||||
if (not utils::file::get_file_size(source_path, file_size)) {
|
||||
throw std::runtime_error("get file size failed|src|" + source_path + '|' +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
|
||||
const auto total_chunks = static_cast<std::size_t>(utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_)));
|
||||
total_size_ =
|
||||
file_size + (total_chunks * encrypting_reader::get_header_size());
|
||||
last_data_chunk_ = total_chunks - 1u;
|
||||
last_data_chunk_size_ = static_cast<std::size_t>(
|
||||
(file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) ? file_size % data_chunk_size_
|
||||
: data_chunk_size_);
|
||||
iv_list_ = std::move(iv_list);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(const encrypting_reader &r)
|
||||
@@ -171,6 +246,8 @@ encrypting_reader::encrypting_reader(const encrypting_reader &r)
|
||||
error_return_(r.error_return_),
|
||||
chunk_buffers_(r.chunk_buffers_),
|
||||
encrypted_file_name_(r.encrypted_file_name_),
|
||||
encrypted_file_path_(r.encrypted_file_path_),
|
||||
iv_list_(r.iv_list_),
|
||||
last_data_chunk_(r.last_data_chunk_),
|
||||
last_data_chunk_size_(r.last_data_chunk_size_),
|
||||
read_offset_(r.read_offset_),
|
||||
@@ -183,23 +260,41 @@ encrypting_reader::~encrypting_reader() {
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t encrypting_reader::calculate_decrypted_size(const std::uint64_t &total_size) {
|
||||
auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size)
|
||||
-> std::uint64_t {
|
||||
return total_size - (utils::divide_with_ceiling(
|
||||
total_size, static_cast<std::uint64_t>(get_encrypted_chunk_size())) *
|
||||
total_size, static_cast<std::uint64_t>(
|
||||
get_encrypted_chunk_size())) *
|
||||
get_header_size());
|
||||
}
|
||||
|
||||
std::shared_ptr<encrypting_reader::iostream> encrypting_reader::create_iostream() const {
|
||||
auto encrypting_reader::calculate_encrypted_size(const std::string &source_path)
|
||||
-> std::uint64_t {
|
||||
std::uint64_t file_size{};
|
||||
if (not utils::file::get_file_size(source_path, file_size)) {
|
||||
throw std::runtime_error("get file size failed|src|" + source_path + '|' +
|
||||
std::to_string(utils::get_last_error_code()));
|
||||
}
|
||||
|
||||
const auto total_chunks = static_cast<std::size_t>(utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_)));
|
||||
return file_size + (total_chunks * encrypting_reader::get_header_size());
|
||||
}
|
||||
|
||||
auto encrypting_reader::create_iostream() const
|
||||
-> std::shared_ptr<encrypting_reader::iostream> {
|
||||
return std::make_shared<encrypting_reader_iostream>(
|
||||
std::make_unique<encrypting_streambuf>(*this));
|
||||
}
|
||||
|
||||
size_t encrypting_reader::reader_function(char *buffer, size_t size, size_t nitems) {
|
||||
const auto read_size = static_cast<std::size_t>(
|
||||
std::min(static_cast<std::uint64_t>(size * nitems), total_size_ - read_offset_));
|
||||
auto encrypting_reader::reader_function(char *buffer, size_t size,
|
||||
size_t nitems) -> size_t {
|
||||
const auto read_size = static_cast<std::size_t>(std::min(
|
||||
static_cast<std::uint64_t>(size * nitems), total_size_ - read_offset_));
|
||||
|
||||
auto chunk = static_cast<std::size_t>(read_offset_ / encrypted_chunk_size_);
|
||||
auto chunk_offset = static_cast<std::size_t>(read_offset_ % encrypted_chunk_size_);
|
||||
auto chunk_offset =
|
||||
static_cast<std::size_t>(read_offset_ % encrypted_chunk_size_);
|
||||
std::size_t total_read = 0u;
|
||||
|
||||
auto ret = false;
|
||||
@@ -210,21 +305,25 @@ size_t encrypting_reader::reader_function(char *buffer, size_t size, size_t nite
|
||||
while (not stop_requested_ && ret && remain) {
|
||||
if (chunk_buffers_.find(chunk) == chunk_buffers_.end()) {
|
||||
auto &chunk_buffer = chunk_buffers_[chunk];
|
||||
std::vector<char> file_data(chunk == last_data_chunk_ ? last_data_chunk_size_
|
||||
: data_chunk_size_);
|
||||
data_buffer file_data(chunk == last_data_chunk_
|
||||
? last_data_chunk_size_
|
||||
: data_chunk_size_);
|
||||
chunk_buffer.resize(file_data.size() + get_header_size());
|
||||
|
||||
std::size_t bytes_read{};
|
||||
if ((ret = source_file_->read_bytes(&file_data[0u], file_data.size(),
|
||||
chunk * data_chunk_size_, bytes_read))) {
|
||||
utils::encryption::encrypt_data(key_, file_data, chunk_buffer);
|
||||
chunk * data_chunk_size_,
|
||||
bytes_read))) {
|
||||
utils::encryption::encrypt_data(iv_list_.at(chunk), key_, file_data,
|
||||
chunk_buffer);
|
||||
}
|
||||
} else if (chunk) {
|
||||
chunk_buffers_.erase(chunk - 1u);
|
||||
}
|
||||
|
||||
auto &chunk_buffer = chunk_buffers_[chunk];
|
||||
const auto to_read = std::min(chunk_buffer.size() - chunk_offset, remain);
|
||||
const auto to_read =
|
||||
std::min(chunk_buffer.size() - chunk_offset, remain);
|
||||
std::memcpy(buffer + total_read, &chunk_buffer[chunk_offset], to_read);
|
||||
total_read += to_read;
|
||||
remain -= to_read;
|
||||
@@ -233,12 +332,13 @@ size_t encrypting_reader::reader_function(char *buffer, size_t size, size_t nite
|
||||
read_offset_ += to_read;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__,
|
||||
e.what() ? e.what() : "unkown error");
|
||||
utils::error::raise_error(__FUNCTION__, e, "exception occurred");
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
return stop_requested_ ? CURL_READFUNC_ABORT : ret ? total_read : error_return_;
|
||||
return stop_requested_ ? CURL_READFUNC_ABORT
|
||||
: ret ? total_read
|
||||
: error_return_;
|
||||
}
|
||||
} // namespace repertory::utils::encryption
|
||||
|
||||
@@ -1,91 +1,136 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/encryption.hpp"
|
||||
#include "events/events.hpp"
|
||||
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory::utils::encryption {
|
||||
api_error decrypt_file_name(const std::string &encryption_token, std::string &file_name) {
|
||||
std::vector<char> buffer;
|
||||
auto decrypt_file_path(const std::string &encryption_token,
|
||||
std::string &file_path) -> api_error {
|
||||
std::string decrypted_file_path{};
|
||||
for (const auto &part : std::filesystem::path(file_path)) {
|
||||
auto file_name = part.string();
|
||||
if (file_name == "/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto res = decrypt_file_name(encryption_token, file_name);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
decrypted_file_path += '/' + file_name;
|
||||
}
|
||||
|
||||
file_path = decrypted_file_path;
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto decrypt_file_name(const std::string &encryption_token,
|
||||
std::string &file_name) -> api_error {
|
||||
data_buffer buffer;
|
||||
if (not utils::from_hex_string(file_name, buffer)) {
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
file_name.clear();
|
||||
if (not utils::encryption::decrypt_data(encryption_token, buffer, file_name)) {
|
||||
if (not utils::encryption::decrypt_data(encryption_token, buffer,
|
||||
file_name)) {
|
||||
return api_error::decryption_error;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
CryptoPP::SecByteBlock generate_key(const std::string &encryption_token) {
|
||||
CryptoPP::SecByteBlock key(CryptoPP::SHA256::DIGESTSIZE);
|
||||
CryptoPP::SHA256().CalculateDigest(
|
||||
reinterpret_cast<CryptoPP::byte *>(&key[0u]),
|
||||
reinterpret_cast<const CryptoPP::byte *>(encryption_token.c_str()),
|
||||
strnlen(encryption_token.c_str(), encryption_token.size()));
|
||||
auto generate_key(const std::string &encryption_token) -> key_type {
|
||||
crypto_hash_sha256_state state{};
|
||||
auto res = crypto_hash_sha256_init(&state);
|
||||
if (res != 0) {
|
||||
throw std::runtime_error("failed to initialize sha256|" +
|
||||
std::to_string(res));
|
||||
}
|
||||
|
||||
return key;
|
||||
if ((res = crypto_hash_sha256_update(
|
||||
&state,
|
||||
reinterpret_cast<const unsigned char *>(encryption_token.c_str()),
|
||||
strnlen(encryption_token.c_str(), encryption_token.size()))) != 0) {
|
||||
throw std::runtime_error("failed to update sha256|" + std::to_string(res));
|
||||
}
|
||||
|
||||
key_type ret{};
|
||||
if ((res = crypto_hash_sha256_final(&state, ret.data())) != 0) {
|
||||
throw std::runtime_error("failed to finalize sha256|" +
|
||||
std::to_string(res));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error read_encrypted_range(
|
||||
const http_range &range, const CryptoPP::SecByteBlock &key,
|
||||
const std::function<api_error(std::vector<char> &ct, const std::uint64_t &start_offset,
|
||||
const std::uint64_t &end_offset)> &reader,
|
||||
const std::uint64_t &total_size, std::vector<char> &data) {
|
||||
static constexpr const auto &encrypted_chunk_size =
|
||||
auto read_encrypted_range(
|
||||
const http_range &range, const key_type &key,
|
||||
const std::function<api_error(data_buffer &ct, std::uint64_t start_offset,
|
||||
std::uint64_t end_offset)> &reader,
|
||||
std::uint64_t total_size, data_buffer &data) -> api_error {
|
||||
const auto encrypted_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_encrypted_chunk_size();
|
||||
static constexpr const auto &data_chunk_size =
|
||||
const auto data_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size();
|
||||
static constexpr const auto &header_size =
|
||||
const auto header_size =
|
||||
utils::encryption::encrypting_reader::get_header_size();
|
||||
|
||||
const auto start_chunk = static_cast<std::size_t>(range.begin / data_chunk_size);
|
||||
const auto start_chunk =
|
||||
static_cast<std::size_t>(range.begin / data_chunk_size);
|
||||
const auto end_chunk = static_cast<std::size_t>(range.end / data_chunk_size);
|
||||
auto remain = range.end - range.begin + 1u;
|
||||
auto source_offset = static_cast<std::size_t>(range.begin % data_chunk_size);
|
||||
|
||||
for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) {
|
||||
std::vector<char> ct;
|
||||
data_buffer ct;
|
||||
const auto start_offset = chunk * encrypted_chunk_size;
|
||||
const auto end_offset =
|
||||
std::min(start_offset + (total_size - (chunk * data_chunk_size)) + header_size - 1u,
|
||||
static_cast<std::uint64_t>(start_offset + encrypted_chunk_size - 1u));
|
||||
const auto end_offset = std::min(
|
||||
start_offset + (total_size - (chunk * data_chunk_size)) + header_size -
|
||||
1u,
|
||||
static_cast<std::uint64_t>(start_offset + encrypted_chunk_size - 1u));
|
||||
|
||||
const auto result = reader(ct, start_offset, end_offset);
|
||||
if (result != api_error::success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<char> source_buffer;
|
||||
data_buffer source_buffer;
|
||||
if (not utils::encryption::decrypt_data(key, ct, source_buffer)) {
|
||||
return api_error::decryption_error;
|
||||
}
|
||||
ct.clear();
|
||||
|
||||
const auto data_size = static_cast<std::size_t>(
|
||||
std::min(remain, static_cast<std::uint64_t>(data_chunk_size - source_offset)));
|
||||
const auto data_size = static_cast<std::size_t>(std::min(
|
||||
remain, static_cast<std::uint64_t>(data_chunk_size - source_offset)));
|
||||
std::copy(source_buffer.begin() + source_offset,
|
||||
source_buffer.begin() + source_offset + data_size, std::back_inserter(data));
|
||||
source_buffer.begin() + source_offset + data_size,
|
||||
std::back_inserter(data));
|
||||
remain -= data_size;
|
||||
source_offset = 0u;
|
||||
}
|
||||
|
||||
172
src/utils/error_utils.cpp
Normal file
172
src/utils/error_utils.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "utils/error_utils.hpp"
|
||||
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
|
||||
namespace repertory::utils::error {
|
||||
void raise_error(std::string_view function, std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function), static_cast<std::string>(msg));
|
||||
}
|
||||
|
||||
void raise_error(std::string_view function, const api_error &e,
|
||||
std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|err|" + api_error_to_string(e));
|
||||
}
|
||||
|
||||
void raise_error(std::string_view function, const std::exception &e,
|
||||
std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|err|" +
|
||||
(e.what() ? e.what() : "unknown error"));
|
||||
}
|
||||
|
||||
void raise_error(std::string_view function, const json &e,
|
||||
std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|err|" + e.dump(2));
|
||||
}
|
||||
|
||||
void raise_error(std::string_view function, std::int64_t e,
|
||||
std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|err|" + std::to_string(e));
|
||||
}
|
||||
|
||||
void raise_error(std::string_view function, const api_error &e,
|
||||
std::string_view file_path, std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|sp|" +
|
||||
static_cast<std::string>(file_path) + "|err|" +
|
||||
api_error_to_string(e));
|
||||
}
|
||||
|
||||
void raise_error(std::string_view function, std::int64_t e,
|
||||
std::string_view file_path, std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|sp|" +
|
||||
static_cast<std::string>(file_path) + "|err|" + std::to_string(e));
|
||||
}
|
||||
|
||||
void raise_error(std::string_view function, const std::exception &e,
|
||||
std::string_view file_path, std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|sp|" +
|
||||
static_cast<std::string>(file_path) + "|err|" +
|
||||
(e.what() ? e.what() : "unknown error"));
|
||||
}
|
||||
|
||||
void raise_api_path_error(std::string_view function, std::string_view api_path,
|
||||
const api_error &e, std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|ap|" +
|
||||
static_cast<std::string>(api_path) + "|err|" +
|
||||
api_error_to_string(e));
|
||||
}
|
||||
|
||||
void raise_api_path_error(std::string_view function, std::string_view api_path,
|
||||
std::int64_t e, std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|ap|" +
|
||||
static_cast<std::string>(api_path) + "|err|" + std::to_string(e));
|
||||
}
|
||||
|
||||
void raise_api_path_error(std::string_view function, std::string_view api_path,
|
||||
const std::exception &e, std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|ap|" +
|
||||
static_cast<std::string>(api_path) + "|err|" +
|
||||
(e.what() ? e.what() : "unknown error"));
|
||||
}
|
||||
|
||||
void raise_api_path_error(std::string_view function, std::string_view api_path,
|
||||
std::string_view source_path, const api_error &e,
|
||||
std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|ap|" +
|
||||
static_cast<std::string>(api_path) + "|sp|" +
|
||||
static_cast<std::string>(source_path) + "|err|" +
|
||||
api_error_to_string(e));
|
||||
}
|
||||
|
||||
void raise_api_path_error(std::string_view function, std::string_view api_path,
|
||||
std::string_view source_path, std::int64_t e,
|
||||
std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|ap|" +
|
||||
static_cast<std::string>(api_path) + "|sp|" +
|
||||
static_cast<std::string>(source_path) + "|err|" + std::to_string(e));
|
||||
}
|
||||
|
||||
void raise_api_path_error(std::string_view function, std::string_view api_path,
|
||||
const json &e, std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|ap|" +
|
||||
static_cast<std::string>(api_path) + "|err|" + e.dump(2));
|
||||
}
|
||||
|
||||
void raise_api_path_error(std::string_view function, std::string_view api_path,
|
||||
std::string_view source_path, const std::exception &e,
|
||||
std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|ap|" +
|
||||
static_cast<std::string>(api_path) + "|sp|" +
|
||||
static_cast<std::string>(source_path) + "|err|" +
|
||||
(e.what() ? e.what() : "unknown error"));
|
||||
}
|
||||
|
||||
void raise_url_error(std::string_view function, std::string_view url,
|
||||
CURLcode e, std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|url|" + static_cast<std::string>(url) +
|
||||
"|err|" + curl_easy_strerror(e));
|
||||
}
|
||||
|
||||
void raise_url_error(std::string_view function, std::string_view url,
|
||||
std::string_view source_path, const std::exception &e,
|
||||
std::string_view msg) {
|
||||
event_system::instance().raise<repertory_exception>(
|
||||
static_cast<std::string>(function),
|
||||
static_cast<std::string>(msg) + "|url|" + static_cast<std::string>(url) +
|
||||
"|sp|" + static_cast<std::string>(source_path) + "|err|" +
|
||||
(e.what() ? e.what() : "unknown error"));
|
||||
}
|
||||
} // namespace repertory::utils::error
|
||||
@@ -1,44 +1,34 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/file_utils.hpp"
|
||||
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory::utils::file {
|
||||
api_error assign_and_get_native_file(filesystem_item &fi, native_file_ptr &nf) {
|
||||
recur_mutex_lock l(*fi.lock);
|
||||
if (fi.handle == REPERTORY_INVALID_HANDLE) {
|
||||
const auto res = native_file::create_or_open(fi.source_path, nf);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
fi.handle = nf->get_handle();
|
||||
} else {
|
||||
nf = native_file::attach(fi.handle);
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
std::uint64_t calculate_used_space(std::string path, const bool &recursive) {
|
||||
auto calculate_used_space(std::string path, bool recursive) -> std::uint64_t {
|
||||
path = utils::path::absolute(path);
|
||||
std::uint64_t ret = 0u;
|
||||
#ifdef _WIN32
|
||||
@@ -50,7 +40,8 @@ std::uint64_t calculate_used_space(std::string path, const bool &recursive) {
|
||||
const auto file_name = std::string(fd.cFileName);
|
||||
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if (recursive && (file_name != ".") && (file_name != "..")) {
|
||||
ret += calculate_used_space(utils::path::combine(path, {file_name}), recursive);
|
||||
ret += calculate_used_space(utils::path::combine(path, {file_name}),
|
||||
recursive);
|
||||
}
|
||||
} else {
|
||||
std::uint64_t file_size{};
|
||||
@@ -67,12 +58,15 @@ std::uint64_t calculate_used_space(std::string path, const bool &recursive) {
|
||||
struct dirent *de{};
|
||||
while ((de = readdir(root)) != nullptr) {
|
||||
if (de->d_type == DT_DIR) {
|
||||
if (recursive && (strcmp(de->d_name, ".") != 0) && (strcmp(de->d_name, "..") != 0)) {
|
||||
ret += calculate_used_space(utils::path::combine(path, {de->d_name}), recursive);
|
||||
if (recursive && (strcmp(de->d_name, ".") != 0) &&
|
||||
(strcmp(de->d_name, "..") != 0)) {
|
||||
ret += calculate_used_space(utils::path::combine(path, {de->d_name}),
|
||||
recursive);
|
||||
}
|
||||
} else {
|
||||
std::uint64_t file_size{};
|
||||
if (get_file_size(utils::path::combine(path, {de->d_name}), file_size)) {
|
||||
if (get_file_size(utils::path::combine(path, {de->d_name}),
|
||||
file_size)) {
|
||||
ret += file_size;
|
||||
}
|
||||
}
|
||||
@@ -88,7 +82,8 @@ void change_to_process_directory() {
|
||||
#ifdef _WIN32
|
||||
std::string file_name;
|
||||
file_name.resize(MAX_PATH);
|
||||
::GetModuleFileNameA(nullptr, &file_name[0u], static_cast<DWORD>(file_name.size()));
|
||||
::GetModuleFileNameA(nullptr, &file_name[0u],
|
||||
static_cast<DWORD>(file_name.size()));
|
||||
|
||||
std::string path = file_name.c_str();
|
||||
::PathRemoveFileSpecA(&path[0u]);
|
||||
@@ -106,19 +101,18 @@ void change_to_process_directory() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool copy_file(std::string from_path, std::string to_path) {
|
||||
auto copy_file(std::string from_path, std::string to_path) -> bool {
|
||||
from_path = utils::path::absolute(from_path);
|
||||
to_path = utils::path::absolute(to_path);
|
||||
if (is_file(from_path) && not is_directory(to_path)) {
|
||||
boost::system::error_code ec{};
|
||||
boost::filesystem::copy_file(from_path, to_path, ec);
|
||||
return not ec.failed();
|
||||
return std::filesystem::copy_file(from_path, to_path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool copy_directory_recursively(std::string from_path, std::string to_path) {
|
||||
auto copy_directory_recursively(std::string from_path, std::string to_path)
|
||||
-> bool {
|
||||
from_path = utils::path::absolute(from_path);
|
||||
to_path = utils::path::absolute(to_path);
|
||||
auto ret = create_full_directory_path(to_path);
|
||||
@@ -131,9 +125,11 @@ bool copy_directory_recursively(std::string from_path, std::string to_path) {
|
||||
ret = true;
|
||||
do {
|
||||
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if ((std::string(fd.cFileName) != ".") && (std::string(fd.cFileName) != "..")) {
|
||||
ret = copy_directory_recursively(utils::path::combine(from_path, {fd.cFileName}),
|
||||
utils::path::combine(to_path, {fd.cFileName}));
|
||||
if ((std::string(fd.cFileName) != ".") &&
|
||||
(std::string(fd.cFileName) != "..")) {
|
||||
ret = copy_directory_recursively(
|
||||
utils::path::combine(from_path, {fd.cFileName}),
|
||||
utils::path::combine(to_path, {fd.cFileName}));
|
||||
}
|
||||
} else {
|
||||
ret = copy_file(utils::path::combine(from_path, {fd.cFileName}),
|
||||
@@ -149,9 +145,11 @@ bool copy_directory_recursively(std::string from_path, std::string to_path) {
|
||||
struct dirent *de{};
|
||||
while (ret && (de = readdir(root))) {
|
||||
if (de->d_type == DT_DIR) {
|
||||
if ((strcmp(de->d_name, ".") != 0) && (strcmp(de->d_name, "..") != 0)) {
|
||||
ret = copy_directory_recursively(utils::path::combine(from_path, {de->d_name}),
|
||||
utils::path::combine(to_path, {de->d_name}));
|
||||
if ((strcmp(de->d_name, ".") != 0) &&
|
||||
(strcmp(de->d_name, "..") != 0)) {
|
||||
ret = copy_directory_recursively(
|
||||
utils::path::combine(from_path, {de->d_name}),
|
||||
utils::path::combine(to_path, {de->d_name}));
|
||||
}
|
||||
} else {
|
||||
ret = copy_file(utils::path::combine(from_path, {de->d_name}),
|
||||
@@ -167,15 +165,16 @@ bool copy_directory_recursively(std::string from_path, std::string to_path) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool create_full_directory_path(std::string path) {
|
||||
auto create_full_directory_path(std::string path) -> bool {
|
||||
#ifdef _WIN32
|
||||
const auto unicode_path = utils::string::from_utf8(utils::path::absolute(path));
|
||||
const auto unicode_path =
|
||||
utils::string::from_utf8(utils::path::absolute(path));
|
||||
return is_directory(path) ||
|
||||
(::SHCreateDirectory(nullptr, unicode_path.c_str()) == ERROR_SUCCESS);
|
||||
#else
|
||||
auto ret = true;
|
||||
const auto paths =
|
||||
utils::string::split(utils::path::absolute(path), utils::path::directory_seperator[0u]);
|
||||
const auto paths = utils::string::split(utils::path::absolute(path),
|
||||
utils::path::directory_seperator[0u]);
|
||||
std::string current_path;
|
||||
for (std::size_t i = 0u; ret && (i < paths.size()); i++) {
|
||||
if (paths[i].empty()) { // Skip root
|
||||
@@ -191,21 +190,22 @@ bool create_full_directory_path(std::string path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool delete_directory(std::string path, const bool &recursive) {
|
||||
auto delete_directory(std::string path, bool recursive) -> bool {
|
||||
if (recursive) {
|
||||
return delete_directory_recursively(path);
|
||||
}
|
||||
|
||||
path = utils::path::absolute(path);
|
||||
#ifdef _WIN32
|
||||
return (not is_directory(path) ||
|
||||
utils::retryable_action([&]() -> bool { return !!::RemoveDirectoryA(path.c_str()); }));
|
||||
return (not is_directory(path) || utils::retryable_action([&]() -> bool {
|
||||
return !!::RemoveDirectoryA(path.c_str());
|
||||
}));
|
||||
#else
|
||||
return not is_directory(path) || (rmdir(path.c_str()) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool delete_directory_recursively(std::string path) {
|
||||
auto delete_directory_recursively(std::string path) -> bool {
|
||||
path = utils::path::absolute(path);
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -213,14 +213,16 @@ bool delete_directory_recursively(std::string path) {
|
||||
const auto search = utils::path::combine(path, {"*.*"});
|
||||
auto find = ::FindFirstFile(search.c_str(), &fd);
|
||||
if (find != INVALID_HANDLE_VALUE) {
|
||||
auto res = false;
|
||||
auto res = true;
|
||||
do {
|
||||
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if ((std::string(fd.cFileName) != ".") && (std::string(fd.cFileName) != "..")) {
|
||||
res = delete_directory_recursively(utils::path::combine(path, {fd.cFileName}));
|
||||
if ((std::string(fd.cFileName) != ".") &&
|
||||
(std::string(fd.cFileName) != "..")) {
|
||||
res = delete_directory_recursively(
|
||||
utils::path::combine(path, {fd.cFileName}));
|
||||
}
|
||||
} else {
|
||||
res = delete_file(utils::path::combine(path, {fd.cFileName}));
|
||||
res = retry_delete_file(utils::path::combine(path, {fd.cFileName}));
|
||||
}
|
||||
} while (res && (::FindNextFile(find, &fd) != 0));
|
||||
|
||||
@@ -234,10 +236,11 @@ bool delete_directory_recursively(std::string path) {
|
||||
while (res && (de = readdir(root))) {
|
||||
if (de->d_type == DT_DIR) {
|
||||
if ((strcmp(de->d_name, ".") != 0) && (strcmp(de->d_name, "..") != 0)) {
|
||||
res = delete_directory_recursively(utils::path::combine(path, {de->d_name}));
|
||||
res = delete_directory_recursively(
|
||||
utils::path::combine(path, {de->d_name}));
|
||||
}
|
||||
} else {
|
||||
res = delete_file(utils::path::combine(path, {de->d_name}));
|
||||
res = retry_delete_file(utils::path::combine(path, {de->d_name}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +251,7 @@ bool delete_directory_recursively(std::string path) {
|
||||
return delete_directory(path, false);
|
||||
}
|
||||
|
||||
bool delete_file(std::string path) {
|
||||
auto delete_file(std::string path) -> bool {
|
||||
path = utils::path::absolute(path);
|
||||
#ifdef _WIN32
|
||||
return (not is_file(path) || utils::retryable_action([&]() -> bool {
|
||||
@@ -264,18 +267,55 @@ bool delete_file(std::string path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string generate_sha256(const std::string &file_path) {
|
||||
std::string value;
|
||||
CryptoPP::SHA256 hash;
|
||||
CryptoPP::FileSource(
|
||||
file_path.c_str(), true,
|
||||
new CryptoPP::HashFilter(hash, new CryptoPP::HexEncoder(new CryptoPP::StringSink(value))));
|
||||
return value;
|
||||
auto generate_sha256(const std::string &file_path) -> std::string {
|
||||
crypto_hash_sha256_state state{};
|
||||
auto res = crypto_hash_sha256_init(&state);
|
||||
if (res != 0) {
|
||||
throw std::runtime_error("failed to initialize sha256|" +
|
||||
std::to_string(res));
|
||||
}
|
||||
|
||||
native_file_ptr nf;
|
||||
if (native_file::open(file_path, nf) != api_error::success) {
|
||||
throw std::runtime_error("failed to open file|" + file_path);
|
||||
}
|
||||
|
||||
{
|
||||
data_buffer buffer(1048576u);
|
||||
std::uint64_t read_offset = 0u;
|
||||
std::size_t bytes_read = 0u;
|
||||
while (
|
||||
nf->read_bytes(buffer.data(), buffer.size(), read_offset, bytes_read)) {
|
||||
if (not bytes_read) {
|
||||
break;
|
||||
}
|
||||
|
||||
read_offset += bytes_read;
|
||||
res = crypto_hash_sha256_update(
|
||||
&state, reinterpret_cast<const unsigned char *>(buffer.data()),
|
||||
bytes_read);
|
||||
if (res != 0) {
|
||||
nf->close();
|
||||
throw std::runtime_error("failed to update sha256|" +
|
||||
std::to_string(res));
|
||||
}
|
||||
}
|
||||
nf->close();
|
||||
}
|
||||
|
||||
std::array<unsigned char, crypto_hash_sha256_BYTES> out{};
|
||||
res = crypto_hash_sha256_final(&state, out.data());
|
||||
if (res != 0) {
|
||||
throw std::runtime_error("failed to finalize sha256|" +
|
||||
std::to_string(res));
|
||||
}
|
||||
|
||||
return utils::to_hex_string(out);
|
||||
}
|
||||
|
||||
std::uint64_t get_available_drive_space(const std::string &path) {
|
||||
auto get_free_drive_space(const std::string &path) -> std::uint64_t {
|
||||
#ifdef _WIN32
|
||||
ULARGE_INTEGER li = {0};
|
||||
ULARGE_INTEGER li{};
|
||||
::GetDiskFreeSpaceEx(path.c_str(), &li, nullptr, nullptr);
|
||||
return li.QuadPart;
|
||||
#endif
|
||||
@@ -290,12 +330,33 @@ std::uint64_t get_available_drive_space(const std::string &path) {
|
||||
#if __APPLE__
|
||||
struct statvfs st {};
|
||||
statvfs(path.c_str(), &st);
|
||||
return st.f_bavail * st.f_frsize;
|
||||
return st.f_bfree * st.f_frsize;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::deque<std::string> get_directory_files(std::string path, const bool &oldest_first,
|
||||
const bool &recursive) {
|
||||
auto get_total_drive_space(const std::string &path) -> std::uint64_t {
|
||||
#ifdef _WIN32
|
||||
ULARGE_INTEGER li{};
|
||||
::GetDiskFreeSpaceEx(path.c_str(), nullptr, &li, nullptr);
|
||||
return li.QuadPart;
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
std::uint64_t ret = 0;
|
||||
struct statfs64 st {};
|
||||
if (statfs64(path.c_str(), &st) == 0) {
|
||||
ret = st.f_blocks * st.f_bsize;
|
||||
}
|
||||
return ret;
|
||||
#endif
|
||||
#if __APPLE__
|
||||
struct statvfs st {};
|
||||
statvfs(path.c_str(), &st);
|
||||
return st.f_blocks * st.f_frsize;
|
||||
#endif
|
||||
}
|
||||
|
||||
auto get_directory_files(std::string path, bool oldest_first, bool recursive)
|
||||
-> std::deque<std::string> {
|
||||
path = utils::path::absolute(path);
|
||||
std::deque<std::string> ret;
|
||||
std::unordered_map<std::string, std::uint64_t> lookup;
|
||||
@@ -305,20 +366,28 @@ std::deque<std::string> get_directory_files(std::string path, const bool &oldest
|
||||
auto find = ::FindFirstFile(search.c_str(), &fd);
|
||||
if (find != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY) {
|
||||
ULARGE_INTEGER li{};
|
||||
li.HighPart = fd.ftLastWriteTime.dwHighDateTime;
|
||||
li.LowPart = fd.ftLastWriteTime.dwLowDateTime;
|
||||
const auto full_path = utils::path::combine(path, {fd.cFileName});
|
||||
lookup[full_path] = li.QuadPart;
|
||||
ret.emplace_back(full_path);
|
||||
const auto full_path = utils::path::combine(path, {fd.cFileName});
|
||||
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
|
||||
FILE_ATTRIBUTE_DIRECTORY) {
|
||||
if (recursive) {
|
||||
const auto sub_files =
|
||||
get_directory_files(full_path, oldest_first, recursive);
|
||||
ret.insert(ret.end(), sub_files.begin(), sub_files.end());
|
||||
} else {
|
||||
ULARGE_INTEGER li{};
|
||||
li.HighPart = fd.ftLastWriteTime.dwHighDateTime;
|
||||
li.LowPart = fd.ftLastWriteTime.dwLowDateTime;
|
||||
lookup[full_path] = li.QuadPart;
|
||||
ret.emplace_back(full_path);
|
||||
}
|
||||
}
|
||||
} while (::FindNextFile(find, &fd) != 0);
|
||||
::FindClose(find);
|
||||
|
||||
std::sort(ret.begin(), ret.end(), [&](const auto &p1, const auto &p2) -> bool {
|
||||
return (oldest_first != 0) == (lookup[p1] < lookup[p2]);
|
||||
});
|
||||
std::sort(ret.begin(), ret.end(),
|
||||
[&](const auto &p1, const auto &p2) -> bool {
|
||||
return (oldest_first != 0) == (lookup[p1] < lookup[p2]);
|
||||
});
|
||||
}
|
||||
#else
|
||||
auto *root = opendir(path.c_str());
|
||||
@@ -327,9 +396,10 @@ std::deque<std::string> get_directory_files(std::string path, const bool &oldest
|
||||
while ((de = readdir(root)) != nullptr) {
|
||||
if (de->d_type == DT_DIR) {
|
||||
if (recursive) {
|
||||
const auto subFiles = get_directory_files(utils::path::combine(path, {de->d_name}),
|
||||
oldest_first, recursive);
|
||||
ret.insert(ret.end(), subFiles.begin(), subFiles.end());
|
||||
const auto sub_files =
|
||||
get_directory_files(utils::path::combine(path, {de->d_name}),
|
||||
oldest_first, recursive);
|
||||
ret.insert(ret.end(), sub_files.begin(), sub_files.end());
|
||||
}
|
||||
} else {
|
||||
ret.emplace_back(utils::path::combine(path, {de->d_name}));
|
||||
@@ -343,25 +413,28 @@ std::deque<std::string> get_directory_files(std::string path, const bool &oldest
|
||||
stat(lookup_path.c_str(), &st);
|
||||
#ifdef __APPLE__
|
||||
lookup[lookup_path] = static_cast<std::uint64_t>(
|
||||
(st.st_mtimespec.tv_sec * NANOS_PER_SECOND) + st.st_mtimespec.tv_nsec);
|
||||
(st.st_mtimespec.tv_sec * NANOS_PER_SECOND) +
|
||||
st.st_mtimespec.tv_nsec);
|
||||
#else
|
||||
lookup[lookup_path] =
|
||||
static_cast<std::uint64_t>((st.st_mtim.tv_sec * NANOS_PER_SECOND) + st.st_mtim.tv_nsec);
|
||||
lookup[lookup_path] = static_cast<std::uint64_t>(
|
||||
(st.st_mtim.tv_sec * NANOS_PER_SECOND) + st.st_mtim.tv_nsec);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
std::sort(ret.begin(), ret.end(), [&](const auto &p1, const auto &p2) -> bool {
|
||||
add_to_lookup(p1);
|
||||
add_to_lookup(p2);
|
||||
return (oldest_first != 0) == (lookup[p1] < lookup[p2]);
|
||||
});
|
||||
std::sort(ret.begin(), ret.end(),
|
||||
[&](const auto &p1, const auto &p2) -> bool {
|
||||
add_to_lookup(p1);
|
||||
add_to_lookup(p2);
|
||||
return (oldest_first != 0) == (lookup[p1] < lookup[p2]);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool get_accessed_time(const std::string &path, std::uint64_t &accessed) {
|
||||
auto get_accessed_time(const std::string &path, std::uint64_t &accessed)
|
||||
-> bool {
|
||||
auto ret = false;
|
||||
accessed = 0;
|
||||
#ifdef _WIN32
|
||||
@@ -372,10 +445,11 @@ bool get_accessed_time(const std::string &path, std::uint64_t &accessed) {
|
||||
struct stat st {};
|
||||
if (stat(path.c_str(), &st) != -1) {
|
||||
#ifdef __APPLE__
|
||||
accessed = static_cast<uint64_t>(st.st_atimespec.tv_nsec +
|
||||
(st.st_atimespec.tv_sec * NANOS_PER_SECOND));
|
||||
accessed = static_cast<uint64_t>(
|
||||
st.st_atimespec.tv_nsec + (st.st_atimespec.tv_sec * NANOS_PER_SECOND));
|
||||
#else
|
||||
accessed = static_cast<uint64_t>(st.st_atim.tv_nsec + (st.st_atim.tv_sec * NANOS_PER_SECOND));
|
||||
accessed = static_cast<uint64_t>(st.st_atim.tv_nsec +
|
||||
(st.st_atim.tv_sec * NANOS_PER_SECOND));
|
||||
#endif
|
||||
#endif
|
||||
ret = true;
|
||||
@@ -384,7 +458,8 @@ bool get_accessed_time(const std::string &path, std::uint64_t &accessed) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool get_modified_time(const std::string &path, std::uint64_t &modified) {
|
||||
auto get_modified_time(const std::string &path, std::uint64_t &modified)
|
||||
-> bool {
|
||||
auto ret = false;
|
||||
modified = 0u;
|
||||
#ifdef _WIN32
|
||||
@@ -395,10 +470,11 @@ bool get_modified_time(const std::string &path, std::uint64_t &modified) {
|
||||
struct stat st {};
|
||||
if (stat(path.c_str(), &st) != -1) {
|
||||
#ifdef __APPLE__
|
||||
modified = static_cast<uint64_t>(st.st_mtimespec.tv_nsec +
|
||||
(st.st_mtimespec.tv_sec * NANOS_PER_SECOND));
|
||||
modified = static_cast<uint64_t>(
|
||||
st.st_mtimespec.tv_nsec + (st.st_mtimespec.tv_sec * NANOS_PER_SECOND));
|
||||
#else
|
||||
modified = static_cast<uint64_t>(st.st_mtim.tv_nsec + (st.st_mtim.tv_sec * NANOS_PER_SECOND));
|
||||
modified = static_cast<uint64_t>(st.st_mtim.tv_nsec +
|
||||
(st.st_mtim.tv_sec * NANOS_PER_SECOND));
|
||||
#endif
|
||||
#endif
|
||||
ret = true;
|
||||
@@ -407,7 +483,7 @@ bool get_modified_time(const std::string &path, std::uint64_t &modified) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool get_file_size(std::string path, std::uint64_t &file_size) {
|
||||
auto get_file_size(std::string path, std::uint64_t &file_size) -> bool {
|
||||
file_size = 0u;
|
||||
path = utils::path::finalize(path);
|
||||
|
||||
@@ -433,7 +509,7 @@ bool get_file_size(std::string path, std::uint64_t &file_size) {
|
||||
return (st.st_size >= 0);
|
||||
}
|
||||
|
||||
bool is_directory(const std::string &path) {
|
||||
auto is_directory(const std::string &path) -> bool {
|
||||
#ifdef _WIN32
|
||||
return ::PathIsDirectory(path.c_str()) != 0;
|
||||
#else
|
||||
@@ -442,36 +518,43 @@ bool is_directory(const std::string &path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_file(const std::string &path) {
|
||||
auto is_file(const std::string &path) -> bool {
|
||||
#ifdef _WIN32
|
||||
return (::PathFileExists(path.c_str()) && not ::PathIsDirectory(path.c_str()));
|
||||
return (::PathFileExists(path.c_str()) &&
|
||||
not ::PathIsDirectory(path.c_str()));
|
||||
#else
|
||||
struct stat st {};
|
||||
return (not stat(path.c_str(), &st) && not S_ISDIR(st.st_mode));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_modified_date_older_than(const std::string &path, const std::chrono::hours &hours) {
|
||||
auto is_modified_date_older_than(const std::string &path,
|
||||
const std::chrono::hours &hours) -> bool {
|
||||
auto ret = false;
|
||||
std::uint64_t modified = 0;
|
||||
if (get_modified_time(path, modified)) {
|
||||
const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(hours);
|
||||
const auto seconds =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(hours);
|
||||
#ifdef _WIN32
|
||||
return (std::chrono::system_clock::from_time_t(modified) + seconds) <
|
||||
std::chrono::system_clock::now();
|
||||
#else
|
||||
return (modified + (seconds.count() * NANOS_PER_SECOND)) < utils::get_time_now();
|
||||
return (modified + (seconds.count() * NANOS_PER_SECOND)) <
|
||||
utils::get_time_now();
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool move_file(std::string from, std::string to) {
|
||||
auto move_file(std::string from, std::string to) -> bool {
|
||||
from = utils::path::finalize(from);
|
||||
to = utils::path::finalize(to);
|
||||
|
||||
const auto directory = utils::path::remove_file_name(to);
|
||||
create_full_directory_path(directory);
|
||||
if (not create_full_directory_path(directory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
const bool ret = ::MoveFile(from.c_str(), to.c_str()) != 0;
|
||||
#else
|
||||
@@ -481,24 +564,7 @@ bool move_file(std::string from, std::string to) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error read_from_source(filesystem_item &fi, const std::size_t &read_size,
|
||||
const std::uint64_t &read_offset, std::vector<char> &data) {
|
||||
native_file_ptr nf;
|
||||
const auto res = assign_and_get_native_file(fi, nf);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
data.resize(read_size);
|
||||
std::size_t bytes_read = 0u;
|
||||
if (not nf->read_bytes(&data[0u], data.size(), read_offset, bytes_read)) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
std::vector<std::string> read_file_lines(const std::string &path) {
|
||||
auto read_file_lines(const std::string &path) -> std::vector<std::string> {
|
||||
std::vector<std::string> ret;
|
||||
if (is_file(path)) {
|
||||
std::ifstream fs(path);
|
||||
@@ -512,30 +578,39 @@ std::vector<std::string> read_file_lines(const std::string &path) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool read_json_file(const std::string &path, json &data) {
|
||||
auto ret = false;
|
||||
auto read_json_file(const std::string &path, json &data) -> bool {
|
||||
if (not utils::file::is_file(path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
std::ifstream file_stream(path.c_str());
|
||||
if (file_stream.is_open()) {
|
||||
std::stringstream ss;
|
||||
ss << file_stream.rdbuf();
|
||||
std::string json_text = ss.str();
|
||||
if (json_text.empty()) {
|
||||
try {
|
||||
std::stringstream ss;
|
||||
ss << file_stream.rdbuf();
|
||||
|
||||
std::string json_text = ss.str();
|
||||
if (not json_text.empty()) {
|
||||
data = json::parse(json_text.c_str());
|
||||
}
|
||||
|
||||
file_stream.close();
|
||||
} else {
|
||||
data = json::parse(json_text.c_str());
|
||||
ret = true;
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
file_stream.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(__FUNCTION__, e, path,
|
||||
"failed to read json file");
|
||||
}
|
||||
|
||||
return ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool reset_modified_time(const std::string &path) {
|
||||
auto reset_modified_time(const std::string &path) -> bool {
|
||||
auto ret = false;
|
||||
#ifdef _WIN32
|
||||
SYSTEMTIME st{};
|
||||
@@ -543,9 +618,10 @@ bool reset_modified_time(const std::string &path) {
|
||||
|
||||
FILETIME ft{};
|
||||
if ((ret = !!::SystemTimeToFileTime(&st, &ft))) {
|
||||
auto handle = ::CreateFileA(path.c_str(), FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||
OPEN_EXISTING, 0, nullptr);
|
||||
auto handle = ::CreateFileA(
|
||||
path.c_str(), FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||
OPEN_EXISTING, 0, nullptr);
|
||||
if ((ret = (handle != INVALID_HANDLE_VALUE))) {
|
||||
ret = !!::SetFileTime(handle, nullptr, &ft, &ft);
|
||||
::CloseHandle(handle);
|
||||
@@ -561,21 +637,27 @@ bool reset_modified_time(const std::string &path) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error truncate_source(filesystem_item &fi, const std::uint64_t &size) {
|
||||
native_file_ptr nf;
|
||||
auto res = assign_and_get_native_file(fi, nf);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
auto retry_delete_directory(const std::string &dir) -> bool {
|
||||
auto deleted = false;
|
||||
for (std::uint8_t i = 0u; not(deleted = delete_directory(dir)) && (i < 200u);
|
||||
i++) {
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
|
||||
if (not nf->truncate(size)) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
return deleted;
|
||||
}
|
||||
|
||||
bool write_json_file(const std::string &path, const json &j) {
|
||||
auto retry_delete_file(const std::string &file) -> bool {
|
||||
auto deleted = false;
|
||||
for (std::uint8_t i = 0u; not(deleted = delete_file(file)) && (i < 200u);
|
||||
i++) {
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
|
||||
return deleted;
|
||||
}
|
||||
|
||||
auto write_json_file(const std::string &path, const json &j) -> bool {
|
||||
std::string data;
|
||||
{
|
||||
std::stringstream ss;
|
||||
@@ -586,27 +668,11 @@ bool write_json_file(const std::string &path, const json &j) {
|
||||
auto ret = (native_file::create_or_open(path, nf) == api_error::success);
|
||||
if (ret) {
|
||||
std::size_t bytes_written = 0u;
|
||||
ret = nf->truncate(0) && nf->write_bytes(&data[0u], data.size(), 0u, bytes_written);
|
||||
ret = nf->truncate(0) &&
|
||||
nf->write_bytes(&data[0u], data.size(), 0u, bytes_written);
|
||||
nf->close();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
api_error write_to_source(filesystem_item &fi, const std::uint64_t &write_offset,
|
||||
const std::vector<char> &data, std::size_t &bytes_written) {
|
||||
bytes_written = 0u;
|
||||
|
||||
native_file_ptr nf;
|
||||
auto res = assign_and_get_native_file(fi, nf);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (not nf->write_bytes(&data[0u], data.size(), write_offset, bytes_written)) {
|
||||
return api_error::os_error;
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
} // namespace repertory::utils::file
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
Copyright <2018-2022> <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 "utils/global_data.hpp"
|
||||
|
||||
namespace repertory {
|
||||
global_data global_data::instance_;
|
||||
|
||||
void global_data::update_used_space(const std::uint64_t &file_size,
|
||||
const std::uint64_t &new_file_size, const bool &cache_only) {
|
||||
if (file_size > new_file_size) {
|
||||
const auto diff = (file_size - new_file_size);
|
||||
used_cache_space_ -= diff;
|
||||
if (not cache_only) {
|
||||
used_drive_space_ -= diff;
|
||||
}
|
||||
} else if (file_size < new_file_size) {
|
||||
const auto diff = (new_file_size - file_size);
|
||||
used_cache_space_ += diff;
|
||||
if (not cache_only) {
|
||||
used_drive_space_ += diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,26 +1,41 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/native_file.hpp"
|
||||
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
#include "utils/unix/unix_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory {
|
||||
native_file_ptr native_file::clone(const native_file_ptr &nf) {
|
||||
auto native_file::get_handle() -> native_handle { return handle_; }
|
||||
|
||||
native_file::~native_file() {
|
||||
if (auto_close) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
auto native_file::clone(const native_file_ptr &nf) -> native_file_ptr {
|
||||
std::string source_path;
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -31,53 +46,79 @@ native_file_ptr native_file::clone(const native_file_ptr &nf) {
|
||||
source_path.resize(PATH_MAX + 1);
|
||||
#ifdef __APPLE__
|
||||
fcntl(nf->get_handle(), F_GETPATH, &source_path[0u]);
|
||||
a
|
||||
#else
|
||||
readlink(("/proc/self/fd/" + utils::string::from_int64(nf->get_handle())).c_str(),
|
||||
readlink(("/proc/self/fd/" + std::to_string(nf->get_handle())).c_str(),
|
||||
&source_path[0u], source_path.size());
|
||||
#endif
|
||||
#endif
|
||||
source_path = source_path.c_str();
|
||||
|
||||
native_file_ptr clone;
|
||||
if (native_file::open(source_path, clone) != api_error::success) {
|
||||
throw std::runtime_error("unable to clone file " + source_path);
|
||||
auto res = native_file::open(source_path, clone);
|
||||
if (res != api_error::success) {
|
||||
throw std::runtime_error("unable to open file|sp|" + source_path + "|err|" +
|
||||
api_error_to_string(res));
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
api_error native_file::create_or_open(const std::string &source_path, native_file_ptr &nf) {
|
||||
auto native_file::create_or_open(const std::string &source_path,
|
||||
[[maybe_unused]] bool should_chmod,
|
||||
native_file_ptr &nf) -> api_error {
|
||||
#ifdef _WIN32
|
||||
auto handle = ::CreateFileA(source_path.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS,
|
||||
FILE_FLAG_RANDOM_ACCESS, nullptr);
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||
OPEN_ALWAYS, FILE_FLAG_RANDOM_ACCESS, nullptr);
|
||||
#else
|
||||
auto handle = ::open(source_path.c_str(), O_CREAT | O_RDWR | O_CLOEXEC, 0600u);
|
||||
chmod(source_path.c_str(), 0600u);
|
||||
auto handle = should_chmod ? ::open(source_path.c_str(),
|
||||
O_CREAT | O_RDWR | O_CLOEXEC, 0600u)
|
||||
: ::open(source_path.c_str(), O_RDWR | O_CLOEXEC);
|
||||
if (should_chmod) {
|
||||
chmod(source_path.c_str(), 0600u);
|
||||
}
|
||||
#endif
|
||||
nf = native_file::attach(handle);
|
||||
return ((handle == REPERTORY_INVALID_HANDLE) ? api_error::os_error : api_error::success);
|
||||
return ((handle == REPERTORY_INVALID_HANDLE) ? api_error::os_error
|
||||
: api_error::success);
|
||||
}
|
||||
|
||||
api_error native_file::open(const std::string &source_path, native_file_ptr &nf) {
|
||||
auto native_file::create_or_open(const std::string &source_path,
|
||||
native_file_ptr &nf) -> api_error {
|
||||
return create_or_open(source_path, true, nf);
|
||||
}
|
||||
|
||||
auto native_file::open(const std::string &source_path, native_file_ptr &nf)
|
||||
-> api_error {
|
||||
return open(source_path, true, nf);
|
||||
}
|
||||
|
||||
auto native_file::open(const std::string &source_path,
|
||||
[[maybe_unused]] bool should_chmod, native_file_ptr &nf)
|
||||
-> api_error {
|
||||
#ifdef _WIN32
|
||||
auto handle = ::CreateFileA(source_path.c_str(), GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_RANDOM_ACCESS, nullptr);
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
|
||||
OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
|
||||
#else
|
||||
auto handle = ::open(source_path.c_str(), O_RDWR | O_CLOEXEC, 0600u);
|
||||
chmod(source_path.c_str(), 0600u);
|
||||
auto handle = should_chmod
|
||||
? ::open(source_path.c_str(), O_RDWR | O_CLOEXEC, 0600u)
|
||||
: ::open(source_path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (should_chmod) {
|
||||
chmod(source_path.c_str(), 0600u);
|
||||
}
|
||||
#endif
|
||||
nf = native_file::attach(handle);
|
||||
return ((handle == REPERTORY_INVALID_HANDLE) ? api_error::os_error : api_error::success);
|
||||
return ((handle == REPERTORY_INVALID_HANDLE) ? api_error::os_error
|
||||
: api_error::success);
|
||||
}
|
||||
|
||||
bool native_file::allocate(const std::uint64_t &file_size) {
|
||||
auto native_file::allocate(std::uint64_t file_size) -> bool {
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER li{};
|
||||
li.QuadPart = file_size;
|
||||
return (::SetFilePointerEx(handle_, li, nullptr, FILE_BEGIN) && ::SetEndOfFile(handle_));
|
||||
return (::SetFilePointerEx(handle_, li, nullptr, FILE_BEGIN) &&
|
||||
::SetEndOfFile(handle_));
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
return (fallocate(handle_, 0, 0, file_size) >= 0);
|
||||
@@ -98,16 +139,17 @@ void native_file::close() {
|
||||
}
|
||||
}
|
||||
|
||||
bool native_file::copy_from(const native_file_ptr &nf) {
|
||||
auto native_file::copy_from(const native_file_ptr &nf) -> bool {
|
||||
auto ret = false;
|
||||
std::uint64_t file_size = 0u;
|
||||
if ((ret = nf->get_file_size(file_size))) {
|
||||
std::vector<char> buffer;
|
||||
data_buffer buffer;
|
||||
buffer.resize(65536u * 2u);
|
||||
std::uint64_t offset = 0u;
|
||||
while (ret && (file_size > 0u)) {
|
||||
std::size_t bytes_read{};
|
||||
if ((ret = nf->read_bytes(&buffer[0u], buffer.size(), offset, bytes_read))) {
|
||||
if ((ret = nf->read_bytes(&buffer[0u], buffer.size(), offset,
|
||||
bytes_read))) {
|
||||
std::size_t bytes_written = 0u;
|
||||
ret = write_bytes(&buffer[0u], bytes_read, offset, bytes_written);
|
||||
file_size -= bytes_read;
|
||||
@@ -120,7 +162,7 @@ bool native_file::copy_from(const native_file_ptr &nf) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool native_file::copy_from(const std::string &path) {
|
||||
auto native_file::copy_from(const std::string &path) -> bool {
|
||||
auto ret = false;
|
||||
native_file_ptr nf;
|
||||
if (native_file::create_or_open(path, nf) == api_error ::success) {
|
||||
@@ -140,7 +182,7 @@ void native_file::flush() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool native_file::get_file_size(std::uint64_t &file_size) {
|
||||
auto native_file::get_file_size(std::uint64_t &file_size) -> bool {
|
||||
auto ret = false;
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER li{};
|
||||
@@ -164,8 +206,9 @@ bool native_file::get_file_size(std::uint64_t &file_size) {
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool native_file::read_bytes(char *buffer, const std::size_t &read_size,
|
||||
const std::uint64_t &read_offset, std::size_t &bytes_read) {
|
||||
auto native_file::read_bytes(char *buffer, std::size_t read_size,
|
||||
std::uint64_t read_offset, std::size_t &bytes_read)
|
||||
-> bool {
|
||||
recur_mutex_lock l(read_write_mutex_);
|
||||
|
||||
auto ret = false;
|
||||
@@ -176,7 +219,8 @@ bool native_file::read_bytes(char *buffer, const std::size_t &read_size,
|
||||
DWORD current_read = 0u;
|
||||
do {
|
||||
current_read = 0u;
|
||||
ret = !!::ReadFile(handle_, &buffer[bytes_read], static_cast<DWORD>(read_size - bytes_read),
|
||||
ret = !!::ReadFile(handle_, &buffer[bytes_read],
|
||||
static_cast<DWORD>(read_size - bytes_read),
|
||||
¤t_read, nullptr);
|
||||
bytes_read += current_read;
|
||||
} while (ret && (bytes_read < read_size) && (current_read != 0));
|
||||
@@ -189,13 +233,14 @@ bool native_file::read_bytes(char *buffer, const std::size_t &read_size,
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
bool native_file::read_bytes(char *buffer, const std::size_t &read_size,
|
||||
const std::uint64_t &read_offset, std::size_t &bytes_read) {
|
||||
auto native_file::read_bytes(char *buffer, std::size_t read_size,
|
||||
std::uint64_t read_offset, std::size_t &bytes_read)
|
||||
-> bool {
|
||||
bytes_read = 0u;
|
||||
ssize_t result = 0;
|
||||
do {
|
||||
result =
|
||||
pread64(handle_, &buffer[bytes_read], read_size - bytes_read, read_offset + bytes_read);
|
||||
result = pread64(handle_, &buffer[bytes_read], read_size - bytes_read,
|
||||
read_offset + bytes_read);
|
||||
if (result > 0) {
|
||||
bytes_read += static_cast<size_t>(result);
|
||||
}
|
||||
@@ -204,20 +249,22 @@ bool native_file::read_bytes(char *buffer, const std::size_t &read_size,
|
||||
return (result >= 0);
|
||||
}
|
||||
#endif
|
||||
bool native_file::truncate(const std::uint64_t &file_size) {
|
||||
auto native_file::truncate(std::uint64_t file_size) -> bool {
|
||||
#ifdef _WIN32
|
||||
recur_mutex_lock l(read_write_mutex_);
|
||||
LARGE_INTEGER li{};
|
||||
li.QuadPart = file_size;
|
||||
return (::SetFilePointerEx(handle_, li, nullptr, FILE_BEGIN) && ::SetEndOfFile(handle_));
|
||||
return (::SetFilePointerEx(handle_, li, nullptr, FILE_BEGIN) &&
|
||||
::SetEndOfFile(handle_));
|
||||
#else
|
||||
return (ftruncate(handle_, file_size) >= 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool native_file::write_bytes(const char *buffer, const std::size_t &write_size,
|
||||
const std::uint64_t &write_offset, std::size_t &bytes_written) {
|
||||
auto native_file::write_bytes(const char *buffer, std::size_t write_size,
|
||||
std::uint64_t write_offset,
|
||||
std::size_t &bytes_written) -> bool {
|
||||
recur_mutex_lock l(read_write_mutex_);
|
||||
|
||||
bytes_written = 0u;
|
||||
@@ -229,7 +276,8 @@ bool native_file::write_bytes(const char *buffer, const std::size_t &write_size,
|
||||
do {
|
||||
DWORD current_write = 0u;
|
||||
ret = !!::WriteFile(handle_, &buffer[bytes_written],
|
||||
static_cast<DWORD>(write_size - bytes_written), ¤t_write, nullptr);
|
||||
static_cast<DWORD>(write_size - bytes_written),
|
||||
¤t_write, nullptr);
|
||||
bytes_written += current_write;
|
||||
} while (ret && (bytes_written < write_size));
|
||||
}
|
||||
@@ -237,13 +285,14 @@ bool native_file::write_bytes(const char *buffer, const std::size_t &write_size,
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
bool native_file::write_bytes(const char *buffer, const std::size_t &write_size,
|
||||
const std::uint64_t &write_offset, std::size_t &bytes_written) {
|
||||
auto native_file::write_bytes(const char *buffer, std::size_t write_size,
|
||||
std::uint64_t write_offset,
|
||||
std::size_t &bytes_written) -> bool {
|
||||
bytes_written = 0;
|
||||
ssize_t result = 0;
|
||||
do {
|
||||
result = pwrite64(handle_, &buffer[bytes_written], write_size - bytes_written,
|
||||
write_offset + bytes_written);
|
||||
result = pwrite64(handle_, &buffer[bytes_written],
|
||||
write_size - bytes_written, write_offset + bytes_written);
|
||||
if (result > 0) {
|
||||
bytes_written += static_cast<size_t>(result);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/path_utils.hpp"
|
||||
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory::utils::path {
|
||||
static std::string &format_path(std::string &path, const std::string &sep,
|
||||
const std::string ¬_sep);
|
||||
|
||||
std::string absolute(std::string path) {
|
||||
auto absolute(std::string path) -> std::string {
|
||||
#ifdef _WIN32
|
||||
if (not path.empty() && ::PathIsRelative(&path[0u])) {
|
||||
std::string temp;
|
||||
@@ -51,35 +53,61 @@ std::string absolute(std::string path) {
|
||||
}
|
||||
#endif
|
||||
|
||||
return path;
|
||||
return finalize(path);
|
||||
}
|
||||
|
||||
std::string combine(std::string path, const std::vector<std::string> &paths) {
|
||||
auto combine(std::string path, const std::vector<std::string> &paths)
|
||||
-> std::string {
|
||||
return finalize(
|
||||
std::accumulate(paths.begin(), paths.end(), path, [](std::string path, const auto &pathPart) {
|
||||
path += (directory_seperator + pathPart);
|
||||
return path;
|
||||
}));
|
||||
std::accumulate(paths.begin(), paths.end(), path,
|
||||
[](std::string next_path, const auto &path_part) {
|
||||
if (not next_path.empty()) {
|
||||
next_path += (directory_seperator + path_part);
|
||||
}
|
||||
return next_path;
|
||||
}));
|
||||
}
|
||||
|
||||
std::string create_api_path(std::string path) {
|
||||
if (path.empty() || (path == ".")) {
|
||||
path = "/";
|
||||
} else {
|
||||
format_path(path, "/", "\\");
|
||||
if (path.find("./") == 0) {
|
||||
path = path.substr(1);
|
||||
}
|
||||
if (path[0u] != '/') {
|
||||
path = "/" + path;
|
||||
}
|
||||
auto create_api_path(std::string path) -> std::string {
|
||||
if (path.empty() || (path == ".") || (path == "/")) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
format_path(path, "/", "\\");
|
||||
if (path.find("./") == 0) {
|
||||
path = path.substr(1);
|
||||
}
|
||||
|
||||
if (path[0u] != '/') {
|
||||
path = "/" + path;
|
||||
}
|
||||
|
||||
if ((path != "/") && utils::string::ends_with(path, "/")) {
|
||||
utils::string::right_trim(path, '/');
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static std::string &format_path(std::string &path, const std::string &sep,
|
||||
const std::string ¬_sep) {
|
||||
auto finalize(std::string path) -> std::string {
|
||||
format_path(path, not_directory_seperator, directory_seperator);
|
||||
format_path(path, directory_seperator, not_directory_seperator);
|
||||
if ((path.size() > 1u) &&
|
||||
(path[path.size() - 1u] == directory_seperator[0u])) {
|
||||
path = path.substr(0u, path.size() - 1u);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if ((path.size() >= 2u) && (path[1u] == ':')) {
|
||||
path[0u] = utils::string::to_lower(std::string(1u, path[0u]))[0u];
|
||||
}
|
||||
#endif
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
auto format_path(std::string &path, const std::string &sep,
|
||||
const std::string ¬_sep) -> std::string & {
|
||||
std::replace(path.begin(), path.end(), not_sep[0u], sep[0u]);
|
||||
|
||||
while (utils::string::contains(path, sep + sep)) {
|
||||
@@ -89,16 +117,7 @@ static std::string &format_path(std::string &path, const std::string &sep,
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string finalize(std::string path) {
|
||||
format_path(path, directory_seperator, not_directory_seperator);
|
||||
if ((path.size() > 1) && (path[path.size() - 1] == directory_seperator[0u])) {
|
||||
path = path.substr(0, path.size() - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string get_parent_api_path(const std::string &path) {
|
||||
auto get_parent_api_path(const std::string &path) -> std::string {
|
||||
std::string ret;
|
||||
if (path != "/") {
|
||||
ret = path.substr(0, path.rfind('/') + 1);
|
||||
@@ -111,7 +130,7 @@ std::string get_parent_api_path(const std::string &path) {
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
std::string get_parent_directory(std::string path) {
|
||||
auto get_parent_directory(std::string path) -> std::string {
|
||||
auto ret = std::string(dirname(&path[0u]));
|
||||
if (ret == ".") {
|
||||
ret = "/";
|
||||
@@ -121,7 +140,7 @@ std::string get_parent_directory(std::string path) {
|
||||
}
|
||||
#endif
|
||||
|
||||
bool is_ads_file_path(const std::string &path) {
|
||||
auto is_ads_file_path([[maybe_unused]] const std::string &path) -> bool {
|
||||
#ifdef _WIN32
|
||||
return utils::string::contains(path, ":");
|
||||
#else
|
||||
@@ -129,7 +148,7 @@ bool is_ads_file_path(const std::string &path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_trash_directory(std::string path) {
|
||||
auto is_trash_directory(std::string path) -> bool {
|
||||
path = create_api_path(utils::string::to_lower(path));
|
||||
if (utils::string::begins_with(path, "/.trash-") ||
|
||||
utils::string::begins_with(path, "/.trashes") ||
|
||||
@@ -139,7 +158,7 @@ bool is_trash_directory(std::string path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string remove_file_name(std::string path) {
|
||||
auto remove_file_name(std::string path) -> std::string {
|
||||
path = finalize(path);
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -160,18 +179,20 @@ std::string remove_file_name(std::string path) {
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
std::string resolve(std::string path) {
|
||||
struct passwd *pw = getpwuid(getuid());
|
||||
std::string home = (pw->pw_dir ? pw->pw_dir : "");
|
||||
if (home.empty() || ((home == "/") && (getuid() != 0))) {
|
||||
home = combine("/home", {pw->pw_name});
|
||||
}
|
||||
auto resolve(std::string path) -> std::string {
|
||||
std::string home{};
|
||||
use_getpwuid(getuid(), [&home](struct passwd *pw) {
|
||||
home = (pw->pw_dir ? pw->pw_dir : "");
|
||||
if (home.empty() || ((home == "/") && (getuid() != 0))) {
|
||||
home = combine("/home", {pw->pw_name});
|
||||
}
|
||||
});
|
||||
|
||||
return finalize(utils::string::replace(path, "~", home));
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string strip_to_file_name(std::string path) {
|
||||
auto strip_to_file_name(std::string path) -> std::string {
|
||||
#ifdef _WIN32
|
||||
return ::PathFindFileName(&path[0u]);
|
||||
#else
|
||||
|
||||
@@ -1,44 +1,57 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/polling.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
|
||||
namespace repertory {
|
||||
polling polling::instance_;
|
||||
|
||||
void polling::frequency_thread(std::function<std::uint32_t()> get_frequency_seconds,
|
||||
bool low_frequency) {
|
||||
void polling::frequency_thread(
|
||||
std::function<std::uint32_t()> get_frequency_seconds, frequency freq) {
|
||||
while (not stop_requested_) {
|
||||
std::deque<std::future<void>> futures;
|
||||
unique_mutex_lock l(mutex_);
|
||||
if (not stop_requested_ && notify_.wait_for(l, std::chrono::seconds(get_frequency_seconds())) ==
|
||||
std::cv_status::timeout) {
|
||||
for (auto &kv : items_) {
|
||||
if (kv.second.low_frequency == low_frequency) {
|
||||
futures.emplace_back(std::async(std::launch::async, [kv]() -> void {
|
||||
event_system::instance().raise<polling_item_begin>(kv.first);
|
||||
kv.second.action();
|
||||
event_system::instance().raise<polling_item_end>(kv.first);
|
||||
}));
|
||||
if (not stop_requested_ &&
|
||||
notify_.wait_for(l, std::chrono::seconds(get_frequency_seconds())) ==
|
||||
std::cv_status::timeout) {
|
||||
for (const auto &kv : items_) {
|
||||
if (kv.second.freq == freq) {
|
||||
futures.emplace_back(
|
||||
std::async(std::launch::async, [this, &freq, kv]() -> void {
|
||||
if (config_->get_event_level() == event_level::verbose ||
|
||||
freq != frequency::second) {
|
||||
event_system::instance().raise<polling_item_begin>(kv.first);
|
||||
}
|
||||
kv.second.action();
|
||||
if (config_->get_event_level() == event_level::verbose ||
|
||||
freq != frequency::second) {
|
||||
event_system::instance().raise<polling_item_end>(kv.first);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
l.unlock();
|
||||
|
||||
while (not futures.empty()) {
|
||||
futures.front().wait();
|
||||
futures.pop_front();
|
||||
@@ -52,40 +65,57 @@ void polling::remove_callback(const std::string &name) {
|
||||
items_.erase(name);
|
||||
}
|
||||
|
||||
void polling::set_callback(const polling_item &pollingItem) {
|
||||
void polling::set_callback(const polling_item &pi) {
|
||||
mutex_lock l(mutex_);
|
||||
items_[pollingItem.name] = pollingItem;
|
||||
items_[pi.name] = pi;
|
||||
}
|
||||
|
||||
void polling::start(app_config *config) {
|
||||
mutex_lock l(start_stop_mutex_);
|
||||
if (not high_frequency_thread_) {
|
||||
event_system::instance().raise<service_started>("polling");
|
||||
config_ = config;
|
||||
stop_requested_ = false;
|
||||
high_frequency_thread_ = std::make_unique<std::thread>([this]() -> void {
|
||||
this->frequency_thread(
|
||||
[this]() -> std::uint32_t { return config_->get_high_frequency_interval_secs(); }, false);
|
||||
[this]() -> std::uint32_t {
|
||||
return config_->get_high_frequency_interval_secs();
|
||||
},
|
||||
frequency::high);
|
||||
});
|
||||
low_frequency_thread_ = std::make_unique<std::thread>([this]() -> void {
|
||||
this->frequency_thread(
|
||||
[this]() -> std::uint32_t { return config_->get_low_frequency_interval_secs(); }, true);
|
||||
[this]() -> std::uint32_t {
|
||||
return config_->get_low_frequency_interval_secs();
|
||||
},
|
||||
frequency::low);
|
||||
});
|
||||
second_frequency_thread_ = std::make_unique<std::thread>([this]() -> void {
|
||||
this->frequency_thread([]() -> std::uint32_t { return 1U; },
|
||||
frequency::second);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void polling::stop() {
|
||||
mutex_lock l(start_stop_mutex_);
|
||||
if (high_frequency_thread_) {
|
||||
event_system::instance().raise<service_shutdown>("polling");
|
||||
{
|
||||
mutex_lock l2(mutex_);
|
||||
stop_requested_ = true;
|
||||
notify_.notify_all();
|
||||
event_system::instance().raise<service_shutdown_begin>("polling");
|
||||
mutex_lock l(start_stop_mutex_);
|
||||
if (high_frequency_thread_) {
|
||||
{
|
||||
stop_requested_ = true;
|
||||
|
||||
mutex_lock l2(mutex_);
|
||||
notify_.notify_all();
|
||||
}
|
||||
high_frequency_thread_->join();
|
||||
low_frequency_thread_->join();
|
||||
second_frequency_thread_->join();
|
||||
high_frequency_thread_.reset();
|
||||
low_frequency_thread_.reset();
|
||||
second_frequency_thread_.reset();
|
||||
}
|
||||
high_frequency_thread_->join();
|
||||
low_frequency_thread_->join();
|
||||
high_frequency_thread_.reset();
|
||||
low_frequency_thread_.reset();
|
||||
event_system::instance().raise<service_shutdown_end>("polling");
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/rocksdb_utils.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "types/startup_exception.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/path_utils.hpp"
|
||||
|
||||
namespace repertory::utils::db {
|
||||
@@ -30,35 +35,37 @@ void create_rocksdb(const app_config &config, const std::string &name,
|
||||
options.db_log_dir = config.get_log_directory();
|
||||
options.keep_log_file_num = 10;
|
||||
|
||||
rocksdb::DB *db_ptr = nullptr;
|
||||
rocksdb::DB *db_ptr{};
|
||||
const auto status = rocksdb::DB::Open(
|
||||
options, utils::path::combine(config.get_data_directory(), {name}), &db_ptr);
|
||||
options, utils::path::combine(config.get_data_directory(), {name}),
|
||||
&db_ptr);
|
||||
if (status.ok()) {
|
||||
db.reset(db_ptr);
|
||||
} else {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, status.ToString());
|
||||
utils::error::raise_error(__FUNCTION__, status.ToString());
|
||||
throw startup_exception(status.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
void create_rocksdb(const app_config &config, const std::string &name,
|
||||
const std::vector<rocksdb::ColumnFamilyDescriptor> &families,
|
||||
std::vector<rocksdb::ColumnFamilyHandle *> &handles,
|
||||
std::unique_ptr<rocksdb::DB> &db) {
|
||||
void create_rocksdb(
|
||||
const app_config &config, const std::string &name,
|
||||
const std::vector<rocksdb::ColumnFamilyDescriptor> &families,
|
||||
std::vector<rocksdb::ColumnFamilyHandle *> &handles,
|
||||
std::unique_ptr<rocksdb::DB> &db) {
|
||||
rocksdb::Options options{};
|
||||
options.create_if_missing = true;
|
||||
options.create_missing_column_families = true;
|
||||
options.db_log_dir = config.get_log_directory();
|
||||
options.keep_log_file_num = 10;
|
||||
|
||||
rocksdb::DB *db_ptr = nullptr;
|
||||
const auto status =
|
||||
rocksdb::DB::Open(options, utils::path::combine(config.get_data_directory(), {name}),
|
||||
families, &handles, &db_ptr);
|
||||
rocksdb::DB *db_ptr{};
|
||||
const auto status = rocksdb::DB::Open(
|
||||
options, utils::path::combine(config.get_data_directory(), {name}),
|
||||
families, &handles, &db_ptr);
|
||||
if (status.ok()) {
|
||||
db.reset(db_ptr);
|
||||
} else {
|
||||
event_system::instance().raise<repertory_exception>(__FUNCTION__, status.ToString());
|
||||
utils::error::raise_error(__FUNCTION__, status.ToString());
|
||||
throw startup_exception(status.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
65
src/utils/single_thread_service_base.cpp
Normal file
65
src/utils/single_thread_service_base.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Copyright <2018-2023> <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 "utils/single_thread_service_base.hpp"
|
||||
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "types/repertory.hpp"
|
||||
|
||||
namespace repertory {
|
||||
void single_thread_service_base::notify_all() const {
|
||||
mutex_lock lock(get_mutex());
|
||||
notify_.notify_all();
|
||||
}
|
||||
|
||||
void single_thread_service_base::start() {
|
||||
mutex_lock lock(mtx_);
|
||||
if (not thread_) {
|
||||
stop_requested_ = false;
|
||||
on_start();
|
||||
thread_ = std::make_unique<std::thread>([this]() {
|
||||
event_system::instance().raise<service_started>(service_name_);
|
||||
while (not stop_requested_) {
|
||||
service_function();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void single_thread_service_base::stop() {
|
||||
if (thread_) {
|
||||
event_system::instance().raise<service_shutdown_begin>(service_name_);
|
||||
unique_mutex_lock lock(mtx_);
|
||||
if (thread_) {
|
||||
stop_requested_ = true;
|
||||
notify_.notify_all();
|
||||
lock.unlock();
|
||||
|
||||
thread_->join();
|
||||
thread_.reset();
|
||||
|
||||
on_stop();
|
||||
}
|
||||
event_system::instance().raise<service_shutdown_end>(service_name_);
|
||||
}
|
||||
}
|
||||
} // namespace repertory
|
||||
@@ -1,121 +1,129 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/string_utils.hpp"
|
||||
#include "common.hpp"
|
||||
|
||||
namespace repertory::utils::string {
|
||||
bool begins_with(const std::string &str, const std::string &val) { return (str.find(val) == 0); }
|
||||
|
||||
bool contains(const std::string &str, const std::string &search) {
|
||||
return (str.find(search) != std::string::npos);
|
||||
}
|
||||
|
||||
bool ends_with(const std::string &str, const std::string &val) {
|
||||
/* constexpr c++20 */ auto ends_with(std::string_view str, std::string_view val)
|
||||
-> bool {
|
||||
if (val.size() > str.size()) {
|
||||
return false;
|
||||
}
|
||||
return std::equal(val.rbegin(), val.rend(), str.rbegin());
|
||||
}
|
||||
|
||||
std::string from_bool(const bool &val) { return std::to_string(val); }
|
||||
auto from_bool(bool val) -> std::string { return std::to_string(val); }
|
||||
|
||||
std::string from_double(const double &value) { return std::to_string(value); }
|
||||
|
||||
std::string from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) {
|
||||
auto from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string {
|
||||
std::stringstream ss;
|
||||
boost::archive::text_oarchive archive(ss);
|
||||
archive << bitset;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string from_int32(const std::int32_t &val) { return std::to_string(val); }
|
||||
|
||||
std::string from_int64(const std::int64_t &val) { return std::to_string(val); }
|
||||
|
||||
std::string from_uint8(const std::uint8_t &val) { return std::to_string(val); }
|
||||
|
||||
std::string from_uint16(const std::uint16_t &val) { return std::to_string(val); }
|
||||
|
||||
std::string from_uint32(const std::uint32_t &val) { return std::to_string(val); }
|
||||
|
||||
std::string from_uint64(const std::uint64_t &val) { return std::to_string(val); }
|
||||
|
||||
std::wstring from_utf8(const std::string &str) {
|
||||
return str.empty() ? L""
|
||||
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(str);
|
||||
auto from_utf8(const std::string &str) -> std::wstring {
|
||||
return str.empty()
|
||||
? L""
|
||||
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
|
||||
.from_bytes(str);
|
||||
}
|
||||
|
||||
bool is_numeric(const std::string &s) {
|
||||
static const auto r = std::regex(R"(^(\+|\-)?(([0-9]*)|(([0-9]*)\.([0-9]*)))$)");
|
||||
return std::regex_match(s, r);
|
||||
/* constexpr c++20 */ auto is_numeric(std::string_view s) -> bool {
|
||||
if ((s.length() > 1u) && (s[0u] == '+' || s[0u] == '-')) {
|
||||
s = s.substr(1u);
|
||||
}
|
||||
|
||||
if (s.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto has_decimal = false;
|
||||
return std::find_if(
|
||||
s.begin(), s.end(),
|
||||
[&has_decimal](const std::string_view::value_type &c) -> bool {
|
||||
if (has_decimal && c == '.') {
|
||||
return true;
|
||||
}
|
||||
if ((has_decimal = has_decimal || c == '.')) {
|
||||
return false;
|
||||
}
|
||||
return not std::isdigit(c);
|
||||
}) == s.end();
|
||||
}
|
||||
|
||||
std::string join(const std::vector<std::string> &arr, const char &delim) {
|
||||
auto join(const std::vector<std::string> &arr, const char &delim)
|
||||
-> std::string {
|
||||
if (arr.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return std::accumulate(std::next(arr.begin()), arr.end(), arr[0u],
|
||||
[&delim](auto s, const auto &v) { return s + delim + v; });
|
||||
return std::accumulate(
|
||||
std::next(arr.begin()), arr.end(), arr[0u],
|
||||
[&delim](auto s, const auto &v) { return s + delim + v; });
|
||||
}
|
||||
|
||||
std::string &left_trim(std::string &s) { return left_trim(s, ' '); }
|
||||
auto left_trim(std::string &s) -> std::string & { return left_trim(s, ' '); }
|
||||
|
||||
std::string &left_trim(std::string &s, const char &c) {
|
||||
auto left_trim(std::string &s, const char &c) -> std::string & {
|
||||
s.erase(0, s.find_first_not_of(c));
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string &replace(std::string &src, const char &character, const char &with) {
|
||||
auto replace(std::string &src, const char &character, const char &with)
|
||||
-> std::string & {
|
||||
std::replace(src.begin(), src.end(), character, with);
|
||||
return src;
|
||||
}
|
||||
|
||||
std::string &replace(std::string &src, const std::string &find, const std::string &with,
|
||||
size_t startPos) {
|
||||
if (!src.empty() && (startPos < src.size())) {
|
||||
while ((startPos = src.find(find, startPos)) != std::string::npos) {
|
||||
src.replace(startPos, find.size(), with);
|
||||
startPos += with.size();
|
||||
auto replace(std::string &src, const std::string &find, const std::string &with,
|
||||
size_t start_pos) -> std::string & {
|
||||
if (!src.empty() && (start_pos < src.size())) {
|
||||
while ((start_pos = src.find(find, start_pos)) != std::string::npos) {
|
||||
src.replace(start_pos, find.size(), with);
|
||||
start_pos += with.size();
|
||||
}
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
std::string replace_copy(std::string src, const char &character, const char &with) {
|
||||
auto replace_copy(std::string src, const char &character, const char &with)
|
||||
-> std::string {
|
||||
std::replace(src.begin(), src.end(), character, with);
|
||||
return src;
|
||||
}
|
||||
|
||||
std::string replace_copy(std::string src, const std::string &find, const std::string &with,
|
||||
size_t startPos) {
|
||||
return replace(src, find, with, startPos);
|
||||
auto replace_copy(std::string src, const std::string &find,
|
||||
const std::string &with, size_t start_pos) -> std::string {
|
||||
return replace(src, find, with, start_pos);
|
||||
}
|
||||
|
||||
std::string &right_trim(std::string &s) { return right_trim(s, ' '); }
|
||||
auto right_trim(std::string &s) -> std::string & { return right_trim(s, ' '); }
|
||||
|
||||
std::string &right_trim(std::string &s, const char &c) {
|
||||
auto right_trim(std::string &s, const char &c) -> std::string & {
|
||||
s.erase(s.find_last_not_of(c) + 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string &str, const char &delim, const bool &should_trim) {
|
||||
auto split(const std::string &str, const char &delim, bool should_trim)
|
||||
-> std::vector<std::string> {
|
||||
std::vector<std::string> ret;
|
||||
std::stringstream ss(str);
|
||||
std::string item;
|
||||
@@ -125,7 +133,7 @@ std::vector<std::string> split(const std::string &str, const char &delim, const
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool to_bool(std::string val) {
|
||||
auto to_bool(std::string val) -> bool {
|
||||
auto b = false;
|
||||
|
||||
trim(val);
|
||||
@@ -142,9 +150,9 @@ bool to_bool(std::string val) {
|
||||
return b;
|
||||
}
|
||||
|
||||
double to_double(const std::string &str) { return std::stod(str); }
|
||||
auto to_double(const std::string &str) -> double { return std::stod(str); }
|
||||
|
||||
boost::dynamic_bitset<> to_dynamic_bitset(const std::string &val) {
|
||||
auto to_dynamic_bitset(const std::string &val) -> boost::dynamic_bitset<> {
|
||||
std::stringstream ss(val);
|
||||
boost::dynamic_bitset<> bitset;
|
||||
boost::archive::text_iarchive archive(ss);
|
||||
@@ -152,44 +160,64 @@ boost::dynamic_bitset<> to_dynamic_bitset(const std::string &val) {
|
||||
return bitset;
|
||||
}
|
||||
|
||||
std::int32_t to_int32(const std::string &val) { return std::stoi(val); }
|
||||
auto to_int32(const std::string &val) -> std::int32_t { return std::stoi(val); }
|
||||
|
||||
std::int64_t to_int64(const std::string &val) { return std::stoll(val); }
|
||||
auto to_int64(const std::string &val) -> std::int64_t {
|
||||
return std::stoll(val);
|
||||
}
|
||||
|
||||
std::string to_lower(std::string str) {
|
||||
auto to_lower(std::string str) -> std::string {
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
|
||||
return str;
|
||||
}
|
||||
|
||||
std::uint8_t to_uint8(const std::string &val) { return static_cast<std::uint8_t>(std::stoul(val)); }
|
||||
auto to_size_t(const std::string &val) -> std::size_t {
|
||||
return static_cast<std::size_t>(std::stoull(val));
|
||||
}
|
||||
|
||||
std::uint16_t to_uint16(const std::string &val) {
|
||||
auto to_uint8(const std::string &val) -> std::uint8_t {
|
||||
return static_cast<std::uint8_t>(std::stoul(val));
|
||||
}
|
||||
|
||||
auto to_uint16(const std::string &val) -> std::uint16_t {
|
||||
return static_cast<std::uint16_t>(std::stoul(val));
|
||||
}
|
||||
|
||||
std::uint32_t to_uint32(const std::string &val) {
|
||||
auto to_uint32(const std::string &val) -> std::uint32_t {
|
||||
return static_cast<std::uint32_t>(std::stoul(val));
|
||||
}
|
||||
|
||||
std::uint64_t to_uint64(const std::string &val) { return std::stoull(val); }
|
||||
auto to_uint64(const std::string &val) -> std::uint64_t {
|
||||
return std::stoull(val);
|
||||
}
|
||||
|
||||
std::string to_upper(std::string str) {
|
||||
auto to_upper(std::string str) -> std::string {
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
|
||||
return str;
|
||||
}
|
||||
|
||||
const std::string &to_utf8(const std::string &str) { return str; }
|
||||
auto to_utf8(std::string str) -> std::string { return str; }
|
||||
|
||||
std::string to_utf8(const std::wstring &str) {
|
||||
return str.empty() ? ""
|
||||
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(str);
|
||||
auto to_utf8(const std::wstring &str) -> std::string {
|
||||
return str.empty()
|
||||
? ""
|
||||
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
|
||||
.to_bytes(str);
|
||||
}
|
||||
|
||||
std::string &trim(std::string &str) { return right_trim(left_trim(str)); }
|
||||
auto trim(std::string &str) -> std::string & {
|
||||
return right_trim(left_trim(str));
|
||||
}
|
||||
|
||||
std::string &trim(std::string &str, const char &c) { return right_trim(left_trim(str, c), c); }
|
||||
auto trim(std::string &str, const char &c) -> std::string & {
|
||||
return right_trim(left_trim(str, c), c);
|
||||
}
|
||||
|
||||
std::string trim_copy(std::string str) { return right_trim(left_trim(str)); }
|
||||
auto trim_copy(std::string str) -> std::string {
|
||||
return right_trim(left_trim(str));
|
||||
}
|
||||
|
||||
std::string trim_copy(std::string str, const char &c) { return right_trim(left_trim(str, c), c); }
|
||||
auto trim_copy(std::string str, const char &c) -> std::string {
|
||||
return right_trim(left_trim(str, c), c);
|
||||
}
|
||||
} // namespace repertory::utils::string
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/throttle.hpp"
|
||||
|
||||
#include "types/repertory.hpp"
|
||||
|
||||
namespace repertory {
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/timeout.hpp"
|
||||
|
||||
#include "types/repertory.hpp"
|
||||
|
||||
namespace repertory {
|
||||
@@ -24,15 +28,16 @@ timeout::timeout(std::function<void()> timeout_callback,
|
||||
const std::chrono::system_clock::duration &duration)
|
||||
: timeout_killed_(duration == 0s) {
|
||||
if (not timeout_killed_) {
|
||||
timeout_thread_ = std::make_unique<std::thread>([this, duration, timeout_callback]() {
|
||||
unique_mutex_lock lock(timeout_mutex_);
|
||||
if (not timeout_killed_) {
|
||||
timeout_notify_.wait_for(lock, duration);
|
||||
if (not timeout_killed_) {
|
||||
timeout_callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
timeout_thread_ =
|
||||
std::make_unique<std::thread>([this, duration, timeout_callback]() {
|
||||
unique_mutex_lock lock(timeout_mutex_);
|
||||
if (not timeout_killed_) {
|
||||
timeout_notify_.wait_for(lock, duration);
|
||||
if (not timeout_killed_) {
|
||||
timeout_callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,39 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 _WIN32
|
||||
|
||||
#include "utils/unix/unix_utils.hpp"
|
||||
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace repertory::utils {
|
||||
#ifndef __APPLE__
|
||||
std::uint64_t convert_to_uint64(const pthread_t &t) { return static_cast<std::uint64_t>(t); }
|
||||
#endif
|
||||
|
||||
int get_last_error_code() { return errno; }
|
||||
|
||||
std::uint64_t get_thread_id() { return convert_to_uint64(pthread_self()); }
|
||||
|
||||
bool is_uid_member_of_group(const uid_t &uid, const gid_t &gid) {
|
||||
auto *pw = getpwuid(uid);
|
||||
std::vector<gid_t> groups;
|
||||
|
||||
int group_count = 0;
|
||||
if (getgrouplist(pw->pw_name, pw->pw_gid, nullptr, &group_count) < 0) {
|
||||
groups.resize(static_cast<unsigned long>(group_count));
|
||||
#ifdef __APPLE__
|
||||
getgrouplist(pw->pw_name, pw->pw_gid, reinterpret_cast<int *>(&groups[0]), &group_count);
|
||||
#else
|
||||
getgrouplist(pw->pw_name, pw->pw_gid, &groups[0], &group_count);
|
||||
#endif
|
||||
}
|
||||
|
||||
return collection_includes(groups, gid);
|
||||
auto convert_to_uint64(const pthread_t &t) -> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(t);
|
||||
}
|
||||
#endif
|
||||
|
||||
int translate_api_error(const api_error &e) {
|
||||
auto from_api_error(const api_error &e) -> int {
|
||||
switch (e) {
|
||||
case api_error::access_denied:
|
||||
return -EACCES;
|
||||
@@ -62,14 +48,14 @@ int translate_api_error(const api_error &e) {
|
||||
case api_error::directory_not_found:
|
||||
return -ENOTDIR;
|
||||
case api_error::download_failed:
|
||||
#if __APPLE__
|
||||
#ifdef __APPLE__
|
||||
return -EBADMSG;
|
||||
#else
|
||||
return -EREMOTEIO;
|
||||
#endif
|
||||
case api_error::error:
|
||||
return -EIO;
|
||||
case api_error::file_exists:
|
||||
case api_error::item_exists:
|
||||
return -EEXIST;
|
||||
case api_error::file_in_use:
|
||||
return -EBUSY;
|
||||
@@ -77,8 +63,10 @@ int translate_api_error(const api_error &e) {
|
||||
return -EINVAL;
|
||||
case api_error::item_not_found:
|
||||
return -ENOENT;
|
||||
case api_error::item_is_file:
|
||||
return -ENOTDIR;
|
||||
case api_error::out_of_memory:
|
||||
return -ENOMEM;
|
||||
case api_error::no_disk_space:
|
||||
return -ENOSPC;
|
||||
case api_error::os_error:
|
||||
return -errno;
|
||||
case api_error::permission_denied:
|
||||
@@ -90,17 +78,11 @@ int translate_api_error(const api_error &e) {
|
||||
case api_error::not_implemented:
|
||||
return -ENOSYS;
|
||||
case api_error::upload_failed:
|
||||
#if __APPLE__
|
||||
return -EBADMSG;
|
||||
#else
|
||||
return -EREMOTEIO;
|
||||
#endif
|
||||
return -ENETDOWN;
|
||||
case api_error::xattr_buffer_small:
|
||||
return -ERANGE;
|
||||
case api_error::xattr_exists:
|
||||
return -EEXIST;
|
||||
case api_error::xattr_invalid_namespace:
|
||||
return -ENOTSUP;
|
||||
case api_error::xattr_not_found:
|
||||
#ifdef __APPLE__
|
||||
return -ENOATTR;
|
||||
@@ -112,19 +94,109 @@ int translate_api_error(const api_error &e) {
|
||||
return -ENAMETOOLONG;
|
||||
#else
|
||||
return -E2BIG;
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
case api_error::XAttrOSXInvalid:
|
||||
return -EINVAL;
|
||||
#endif
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
auto get_last_error_code() -> int { return errno; }
|
||||
|
||||
auto get_thread_id() -> std::uint64_t {
|
||||
return convert_to_uint64(pthread_self());
|
||||
}
|
||||
|
||||
auto is_uid_member_of_group(const uid_t &uid, const gid_t &gid) -> bool {
|
||||
static const auto function_name = __FUNCTION__;
|
||||
|
||||
std::vector<gid_t> groups{};
|
||||
use_getpwuid(uid, [&groups](struct passwd *pw) {
|
||||
int group_count{};
|
||||
if (getgrouplist(pw->pw_name, pw->pw_gid, nullptr, &group_count) < 0) {
|
||||
groups.resize(static_cast<std::size_t>(group_count));
|
||||
#ifdef __APPLE__
|
||||
getgrouplist(pw->pw_name, pw->pw_gid,
|
||||
reinterpret_cast<int *>(groups.data()), &group_count);
|
||||
#else
|
||||
getgrouplist(pw->pw_name, pw->pw_gid, groups.data(), &group_count);
|
||||
#endif
|
||||
}
|
||||
});
|
||||
|
||||
return collection_includes(groups, gid);
|
||||
}
|
||||
|
||||
auto to_api_error(int e) -> api_error {
|
||||
switch (abs(e)) {
|
||||
case 0:
|
||||
return api_error::success;
|
||||
case EBADF:
|
||||
return api_error::invalid_handle;
|
||||
case EACCES:
|
||||
return api_error::access_denied;
|
||||
case EFAULT:
|
||||
return api_error::bad_address;
|
||||
case EOF:
|
||||
return api_error::directory_end_of_files;
|
||||
case EISDIR:
|
||||
return api_error::directory_exists;
|
||||
case ENOTEMPTY:
|
||||
return api_error::directory_not_empty;
|
||||
case ENOTDIR:
|
||||
return api_error::directory_not_found;
|
||||
#ifdef __APPLE__
|
||||
case EBADMSG:
|
||||
return api_error::download_failed;
|
||||
#else
|
||||
case EREMOTEIO:
|
||||
return api_error::download_failed;
|
||||
#endif
|
||||
case EIO:
|
||||
return api_error::error;
|
||||
case EEXIST:
|
||||
return api_error::item_exists;
|
||||
case EBUSY:
|
||||
return api_error::file_in_use;
|
||||
case EINVAL:
|
||||
return api_error::invalid_operation;
|
||||
case ENOENT:
|
||||
return api_error::item_not_found;
|
||||
case ENOMEM:
|
||||
return api_error::out_of_memory;
|
||||
case EPERM:
|
||||
return api_error::permission_denied;
|
||||
case ENOSPC:
|
||||
return api_error::no_disk_space;
|
||||
case ENOTSUP:
|
||||
return api_error::not_supported;
|
||||
case ENOSYS:
|
||||
return api_error::not_implemented;
|
||||
case ENETDOWN:
|
||||
return api_error::upload_failed;
|
||||
case ERANGE:
|
||||
return api_error::xattr_buffer_small;
|
||||
#ifdef __APPLE__
|
||||
case ENOATTR:
|
||||
return api_error::xattr_not_found;
|
||||
#else
|
||||
case ENODATA:
|
||||
return api_error::xattr_not_found;
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
case ENAMETOOLONG:
|
||||
return api_error::xattr_too_big;
|
||||
#else
|
||||
case E2BIG:
|
||||
return api_error::xattr_too_big;
|
||||
#endif
|
||||
default:
|
||||
return api_error::error;
|
||||
}
|
||||
}
|
||||
|
||||
void set_last_error_code(int error_code) { errno = error_code; }
|
||||
|
||||
std::int32_t unix_error_to_windows(const int &e) {
|
||||
auto unix_error_to_windows(int e) -> std::int32_t {
|
||||
switch (e) {
|
||||
case 0:
|
||||
return STATUS_SUCCESS;
|
||||
@@ -146,7 +218,7 @@ std::int32_t unix_error_to_windows(const int &e) {
|
||||
case EIO:
|
||||
return STATUS_UNEXPECTED_IO_ERROR;
|
||||
case EISDIR:
|
||||
return STATUS_FILE_IS_A_DIRECTORY;
|
||||
return STATUS_OBJECT_NAME_EXISTS;
|
||||
case EMFILE:
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
case ENOENT:
|
||||
@@ -158,18 +230,31 @@ std::int32_t unix_error_to_windows(const int &e) {
|
||||
case ENOSPC:
|
||||
return STATUS_DEVICE_INSUFFICIENT_RESOURCES;
|
||||
case ENOTDIR:
|
||||
return STATUS_OBJECT_PATH_INVALID;
|
||||
return STATUS_OBJECT_NAME_NOT_FOUND;
|
||||
default:
|
||||
return STATUS_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
UINT64 unix_time_to_windows_time(const remote::file_time &ts) {
|
||||
auto unix_time_to_windows_time(const remote::file_time &ts) -> UINT64 {
|
||||
return (ts / 100ull) + 116444736000000000ull;
|
||||
}
|
||||
|
||||
void windows_create_to_unix(const UINT32 &create_options, const UINT32 &granted_access,
|
||||
std::uint32_t &flags, remote::file_mode &mode) {
|
||||
void use_getpwuid(uid_t uid, std::function<void(struct passwd *pw)> fn) {
|
||||
static std::mutex mtx{};
|
||||
mutex_lock lock{mtx};
|
||||
auto *pw = getpwuid(uid);
|
||||
if (not pw) {
|
||||
utils::error::raise_error(__FUNCTION__, "'getpwuid' returned nullptr");
|
||||
return;
|
||||
}
|
||||
|
||||
fn(pw);
|
||||
}
|
||||
|
||||
void windows_create_to_unix(const UINT32 &create_options,
|
||||
const UINT32 &granted_access, std::uint32_t &flags,
|
||||
remote::file_mode &mode) {
|
||||
mode = S_IRUSR | S_IWUSR;
|
||||
flags = O_CREAT | O_RDWR;
|
||||
if (create_options & FILE_DIRECTORY_FILE) {
|
||||
@@ -177,13 +262,14 @@ void windows_create_to_unix(const UINT32 &create_options, const UINT32 &granted_
|
||||
flags = O_DIRECTORY;
|
||||
}
|
||||
|
||||
if ((granted_access & GENERIC_EXECUTE) || (granted_access & FILE_GENERIC_EXECUTE) ||
|
||||
if ((granted_access & GENERIC_EXECUTE) ||
|
||||
(granted_access & FILE_GENERIC_EXECUTE) ||
|
||||
(granted_access & FILE_EXECUTE)) {
|
||||
mode |= (S_IXUSR);
|
||||
}
|
||||
}
|
||||
|
||||
remote::file_time windows_time_to_unix_time(const std::uint64_t &t) {
|
||||
auto windows_time_to_unix_time(std::uint64_t t) -> remote::file_time {
|
||||
return (t - 116444736000000000ull) * 100ull;
|
||||
}
|
||||
} // namespace repertory::utils
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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 "utils/utils.hpp"
|
||||
|
||||
#include "app_config.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "events/event_system.hpp"
|
||||
#include "events/events.hpp"
|
||||
#include "providers/i_provider.hpp"
|
||||
#include "types/skynet.hpp"
|
||||
#include "types/startup_exception.hpp"
|
||||
#include "utils/com_init_wrapper.hpp"
|
||||
#include "utils/native_file.hpp"
|
||||
@@ -29,18 +32,9 @@
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
namespace repertory::utils {
|
||||
hastings api_currency_to_hastings(const api_currency ¤cy) {
|
||||
ttmath::Parser<api_currency> parser;
|
||||
parser.Parse(currency.ToString() + " * (10 ^ 24)");
|
||||
ttmath::Conv conv;
|
||||
conv.scient_from = 256;
|
||||
conv.base = 10u;
|
||||
conv.round = 0;
|
||||
return parser.stack[0u].value.ToString(conv);
|
||||
}
|
||||
|
||||
void calculate_allocation_size(const bool &directory, const std::uint64_t &file_size,
|
||||
UINT64 allocation_size, std::string &allocation_meta_size) {
|
||||
void calculate_allocation_size(bool directory, std::uint64_t file_size,
|
||||
UINT64 allocation_size,
|
||||
std::string &allocation_meta_size) {
|
||||
if (directory) {
|
||||
allocation_meta_size = "0";
|
||||
return;
|
||||
@@ -49,20 +43,24 @@ void calculate_allocation_size(const bool &directory, const std::uint64_t &file_
|
||||
if (file_size > allocation_size) {
|
||||
allocation_size = file_size;
|
||||
}
|
||||
allocation_size = ((allocation_size == 0u) ? WINFSP_ALLOCATION_UNIT : allocation_size);
|
||||
allocation_size =
|
||||
utils::divide_with_ceiling(allocation_size, WINFSP_ALLOCATION_UNIT) * WINFSP_ALLOCATION_UNIT;
|
||||
((allocation_size == 0u) ? WINFSP_ALLOCATION_UNIT : allocation_size);
|
||||
allocation_size =
|
||||
utils::divide_with_ceiling(allocation_size, WINFSP_ALLOCATION_UNIT) *
|
||||
WINFSP_ALLOCATION_UNIT;
|
||||
allocation_meta_size = std::to_string(allocation_size);
|
||||
}
|
||||
|
||||
std::size_t calculate_read_size(const uint64_t &total_size, const std::size_t &read_size,
|
||||
const uint64_t &offset) {
|
||||
return static_cast<std::size_t>(((offset + read_size) > total_size)
|
||||
? ((offset < total_size) ? total_size - offset : 0u)
|
||||
: read_size);
|
||||
auto calculate_read_size(const uint64_t &total_size, std::size_t read_size,
|
||||
const uint64_t &offset) -> std::size_t {
|
||||
return static_cast<std::size_t>(
|
||||
((offset + read_size) > total_size)
|
||||
? ((offset < total_size) ? total_size - offset : 0u)
|
||||
: read_size);
|
||||
}
|
||||
|
||||
int compare_version_strings(std::string version1, std::string version2) {
|
||||
auto compare_version_strings(std::string version1, std::string version2)
|
||||
-> int {
|
||||
if (utils::string::contains(version1, "-")) {
|
||||
version1 = utils::string::split(version1, '-')[0u];
|
||||
}
|
||||
@@ -94,44 +92,33 @@ int compare_version_strings(std::string version1, std::string version2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uint64_t convert_api_date(const std::string &date) {
|
||||
//"2019-02-21T02:24:37.653091916-06:00"
|
||||
const auto parts = utils::string::split(date, '.');
|
||||
const auto dt = parts[0];
|
||||
const auto nanos = utils::string::to_uint64(utils::string::split(parts[1u], '-')[0u]);
|
||||
auto convert_api_date(const std::string &date) -> std::uint64_t {
|
||||
// 2009-10-12T17:50:30.000Z
|
||||
const auto date_parts = utils::string::split(date, '.');
|
||||
const auto date_time = date_parts[0U];
|
||||
const auto nanos =
|
||||
utils::string::to_uint64(utils::string::split(date_parts[1U], 'Z')[0U]);
|
||||
|
||||
struct tm tm1 {};
|
||||
#ifdef _WIN32
|
||||
auto convert_time = [](time_t t) -> std::uint64_t {
|
||||
const auto ll = Int32x32To64(t, 10000000) + 116444736000000000ull;
|
||||
ULARGE_INTEGER ft{};
|
||||
ft.LowPart = static_cast<DWORD>(ll);
|
||||
ft.HighPart = static_cast<DWORD>(ll >> 32);
|
||||
return ft.QuadPart;
|
||||
};
|
||||
|
||||
const auto parts2 = utils::string::split(dt, 'T');
|
||||
const auto date_parts = utils::string::split(parts2[0u], '-');
|
||||
const auto time_parts = utils::string::split(parts2[1u], ':');
|
||||
tm1.tm_year = utils::string::to_int32(date_parts[0u]) - 1900;
|
||||
tm1.tm_mon = utils::string::to_int32(date_parts[1u]) - 1;
|
||||
tm1.tm_mday = utils::string::to_int32(date_parts[2u]);
|
||||
tm1.tm_hour = utils::string::to_uint32(time_parts[0u]);
|
||||
tm1.tm_min = utils::string::to_uint32(time_parts[1u]);
|
||||
tm1.tm_sec = utils::string::to_uint32(time_parts[2u]);
|
||||
tm1.tm_wday = -1;
|
||||
tm1.tm_yday = -1;
|
||||
tm1.tm_isdst = -1;
|
||||
return (nanos / 100) + convert_time(mktime(&tm1));
|
||||
utils::strptime(date_time.c_str(), "%Y-%m-%dT%T", &tm1);
|
||||
#else
|
||||
strptime(&dt[0], "%Y-%m-%dT%T", &tm1);
|
||||
return nanos + (mktime(&tm1) * NANOS_PER_SECOND);
|
||||
strptime(date_time.c_str(), "%Y-%m-%dT%T", &tm1);
|
||||
#endif
|
||||
return nanos + (mktime(&tm1) * NANOS_PER_SECOND);
|
||||
}
|
||||
|
||||
CURL *create_curl() { return reset_curl(curl_easy_init()); }
|
||||
auto create_curl() -> CURL * {
|
||||
static std::recursive_mutex mtx;
|
||||
|
||||
std::string create_uuid_string() {
|
||||
unique_recur_mutex_lock l(mtx);
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
l.unlock();
|
||||
|
||||
return reset_curl(curl_easy_init());
|
||||
}
|
||||
|
||||
auto create_uuid_string() -> std::string {
|
||||
#ifdef _WIN32
|
||||
UUID guid{};
|
||||
UuidCreate(&guid);
|
||||
@@ -161,11 +148,13 @@ std::string create_uuid_string() {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string create_volume_label(const provider_type &pt) {
|
||||
auto create_volume_label(const provider_type &pt) -> std::string {
|
||||
return "repertory_" + app_config::get_provider_name(pt);
|
||||
}
|
||||
|
||||
download_type download_type_from_string(std::string type, const download_type &default_type) {
|
||||
auto download_type_from_string(std::string type,
|
||||
const download_type &default_type)
|
||||
-> download_type {
|
||||
type = utils::string::to_lower(utils::string::trim(type));
|
||||
if (type == "direct") {
|
||||
return download_type::direct;
|
||||
@@ -178,7 +167,7 @@ download_type download_type_from_string(std::string type, const download_type &d
|
||||
return default_type;
|
||||
}
|
||||
|
||||
std::string download_type_to_string(const download_type &type) {
|
||||
auto download_type_to_string(const download_type &type) -> std::string {
|
||||
switch (type) {
|
||||
case download_type::direct:
|
||||
return "direct";
|
||||
@@ -193,7 +182,7 @@ std::string download_type_to_string(const download_type &type) {
|
||||
|
||||
#ifdef _WIN32
|
||||
// https://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/
|
||||
remote::file_time filetime_to_unix_time(const FILETIME &ft) {
|
||||
auto filetime_to_unix_time(const FILETIME &ft) -> remote::file_time {
|
||||
LARGE_INTEGER date{};
|
||||
date.HighPart = ft.dwHighDateTime;
|
||||
date.LowPart = ft.dwLowDateTime;
|
||||
@@ -209,7 +198,7 @@ void unix_time_to_filetime(const remote::file_time &ts, FILETIME &ft) {
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string generate_random_string(const std::uint16_t &length) {
|
||||
auto generate_random_string(std::uint16_t length) -> std::string {
|
||||
srand(static_cast<unsigned int>(get_time_now()));
|
||||
|
||||
std::string ret;
|
||||
@@ -217,13 +206,18 @@ std::string generate_random_string(const std::uint16_t &length) {
|
||||
for (std::uint16_t i = 0u; i < length; i++) {
|
||||
do {
|
||||
ret[i] = static_cast<char>(rand() % 74 + 48);
|
||||
} while (((ret[i] >= 91) && (ret[i] <= 96)) || ((ret[i] >= 58) && (ret[i] <= 64)));
|
||||
} while (((ret[i] >= 91) && (ret[i] <= 96)) ||
|
||||
((ret[i] >= 58) && (ret[i] <= 64)));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string get_environment_variable(const std::string &variable) {
|
||||
auto get_attributes_from_meta(const api_meta_map &meta) -> DWORD {
|
||||
return static_cast<DWORD>(utils::string::to_uint32(meta.at(META_ATTRIBUTES)));
|
||||
}
|
||||
|
||||
auto get_environment_variable(const std::string &variable) -> std::string {
|
||||
#ifdef _WIN32
|
||||
std::string value;
|
||||
auto sz = ::GetEnvironmentVariable(&variable[0], nullptr, 0);
|
||||
@@ -239,7 +233,7 @@ std::string get_environment_variable(const std::string &variable) {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::uint64_t get_file_time_now() {
|
||||
auto get_file_time_now() -> std::uint64_t {
|
||||
#ifdef _WIN32
|
||||
SYSTEMTIME st{};
|
||||
::GetSystemTime(&st);
|
||||
@@ -254,7 +248,8 @@ std::uint64_t get_file_time_now() {
|
||||
void get_local_time_now(struct tm &local_time) {
|
||||
memset(&local_time, 0, sizeof(local_time));
|
||||
|
||||
const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
const auto now =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
#ifdef _WIN32
|
||||
localtime_s(&local_time, &now);
|
||||
#else
|
||||
@@ -265,10 +260,8 @@ void get_local_time_now(struct tm &local_time) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#endif
|
||||
|
||||
bool get_next_available_port(std::uint16_t first_port, std::uint16_t &available_port) {
|
||||
auto get_next_available_port(std::uint16_t first_port,
|
||||
std::uint16_t &available_port) -> bool {
|
||||
using namespace boost::asio;
|
||||
using ip::tcp;
|
||||
boost::system::error_code ec;
|
||||
@@ -285,28 +278,25 @@ bool get_next_available_port(std::uint16_t first_port, std::uint16_t &available_
|
||||
return not ec;
|
||||
}
|
||||
|
||||
std::uint64_t get_time_now() {
|
||||
auto get_time_now() -> std::uint64_t {
|
||||
#ifdef _WIN32
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
|
||||
#else
|
||||
#if __APPLE__
|
||||
return std::chrono::nanoseconds(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
return std::chrono::nanoseconds(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
#else
|
||||
return static_cast<std::uint64_t>(
|
||||
std::chrono::nanoseconds(std::chrono::high_resolution_clock::now().time_since_epoch())
|
||||
std::chrono::nanoseconds(
|
||||
std::chrono::high_resolution_clock::now().time_since_epoch())
|
||||
.count());
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
api_currency hastings_string_to_api_currency(const std::string &amount) {
|
||||
ttmath::Parser<api_currency> parser;
|
||||
parser.Parse(amount + " / (10 ^ 24)");
|
||||
return parser.stack[0].value;
|
||||
}
|
||||
|
||||
CURL *reset_curl(CURL *curl_handle) {
|
||||
auto reset_curl(CURL *curl_handle) -> CURL * {
|
||||
curl_easy_reset(curl_handle);
|
||||
#if __APPLE__
|
||||
curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
|
||||
@@ -314,56 +304,40 @@ CURL *reset_curl(CURL *curl_handle) {
|
||||
return curl_handle;
|
||||
}
|
||||
|
||||
bool retryable_action(const std::function<bool()> &action) {
|
||||
auto retryable_action(const std::function<bool()> &action) -> bool {
|
||||
auto succeeded = false;
|
||||
for (auto i = 0; not(succeeded = action()) && (i < 10); i++) {
|
||||
for (std::uint8_t i = 0u; not(succeeded = action()) && (i < 20u); i++) {
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
/* bool parse_url(const std::string &url, HostConfig &hc) { */
|
||||
/* static const auto pathRegex = std::regex( */
|
||||
/* R"(^((\w+):)?(\/\/((\w+)?(:(\w+))?@)?([^\/\?:]+)(:(\d+))?)?(\/?([^\/\?#][^\?#]*)?)?(\?([^#]+))?(#(\w*))?$)");
|
||||
*/
|
||||
/* */
|
||||
/* std::smatch results; */
|
||||
/* if (std::regex_search(url, results, pathRegex)) { */
|
||||
/* hc.HostNameOrIp = results[8u].str(); */
|
||||
/* hc.Path = utils::path::create_api_path(results[11u].str()); */
|
||||
/* hc.Protocol = utils::string::to_lower(results[2u].str()); */
|
||||
/* hc.ApiPort = results[10u].str().empty() ? ((hc.Protocol == "https") ? 443u : 80u) */
|
||||
/* : utils::string::to_uint16(results[10u].str()); */
|
||||
/* return not hc.HostNameOrIp.empty() && not hc.Protocol.empty() && */
|
||||
/* ((hc.Protocol == "http") || (hc.Protocol == "https")); */
|
||||
/* } */
|
||||
/* */
|
||||
/* return false; */
|
||||
/* } */
|
||||
|
||||
void spin_wait_for_mutex(std::function<bool()> complete, std::condition_variable &cv,
|
||||
std::mutex &mtx, const std::string &text) {
|
||||
void spin_wait_for_mutex(std::function<bool()> complete,
|
||||
std::condition_variable &cv, std::mutex &mtx,
|
||||
const std::string &text) {
|
||||
while (not complete()) {
|
||||
unique_mutex_lock l(mtx);
|
||||
if (not complete()) {
|
||||
if (not text.empty()) {
|
||||
/* event_system::instance().raise<DebugLog>(__FUNCTION__, "spin_wait_for_mutex", text); */
|
||||
/* event_system::instance().raise<DebugLog>(__FUNCTION__,
|
||||
* "spin_wait_for_mutex", text); */
|
||||
}
|
||||
cv.wait_for(l, 5s);
|
||||
cv.wait_for(l, 1s);
|
||||
}
|
||||
l.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void spin_wait_for_mutex(bool &complete, std::condition_variable &cv, std::mutex &mtx,
|
||||
const std::string &text) {
|
||||
void spin_wait_for_mutex(bool &complete, std::condition_variable &cv,
|
||||
std::mutex &mtx, const std::string &text) {
|
||||
while (not complete) {
|
||||
unique_mutex_lock l(mtx);
|
||||
if (not complete) {
|
||||
if (not text.empty()) {
|
||||
/* event_system::instance().raise<DebugLog>(__FUNCTION__, "spin_wait_for_mutex", text); */
|
||||
/* event_system::instance().raise<DebugLog>(__FUNCTION__,
|
||||
* "spin_wait_for_mutex", text); */
|
||||
}
|
||||
cv.wait_for(l, 5s);
|
||||
cv.wait_for(l, 1s);
|
||||
}
|
||||
l.unlock();
|
||||
}
|
||||
|
||||
@@ -1,73 +1,43 @@
|
||||
/*
|
||||
Copyright <2018-2022> <scott.e.graves@protonmail.com>
|
||||
Copyright <2018-2023> <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
|
||||
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 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.
|
||||
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.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "utils/windows/windows_utils.hpp"
|
||||
|
||||
#include "types/startup_exception.hpp"
|
||||
#include "utils/com_init_wrapper.hpp"
|
||||
#include "utils/string_utils.hpp"
|
||||
|
||||
#ifndef STATUS_DEVICE_INSUFFICIENT_RESOURCES
|
||||
#define STATUS_DEVICE_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC0000468L)
|
||||
#endif
|
||||
|
||||
namespace repertory::utils {
|
||||
DWORD get_last_error_code() { return ::GetLastError(); }
|
||||
|
||||
const std::string &get_local_app_data_directory() {
|
||||
static std::string app_data = ([]() -> std::string {
|
||||
com_init_wrapper cw;
|
||||
PWSTR local_app_data{};
|
||||
if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &local_app_data))) {
|
||||
auto app_data = utils::string::to_utf8(local_app_data);
|
||||
::CoTaskMemFree(local_app_data);
|
||||
return app_data;
|
||||
}
|
||||
|
||||
throw startup_exception("unable to detect local application data folder");
|
||||
})();
|
||||
|
||||
return app_data;
|
||||
}
|
||||
|
||||
std::uint64_t get_accessed_time_from_meta(const api_meta_map &meta) {
|
||||
return utils::string::to_uint64(meta.at(META_ACCESSED));
|
||||
}
|
||||
|
||||
DWORD get_attributes_from_meta(const api_meta_map &meta) {
|
||||
return static_cast<DWORD>(utils::string::to_uint32(meta.at(META_ATTRIBUTES)));
|
||||
}
|
||||
|
||||
std::uint64_t get_changed_time_from_meta(const api_meta_map &meta) {
|
||||
return utils::string::to_uint64(meta.at(META_MODIFIED));
|
||||
}
|
||||
|
||||
std::uint64_t get_creation_time_from_meta(const api_meta_map &meta) {
|
||||
return utils::string::to_uint64(meta.at(META_CREATION));
|
||||
}
|
||||
|
||||
std::uint64_t get_written_time_from_meta(const api_meta_map &meta) {
|
||||
return utils::string::to_uint64(meta.at(META_WRITTEN));
|
||||
}
|
||||
|
||||
std::uint64_t get_thread_id() { return static_cast<std::uint64_t>(::GetCurrentThreadId()); }
|
||||
|
||||
NTSTATUS translate_api_error(const api_error &e) {
|
||||
auto from_api_error(const api_error &e) -> NTSTATUS {
|
||||
switch (e) {
|
||||
case api_error::access_denied:
|
||||
return FspNtStatusFromWin32(ERROR_ACCESS_DENIED);
|
||||
return STATUS_ACCESS_DENIED;
|
||||
case api_error::bad_address:
|
||||
return STATUS_INVALID_ADDRESS;
|
||||
case api_error::buffer_too_small:
|
||||
return STATUS_BUFFER_TOO_SMALL;
|
||||
case api_error::buffer_overflow:
|
||||
@@ -84,20 +54,24 @@ NTSTATUS translate_api_error(const api_error &e) {
|
||||
return FspNtStatusFromWin32(ERROR_INTERNAL_ERROR);
|
||||
case api_error::error:
|
||||
return FspNtStatusFromWin32(ERROR_INTERNAL_ERROR);
|
||||
case api_error::file_exists:
|
||||
case api_error::invalid_handle:
|
||||
return STATUS_INVALID_HANDLE;
|
||||
case api_error::invalid_operation:
|
||||
return STATUS_INVALID_PARAMETER;
|
||||
case api_error::item_exists:
|
||||
return FspNtStatusFromWin32(ERROR_FILE_EXISTS);
|
||||
case api_error::file_in_use:
|
||||
return FspNtStatusFromWin32(ERROR_BUSY);
|
||||
return STATUS_DEVICE_BUSY;
|
||||
case api_error::incompatible_version:
|
||||
return STATUS_CLIENT_SERVER_PARAMETERS_INVALID;
|
||||
case api_error::invalid_operation:
|
||||
return FspNtStatusFromWin32(ERROR_INTERNAL_ERROR);
|
||||
case api_error::item_not_found:
|
||||
return STATUS_OBJECT_NAME_NOT_FOUND;
|
||||
case api_error::item_is_file:
|
||||
return STATUS_ACCESS_DENIED;
|
||||
case api_error::no_disk_space:
|
||||
return STATUS_DEVICE_INSUFFICIENT_RESOURCES;
|
||||
case api_error::os_error:
|
||||
return FspNtStatusFromWin32(::GetLastError());
|
||||
case api_error::out_of_memory:
|
||||
return STATUS_NO_MEMORY;
|
||||
case api_error::permission_denied:
|
||||
return FspNtStatusFromWin32(ERROR_ACCESS_DENIED);
|
||||
case api_error::success:
|
||||
@@ -112,7 +86,47 @@ NTSTATUS translate_api_error(const api_error &e) {
|
||||
return FspNtStatusFromWin32(ERROR_INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
bool is_process_elevated() {
|
||||
|
||||
auto get_last_error_code() -> DWORD { return ::GetLastError(); }
|
||||
|
||||
auto get_local_app_data_directory() -> const std::string & {
|
||||
static std::string app_data = ([]() -> std::string {
|
||||
com_init_wrapper cw;
|
||||
PWSTR local_app_data{};
|
||||
if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr,
|
||||
&local_app_data))) {
|
||||
auto app_data = utils::string::to_utf8(local_app_data);
|
||||
::CoTaskMemFree(local_app_data);
|
||||
return app_data;
|
||||
}
|
||||
|
||||
throw startup_exception("unable to detect local application data folder");
|
||||
})();
|
||||
|
||||
return app_data;
|
||||
}
|
||||
|
||||
auto get_accessed_time_from_meta(const api_meta_map &meta) -> std::uint64_t {
|
||||
return utils::string::to_uint64(meta.at(META_ACCESSED));
|
||||
}
|
||||
|
||||
auto get_changed_time_from_meta(const api_meta_map &meta) -> std::uint64_t {
|
||||
return utils::string::to_uint64(meta.at(META_MODIFIED));
|
||||
}
|
||||
|
||||
auto get_creation_time_from_meta(const api_meta_map &meta) -> std::uint64_t {
|
||||
return utils::string::to_uint64(meta.at(META_CREATION));
|
||||
}
|
||||
|
||||
auto get_written_time_from_meta(const api_meta_map &meta) -> std::uint64_t {
|
||||
return utils::string::to_uint64(meta.at(META_WRITTEN));
|
||||
}
|
||||
|
||||
auto get_thread_id() -> std::uint64_t {
|
||||
return static_cast<std::uint64_t>(::GetCurrentThreadId());
|
||||
}
|
||||
|
||||
auto is_process_elevated() -> bool {
|
||||
auto ret = false;
|
||||
HANDLE token = INVALID_HANDLE_VALUE;
|
||||
if (::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
|
||||
@@ -130,7 +144,7 @@ bool is_process_elevated() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
int run_process_elevated(int argc, char *argv[]) {
|
||||
auto run_process_elevated(int argc, char *argv[]) -> int {
|
||||
std::cout << "Elevating Process" << std::endl;
|
||||
std::string parameters = "-hidden";
|
||||
for (int i = 1; i < argc; i++) {
|
||||
@@ -161,7 +175,19 @@ int run_process_elevated(int argc, char *argv[]) {
|
||||
|
||||
void set_last_error_code(DWORD error_code) { ::SetLastError(error_code); }
|
||||
|
||||
int unix_access_mask_to_windows(std::int32_t mask) {
|
||||
// https://stackoverflow.com/questions/321849/strptime-equivalent-on-windows
|
||||
auto strptime(const char *s, const char *f, struct tm *tm) -> const char * {
|
||||
std::istringstream input(s);
|
||||
input.imbue(std::locale(setlocale(LC_ALL, nullptr)));
|
||||
input >> std::get_time(tm, f);
|
||||
if (input.fail()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<const char *>(s + input.tellg());
|
||||
}
|
||||
|
||||
auto unix_access_mask_to_windows(std::int32_t mask) -> int {
|
||||
if (mask & 1) {
|
||||
mask -= 1;
|
||||
if (not mask) {
|
||||
@@ -172,25 +198,43 @@ int unix_access_mask_to_windows(std::int32_t mask) {
|
||||
return mask & 6;
|
||||
}
|
||||
|
||||
int unix_open_flags_to_flags_and_perms(const remote::file_mode &mode,
|
||||
const remote::open_flags &flags, std::int32_t &perms) {
|
||||
auto unix_open_flags_to_flags_and_perms(const remote::file_mode & /*mode*/,
|
||||
const remote::open_flags &flags,
|
||||
std::int32_t &perms) -> int {
|
||||
auto ret = _O_BINARY | _O_RANDOM;
|
||||
ret |= (((flags & remote::open_flags::create) == remote::open_flags::create) ? _O_CREAT : 0);
|
||||
ret |= (((flags & remote::open_flags::excl) == remote::open_flags::excl) ? _O_EXCL : 0);
|
||||
ret |= (((flags & remote::open_flags::truncate) == remote::open_flags::truncate) ? _O_TRUNC : 0);
|
||||
ret |= (((flags & remote::open_flags::append) == remote::open_flags::append) ? _O_APPEND : 0);
|
||||
ret |= (((flags & remote::open_flags::temp_file) == remote::open_flags::temp_file) ? _O_TEMPORARY
|
||||
: 0);
|
||||
ret |= ((flags & remote::open_flags::write_only) == remote::open_flags::write_only) ? _O_WRONLY
|
||||
: ((flags & remote::open_flags::read_write) == remote::open_flags::read_write) ? _O_RDWR
|
||||
: _O_RDONLY;
|
||||
ret |= (((flags & remote::open_flags::create) == remote::open_flags::create)
|
||||
? _O_CREAT
|
||||
: 0);
|
||||
ret |= (((flags & remote::open_flags::excl) == remote::open_flags::excl)
|
||||
? _O_EXCL
|
||||
: 0);
|
||||
ret |=
|
||||
(((flags & remote::open_flags::truncate) == remote::open_flags::truncate)
|
||||
? _O_TRUNC
|
||||
: 0);
|
||||
ret |= (((flags & remote::open_flags::append) == remote::open_flags::append)
|
||||
? _O_APPEND
|
||||
: 0);
|
||||
ret |= (((flags & remote::open_flags::temp_file) ==
|
||||
remote::open_flags::temp_file)
|
||||
? _O_TEMPORARY
|
||||
: 0);
|
||||
ret |= ((flags & remote::open_flags::write_only) ==
|
||||
remote::open_flags::write_only)
|
||||
? _O_WRONLY
|
||||
: ((flags & remote::open_flags::read_write) ==
|
||||
remote::open_flags::read_write)
|
||||
? _O_RDWR
|
||||
: _O_RDONLY;
|
||||
|
||||
perms = _S_IREAD | _S_IWRITE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
remote::file_time time64_to_unix_time(const __time64_t &t) { return t * NANOS_PER_SECOND; }
|
||||
auto time64_to_unix_time(const __time64_t &t) -> remote::file_time {
|
||||
return t * NANOS_PER_SECOND;
|
||||
}
|
||||
} // namespace repertory::utils
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
Reference in New Issue
Block a user