From 7296b3096a0889f638f30c3061cc4bb34eb4d06b Mon Sep 17 00:00:00 2001 From: asypost Date: Thu, 30 Jul 2026 20:59:22 +0800 Subject: [PATCH] bug fix --- CMakeLists.txt | 3 +- include/seafile/api/seafile_api.hpp | 147 ++++++--- include/seafile/fs/file_context.hpp | 1 + include/seafile/fs/seafile_file_system.hpp | 2 - include/seafile/fs/seafile_store.hpp | 2 - include/seafile/fs/setting.hpp | 23 ++ include/util/icu_raii.hpp | 1 + include/util/string_util.hpp | 2 +- src/main.cpp | 6 +- src/seafile/fs/seafile_file_system.cpp | 33 +- src/seafile/fs/seafile_store.cpp | 362 ++++++++++++++------- src/seafile/fs/winfsp_adapter.cpp | 89 ++++- src/util/string_util.cpp | 2 +- 13 files changed, 467 insertions(+), 206 deletions(-) create mode 100644 include/seafile/fs/setting.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 012aea7..a76a9e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,6 @@ find_package(tl-expected CONFIG REQUIRED) find_package(nlohmann_json CONFIG REQUIRED) find_package(date CONFIG REQUIRED) find_package(cpr CONFIG REQUIRED) -find_package(argparse CONFIG REQUIRED) find_package(ada CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) find_package(ICU REQUIRED COMPONENTS uc i18n) @@ -65,6 +64,7 @@ add_executable(seadisk src/main.cpp src/util/application_path.cpp include/seafile/fs/storage/repo_info.hpp src/seafile/fs/storage/repo_info.cpp + include/seafile/fs/setting.hpp ) target_compile_definitions(seadisk PRIVATE @@ -90,7 +90,6 @@ target_link_libraries(seadisk PRIVATE date::date date::date-tz cpr::cpr - argparse::argparse ada::ada spdlog::spdlog ICU::uc diff --git a/include/seafile/api/seafile_api.hpp b/include/seafile/api/seafile_api.hpp index 0378e3b..a5d6ce5 100644 --- a/include/seafile/api/seafile_api.hpp +++ b/include/seafile/api/seafile_api.hpp @@ -25,50 +25,65 @@ #include "token_type.hpp" #include "seafile/util/version.hpp" -namespace seafile::api { - class SeaFileApi final : public ISeaFileApi { +namespace seafile::api +{ + class SeaFileApi final : public ISeaFileApi + { //base url of the seafile api server std::string base_url; std::string token; TokenType token_type = TokenType::BEARER; + cpr::ConnectionPool connection_pool; /** * @brief make a url string from given path * @param path url path * @return the whole url string */ - [[nodiscard]] std::string makeUrl(const std::string &path) const { + [[nodiscard]] std::string makeUrl(const std::string& path) const + { auto url = ada::parse(this->base_url); + if (!url) + { + return {}; + } url->set_pathname(path); return std::string(url->get_href()); } - template + template static tl::expected - parseResponse(const cpr::Response &response) { - if (response.error) { + parseResponse(const cpr::Response& response) + { + if (response.error) + { return tl::make_unexpected( SeafileException( response.error.message)); } - if (response.status_code != 200 && response.status_code != 206) { + if (response.status_code != 200 && response.status_code != 206) + { return tl::make_unexpected( SeafileException( std::format("Http Error: {}", response.status_code))); } - try { + try + { return nlohmann::json::parse(response.text) - .get(); - } catch (const std::exception &e) { + .get(); + } + catch (const std::exception& e) + { return tl::make_unexpected( SeafileException(e.what())); } } - static std::string makeAuthToken(const std::string &token, const TokenType &token_type) { + static std::string makeAuthToken(const std::string& token, const TokenType& token_type) + { return token_type == TokenType::BEARER ? std::format("Bearer {}", token) : std::format("Token {}", token); @@ -81,15 +96,16 @@ namespace seafile::api { * @param body post body data * @return T if success else SeafileException */ - template - tl::expected post(const std::string &path, const nlohmann::json &body) { + template + tl::expected post(const std::string& path, const nlohmann::json& body) + { const auto url = makeUrl(path); const auto response = cpr::Post( cpr::Url{url}, cpr::Header{ {"accept", "application/json"}, {"content-type", "application/json"} - }, cpr::Body{body.dump()}); + }, cpr::Body{body.dump()}, connection_pool); return parseResponse(response); } @@ -98,7 +114,8 @@ namespace seafile::api { * @brief primary constructor * @param base_url base url of seafile server */ - explicit SeaFileApi(std::string base_url) : base_url(std::move(base_url)) { + explicit SeaFileApi(std::string base_url) : base_url(std::move(base_url)) + { } /** @@ -108,19 +125,25 @@ namespace seafile::api { * @return token when success else SeafileException */ tl::expected - auth(const std::string &username, const std::string &password) override { + auth(const std::string& username, const std::string& password) override + { const nlohmann::json body = { {"username", username}, {"password", password} }; - const auto &auth_token = this->post("/api2/auth-token/", body); - if (auth_token) { + const auto& auth_token = this->post("/api2/auth-token/", body); + if (auth_token) + { this->token = auth_token->token; - if (const auto &server_info = this->serverInfo()) { - if (const auto version = util::Version(server_info->version); version.major() < 11) { + if (const auto& server_info = this->serverInfo()) + { + if (const auto version = util::Version(server_info->version); version.major() < 11) + { this->token_type = TokenType::TOKEN; } - } else { + } + else + { return tl::make_unexpected(server_info.error()); } } @@ -128,12 +151,13 @@ namespace seafile::api { } tl::expected - serverInfo() const override { + serverInfo() const override + { const auto response = cpr::Get( cpr::Url{makeUrl("/api2/server-info/")}, cpr::Header{ - {"accept", "application/json"} - }); + {"accept", "application/json"}, + }, connection_pool); return parseResponse(response); } @@ -143,19 +167,21 @@ namespace seafile::api { * @return default repo info */ tl::expected - defaultRepo() const override { + defaultRepo() const override + { const auto auth = makeAuthToken(token, token_type); const auto url = makeUrl("/api2/default-repo/"); const auto response = cpr::Get(cpr::Url{url}, cpr::Header{ {"accept", "application/json"}, {"authorization", auth} - }); + }, connection_pool); return parseResponse(response); } tl::expected - repoInfo(const std::string &repo_id) const override { + repoInfo(const std::string& repo_id) const override + { const auto url = makeUrl(std::format("/api2/repos/{}/", repo_id)); const auto auth = makeAuthToken(token, token_type); @@ -163,12 +189,13 @@ namespace seafile::api { cpr::Header{ {"accept", "application/json"}, {"authorization", auth} - }); + }, connection_pool); return parseResponse(response); } tl::expected, SeafileException> - repos(const std::string &type, const std::string &name_contains) const override { + repos(const std::string& type, const std::string& name_contains) const override + { const auto url = makeUrl("/api2/repos/"); const auto auth = makeAuthToken(token, token_type); const auto response = cpr::Get(cpr::Url{url}, @@ -179,10 +206,10 @@ namespace seafile::api { cpr::Parameters{ {"type", type}, {"nameContains", name_contains} - }); + }, connection_pool); - return parseResponse >(response); + return parseResponse>(response); } /** @@ -192,7 +219,8 @@ namespace seafile::api { * @return repo entries */ tl::expected, SeafileException> - repoDir(const std::string &repo_id, const std::string &path) const override { + repoDir(const std::string& repo_id, const std::string& path) const override + { const auto url = cpr::Url{makeUrl(std::format("/api2/repos/{}/dir/", repo_id))}; const auto auth = makeAuthToken(token, token_type); @@ -203,12 +231,15 @@ namespace seafile::api { }, cpr::Parameters{ {"p", path} - }); - return parseResponse >(response); + }, + connection_pool, + cpr::ReserveSize(512 * 1024)); + return parseResponse>(response); } tl::expected - accountInfo() const override { + accountInfo() const override + { const auto url = cpr::Url{makeUrl("/api2/account/info/")}; const auto auth = makeAuthToken(token, token_type); @@ -216,12 +247,13 @@ namespace seafile::api { cpr::Header{ {"accept", "application/json"}, {"authorization", auth} - }); + }, connection_pool); return parseResponse(response); } tl::expected - fileDetail(const std::string &repo_id, const std::string &path) const override { + fileDetail(const std::string& repo_id, const std::string& path) const override + { const auto url = cpr::Url{makeUrl(std::format("/api2/repos/{}/file/detail/", repo_id))}; const auto auth = makeAuthToken(token, token_type); @@ -231,12 +263,13 @@ namespace seafile::api { {"authorization", auth} }, cpr::Parameters{ {"p", path} - }); + }, connection_pool); return parseResponse(response); } tl::expected - downloadFileUrl(const std::string &repo_id, const std::string &path) const override { + downloadFileUrl(const std::string& repo_id, const std::string& path) const override + { const auto auth = makeAuthToken(token, token_type); const auto url = cpr::Url{makeUrl(std::format("/api2/repos/{}/file/", repo_id))}; const auto response = cpr::Get(url, @@ -245,17 +278,19 @@ namespace seafile::api { {"authorization", auth} }, cpr::Parameters{ {"p", path} - }); + }, connection_pool); return parseResponse(response); } tl::expected - downloadFile(const std::string &repo_id, - const std::string &path, + downloadFile(const std::string& repo_id, + const std::string& path, const std::optional start, - const std::optional end) const override { - if (auto download_url = downloadFileUrl(repo_id, path); download_url.has_value()) { + const std::optional end) const override + { + if (auto download_url = downloadFileUrl(repo_id, path); download_url.has_value()) + { const auto auth = makeAuthToken(token, token_type); const auto url = cpr::Url{download_url.value()}; auto header = cpr::Header{ @@ -266,21 +301,33 @@ namespace seafile::api { start ? std::to_string(*start) : "", end ? std::to_string(*end) : ""); header.emplace("range", range); - if (const auto &response = cpr::Get(url, header); - response.error || (response.status_code != 200 && response.status_code != 206)) { + + size_t content_size = 1024 * 4; + if (start.has_value() && end.has_value()) + { + content_size = end.value() - start.value(); + } + if (const auto& response = cpr::Get(url, header, connection_pool, cpr::ReserveSize(content_size)); + response.error || (response.status_code != 200 && response.status_code != 206)) + { return tl::make_unexpected(SeafileException{ std::format("Download bytes range {} failed, HTTP Error:{}", range, response.status_code) }); - } else { + } + else + { return response.text; } - } else { + } + else + { return download_url; } } tl::expected - dirDetail(const std::string &repo_id, const std::string &path) const override { + dirDetail(const std::string& repo_id, const std::string& path) const override + { const auto auth = makeAuthToken(token, token_type); const auto url = cpr::Url{makeUrl(std::format("/api/v2.1/repos/{}/dir/detail/", repo_id))}; const auto response = cpr::Get(url, cpr::Header{ @@ -288,7 +335,7 @@ namespace seafile::api { {"authorization", auth} }, cpr::Parameters{ {"path", path} - }); + }, connection_pool); return parseResponse(response); } diff --git a/include/seafile/fs/file_context.hpp b/include/seafile/fs/file_context.hpp index 4955add..3f6c1d5 100644 --- a/include/seafile/fs/file_context.hpp +++ b/include/seafile/fs/file_context.hpp @@ -4,6 +4,7 @@ #ifndef SEADISK_FILE_CONTEXT_HPP #define SEADISK_FILE_CONTEXT_HPP +#include #include namespace seafile::fs { diff --git a/include/seafile/fs/seafile_file_system.hpp b/include/seafile/fs/seafile_file_system.hpp index daa271e..ad0bff5 100644 --- a/include/seafile/fs/seafile_file_system.hpp +++ b/include/seafile/fs/seafile_file_system.hpp @@ -18,8 +18,6 @@ namespace seafile::fs class SeafileFileSystem final { using SeafileException = api::SeafileException; - using RepoInfo = storage::RepoInfo; - using RepoEntry = storage::RepoEntry; std::unique_ptr store; std::wstring volume_label; diff --git a/include/seafile/fs/seafile_store.hpp b/include/seafile/fs/seafile_store.hpp index a6a9a28..694afbf 100644 --- a/include/seafile/fs/seafile_store.hpp +++ b/include/seafile/fs/seafile_store.hpp @@ -29,8 +29,6 @@ namespace seafile::fs tl::expected auth(); - [[nodiscard]] tl::expected defaultRepoId() const; - ///初始化数据库信息 [[nodiscard]] tl::expected init(); diff --git a/include/seafile/fs/setting.hpp b/include/seafile/fs/setting.hpp new file mode 100644 index 0000000..1a8fe40 --- /dev/null +++ b/include/seafile/fs/setting.hpp @@ -0,0 +1,23 @@ +// +// Created by Shell on 2026-07-30. +// + +#ifndef SEADISK_SETTING_HPP +#define SEADISK_SETTING_HPP +#include +#include + +namespace seafile::fs +{ + struct Setting final + { + std::string server; + std::string user; + std::string password; + std::string mount_point; + std::string mount_label; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Setting, server, user, password, mount_point, mount_label); + }; +} +#endif //SEADISK_SETTING_HPP diff --git a/include/util/icu_raii.hpp b/include/util/icu_raii.hpp index e2d53cb..386610e 100644 --- a/include/util/icu_raii.hpp +++ b/include/util/icu_raii.hpp @@ -4,6 +4,7 @@ #ifndef SEADISK_ICU_RAII_H #define SEADISK_ICU_RAII_H +#include #include struct UCharsetDetectorDeleter final diff --git a/include/util/string_util.hpp b/include/util/string_util.hpp index d22a41b..6bc8e29 100644 --- a/include/util/string_util.hpp +++ b/include/util/string_util.hpp @@ -20,8 +20,8 @@ namespace shell::util { static std::string utf16le_to_utf8(const std::wstring& input); static std::string utf16le_to_utf8(const wchar_t* input); - }; #endif + }; } #endif //SEADISK_STRING_UTIL_HPP \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f4848cc..e3a5ff7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include #include "seafile/fs/winfsp_adapter.hpp" +#include using namespace seafile::fs; @@ -9,7 +10,10 @@ auto wmain(int argc, wchar_t** argv) -> int if (!NT_SUCCESS(FspLoad(nullptr))) return ERROR_DELAY_LOAD_FAILED; + spdlog::set_level(spdlog::level::err); + FspDebugLogSetHandle(GetStdHandle(STD_ERROR_HANDLE)); - return FspServiceRun(const_cast(L"SeaDisk"), WinFSPAdapter::SvcStart, WinFSPAdapter::SvcStop, nullptr); + WCHAR serviceName[] = L"SeaDisk"; + return FspServiceRun(serviceName, WinFSPAdapter::SvcStart, WinFSPAdapter::SvcStop, nullptr); } diff --git a/src/seafile/fs/seafile_file_system.cpp b/src/seafile/fs/seafile_file_system.cpp index d3a9fef..457b55d 100644 --- a/src/seafile/fs/seafile_file_system.cpp +++ b/src/seafile/fs/seafile_file_system.cpp @@ -72,7 +72,26 @@ namespace seafile::fs } else { - *PFileAttributes = FILE_ATTRIBUTE_NORMAL; + const std::wstring win_path(FileName); + const auto path = normalize_path(shell::util::StringUtil::utf16le_to_utf8(win_path)); + const auto path_info = store->pathInfo(path); + if (!path_info) + { + return STATUS_OBJECT_NAME_NOT_FOUND; + } + if (const auto& info = path_info.value(); std::holds_alternative(info)) + { + *PFileAttributes = FILE_ATTRIBUTE_DIRECTORY; + }else + { + if (const auto& entry = std::get(info); entry.type == "dir") + { + *PFileAttributes = FILE_ATTRIBUTE_DIRECTORY; + }else + { + *PFileAttributes = FILE_ATTRIBUTE_NORMAL; + } + } } } @@ -118,7 +137,11 @@ namespace seafile::fs if (full_path == "/") { const auto ctx = new SeaFileContext{ - "", "/", "/", SeaFileContext::Type::DIRECTORY, 0 + .repo_id = "", + .repo_path = "/", + .full_path = "/", + .type = SeaFileContext::Type::DIRECTORY, + .size = 0 }; *PFileContext = ctx; SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, 0, 0); @@ -179,6 +202,7 @@ namespace seafile::fs *PBytesTransferred = 0; return STATUS_SUCCESS; } + spdlog::info(std::format("start read file {} bytes offset {} length {}", fc->repo_path, Offset, bytesToRead)); auto result = store->readFile(fc->repo_id, fc->repo_path, Offset, bytesToRead); if (result.has_value()) { @@ -276,10 +300,9 @@ namespace seafile::fs { const auto& entry = entries[i]; const auto entry_name = shell::util::StringUtil::utf8_to_utf16le(entry.name); - const auto attrs = (entry.type == "dir") ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_ARCHIVE; - if (!add_entry(entry_name, entry.size.value_or(0), attrs, entry.mtime)) + if (const auto attrs = (entry.type == "dir") ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_ARCHIVE; !add_entry(entry_name, entry.size.value_or(0), attrs, entry.mtime)) { - break; + return STATUS_SUCCESS; } } } diff --git a/src/seafile/fs/seafile_store.cpp b/src/seafile/fs/seafile_store.cpp index 20510bf..9fc3fbd 100644 --- a/src/seafile/fs/seafile_store.cpp +++ b/src/seafile/fs/seafile_store.cpp @@ -13,30 +13,38 @@ #include "util/application_path.hpp" #include "util/string_util.hpp" -namespace seafile::fs { +namespace seafile::fs +{ using RepoEntry = storage::RepoEntry; using AccountInfo = api::AccountInfo; using RepoInfo = storage::RepoInfo; - namespace { - std::string normalizePath(std::string path) { + namespace + { + std::string normalizePath(std::string path) + { std::ranges::replace(path, '\\', '/'); - while (path.size() > 1 && path.back() == '/') { + while (path.size() > 1 && path.back() == '/') + { path.pop_back(); } return path.empty() ? "/" : path; } - std::vector splitPath(const std::string &path) { + std::vector splitPath(const std::string& path) + { auto parts = shell::util::StringUtil::split(path, "/"); std::erase(parts, ""); return parts; } - std::string joinPath(const std::vector &parts, const size_t start) { + std::string joinPath(const std::vector& parts, const size_t start) + { std::string result; - for (size_t i = start; i < parts.size(); ++i) { - if (!result.empty()) { + for (size_t i = start; i < parts.size(); ++i) + { + if (!result.empty()) + { result += '/'; } result += parts[i]; @@ -44,18 +52,22 @@ namespace seafile::fs { return result.empty() ? "/" : "/" + result; } - std::string parentPath(const std::string &path) { - if (path == "/") { + std::string parentPath(const std::string& path) + { + if (path == "/") + { return "/"; } const auto pos = path.find_last_of('/'); - if (pos == std::string::npos || pos == 0) { + if (pos == std::string::npos || pos == 0) + { return "/"; } return path.substr(0, pos); } - auto make_storage(const std::string &path) { + auto make_storage(const std::string& path) + { // clang-format off return sqlite_orm::make_storage(path, sqlite_orm::make_table("account_info", @@ -93,6 +105,7 @@ namespace seafile::fs { sqlite_orm::make_column("magic", &RepoInfo::magic), sqlite_orm::make_column("random_key", &RepoInfo::random_key) ), + sqlite_orm::make_index("idx_repo_entry_path", &RepoEntry::path), sqlite_orm::make_table("repo_entry", sqlite_orm::make_column("repo_id", &RepoEntry::repo_id), sqlite_orm::make_column("type", &RepoEntry::type), @@ -117,26 +130,33 @@ namespace seafile::fs { } } - struct SeafileStore::Database { + struct SeafileStore::Database + { using Storage = decltype(make_storage("")); Storage storage; - std::recursive_mutex mutex; + std::recursive_mutex mutex; - explicit Database(const std::string &path) - : storage(make_storage(path)) { + explicit Database(const std::string& path) + : storage(make_storage(path)) + { + storage.pragma.journal_mode(sqlite_orm::journal_mode::WAL); + storage.pragma.synchronous(1); } }; - tl::expected SeafileStore::auth() { + tl::expected SeafileStore::auth() + { const auto auth_info = this->api.auth(this->username, this->password); - if (auth_info) { + if (auth_info) + { return {}; } return tl::make_unexpected(api::SeafileException(auth_info.error())); } - SeafileStore::SeafileStore(const std::string &db_path, const std::string &server, const std::string &user, - const std::string &password) : api(server) { + SeafileStore::SeafileStore(const std::string& db_path, const std::string& server, const std::string& user, + const std::string& password) : api(server) + { this->database = std::make_unique(db_path); this->username = user; this->password = password; @@ -144,10 +164,10 @@ namespace seafile::fs { } tl::expected SeafileStore::findRepoEntry( - const std::string &repo_id, const std::string &path) { - + const std::string& repo_id, const std::string& path) + { std::lock_guard lock(database->mutex); - auto &storage = this->database->storage; + auto& storage = this->database->storage; const auto entries = storage.get_all( sqlite_orm::where( sqlite_orm::and_( @@ -155,89 +175,109 @@ namespace seafile::fs { sqlite_orm::c(&RepoEntry::path) == path ) )); - if (!entries.empty()) { + if (!entries.empty()) + { return entries.at(0); } return tl::make_unexpected(api::SeafileException("entry not exists")); } - tl::expected SeafileStore::defaultRepoId() const { - const auto repo = this->api.defaultRepo(); - if (repo) { - if (repo->exists) { - return repo->repo_id.value(); - } - return tl::make_unexpected(api::SeafileException("No default repo")); - } - return tl::make_unexpected(api::SeafileException(repo.error())); - } - tl::expected SeafileStore::checkIsUpdated(const std::string &repo_id) { + tl::expected SeafileStore::checkIsUpdated(const std::string& repo_id) + { std::lock_guard lock(database->mutex); - auto &storage = this->database->storage; + auto& storage = this->database->storage; const auto repos = storage.get_all( sqlite_orm::where( sqlite_orm::c(&RepoInfo::id) == repo_id )); - if (repos.empty()) { + if (repos.empty()) + { return false; } const auto& repo = repos.at(0); - const auto &commit_id = repo.head_commit_id; + const auto& commit_id = repo.head_commit_id; const auto repo_info = this->api.repoInfo(repo_id); - if (repo_info) { + if (repo_info) + { return repo_info.value().head_commit_id == commit_id; } return tl::make_unexpected(repo_info.error()); } - tl::expected SeafileStore::checkAndSync(const std::string &repo_id) { - if (!this->checkIsUpdated(repo_id)) { - - try { + tl::expected SeafileStore::checkAndSync(const std::string& repo_id) + { + const auto is_updated = this->checkIsUpdated(repo_id); + if (!is_updated) + { + return tl::make_unexpected(is_updated.error()); + } + if (!is_updated.value()) + { + try + { { std::lock_guard lock(database->mutex); - auto &storage = this->database->storage; + auto& storage = this->database->storage; storage.remove_all(sqlite_orm::where( sqlite_orm::c(&RepoEntry::repo_id) == repo_id ) ); } const auto repo_entries = this->api.repoDir(repo_id, "/"); - if (!repo_entries) { + if (!repo_entries) + { return tl::make_unexpected(repo_entries.error()); } { std::lock_guard lock(database->mutex); - auto &storage = this->database->storage; - for (const auto &repo_entry: *repo_entries) { - storage.replace(RepoEntry::from_api_model(repo_id, "/", repo_entry)); - } + auto& storage = this->database->storage; + std::vector entries; + entries.reserve(repo_entries->size()); + std::transform(repo_entries->begin(), repo_entries->end(), std::back_inserter(entries), + [&repo_id](const auto& entry) + { + return RepoEntry::from_api_model(repo_id, "/", entry); + }); + storage.transaction([&entries,&storage] + { + storage.replace_range(entries.begin(), entries.end()); + return true; + }); } - } catch (std::exception &e) { + } + catch (std::exception& e) + { return tl::make_unexpected(api::SeafileException(e.what())); } } return {}; } - tl::expected SeafileStore::init() { - try { + tl::expected SeafileStore::init() + { + try + { std::lock_guard lock(database->mutex); this->database->storage.sync_schema(); - } catch (std::exception &e) { + } + catch (std::exception& e) + { return tl::make_unexpected(api::SeafileException(e.what())); } - if (const auto auth_token = this->auth(); !auth_token) { + if (const auto auth_token = this->auth(); !auth_token) + { return tl::make_unexpected(auth_token.error()); } return this->sync(); } - tl::expected SeafileStore::syncUserInfo() { + tl::expected SeafileStore::syncUserInfo() + { const auto user_info = this->api.accountInfo(); - if (!user_info) { + if (!user_info) + { return tl::make_unexpected(user_info.error()); } std::lock_guard lock(database->mutex); @@ -245,83 +285,129 @@ namespace seafile::fs { return {}; } - tl::expected, api::SeafileException> SeafileStore::syncRepos() { + tl::expected, api::SeafileException> SeafileStore::syncRepos() + { const auto repos = this->api.repos("mine", ""); - if (!repos) { + if (!repos) + { return tl::make_unexpected(repos.error()); } std::lock_guard lock(database->mutex); - auto &storage = this->database->storage; + auto& storage = this->database->storage; std::vector result; result.reserve(repos->size()); - for (const auto &repo: *repos) { + for (const auto& repo : *repos) + { const auto storage_repo = RepoInfo::from_api_model(repo); result.push_back(storage_repo); - try { + try + { const auto existings = storage.get_all( sqlite_orm::where(sqlite_orm::c(&RepoInfo::id) == storage_repo.id)); std::optional existing = std::nullopt; - if (!existings.empty()) { + if (!existings.empty()) + { existing = existings.at(0); } - if (existing && existing->head_commit_id != storage_repo.head_commit_id) { + if (existing && existing->head_commit_id != storage_repo.head_commit_id) + { storage.remove_all(sqlite_orm::where( sqlite_orm::c(&RepoEntry::repo_id) == storage_repo.id)); } storage.replace(storage_repo); - } catch (std::exception &e) { + } + catch (std::exception& e) + { return tl::make_unexpected(api::SeafileException(e.what())); } } return result; } - tl::expected SeafileStore::findRepoByName(const std::string &name) { - std::lock_guard lock(database->mutex); - auto &storage = this->database->storage; - try { - auto repos = storage.get_all( - sqlite_orm::where(sqlite_orm::c(&RepoInfo::name) == name)); - if (!repos.empty()) { - return repos.at(0); - } - if (const auto synced = this->syncRepos(); synced) { + tl::expected SeafileStore::findRepoByName(const std::string& name) + { + try + { + std::vector repos; + { + std::lock_guard lock(database->mutex); + auto& storage = this->database->storage; repos = storage.get_all( sqlite_orm::where(sqlite_orm::c(&RepoInfo::name) == name)); - if (!repos.empty()) { - return repos.at(0); - } - } else { - return tl::make_unexpected(synced.error()); } - }catch (const std::exception &e) { + if (!repos.empty()) + { + return repos.at(0); + } + // if (const auto synced = this->syncRepos(); synced) + // { + // std::lock_guard lock(database->mutex); + // auto& storage = this->database->storage; + // repos = storage.get_all( + // sqlite_orm::where(sqlite_orm::c(&RepoInfo::name) == name)); + // if (!repos.empty()) + // { + // return repos.at(0); + // } + // } + // else + // { + // return tl::make_unexpected(synced.error()); + // } + } + catch (const std::exception& e) + { return tl::make_unexpected(api::SeafileException(e.what())); } return tl::make_unexpected(api::SeafileException(std::format("repo not found: {}", name))); } tl::expected SeafileStore::fetchAndStoreRepoDir( - const std::string &repo_id, const std::string &parent_path) { + const std::string& repo_id, const std::string& parent_path) + { const auto entries = this->api.repoDir(repo_id, parent_path); - if (!entries) { + if (!entries) + { return tl::make_unexpected(entries.error()); } - std::lock_guard lock(database->mutex); - auto &storage = this->database->storage; - try { - for (const auto &entry: *entries) { - storage.replace(RepoEntry::from_api_model(repo_id, parent_path, entry)); - } - } catch (std::exception &e) { + try + { + std::vector repo_entries; + repo_entries.reserve(entries->size()); + std::transform(entries->begin(), entries->end(), std::back_inserter(repo_entries), + [&repo_id,&parent_path](const auto& entry) + { + return RepoEntry::from_api_model(repo_id, parent_path, entry); + }); + std::lock_guard lock(database->mutex); + auto& storage = this->database->storage; + constexpr std::size_t batch_size = 500; + storage.transaction([&] + { + for (auto it = repo_entries.begin(); it != repo_entries.end();) + { + auto end = it; + std::advance(end, std::min(batch_size,static_cast(std::distance(it, repo_entries.end())))); + + storage.replace_range(it, end); + + it = end; + } + return true; + }); + } + catch (std::exception& e) + { return tl::make_unexpected(api::SeafileException(e.what())); } return {}; } std::vector SeafileStore::queryDirectoryChildren( - const std::string &repo_id, const std::string &parent_path) { + const std::string& repo_id, const std::string& parent_path) + { std::lock_guard lock(database->mutex); - auto &storage = this->database->storage; + auto& storage = this->database->storage; const std::string prefix = (parent_path == "/") ? "" : parent_path; const auto all = storage.get_all( sqlite_orm::where( @@ -329,10 +415,12 @@ namespace seafile::fs { sqlite_orm::c(&RepoEntry::repo_id) == repo_id, sqlite_orm::like(&RepoEntry::path, prefix + "/%") ) - )); + ),sqlite_orm::order_by(&RepoEntry::name)); std::vector children; - for (auto &entry: all) { - if (const auto rest = entry.path.substr(prefix.size() + 1); rest.find('/') == std::string::npos) { + for (auto& entry : all) + { + if (const auto rest = entry.path.substr(prefix.size() + 1); rest.find('/') == std::string::npos) + { children.push_back(entry); } } @@ -340,99 +428,122 @@ namespace seafile::fs { } tl::expected, api::SeafileException> SeafileStore::pathInfo( - const std::string &path) { + const std::string& path) + { const auto normalized = normalizePath(path); const auto parts = splitPath(normalized); - if (parts.empty()) { + if (parts.empty()) + { return tl::make_unexpected(api::SeafileException("pathInfo does not support root path")); } - const auto &repo_name = parts[0]; + const auto& repo_name = parts[0]; const auto repo = findRepoByName(repo_name); - if (!repo) { + if (!repo) + { return tl::make_unexpected(repo.error()); } - if (parts.size() == 1) { + if (parts.size() == 1) + { return std::variant(*repo); } const auto inner_path = joinPath(parts, 1); auto entry = findRepoEntry(repo->id, inner_path); - if (entry) { + if (entry) + { return std::variant(*entry); } const auto parent_path = parentPath(inner_path); - if (const auto res = fetchAndStoreRepoDir(repo->id, parent_path); !res) { + if (const auto res = fetchAndStoreRepoDir(repo->id, parent_path); !res) + { return tl::make_unexpected(res.error()); } entry = findRepoEntry(repo->id, inner_path); - if (entry) { + if (entry) + { return std::variant(*entry); } return tl::make_unexpected(api::SeafileException(std::format("path not found: {}", path))); } - tl::expected, std::vector >, api::SeafileException> - SeafileStore::listDir(const std::string &path) { + tl::expected, std::vector>, api::SeafileException> + SeafileStore::listDir(const std::string& path) + { const auto normalized = normalizePath(path); const auto parts = splitPath(normalized); - std::lock_guard lock(database->mutex); - auto &storage = this->database->storage; - if (parts.empty()) { - auto repos = storage.get_all(); - if (repos.empty()) { - if (const auto synced = this->syncRepos(); synced) { + if (parts.empty()) + { + std::lock_guard lock(database->mutex); + auto& storage = this->database->storage; + auto repos = storage.get_all(sqlite_orm::order_by(&RepoInfo::id)); + if (repos.empty()) + { + if (const auto synced = this->syncRepos(); synced) + { repos = storage.get_all(); - } else { + } + else + { return tl::make_unexpected(synced.error()); } } - return std::variant, std::vector >(std::move(repos)); + return std::variant, std::vector>(std::move(repos)); } - const auto &repo_name = parts[0]; + const auto& repo_name = parts[0]; const auto repo = findRepoByName(repo_name); - if (!repo) { + if (!repo) + { return tl::make_unexpected(repo.error()); } const auto inner_path = joinPath(parts, 1); auto children = queryDirectoryChildren(repo->id, inner_path); - if (children.empty()) { - if (const auto res = fetchAndStoreRepoDir(repo->id, inner_path); !res) { + if (children.empty()) + { + if (const auto res = fetchAndStoreRepoDir(repo->id, inner_path); !res) + { return tl::make_unexpected(res.error()); } children = queryDirectoryChildren(repo->id, inner_path); } - return std::variant, std::vector >(std::move(children)); + return std::variant, std::vector>(std::move(children)); } tl::expected SeafileStore::readFile( - const std::string& repo_id, const std::string& path, const size_t offset, const size_t length) const { - if (length == 0) { + const std::string& repo_id, const std::string& path, const size_t offset, const size_t length) const + { + if (length == 0) + { return std::string{}; } return this->api.downloadFile(repo_id, path, offset, offset + length - 1); } - tl::expected SeafileStore::accountInfo() const { + tl::expected SeafileStore::accountInfo() const + { std::lock_guard lock(database->mutex); auto& storage = this->database->storage; - if (const auto infos = storage.get_all(); !infos.empty()) { + if (const auto infos = storage.get_all(); !infos.empty()) + { return infos.front(); } return tl::make_unexpected(api::SeafileException("account info not synced")); } - tl::expected SeafileStore::sync() { - if (const auto sync_account = this->syncUserInfo(); !sync_account) { + tl::expected SeafileStore::sync() + { + if (const auto sync_account = this->syncUserInfo(); !sync_account) + { return tl::make_unexpected(sync_account.error()); } - if (const auto sync_repos = syncRepos(); !sync_repos) { + if (const auto sync_repos = syncRepos(); !sync_repos) + { return tl::make_unexpected(sync_repos.error()); } return {}; @@ -440,7 +551,8 @@ namespace seafile::fs { SeafileStore::~SeafileStore() = default; - std::filesystem::path SeafileStore::storePath() { + std::filesystem::path SeafileStore::storePath() + { return shell::util::ApplicationPath::get().parent_path() / "seafile.sqlite"; } } diff --git a/src/seafile/fs/winfsp_adapter.cpp b/src/seafile/fs/winfsp_adapter.cpp index dfb2966..be42179 100644 --- a/src/seafile/fs/winfsp_adapter.cpp +++ b/src/seafile/fs/winfsp_adapter.cpp @@ -4,14 +4,36 @@ // // Created by Shell on 2026/6/20. // +#include #include #include #include +#include "seafile/fs/setting.hpp" +#include "util/application_path.hpp" +#include "util/string_util.hpp" + namespace seafile::fs { namespace { + tl::expected readSetting() + { + try + { + const auto path = shell::util::ApplicationPath::get().parent_path() / "seafile.json"; + const auto size = std::filesystem::file_size(path); + std::string data(size, '\0'); + std::ifstream file(path, std::ios::binary); + file.read(data.data(), size); + return nlohmann::json::parse(data).get(); + } + catch (const std::exception& ex) + { + return tl::make_unexpected(ex); + } + } + FSP_FILE_SYSTEM_INTERFACE FsInterface = { .GetVolumeInfo = WinFSPAdapter::GetVolumeInfo, .SetVolumeLabel = WinFSPAdapter::SetVolumeLabel_, @@ -29,6 +51,7 @@ namespace seafile::fs .GetDirInfoByName = WinFSPAdapter::GetDirInfoByName }; std::unordered_map fstab; + std::shared_mutex fstab_mutex; } NTSTATUS WinFSPAdapter::SvcStart(FSP_SERVICE* Service, ULONG argc, PWSTR* argv) @@ -39,7 +62,14 @@ namespace seafile::fs GetSystemTimeAsFileTime(&CreationTime); - std::memset(&VolumeParams, 0, sizeof(VolumeParams)); + const auto setting = readSetting(); + if (!setting.has_value()) + { + spdlog::error(std::format("read setting failed: {}", setting.error().what())); + return STATUS_UNSUCCESSFUL; + } + + VolumeParams.SectorSize = 4096; VolumeParams.SectorsPerAllocationUnit = 1; VolumeParams.MaxComponentLength = 255; @@ -55,8 +85,9 @@ namespace seafile::fs VolumeParams.ReadOnlyVolume = 1; wcscpy_s(VolumeParams.FileSystemName, sizeof VolumeParams.FileSystemName / sizeof(WCHAR), L"SeafileFS"); + WCHAR deviceName[] = L"" FSP_FSCTL_DISK_DEVICE_NAME; NTSTATUS Result = FspFileSystemCreate( - const_cast(L"" FSP_FSCTL_DISK_DEVICE_NAME), + deviceName, &VolumeParams, &FsInterface, &FileSystem); @@ -65,12 +96,12 @@ namespace seafile::fs { return Result; } - auto mount_point = std::wstring(L"X:"); + auto mount_point = std::wstring(shell::util::StringUtil::utf8_to_utf16le(setting.value().mount_point)); auto store = std::make_unique( SeafileStore::storePath().string(), - "https://pan.hanwangsoft.com", - "asypost@yeah.net", - "Shell0805@china"); + setting.value().server, + setting.value().user, + setting.value().password); if (const auto ret = store->init(); !ret.has_value()) { spdlog::error(std::format("store init failed: {}", ret.error().what())); @@ -78,17 +109,24 @@ namespace seafile::fs FspFileSystemDelete(FileSystem); return Result; } - auto seafile_file_system = new SeafileFileSystem(std::move(store), L"Seafile"); + auto seafile_file_system = new SeafileFileSystem(std::move(store), + shell::util::StringUtil::utf8_to_utf16le( + setting.value().mount_label)); FileSystem->UserContext = seafile_file_system; - fstab.insert({FileSystem, seafile_file_system}); + { + std::lock_guard lock(fstab_mutex); + fstab.insert({FileSystem, seafile_file_system}); + } Result = FspFileSystemSetMountPoint(FileSystem, mount_point.data()); if (!NT_SUCCESS(Result)) { + { + std::lock_guard lock(fstab_mutex); + fstab.erase(FileSystem); + } FspFileSystemDelete(FileSystem); - fstab.erase(FileSystem); - delete seafile_file_system; return Result; } @@ -97,8 +135,11 @@ namespace seafile::fs if (!NT_SUCCESS(Result)) { + { + std::lock_guard lock(fstab_mutex); + fstab.erase(FileSystem); + } FspFileSystemDelete(FileSystem); - fstab.erase(FileSystem); delete seafile_file_system; return Result; @@ -112,21 +153,24 @@ namespace seafile::fs NTSTATUS WinFSPAdapter::SvcStop(FSP_SERVICE* Service) { - for (auto& [fileSystem, seafileFs] : fstab) + decltype(fstab) snapshot; + { + std::lock_guard lock(fstab_mutex); + snapshot = std::move(fstab); + } + for (auto& [fileSystem, seafileFs] : snapshot) { FspFileSystemStopDispatcher(fileSystem); - fileSystem->UserContext = nullptr; FspFileSystemDelete(fileSystem); delete seafileFs; } - - fstab.clear(); return STATUS_SUCCESS; } NTSTATUS WinFSPAdapter::GetVolumeInfo(FSP_FILE_SYSTEM* FileSystem, FSP_FSCTL_VOLUME_INFO* VolumeInfo) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->GetVolumeInfo(VolumeInfo); } @@ -135,12 +179,14 @@ namespace seafile::fs FSP_FSCTL_FILE_INFO* FileInfo) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->Overwrite(FileContext, FileAttributes, ReplaceFileAttributes, AllocationSize, FileInfo); } void WinFSPAdapter::Close(FSP_FILE_SYSTEM* FileSystem, PVOID FileContext) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return; fs->Close(FileContext); } @@ -149,14 +195,16 @@ namespace seafile::fs UINT64 AllocationSize, PVOID* PFileContext, FSP_FSCTL_FILE_INFO* FileInfo) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->Create(FileName, CreateOptions, - GrantedAccess, FileAttributes, SecurityDescriptor, - AllocationSize, PFileContext, FileInfo); + GrantedAccess, FileAttributes, SecurityDescriptor, + AllocationSize, PFileContext, FileInfo); } VOID WinFSPAdapter::Cleanup(FSP_FILE_SYSTEM* FileSystem, PVOID FileContext, PWSTR FileName, ULONG Flags) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return; fs->Cleanup(FileContext, FileName, Flags); } @@ -164,6 +212,7 @@ namespace seafile::fs PVOID Buffer, ULONG BufferLength, PULONG PBytesTransferred) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->ReadDirectory(FileContext0, Pattern, Marker, Buffer, BufferLength, PBytesTransferred); } @@ -172,6 +221,7 @@ namespace seafile::fs FSP_FSCTL_DIR_INFO* DirInfo) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->GetDirInfoByName(FileContext, FileName, DirInfo); } @@ -181,6 +231,7 @@ namespace seafile::fs SIZE_T* PSecurityDescriptorSize) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->GetSecurityByName(FileName, PFileAttributes, SecurityDescriptor, PSecurityDescriptorSize); } @@ -188,6 +239,7 @@ namespace seafile::fs FSP_FSCTL_VOLUME_INFO* VolumeInfo) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->SetVolumeLabel_(VolumeLabel, VolumeInfo); } @@ -195,6 +247,7 @@ namespace seafile::fs UINT32 GrantedAccess, PVOID* PFileContext, FSP_FSCTL_FILE_INFO* FileInfo) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->Open(FileName, CreateOptions, GrantedAccess, PFileContext, FileInfo); } @@ -202,6 +255,7 @@ namespace seafile::fs ULONG Length, PULONG PBytesTransferred) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->Read(FileContext, Buffer, Offset, Length, PBytesTransferred); } @@ -209,6 +263,7 @@ namespace seafile::fs FSP_FSCTL_FILE_INFO* FileInfo) { const auto fs = static_cast(FileSystem->UserContext); + if (!fs) return STATUS_INVALID_PARAMETER; return fs->GetFileInfo(Handle, FileInfo); } diff --git a/src/util/string_util.cpp b/src/util/string_util.cpp index 4b6cbf2..bdde9b4 100644 --- a/src/util/string_util.cpp +++ b/src/util/string_util.cpp @@ -103,7 +103,7 @@ namespace shell::util std::wstring StringUtil::utf8_to_utf16le(const std::string& input) { const icu::UnicodeString unicode = icu::UnicodeString::fromUTF8(input); - return std::wstring{unicode.getBuffer(), unicode.getBuffer() + unicode.length()}; + return std::wstring(unicode.getTerminatedBuffer()); } std::string StringUtil::utf16le_to_utf8(const std::wstring& input)