diff --git a/CMakeLists.txt b/CMakeLists.txt index 93160a8..012aea7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,11 +55,7 @@ add_executable(seadisk src/main.cpp src/seafile/fs/winfsp_adapter.cpp include/seafile/fs/winfsp_adapter.hpp src/util/string_util.cpp - include/seafile/cache/cache.hpp include/seafile/api/seafile_api_interface.hpp - include/seafile/api/seafile_api_interface.hpp - include/seafile/api/cached_seafile_api.hpp - include/seafile/fs/seafile_store.hpp include/seafile/fs/seafile_store.hpp src/seafile/fs/seafile_store.cpp include/seafile/fs/storage/repo_entry.hpp @@ -67,6 +63,8 @@ add_executable(seadisk src/main.cpp include/seafile/util/version.hpp src/seafile/util/version.cpp src/util/application_path.cpp + include/seafile/fs/storage/repo_info.hpp + src/seafile/fs/storage/repo_info.cpp ) target_compile_definitions(seadisk PRIVATE diff --git a/include/seafile/api/cached_seafile_api.hpp b/include/seafile/api/cached_seafile_api.hpp deleted file mode 100644 index eecedf8..0000000 --- a/include/seafile/api/cached_seafile_api.hpp +++ /dev/null @@ -1,142 +0,0 @@ -// -// Created by Shell on 2026-07-14. -// - -#ifndef SEADISK_CACHED_SEAFILE_API_HPP -#define SEADISK_CACHED_SEAFILE_API_HPP -#include "seafile_api_interface.hpp" -#include "seafile/cache/cache.hpp" - -namespace seafile::api { - class CachedSeafileApi final : public ISeaFileApi { - mutable cache::Cache file_info_cache; - mutable cache::Cache dir_info_cache; - mutable cache::Cache > repo_entries_cache; - mutable cache::Cache account_info_cache; - - std::shared_ptr underlayer_api; - std::chrono::seconds default_ttl = std::chrono::seconds(300); - - static std::string makeKey( - std::string_view prefix, - std::initializer_list args - ) { - std::string key(prefix); - - for (auto &arg: args) { - key += ':'; - key += arg; - } - - return key; - } - - public: - explicit CachedSeafileApi(std::shared_ptr api) : underlayer_api(std::move(api)) { - }; - - tl::expected - auth( - const std::string &username, - const std::string &password - ) override { - return underlayer_api->auth(username, password); - } - - - tl::expected - serverInfo() const override { - return underlayer_api->serverInfo(); - } - - - tl::expected - defaultRepo() const override { - return underlayer_api->defaultRepo(); - } - - - tl::expected - repoInfo(const std::string &repo_id) const override { - return underlayer_api->repoInfo(repo_id); - } - - - tl::expected, SeafileException> - repoDir(const std::string &repo_id, const std::string &path) const override { - const auto key = makeKey("repoDir", {repo_id, path}); - - if (const auto value = repo_entries_cache.get(key)) { - return *value; - } - - auto result = underlayer_api->repoDir(repo_id, path); - if (result) { - repo_entries_cache.put(key, result.value(), default_ttl); - } - return result; - } - - - tl::expected - accountInfo() const override { - return underlayer_api->accountInfo(); - } - - - tl::expected - fileDetail(const std::string &repo_id, const std::string &path) const override { - const auto key = makeKey("file", {repo_id, path}); - - if (const auto value = file_info_cache.get(key)) { - return *value; - } - - auto result = underlayer_api->fileDetail(repo_id, path); - - if (result) { - file_info_cache.put(key, result.value(), default_ttl); - } - - - return result; - } - - - tl::expected - downloadFileUrl(const std::string &repo_id, const std::string &path) const override { - return underlayer_api->downloadFileUrl(repo_id, path); - } - - - tl::expected - downloadFile(const std::string &repo_id, const std::string &path, std::optional start, - std::optional end) const override { - return underlayer_api->downloadFile(repo_id, path, start, end); - } - - - tl::expected - dirDetail(const std::string &repo_id, const std::string &path) const override { - const auto key = makeKey("dir", {repo_id, path}); - - if (auto value = dir_info_cache.get(key)) { - return *value; - } - - auto result = underlayer_api->dirDetail(repo_id, path); - - if (result) { - dir_info_cache.put(key, result.value(), default_ttl); - } - - return result; - } - - tl::expected, SeafileException> repos(const std::string &type, - const std::string &name_contains) const override { - return underlayer_api->repos(type, name_contains); - } - }; -} -#endif //SEADISK_CACHED_SEAFILE_API_HPP diff --git a/include/seafile/api/seafile_api.hpp b/include/seafile/api/seafile_api.hpp index 5183e59..0378e3b 100644 --- a/include/seafile/api/seafile_api.hpp +++ b/include/seafile/api/seafile_api.hpp @@ -268,7 +268,9 @@ namespace seafile::api { header.emplace("range", range); if (const auto &response = cpr::Get(url, header); response.error || (response.status_code != 200 && response.status_code != 206)) { - return tl::make_unexpected(SeafileException{std::format("HTTP Error:{}", response.status_code)}); + return tl::make_unexpected(SeafileException{ + std::format("Download bytes range {} failed, HTTP Error:{}", range, response.status_code) + }); } else { return response.text; } diff --git a/include/seafile/cache/cache.hpp b/include/seafile/cache/cache.hpp deleted file mode 100644 index 80d6564..0000000 --- a/include/seafile/cache/cache.hpp +++ /dev/null @@ -1,112 +0,0 @@ -// -// Created by Shell on 2026/7/13. -// - -#ifndef SEADISK_CACHE_HPP -#define SEADISK_CACHE_HPP -#include -#include -#include - -namespace seafile::cache { - template - class Cache final{ - public: - using Clock = std::chrono::steady_clock; - using Duration = Clock::duration; - - /// 设置缓存 - /// @param key 键 - /// @param value 值 - /// @param ttl 超时时间 - void put(const K &key, V value, const Duration ttl) { - std::unique_lock lock(mutex_); - - cache_[key] = Entry{ - std::move(value), - Clock::now() + ttl - }; - } - - /// 获取缓存值 - /// @param key 键 - /// @return 缓存值 - std::optional get(const K &key){ - // 第一阶段:读锁 - { - std::shared_lock lock(mutex_); - - auto it = cache_.find(key); - if (it == cache_.end()) - return std::nullopt; - - if (Clock::now() < it->second.expire_time) - return it->second.value; - } - - // 第二阶段:删除过期数据 - { - std::unique_lock lock(mutex_); - - auto it = cache_.find(key); - if (it != cache_.end() && - Clock::now() >= it->second.expire_time) - { - cache_.erase(it); - } - } - - return std::nullopt; - } - - /// 移除缓存 - /// @param key 键 - void remove(const K &key) { - std::unique_lock lock(mutex_); - cache_.erase(key); - } - - ///清空缓存 - void clear() { - std::unique_lock lock(mutex_); - cache_.clear(); - } - - ///清理过时的缓存 - void cleanup() { - auto now = Clock::now(); - - std::unique_lock lock(mutex_); - - for (auto it = cache_.begin(); it != cache_.end();) - { - if (it->second.expireTime <= now) - { - it = cache_.erase(it); - } - else - { - ++it; - } - } - } - - ///缓存大小 - size_t size() const { - std::shared_lock lock(mutex_); - return cache_.size(); - } - - private: - struct Entry final { - V value; - Clock::time_point expire_time; - }; - - mutable std::shared_mutex mutex_; - - std::unordered_map cache_; - }; -} - -#endif //SEADISK_CACHE_HPP diff --git a/include/seafile/fs/file_context.hpp b/include/seafile/fs/file_context.hpp index ef68991..4955add 100644 --- a/include/seafile/fs/file_context.hpp +++ b/include/seafile/fs/file_context.hpp @@ -15,9 +15,14 @@ namespace seafile::fs DIRECTORY, }; - + /// 仓库 id std::string repo_id; - std::string path; + + /// 仓库内绝对路径(如 /folder/file.txt) + std::string repo_path; + + /// 虚拟全路径(如 /repo-name/folder/file.txt) + std::string full_path; Type type; diff --git a/include/seafile/fs/seafile_file_system.hpp b/include/seafile/fs/seafile_file_system.hpp index e79cd93..daa271e 100644 --- a/include/seafile/fs/seafile_file_system.hpp +++ b/include/seafile/fs/seafile_file_system.hpp @@ -4,38 +4,34 @@ #ifndef SEADISK_SEAFILE_FILE_SYSTEM_HPP #define SEADISK_SEAFILE_FILE_SYSTEM_HPP +#include #include #include #include "seafile/api/seafile_api.hpp" -#include "seafile/api/token_type.hpp" -#include "seafile/api/cached_seafile_api.hpp" -#include "seafile/cache/cache.hpp" +#include "seafile/fs/seafile_store.hpp" +#include "seafile/fs/storage/repo_entry.hpp" +#include "seafile/fs/storage/repo_info.hpp" namespace seafile::fs { class SeafileFileSystem final { using SeafileException = api::SeafileException; - using TokenType = api::TokenType; - using ISeaFileApi = api::ISeaFileApi; - using RepoEntry = api::RepoEntry; + using RepoInfo = storage::RepoInfo; + using RepoEntry = storage::RepoEntry; - - std::string repo_id; - std::shared_ptr api; + std::unique_ptr store; std::wstring volume_label; public: - explicit SeafileFileSystem(const std::string& server, - const std::wstring& volume_label) : volume_label(volume_label) + explicit SeafileFileSystem(std::unique_ptr store, + const std::wstring& volume_label) + : store(std::move(store)), volume_label(volume_label) { - this->api = std::make_shared( - std::make_shared(server) - ); } - tl::expected initialize(const std::string& username, const std::string& password); + tl::expected initialize(); SeafileFileSystem(const SeafileFileSystem&) = delete; diff --git a/include/seafile/fs/seafile_store.hpp b/include/seafile/fs/seafile_store.hpp index 4b4342e..63847fd 100644 --- a/include/seafile/fs/seafile_store.hpp +++ b/include/seafile/fs/seafile_store.hpp @@ -8,6 +8,7 @@ #include #include "storage/repo_entry.hpp" +#include "storage/repo_info.hpp" namespace seafile::fs { @@ -40,20 +41,41 @@ namespace seafile::fs /// 检查是否需要更新元素据,如果需要同步则自动同步 tl::expected checkAndSync(const std::string& repo_id); - /// 同步元数据 - [[nodiscard]] tl::expected syncRepoEntry(const std::string& repo_id) const; + /// 返回路径的信息 + /// @param path 需要查询的路径 + /// @return 路径信息 + [[nodiscard]] tl::expected, api::SeafileException> pathInfo( + const std::string& path); + + /// 返回路径下面的子项目信息 + /// @param path 路径 + /// @return 子项目信息 + [[nodiscard]] tl::expected, std::vector >, + api::SeafileException> listDir(const std::string& path); + + /// 读取文件内容 + /// @param repo_id 仓库 id + /// @param path 仓库内绝对路径 + /// @param offset 起始偏移 + /// @param length 读取长度 + /// @return 文件内容 + [[nodiscard]] tl::expected readFile( + const std::string& repo_id, const std::string& path, size_t offset, size_t length) const; + + /// 获取已缓存的账号信息(用于卷大小) + /// @return 账号信息 + [[nodiscard]] tl::expected accountInfo() const; /// 同步用户信息 - tl::expected syncUserInfo(); + [[nodiscard]] tl::expected syncUserInfo(); /// 同步repo信息 - tl::expected syncRepos(); + [[nodiscard]] tl::expected, api::SeafileException> syncRepos(); /// 同步 - tl::expected sync(); - - static std::filesystem::path storePath() ; + [[nodiscard]] tl::expected sync(); + static std::filesystem::path storePath(); /// 获取路径信息 /// @param path 路径 @@ -62,6 +84,14 @@ namespace seafile::fs const std::string& repo_id, const std::string& path); private: + tl::expected findRepoByName(const std::string& name); + + tl::expected fetchAndStoreRepoDir( + const std::string& repo_id, const std::string& parent_path); + + std::vector queryDirectChildren( + const std::string& repo_id, const std::string& parent_path); + struct Database; std::unique_ptr database; std::string api_server; diff --git a/include/seafile/fs/storage/repo_entry.hpp b/include/seafile/fs/storage/repo_entry.hpp index a48033d..fc24c61 100644 --- a/include/seafile/fs/storage/repo_entry.hpp +++ b/include/seafile/fs/storage/repo_entry.hpp @@ -59,7 +59,7 @@ namespace seafile::fs::storage /// Whether the entry is starred by the current user. std::optional starred{}; - static auto from_api_model(const std::string& repo_id, const std::string& path, + static auto from_api_model(const std::string& repo_id, const std::string& parent_path, const api::RepoEntry& entry) -> RepoEntry; }; } diff --git a/include/seafile/fs/storage/repo_info.hpp b/include/seafile/fs/storage/repo_info.hpp new file mode 100644 index 0000000..3597dbe --- /dev/null +++ b/include/seafile/fs/storage/repo_info.hpp @@ -0,0 +1,73 @@ +// +// Created by Shell on 2026/7/30. +// + +#ifndef SEADISK_STORAGE_REPO_INFO_HPP +#define SEADISK_STORAGE_REPO_INFO_HPP +#include + +#include "seafile/api/repo_info.hpp" + +namespace seafile::fs::storage { + /** + *@brief Seafile repository metadata. + */ + struct RepoInfo final { + /// Object type, usually "repo". + std::string type; + + /// Repository UUID. + std::string id; + + /// Repository owner identifier. + std::string owner; + + /// Repository name. + std::string name; + + /// Last modification time (Unix timestamp, seconds). + std::int64_t mtime{}; + + /// Repository size in bytes. + std::uint64_t size{}; + + /// Indicates whether the repository is encrypted. + bool encrypted{}; + + /// Root directory object ID. + std::string root; + + /// Current user's permission (e.g. "r", "rw"). + std::string permission; + + /// Internal email address of the last modifier. + std::string modifier_email; + + /// Contact email address of the last modifier. + std::string modifier_contact_email; + + /// Display name of the last modifier. + std::string modifier_name; + + /// Total number of files in the repository. + std::uint64_t file_count{}; + + /// Head commit identifier. + std::string head_commit_id; + + /// Repository encryption version. + int enc_version{}; + + /// Encryption salt. + std::string salt; + + /// Encryption magic value. + std::string magic; + + /// Repository random key. + std::string random_key; + + static auto from_api_model(const api::RepoInfo& repo_info) -> RepoInfo; + }; +} +#endif //SEADISK_STORAGE_REPO_INFO_HPP diff --git a/src/main.cpp b/src/main.cpp index 419a8a9..f4848cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,5 @@ #include -#include "seafile/fs/seafile_store.hpp" #include "seafile/fs/winfsp_adapter.hpp" using namespace seafile::fs; @@ -11,12 +10,6 @@ auto wmain(int argc, wchar_t** argv) -> int return ERROR_DELAY_LOAD_FAILED; FspDebugLogSetHandle(GetStdHandle(STD_ERROR_HANDLE)); - SeafileStore store{SeafileStore::storePath().string(),"https://pan.hanwangsoft.com","asypost@yeah.net","Shell0805@china"}; - const auto result = store.init(); - if (!result) - { - std::wcout << result.error().what() << std::endl; - } return FspServiceRun(const_cast(L"SeaDisk"), WinFSPAdapter::SvcStart, WinFSPAdapter::SvcStop, nullptr); } diff --git a/src/seafile/fs/seafile_file_system.cpp b/src/seafile/fs/seafile_file_system.cpp index 911b36b..d3a9fef 100644 --- a/src/seafile/fs/seafile_file_system.cpp +++ b/src/seafile/fs/seafile_file_system.cpp @@ -17,7 +17,6 @@ namespace seafile::fs return UNIX_EPOCH_AS_FILETIME + unix_time * HUNDREDS_OF_NANOSECONDS_PER_SECOND; } - std::string normalize_path(const std::string& path) { std::string result = path; @@ -47,23 +46,13 @@ namespace seafile::fs } } - - tl::expected SeafileFileSystem::initialize(const std::string& username, - const std::string& password) + tl::expected SeafileFileSystem::initialize() { - const auto authToken = api->auth(username, password); - if (authToken.has_value()) + if (this->store) { - if (const auto repo = api->defaultRepo(); repo.has_value()) - { - this->repo_id = repo.value().repo_id.value_or(""); - return {}; - } - spdlog::error("no default repo found"); - return tl::make_unexpected(SeafileException("No default repo found")); + return this->store->init(); } - spdlog::error(std::format("user login failed:{}", authToken.error().what())); - return tl::make_unexpected(authToken.error()); + return tl::make_unexpected(SeafileException("store is null")); } void SeafileFileSystem::Close(PVOID FileContext) @@ -98,10 +87,9 @@ namespace seafile::fs return STATUS_MEDIA_WRITE_PROTECTED; } - NTSTATUS SeafileFileSystem::GetVolumeInfo(FSP_FSCTL_VOLUME_INFO* VolumeInfo) const { - if (const auto userInfo = api->accountInfo(); userInfo.has_value()) + if (const auto userInfo = store->accountInfo(); userInfo.has_value()) { const auto& info = userInfo.value(); VolumeInfo->FreeSize = info.total - info.usage; @@ -126,66 +114,82 @@ namespace seafile::fs NTSTATUS SeafileFileSystem::Open(PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, PVOID* PFileContext, FSP_FSCTL_FILE_INFO* FileInfo) { - const auto path = normalize_path(shell::util::StringUtil::utf16le_to_utf8(FileName)); - if (repo_id.empty()) + const auto full_path = normalize_path(shell::util::StringUtil::utf16le_to_utf8(FileName)); + if (full_path == "/") { - spdlog::error(std::format("can't open file: {}, repo id is empty", path)); + const auto ctx = new SeaFileContext{ + "", "/", "/", SeaFileContext::Type::DIRECTORY, 0 + }; + *PFileContext = ctx; + SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, 0, 0); + spdlog::info("open root directory success"); + return STATUS_SUCCESS; + } + const auto info = store->pathInfo(full_path); + if (!info.has_value()) + { + spdlog::error(std::format("can't open file: {}, {}", full_path, info.error().what())); return STATUS_OBJECT_NAME_NOT_FOUND; } - - if (path == "/") + if (std::holds_alternative(*info)) { - auto repo = api->repoInfo(repo_id); - if (!repo.has_value()) - return STATUS_OBJECT_NAME_NOT_FOUND; - const auto ctx = new SeaFileContext{repo->id, path, SeaFileContext::Type::DIRECTORY, 0}; + const auto& repo = std::get(*info); + const auto ctx = new SeaFileContext{ + repo.id, "/", full_path, SeaFileContext::Type::DIRECTORY, repo.size + }; *PFileContext = ctx; - SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, repo->size, repo->mtime); - spdlog::info(std::format("open directory {} success", path)); + SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, repo.size, repo.mtime); + spdlog::info(std::format("open repo root {} success", full_path)); return STATUS_SUCCESS; } - if (const auto dirInfo = api->dirDetail(repo_id, path); dirInfo.has_value()) + const auto& entry = std::get(*info); + if (entry.type == "dir") { - const auto& info = dirInfo.value(); - const auto ctx = new SeaFileContext{info.repo_id, info.path, SeaFileContext::Type::DIRECTORY, 0}; + const auto ctx = new SeaFileContext{ + entry.repo_id, entry.path, full_path, SeaFileContext::Type::DIRECTORY, 0 + }; *PFileContext = ctx; - SetFileInfo(FileInfo,FILE_ATTRIBUTE_DIRECTORY, 0, 0); - spdlog::info(std::format("open directory {} success", path)); - return STATUS_SUCCESS; + SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, 0, entry.mtime); + spdlog::info(std::format("open directory {} success", full_path)); } - - - if (const auto fileInfo = api->fileDetail(repo_id, path); fileInfo.has_value()) + else { - const auto& info = fileInfo.value(); - const auto ctx = new SeaFileContext(repo_id, path, SeaFileContext::Type::FILE, info.size); + const auto ctx = new SeaFileContext{ + entry.repo_id, entry.path, full_path, SeaFileContext::Type::FILE, entry.size.value_or(0) + }; *PFileContext = ctx; - SetFileInfo(FileInfo,FILE_ATTRIBUTE_ARCHIVE, info.size, 0); - spdlog::info(std::format("open file {} success", path)); - return STATUS_SUCCESS; + SetFileInfo(FileInfo, FILE_ATTRIBUTE_ARCHIVE, entry.size.value_or(0), entry.mtime); + spdlog::info(std::format("open file {} success", full_path)); } - - return STATUS_OBJECT_NAME_NOT_FOUND; + return STATUS_SUCCESS; } NTSTATUS SeafileFileSystem::Read(PVOID FileContext, PVOID Buffer, UINT64 Offset, ULONG Length, PULONG PBytesTransferred) { const auto fc = static_cast(FileContext); - auto result = api->downloadFile( - fc->repo_id, fc->path, - Offset, Offset + Length - 1); + if (Offset >= fc->size) { + *PBytesTransferred = 0; + return STATUS_SUCCESS; + } + auto bytesToRead = std::min(Length,static_cast(fc->size - Offset)); + if (bytesToRead == 0) { + *PBytesTransferred = 0; + return STATUS_SUCCESS; + } + auto result = store->readFile(fc->repo_id, fc->repo_path, Offset, bytesToRead); if (result.has_value()) { const auto& data = result.value(); - const auto copy_len = (std::min)(data.size(), static_cast(Length)); + const auto copy_len = (std::min)(data.size(), static_cast(bytesToRead)); memcpy(Buffer, data.data(), copy_len); *PBytesTransferred = static_cast(copy_len); - spdlog::info(std::format("read file {} bytes offset {} length {} success", fc->path, Offset, Length)); + spdlog::info(std::format("read file {} bytes offset {} length {} success", fc->repo_path, Offset, bytesToRead)); return STATUS_SUCCESS; } + spdlog::error(std::format("read file {} failed: {}", fc->repo_path, result.error().what())); return STATUS_UNSUCCESSFUL; } @@ -195,9 +199,66 @@ namespace seafile::fs const auto fc = static_cast(FileContext); *PBytesTransferred = 0; - if (auto entries_result = api->repoDir(fc->repo_id, fc->path); entries_result.has_value()) + const auto result = store->listDir(fc->full_path); + if (!result.has_value()) { - const auto& entries = entries_result.value(); + spdlog::error(std::format("read directory {} failed: {}", fc->full_path, result.error().what())); + return STATUS_UNSUCCESSFUL; + } + + auto add_entry = [&](const std::wstring& name, uint64_t size, uint32_t attrs, int64_t mtime) + { + const auto entry_size = sizeof(FSP_FSCTL_DIR_INFO) + ((name.size() + 1) * sizeof(WCHAR)); + const auto dir_info = static_cast(std::malloc(entry_size)); + std::memset(dir_info, 0, entry_size); + dir_info->Size = entry_size; + SetFileInfo(&dir_info->FileInfo, attrs, size, mtime); + wcscpy_s(dir_info->FileNameBuf, name.size() + 1, name.data()); + if (*PBytesTransferred + entry_size < BufferLength) + { + if (!FspFileSystemAddDirInfo(dir_info, Buffer, BufferLength, PBytesTransferred)) + { + std::free(dir_info); + return false; + } + } + else + { + std::free(dir_info); + return false; + } + std::free(dir_info); + return true; + }; + + if (std::holds_alternative>(*result)) + { + const auto& repos = std::get>(*result); + size_t start = 0; + if (Marker) + { + const auto marker = shell::util::StringUtil::utf16le_to_utf8(Marker); + for (auto i = 0; i < repos.size(); ++i) + { + if (repos[i].name == marker) + { + start = i + 1; + break; + } + } + } + for (auto i = start; i < repos.size(); ++i) + { + const auto& repo = repos[i]; + if (const auto repo_name = shell::util::StringUtil::utf8_to_utf16le(repo.name); !add_entry(repo_name, repo.size, FILE_ATTRIBUTE_DIRECTORY, repo.mtime)) + { + break; + } + } + } + else + { + const auto& entries = std::get>(*result); size_t start = 0; if (Marker) { @@ -215,66 +276,44 @@ namespace seafile::fs { const auto& entry = entries[i]; const auto entry_name = shell::util::StringUtil::utf8_to_utf16le(entry.name); - const auto size = sizeof(FSP_FSCTL_DIR_INFO) + ((entry_name.size() + 1) * sizeof(WCHAR)); - - const auto dir_info = static_cast(std::malloc(size)); - std::memset(dir_info, 0, size); - dir_info->Size = size; - dir_info->FileInfo.FileSize = entry.size.value_or(0); - if (entry.type == "file") - { - dir_info->FileInfo.FileAttributes = FILE_ATTRIBUTE_ARCHIVE; - } - else - { - dir_info->FileInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY; - } - wcscpy_s(dir_info->FileNameBuf, entry_name.size() + 1, entry_name.data()); - if (*PBytesTransferred + size < BufferLength) - { - if (!FspFileSystemAddDirInfo(dir_info, Buffer, BufferLength, PBytesTransferred)) - { - break; - } - } - else + 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)) { break; } } - - - return STATUS_SUCCESS; - } - else - { - spdlog::error(std::format("read directory {} failed: {}", fc->path, entries_result.error().what())); } - return STATUS_UNSUCCESSFUL; + FspFileSystemAddDirInfo(nullptr, Buffer, BufferLength, PBytesTransferred); + return STATUS_SUCCESS; } NTSTATUS SeafileFileSystem::GetFileInfo(HANDLE Handle, FSP_FSCTL_FILE_INFO* FileInfo) { - auto fc = static_cast(Handle); - if (fc->repo_id.empty() || fc->path == "/" || fc->path.back() == '/') + const auto fc = static_cast(Handle); + if (fc->full_path == "/") { - if (fc->repo_id.empty()) - { - SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, 0, 0); - return STATUS_SUCCESS; - } - auto repo = api->repoInfo(fc->repo_id); - if (!repo.has_value()) - return STATUS_UNSUCCESSFUL; - SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, repo->size, repo->mtime); + SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, 0, 0); return STATUS_SUCCESS; } - auto fdetail = api->fileDetail(fc->repo_id, fc->path); - if (!fdetail.has_value()) + const auto info = store->pathInfo(fc->full_path); + if (!info.has_value()) + { + spdlog::error(std::format("get file info {} failed: {}", fc->full_path, info.error().what())); return STATUS_UNSUCCESSFUL; - SetFileInfo(FileInfo, FILE_ATTRIBUTE_NORMAL, fdetail->size, fdetail->mtime); - spdlog::info(std::format("get file {} info success", fc->path)); + } + + if (std::holds_alternative(*info)) + { + const auto& repo = std::get(*info); + SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, repo.size, repo.mtime); + return STATUS_SUCCESS; + } + + const auto& entry = std::get(*info); + const auto attrs = (entry.type == "dir") ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_ARCHIVE; + SetFileInfo(FileInfo, attrs, entry.size.value_or(0), entry.mtime); + spdlog::info(std::format("get file {} info success", fc->full_path)); return STATUS_SUCCESS; } @@ -288,7 +327,7 @@ namespace seafile::fs NTSTATUS SeafileFileSystem::GetDirInfoByName(PVOID FileContext, PWSTR FileName, FSP_FSCTL_DIR_INFO* DirInfo) { - auto fc = static_cast(FileContext); + const auto fc = static_cast(FileContext); std::wstring name(FileName); if (name == L"." || name == L"..") @@ -300,33 +339,41 @@ namespace seafile::fs return STATUS_SUCCESS; } - std::string utf8_name; - for (const auto c : name) - utf8_name += static_cast(c); + const std::string utf8_name = shell::util::StringUtil::utf16le_to_utf8(name); - if (fc->repo_id.empty()) + std::string child_full_path; + if (fc->full_path == "/") { - return STATUS_OBJECT_NAME_NOT_FOUND; + child_full_path = "/" + utf8_name; + } + else + { + child_full_path = fc->full_path + "/" + utf8_name; } - auto entries = api->repoDir(fc->repo_id, fc->path); - if (!entries.has_value()) + const auto info = store->pathInfo(child_full_path); + if (!info.has_value()) return STATUS_OBJECT_NAME_NOT_FOUND; - for (const auto& entry : entries.value()) + if (std::holds_alternative(*info)) { - if (entry.name != utf8_name) - continue; + const auto& repo = std::get(*info); memset(DirInfo, 0, sizeof(FSP_FSCTL_DIR_INFO)); const auto name_bytes = (name.size() + 1) * sizeof(WCHAR); DirInfo->Size = offsetof(FSP_FSCTL_DIR_INFO, FileNameBuf) + name_bytes; - const auto attrs = (entry.type == "dir") ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL; - SetFileInfo(&DirInfo->FileInfo, attrs, entry.size.value_or(0), entry.mtime); + SetFileInfo(&DirInfo->FileInfo, FILE_ATTRIBUTE_DIRECTORY, repo.size, repo.mtime); wcscpy_s(DirInfo->FileNameBuf, name.size() + 1, FileName); return STATUS_SUCCESS; } - return STATUS_OBJECT_NAME_NOT_FOUND; + const auto& entry = std::get(*info); + memset(DirInfo, 0, sizeof(FSP_FSCTL_DIR_INFO)); + const auto name_bytes = (name.size() + 1) * sizeof(WCHAR); + DirInfo->Size = offsetof(FSP_FSCTL_DIR_INFO, FileNameBuf) + name_bytes; + const auto attrs = (entry.type == "dir") ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL; + SetFileInfo(&DirInfo->FileInfo, attrs, entry.size.value_or(0), entry.mtime); + wcscpy_s(DirInfo->FileNameBuf, name.size() + 1, FileName); + return STATUS_SUCCESS; } NTSTATUS SeafileFileSystem::SetVolumeLabel_(PWSTR VolumeLabel, FSP_FSCTL_VOLUME_INFO* VolumeInfo) diff --git a/src/seafile/fs/seafile_store.cpp b/src/seafile/fs/seafile_store.cpp index 76aea14..14c949f 100644 --- a/src/seafile/fs/seafile_store.cpp +++ b/src/seafile/fs/seafile_store.cpp @@ -1,300 +1,439 @@ -#include "seafile/fs/seafile_store.hpp" - -#include -#include - -#include "seafile/api/account_info.hpp" -#include "seafile/api/repo_info.hpp" -#include "seafile/fs/storage/repo_entry.hpp" -#include -#include "util/application_path.hpp" - -// -// Created by Shell on 2026/7/16. -// -namespace seafile::fs -{ - using RepoEntry = storage::RepoEntry; - using AccountInfo = api::AccountInfo; - using RepoInfo = api::RepoInfo; - - namespace - { - auto make_storage(const std::string& path) - { - // clang-format off - return sqlite_orm::make_storage(path, - sqlite_orm::make_table("account_info", - sqlite_orm::make_column("space_usage", &AccountInfo::space_usage), - sqlite_orm::make_column("avatar_url", &AccountInfo::avatar_url), - sqlite_orm::make_column("email", &AccountInfo::email, sqlite_orm::primary_key()), - sqlite_orm::make_column("name", &AccountInfo::name), - sqlite_orm::make_column("total", &AccountInfo::total), - sqlite_orm::make_column("usage", &AccountInfo::usage), - sqlite_orm::make_column("login_id", &AccountInfo::login_id), - sqlite_orm::make_column("department", &AccountInfo::department), - sqlite_orm::make_column("contact_email", &AccountInfo::contact_email), - sqlite_orm::make_column("institution", &AccountInfo::institution), - sqlite_orm::make_column("is_staff", &AccountInfo::is_staff), - sqlite_orm::make_column("file_updates_email_interval", &AccountInfo::file_updates_email_interval), - sqlite_orm::make_column("collaborate_email_interval",&AccountInfo::collaborate_email_interval) - ), - sqlite_orm::make_table("repo_info", - sqlite_orm::make_column("type",&RepoInfo::type), - sqlite_orm::make_column("id",&RepoInfo::id, sqlite_orm::primary_key()), - sqlite_orm::make_column("owner",&RepoInfo::owner), - sqlite_orm::make_column("name",&RepoInfo::name), - sqlite_orm::make_column("mtime",&RepoInfo::mtime), - sqlite_orm::make_column("size",&RepoInfo::size), - sqlite_orm::make_column("encrypted",&RepoInfo::encrypted), - sqlite_orm::make_column("root",&RepoInfo::root), - sqlite_orm::make_column("permission",&RepoInfo::permission), - sqlite_orm::make_column("modifier_email",&RepoInfo::modifier_email), - sqlite_orm::make_column("modifier_contact_email",&RepoInfo::modifier_contact_email), - sqlite_orm::make_column("modifier_name",&RepoInfo::modifier_name), - sqlite_orm::make_column("file_count",&RepoInfo::file_count), - sqlite_orm::make_column("head_commit_id",&RepoInfo::head_commit_id), - sqlite_orm::make_column("enc_version",&RepoInfo::enc_version), - sqlite_orm::make_column("salt",&RepoInfo::salt), - sqlite_orm::make_column("magic",&RepoInfo::magic), - sqlite_orm::make_column("random_key",&RepoInfo::random_key) - ), - sqlite_orm::make_table("repo_entry", - sqlite_orm::make_column("type",&RepoEntry::type), - sqlite_orm::make_column("name",&RepoEntry::name), - sqlite_orm::make_column("path",&RepoEntry::path), - sqlite_orm::make_column("id", &RepoEntry::id, sqlite_orm::primary_key()), - sqlite_orm::make_column("mtime",&RepoEntry::mtime), - sqlite_orm::make_column("modifier_email",&RepoEntry::modifier_email), - sqlite_orm::make_column("size",&RepoEntry::size), - sqlite_orm::make_column("is_locked",&RepoEntry::is_locked), - sqlite_orm::make_column("lock_owner",&RepoEntry::lock_owner), - sqlite_orm::make_column("lock_time",&RepoEntry::lock_time), - sqlite_orm::make_column("locked_by_me",&RepoEntry::locked_by_me), - sqlite_orm::make_column("permission",&RepoEntry::permission), - sqlite_orm::make_column("modifier_contact_email",&RepoEntry::modifier_contact_email), - sqlite_orm::make_column("modifier_name",&RepoEntry::modifier_name), - sqlite_orm::make_column("starred",&RepoEntry::starred)) - ); - //clang-format on - } - } - - struct SeafileStore::Database - { - using Storage = decltype(make_storage("")); - Storage storage; - - explicit Database(const std::string& path) - : storage(make_storage(path)) - { - } - }; - - tl::expected SeafileStore::auth() - { - if (const auto auth_info = this->api.auth(this->username, this->password)) - { - return {}; - } - else - { - 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) - { - this->database = std::make_unique(db_path); - this->username = user; - this->password = password; - this->api_server = server; - } - - tl::expected SeafileStore::findRepoEntry( - const std::string& repo_id, const std::string& path) - { - auto& storage = this->database->storage; - const auto entry = storage.get_optional( - sqlite_orm::where( - sqlite_orm::and_( - sqlite_orm::c(&RepoEntry::path) == path, - sqlite_orm::c(&RepoEntry::repo_id) == repo_id - ) - )); - if (entry) - { - return *entry; - } - 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) - { - auto& storage = this->database->storage; - const auto repo = storage.get_optional( - sqlite_orm::where( - sqlite_orm::c(&RepoInfo::id) == repo_id - )); - if (!repo) - { - return false; - } - const auto& commit_id = repo.value().head_commit_id; - const auto repo_info = this->api.repoInfo(repo_id); - if (repo_info) - { - return repo_info.value().head_commit_id == commit_id; - } - - return tl::make_unexpected(repo_info.error()); - } - - tl::expected SeafileStore::syncRepoEntry(const std::string& repo_id) const - { - std::stack entry_stack; - std::string path = "/"; - std::vector data; - - entry_stack.push(RepoEntry{.repo_id = repo_id, .path = path}); - - while (!entry_stack.empty()) - { - const auto parent = entry_stack.top(); - entry_stack.pop(); - path = parent.path; - auto entries = this->api.repoDir(repo_id, path); - if (!entries) - { - return tl::make_unexpected(entries.error()); - } - for (auto& entry : *entries) - { - const auto entry_info = RepoEntry::from_api_model(repo_id, std::format("{}/{}", path, entry.name), - entry); - if (entry_info.type == "dir") - { - entry_stack.push(entry_info); - } - data.push_back(entry_info); - } - } - try - { - this->database->storage.transaction([&] - { - for (const auto& entry : data) - { - this->database->storage.replace(entry); - } - return true; - }); - } - catch (std::exception& e) - { - return tl::make_unexpected(api::SeafileException(e.what())); - } - return {}; - } - - tl::expected SeafileStore::checkAndSync(const std::string& repo_id) - { - if (!this->checkIsUpdated(repo_id)) - { - return this->syncRepoEntry(repo_id); - } - return {}; - } - - tl::expected SeafileStore::init() - { - if (const auto& store_path = storePath(); !std::filesystem::exists(store_path)) - { - try - { - this->database->storage.sync_schema(); - } - catch (std::exception& e) - { - return tl::make_unexpected(api::SeafileException(e.what())); - } - } - const auto auth_token = this->auth(); - if (!auth_token) - { - return tl::make_unexpected(auth_token.error()); - } - return this->sync();; - } - - tl::expected SeafileStore::syncUserInfo() - { - const auto user_info = this->api.accountInfo(); - if (!user_info) - { - return tl::make_unexpected(user_info.error()); - } - this->database->storage.replace(*user_info); - return {}; - } - - tl::expected SeafileStore::syncRepos() - { - const auto repos = this->api.repos("mine", ""); - if (!repos) - { - return tl::make_unexpected(repos.error()); - } - for (const auto& repo : *repos) - { - try - { - this->database->storage.replace(repo); - } - catch (std::exception& e) - { - return tl::make_unexpected(api::SeafileException(e.what())); - } - if (const auto sync_repo = syncRepoEntry(repo.id); !sync_repo) - { - return tl::make_unexpected(sync_repo.error()); - } - } - return {}; - } - - 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) - { - return tl::make_unexpected(sync_repos.error()); - } - return {}; - } - - SeafileStore::~SeafileStore() = default; - - std::filesystem::path SeafileStore::storePath() - { - return shell::util::ApplicationPath::get().parent_path() / "seafile.sqlite"; - } -} +#include "seafile/fs/seafile_store.hpp" + +#include +#include +#include +#include +#include + +#include "seafile/api/account_info.hpp" +#include "seafile/api/repo_info.hpp" +#include "seafile/fs/storage/repo_entry.hpp" +#include "seafile/fs/storage/repo_info.hpp" +#include "util/application_path.hpp" +#include "util/string_util.hpp" + +namespace seafile::fs { + using RepoEntry = storage::RepoEntry; + using AccountInfo = api::AccountInfo; + using RepoInfo = storage::RepoInfo; + + namespace { + std::string normalizePath(std::string path) { + std::ranges::replace(path, '\\', '/'); + while (path.size() > 1 && path.back() == '/') { + path.pop_back(); + } + return path.empty() ? "/" : 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 result; + for (size_t i = start; i < parts.size(); ++i) { + if (!result.empty()) { + result += '/'; + } + result += parts[i]; + } + return result.empty() ? "/" : "/" + result; + } + + std::string parentPath(const std::string &path) { + if (path == "/") { + return "/"; + } + const auto pos = path.find_last_of('/'); + if (pos == std::string::npos || pos == 0) { + return "/"; + } + return path.substr(0, pos); + } + + auto make_storage(const std::string &path) { + // clang-format off + return sqlite_orm::make_storage(path, + sqlite_orm::make_table("account_info", + sqlite_orm::make_column("space_usage", &AccountInfo::space_usage), + sqlite_orm::make_column("avatar_url", &AccountInfo::avatar_url), + sqlite_orm::make_column("email", &AccountInfo::email, sqlite_orm::primary_key()), + sqlite_orm::make_column("name", &AccountInfo::name), + sqlite_orm::make_column("total", &AccountInfo::total), + sqlite_orm::make_column("usage", &AccountInfo::usage), + sqlite_orm::make_column("login_id", &AccountInfo::login_id), + sqlite_orm::make_column("department", &AccountInfo::department), + sqlite_orm::make_column("contact_email", &AccountInfo::contact_email), + sqlite_orm::make_column("institution", &AccountInfo::institution), + sqlite_orm::make_column("is_staff", &AccountInfo::is_staff), + sqlite_orm::make_column("file_updates_email_interval", &AccountInfo::file_updates_email_interval), + sqlite_orm::make_column("collaborate_email_interval", &AccountInfo::collaborate_email_interval) + ), + sqlite_orm::make_table("repo_info", + sqlite_orm::make_column("type", &RepoInfo::type), + sqlite_orm::make_column("id", &RepoInfo::id, sqlite_orm::primary_key()), + sqlite_orm::make_column("owner", &RepoInfo::owner), + sqlite_orm::make_column("name", &RepoInfo::name), + sqlite_orm::make_column("mtime", &RepoInfo::mtime), + sqlite_orm::make_column("size", &RepoInfo::size), + sqlite_orm::make_column("encrypted", &RepoInfo::encrypted), + sqlite_orm::make_column("root", &RepoInfo::root), + sqlite_orm::make_column("permission", &RepoInfo::permission), + sqlite_orm::make_column("modifier_email", &RepoInfo::modifier_email), + sqlite_orm::make_column("modifier_contact_email", &RepoInfo::modifier_contact_email), + sqlite_orm::make_column("modifier_name", &RepoInfo::modifier_name), + sqlite_orm::make_column("file_count", &RepoInfo::file_count), + sqlite_orm::make_column("head_commit_id", &RepoInfo::head_commit_id), + sqlite_orm::make_column("enc_version", &RepoInfo::enc_version), + sqlite_orm::make_column("salt", &RepoInfo::salt), + sqlite_orm::make_column("magic", &RepoInfo::magic), + sqlite_orm::make_column("random_key", &RepoInfo::random_key) + ), + sqlite_orm::make_table("repo_entry", + sqlite_orm::make_column("repo_id", &RepoEntry::repo_id), + sqlite_orm::make_column("type", &RepoEntry::type), + sqlite_orm::make_column("name", &RepoEntry::name), + sqlite_orm::make_column("path", &RepoEntry::path), + sqlite_orm::make_column("id", &RepoEntry::id), + sqlite_orm::make_column("mtime", &RepoEntry::mtime), + sqlite_orm::make_column("modifier_email", &RepoEntry::modifier_email), + sqlite_orm::make_column("size", &RepoEntry::size), + sqlite_orm::make_column("is_locked", &RepoEntry::is_locked), + sqlite_orm::make_column("lock_owner", &RepoEntry::lock_owner), + sqlite_orm::make_column("lock_time", &RepoEntry::lock_time), + sqlite_orm::make_column("locked_by_me", &RepoEntry::locked_by_me), + sqlite_orm::make_column("permission", &RepoEntry::permission), + sqlite_orm::make_column("modifier_contact_email", &RepoEntry::modifier_contact_email), + sqlite_orm::make_column("modifier_name", &RepoEntry::modifier_name), + sqlite_orm::make_column("starred", &RepoEntry::starred), + sqlite_orm::primary_key(&RepoEntry::repo_id, &RepoEntry::path) + ) + ); + //clang-format on + } + } + + struct SeafileStore::Database { + using Storage = decltype(make_storage("")); + Storage storage; + std::recursive_mutex mutex; + + explicit Database(const std::string &path) + : storage(make_storage(path)) { + } + }; + + tl::expected SeafileStore::auth() { + const auto auth_info = this->api.auth(this->username, this->password); + 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) { + this->database = std::make_unique(db_path); + this->username = user; + this->password = password; + this->api_server = server; + } + + tl::expected SeafileStore::findRepoEntry( + const std::string &repo_id, const std::string &path) { + + std::lock_guard lock(database->mutex); + auto &storage = this->database->storage; + const auto entries = storage.get_all( + sqlite_orm::where( + sqlite_orm::and_( + sqlite_orm::c(&RepoEntry::repo_id) == repo_id, + sqlite_orm::c(&RepoEntry::path) == path + ) + )); + 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) { + std::lock_guard lock(database->mutex); + auto &storage = this->database->storage; + const auto repos = storage.get_all( + sqlite_orm::where( + sqlite_orm::c(&RepoInfo::id) == repo_id + )); + if (repos.empty()) { + return false; + } + const auto& repo = repos.at(0); + const auto &commit_id = repo.head_commit_id; + const auto repo_info = this->api.repoInfo(repo_id); + 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)) { + std::lock_guard lock(database->mutex); + auto &storage = this->database->storage; + try { + storage.remove(sqlite_orm::where( + sqlite_orm::c(&RepoEntry::repo_id) == repo_id + ) + ); + const auto repo_entries = this->api.repoDir(repo_id, "/"); + if (!repo_entries) { + return tl::make_unexpected(repo_entries.error()); + } + for (const auto &repo_entry: *repo_entries) { + storage.replace(RepoEntry::from_api_model(repo_id, "/", repo_entry)); + } + } catch (std::exception &e) { + return tl::make_unexpected(api::SeafileException(e.what())); + } + } + return {}; + } + + tl::expected SeafileStore::init() { + try { + std::lock_guard lock(database->mutex); + this->database->storage.sync_schema(); + } catch (std::exception &e) { + return tl::make_unexpected(api::SeafileException(e.what())); + } + if (const auto auth_token = this->auth(); !auth_token) { + return tl::make_unexpected(auth_token.error()); + } + return this->sync(); + } + + tl::expected SeafileStore::syncUserInfo() { + const auto user_info = this->api.accountInfo(); + if (!user_info) { + return tl::make_unexpected(user_info.error()); + } + std::lock_guard lock(database->mutex); + this->database->storage.replace(*user_info); + return {}; + } + + tl::expected, api::SeafileException> SeafileStore::syncRepos() { + const auto repos = this->api.repos("mine", ""); + if (!repos) { + return tl::make_unexpected(repos.error()); + } + std::lock_guard lock(database->mutex); + auto &storage = this->database->storage; + std::vector result; + result.reserve(repos->size()); + for (const auto &repo: *repos) { + const auto storage_repo = RepoInfo::from_api_model(repo); + result.push_back(storage_repo); + 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()) { + existing = existings.at(0); + } + if (existing && existing->head_commit_id != storage_repo.head_commit_id) { + storage.remove(sqlite_orm::where( + sqlite_orm::c(&RepoEntry::repo_id) == storage_repo.id)); + } + storage.replace(storage_repo); + } 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) { + 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 auto entries = this->api.repoDir(repo_id, parent_path); + 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) { + return tl::make_unexpected(api::SeafileException(e.what())); + } + return {}; + } + + std::vector SeafileStore::queryDirectChildren( + const std::string &repo_id, const std::string &parent_path) { + std::lock_guard lock(database->mutex); + auto &storage = this->database->storage; + const std::string prefix = (parent_path == "/") ? "" : parent_path; + const auto all = storage.get_all( + sqlite_orm::where( + sqlite_orm::and_( + sqlite_orm::c(&RepoEntry::repo_id) == repo_id, + sqlite_orm::like(&RepoEntry::path, prefix + "/%") + ) + )); + std::vector children; + for (auto &entry: all) { + if (const auto rest = entry.path.substr(prefix.size() + 1); rest.find('/') == std::string::npos) { + children.push_back(entry); + } + } + return children; + } + + tl::expected, api::SeafileException> SeafileStore::pathInfo( + const std::string &path) { + const auto normalized = normalizePath(path); + const auto parts = splitPath(normalized); + if (parts.empty()) { + return tl::make_unexpected(api::SeafileException("pathInfo does not support root path")); + } + + const auto &repo_name = parts[0]; + const auto repo = findRepoByName(repo_name); + if (!repo) { + return tl::make_unexpected(repo.error()); + } + + if (parts.size() == 1) { + return std::variant(*repo); + } + + const auto inner_path = joinPath(parts, 1); + auto entry = findRepoEntry(repo->id, inner_path); + if (entry) { + return std::variant(*entry); + } + + const auto parent_path = parentPath(inner_path); + if (const auto res = fetchAndStoreRepoDir(repo->id, parent_path); !res) { + return tl::make_unexpected(res.error()); + } + + entry = findRepoEntry(repo->id, inner_path); + 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) { + 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) { + repos = storage.get_all(); + } else { + return tl::make_unexpected(synced.error()); + } + } + return std::variant, std::vector >(std::move(repos)); + } + + const auto &repo_name = parts[0]; + const auto repo = findRepoByName(repo_name); + if (!repo) { + return tl::make_unexpected(repo.error()); + } + + const auto inner_path = joinPath(parts, 1); + auto children = queryDirectChildren(repo->id, inner_path); + if (children.empty()) { + if (const auto res = fetchAndStoreRepoDir(repo->id, inner_path); !res) { + return tl::make_unexpected(res.error()); + } + children = queryDirectChildren(repo->id, inner_path); + } + 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) { + return std::string{}; + } + return this->api.downloadFile(repo_id, path, offset, offset + length - 1); + } + + 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()) { + 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) { + return tl::make_unexpected(sync_account.error()); + } + if (const auto sync_repos = syncRepos(); !sync_repos) { + return tl::make_unexpected(sync_repos.error()); + } + return {}; + } + + SeafileStore::~SeafileStore() = default; + + std::filesystem::path SeafileStore::storePath() { + return shell::util::ApplicationPath::get().parent_path() / "seafile.sqlite"; + } +} diff --git a/src/seafile/fs/storage/repo_entry.cpp b/src/seafile/fs/storage/repo_entry.cpp index d7071e3..c769aef 100644 --- a/src/seafile/fs/storage/repo_entry.cpp +++ b/src/seafile/fs/storage/repo_entry.cpp @@ -6,12 +6,22 @@ namespace seafile::fs::storage { - auto RepoEntry::from_api_model(const std::string& repo_id, const std::string& path, + auto RepoEntry::from_api_model(const std::string& repo_id, const std::string& parent_path, const api::RepoEntry& entry) -> RepoEntry { + std::string entry_path; + if (parent_path == "/") + { + entry_path = "/" + entry.name; + } + else + { + entry_path = parent_path + "/" + entry.name; + } + return RepoEntry{ .repo_id = repo_id, - .path = path, + .path = entry_path, .type = entry.type, .name = entry.name, .id = entry.id, diff --git a/src/seafile/fs/storage/repo_info.cpp b/src/seafile/fs/storage/repo_info.cpp new file mode 100644 index 0000000..fdb9df5 --- /dev/null +++ b/src/seafile/fs/storage/repo_info.cpp @@ -0,0 +1,30 @@ +// +// Created by Shell on 2026/7/30. +// +#include "seafile/fs/storage/repo_info.hpp" + +namespace seafile::fs::storage { + auto RepoInfo::from_api_model(const api::RepoInfo& repo_info) -> RepoInfo + { + return RepoInfo{ + .type = repo_info.type, + .id = repo_info.id, + .owner = repo_info.owner, + .name = repo_info.name, + .mtime = repo_info.mtime, + .size = repo_info.size, + .encrypted = repo_info.encrypted, + .root = repo_info.root, + .permission = repo_info.permission, + .modifier_email = repo_info.modifier_email, + .modifier_contact_email = repo_info.modifier_contact_email, + .modifier_name = repo_info.modifier_name, + .file_count = repo_info.file_count, + .head_commit_id = repo_info.head_commit_id, + .enc_version = repo_info.enc_version, + .salt = repo_info.salt, + .magic = repo_info.magic, + .random_key = repo_info.random_key, + }; + } +} \ No newline at end of file diff --git a/src/seafile/fs/winfsp_adapter.cpp b/src/seafile/fs/winfsp_adapter.cpp index 3e6e7fd..dfb2966 100644 --- a/src/seafile/fs/winfsp_adapter.cpp +++ b/src/seafile/fs/winfsp_adapter.cpp @@ -5,6 +5,8 @@ // Created by Shell on 2026/6/20. // #include +#include +#include namespace seafile::fs { @@ -64,15 +66,19 @@ namespace seafile::fs return Result; } auto mount_point = std::wstring(L"X:"); - auto seafile_file_system = new SeafileFileSystem("https://pan.hanwangsoft.com", L"Seafile"); - if (const auto ret= seafile_file_system->initialize("asypost@yeah.net", "Shell0805@china"); !ret.has_value()) + auto store = std::make_unique( + SeafileStore::storePath().string(), + "https://pan.hanwangsoft.com", + "asypost@yeah.net", + "Shell0805@china"); + if (const auto ret = store->init(); !ret.has_value()) { + spdlog::error(std::format("store init failed: {}", ret.error().what())); Result = STATUS_UNSUCCESSFUL; FspFileSystemDelete(FileSystem); - delete seafile_file_system; - return Result; } + auto seafile_file_system = new SeafileFileSystem(std::move(store), L"Seafile"); FileSystem->UserContext = seafile_file_system; fstab.insert({FileSystem, seafile_file_system});