diff --git a/CMakeLists.txt b/CMakeLists.txt index 0160439..330bff8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,16 @@ add_executable(seadisk src/main.cpp include/seafile/exception.hpp include/seafile/server_info.hpp include/seafile/auth_token.hpp + include/seafile/default_repo.hpp + include/seafile/token_type.hpp + include/seafile/repo_info.hpp + include/seafile/repo_entry.hpp + include/seafile/account_info.hpp + include/seafile/file_info.hpp + include/seafile/dir_info.hpp + include/seafile/fs/file_context.hpp + include/seafile/fs/seafile_file_system.hpp + src/seafile/fs/seafile_file_system.cpp ) target_compile_definitions(seadisk PRIVATE diff --git a/include/seafile/account_info.hpp b/include/seafile/account_info.hpp new file mode 100644 index 0000000..ad83c5a --- /dev/null +++ b/include/seafile/account_info.hpp @@ -0,0 +1,74 @@ +// +// Created by Shell on 2026-06-18. +// + +#ifndef SEADISK_ACCOUNT_INFO_HPP +#define SEADISK_ACCOUNT_INFO_HPP +#include +#include + +namespace seafile +{ + /** + * @brief Seafile user profile information. + */ + struct AccountInfo final + { + /// Space usage percentage. + std::string space_usage; + + /// User avatar URL. + std::string avatar_url; + + /// Login email address. + std::string email; + + /// Display name. + std::string name; + + /// Total available storage space in bytes. + std::uint64_t total{}; + + /// Used storage space in bytes. + std::uint64_t usage{}; + + /// Login identifier. + std::string login_id; + + /// User department. + std::string department; + + /// Contact email address. + std::string contact_email; + + /// Institution or organization name. + std::string institution; + + /// Indicates whether the user is a staff member. + bool is_staff{}; + + /// File update notification interval. + int file_updates_email_interval{}; + + /// Collaboration notification interval. + int collaborate_email_interval{}; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + AccountInfo, + space_usage, + avatar_url, + email, + name, + total, + usage, + login_id, + department, + contact_email, + institution, + is_staff, + file_updates_email_interval, + collaborate_email_interval) + }; +} + +#endif //SEADISK_ACCOUNT_INFO_HPP diff --git a/include/seafile/default_repo.hpp b/include/seafile/default_repo.hpp new file mode 100644 index 0000000..8f12a88 --- /dev/null +++ b/include/seafile/default_repo.hpp @@ -0,0 +1,32 @@ +// +// Created by Shell on 2026-06-18. +// + +#ifndef SEADISK_DEFAULT_REPO_HPP +#define SEADISK_DEFAULT_REPO_HPP +#include +#include +#include + +namespace seafile +{ + /** + * default repo response + */ + struct DefaultRepo final + { + /** + * indicates if default repo exists + */ + bool exists = false; + + /** + * default repo id + */ + std::optional repo_id; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(DefaultRepo, exists, repo_id); + }; +} + +#endif //SEADISK_DEFAULT_REPO_HPP diff --git a/include/seafile/dir_info.hpp b/include/seafile/dir_info.hpp new file mode 100644 index 0000000..2b9fc38 --- /dev/null +++ b/include/seafile/dir_info.hpp @@ -0,0 +1,40 @@ +// +// Created by Shell on 2026-06-18. +// + +#ifndef SEADISK_DIR_INFO_HPP +#define SEADISK_DIR_INFO_HPP +#include +#include +namespace seafile +{ + /** + * @brief Repository directory information. + */ + struct DirInfo final + { + /// Repository identifier. + std::string repo_id; + + /// Full directory path within the repository. + std::string path; + + /// Directory name. + std::string name; + + /// Last modification time in ISO 8601 format. + std::string mtime; + + /// Current user's permission (e.g. "r", "rw"). + std::string permission; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + DirInfo, + repo_id, + path, + name, + mtime, + permission) + }; +} +#endif //SEADISK_DIR_INFO_HPP diff --git a/include/seafile/file_info.hpp b/include/seafile/file_info.hpp new file mode 100644 index 0000000..4b4f10d --- /dev/null +++ b/include/seafile/file_info.hpp @@ -0,0 +1,74 @@ +// +// Created by Shell on 2026-06-18. +// + +#ifndef SEADISK_FILE_INFO_HPP +#define SEADISK_FILE_INFO_HPP +#include +#include +#include + +namespace seafile +{ + /** + * @brief Detailed file metadata. + */ + struct FileInfo final + { + /// Object type, usually "file". + std::string type; + + /// File object identifier. + std::string id; + + /// File name. + std::string name; + + /// Current user's permission (e.g. "r", "rw"). + std::string permission; + + /// Last modification time (Unix timestamp, seconds). + std::int64_t mtime{}; + + /// Last modification time in ISO 8601 format. + std::string last_modified; + + /// Internal email address of the last modifier. + std::string last_modifier_email; + + /// Display name of the last modifier. + std::string last_modifier_name; + + /// Contact email address of the last modifier. + std::string last_modifier_contact_email; + + /// File size in bytes. + std::uint64_t size{}; + + /// Whether the file is starred by the current user. + bool starred{}; + + /// Total number of comments. + std::uint32_t comment_total{}; + + /// Whether the current user can edit the file. + bool can_edit{}; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE( + FileInfo, + type, + id, + name, + permission, + mtime, + last_modified, + last_modifier_email, + last_modifier_name, + last_modifier_contact_email, + size, + starred, + comment_total, + can_edit) + }; +} +#endif //SEADISK_FILE_INFO_HPP diff --git a/include/seafile/fs/file_context.hpp b/include/seafile/fs/file_context.hpp new file mode 100644 index 0000000..aace52e --- /dev/null +++ b/include/seafile/fs/file_context.hpp @@ -0,0 +1,21 @@ +// +// Created by Shell on 2026-06-18. +// + +#ifndef SEADISK_FILE_CONTEXT_HPP +#define SEADISK_FILE_CONTEXT_HPP +#include +namespace seafile::fs +{ + struct FileContext final + { + std::string repo_id; + std::string path; + + uint64_t size{}; + + std::string download_url; + }; +} + +#endif //SEADISK_FILE_CONTEXT_HPP diff --git a/include/seafile/fs/seafile_file_system.hpp b/include/seafile/fs/seafile_file_system.hpp new file mode 100644 index 0000000..fcac8f5 --- /dev/null +++ b/include/seafile/fs/seafile_file_system.hpp @@ -0,0 +1,69 @@ +// +// Created by Shell on 2026-06-18. +// + +#ifndef SEADISK_SEAFILE_FILE_SYSTEM_HPP +#define SEADISK_SEAFILE_FILE_SYSTEM_HPP +#include +#include + +#include "seafile/seafile_api.hpp" +#include "seafile/token_type.hpp" + +namespace seafile::fs +{ + class SeafileFileSystem final + { + std::string repo_id; + std::string token; + TokenType token_type = TokenType::TOKEN; + SeaFileApi api; + std::wstring mount_point; + FSP_FILE_SYSTEM* FileSystem = nullptr; + + public: + explicit SeafileFileSystem(const std::string& server, + const std::wstring& mount_point) : api(server), mount_point(mount_point) + { + } + + void auth(const std::string& username, const std::string& password); + + SeafileFileSystem(const SeafileFileSystem&) = delete; + SeafileFileSystem(SeafileFileSystem&&) = delete; + SeafileFileSystem& operator=(const SeafileFileSystem&) = delete; + SeafileFileSystem& operator=(SeafileFileSystem&&) = delete; + + NTSTATUS SvcStart(FSP_SERVICE* Service, ULONG argc, PWSTR* argv); + + NTSTATUS SvcStop(FSP_SERVICE* Service); + + NTSTATUS GetVolumeInfo(FSP_FSCTL_VOLUME_INFO* VolumeInfo); + + NTSTATUS SetVolumeLabel_(PWSTR VolumeLabel, + FSP_FSCTL_VOLUME_INFO* VolumeInfo); + + NTSTATUS GetSecurityByName(PWSTR FileName, + PUINT32 PFileAttributes, + PSECURITY_DESCRIPTOR SecurityDescriptor, + SIZE_T* PSecurityDescriptorSize); + + NTSTATUS Open( + PWSTR FileName, + UINT32 CreateOptions, + UINT32 GrantedAccess, + PVOID* PFileContext, + FSP_FSCTL_FILE_INFO* FileInfo); + + NTSTATUS GetFileInfoInternal(HANDLE Handle, FSP_FSCTL_FILE_INFO* FileInfo); + + + VOID Close(FSP_FILE_SYSTEM* FileSystem, + PVOID FileContext0); + + NTSTATUS ReadDirectory(FSP_FILE_SYSTEM* FileSystem, + PVOID FileContext0, PWSTR Pattern, PWSTR Marker, + PVOID Buffer, ULONG BufferLength, PULONG PBytesTransferred); + }; +} +#endif //SEADISK_SEAFILE_FILE_SYSTEM_HPP diff --git a/include/seafile/repo_entry.hpp b/include/seafile/repo_entry.hpp new file mode 100644 index 0000000..0d4e605 --- /dev/null +++ b/include/seafile/repo_entry.hpp @@ -0,0 +1,78 @@ +// +// Created by Shell on 2026-06-18. +// + +#ifndef SEADISK_REPO_ENTRY_HPP +#define SEADISK_REPO_ENTRY_HPP +#include +#include +#include + +namespace seafile +{ + /** + * @brief File or directory entry metadata. + */ + struct RepoEntry final + { + /// Entry type ("file" or "dir"). + std::string type; + + /// File or directory name. + std::string name; + + /// Object identifier. + std::string id; + + /// Last modification time (Unix timestamp, seconds). + std::int64_t mtime{}; + + /// Internal email address of the last modifier. + std::optional modifier_email; + + /// File size in bytes. For directories this may be 0. + std::optional size{}; + + /// Whether the file is currently locked. + std::optional is_locked{}; + + /// Lock owner email or identifier. Null if unlocked. + std::optional lock_owner; + + /// Lock timestamp (Unix timestamp, seconds). + std::optional lock_time{}; + + /// Whether the current user owns the lock. + std::optional locked_by_me{}; + + /// Current user's permission (e.g. "r", "rw"). + std::optional permission; + + /// Contact email address of the last modifier. + std::optional modifier_contact_email; + + /// Display name of the last modifier. + std::optional modifier_name; + + /// Whether the entry is starred by the current user. + std::optional starred{}; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + RepoEntry, + modifier_email, + size, + is_locked, + lock_owner, + lock_time, + locked_by_me, + type, + name, + id, + mtime, + permission, + modifier_contact_email, + modifier_name, + starred); + }; +} +#endif //SEADISK_REPO_ENTRY_HPP diff --git a/include/seafile/repo_info.hpp b/include/seafile/repo_info.hpp new file mode 100644 index 0000000..2a0e234 --- /dev/null +++ b/include/seafile/repo_info.hpp @@ -0,0 +1,94 @@ +// +// Created by Shell on 2026-06-18. +// + +#ifndef SEADISK_REPO_INFO_HPP +#define SEADISK_REPO_INFO_HPP +#include +#include +#include + +namespace seafile +{ + /** + *@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; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + RepoInfo, + type, + id, + owner, + name, + mtime, + size, + encrypted, + root, + permission, + modifier_email, + modifier_contact_email, + modifier_name, + file_count, + head_commit_id, + enc_version, + salt, + magic, + random_key) + }; +} +#endif //SEADISK_REPO_INFO_HPP diff --git a/include/seafile/seafile_api.hpp b/include/seafile/seafile_api.hpp index 699cc44..143ba90 100644 --- a/include/seafile/seafile_api.hpp +++ b/include/seafile/seafile_api.hpp @@ -13,61 +13,84 @@ #include #include +#include "account_info.hpp" #include "auth_token.hpp" +#include "default_repo.hpp" +#include "file_info.hpp" +#include "repo_entry.hpp" +#include "repo_info.hpp" #include "server_info.hpp" +#include "token_type.hpp" -namespace seafile { - class SeaFileApi final { +namespace seafile +{ + class SeaFileApi final + { //base url of the seafile api server std::string base_url; /** - * make a url string from given path + * @brief make a url string from given path * @param path url path * @return the whole url string */ - std::string makeUrl(const std::string &path) { + [[nodiscard]] std::string makeUrl(const std::string& path) const + { auto url = ada::parse(this->base_url); url->set_pathname(path); return std::string(url->get_href()); } - template - tl::expected - parseResponse(const cpr::Response &response) { - if (response.error) { + template + static tl::expected + parseResponse(const cpr::Response& response) + { + if (response.error) + { return tl::make_unexpected( SeafileException( response.error.message)); } - if (response.status_code != 200) { + if (response.status_code != 200) + { 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) + { + return token_type == TokenType::BEARER + ? std::format("Bearer {}", token) + : std::format("Token {}", token); + } + /** - * post body and fetch result + * @brief post body and fetch result * @tparam T return type * @param path target url path * @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); - auto response = cpr::Post( + const auto response = cpr::Post( cpr::Url{url}, cpr::Header{ {"accept", "application/json"}, @@ -78,20 +101,22 @@ namespace seafile { public: /** - * primary constructor + * @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)) + { } /** - * + * @brief Obtain account token * @param username username * @param password password * @return token when success else SeafileException */ tl::expected - authToken(const std::string &username, const std::string &password) { + authToken(const std::string& username, const std::string& password) + { const nlohmann::json body = { {"username", username}, {"password", password} @@ -100,8 +125,9 @@ namespace seafile { } tl::expected - serverInfo() { - auto response = cpr::Get( + serverInfo() const + { + const auto response = cpr::Get( cpr::Url{makeUrl("/api2/server-info/")}, cpr::Header{ {"accept", "application/json"} @@ -109,6 +135,143 @@ namespace seafile { return parseResponse(response); } + + /** + * @brief get the default repo of user + * @param token token + * @param token_type token type + * @return default repo info + */ + tl::expected + defaultRepo(const std::string& token, const TokenType token_type) const + { + 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} + }); + return parseResponse(response); + } + + tl::expected + repoInfo(const std::string& repo_id, const std::string& token, const TokenType token_type) const + { + const auto url = makeUrl(std::format("/api2/repos/{}/", repo_id)); + const auto auth = makeAuthToken(token, token_type); + + const auto response = cpr::Get(cpr::Url{url}, + cpr::Header{ + {"accept", "application/json"}, + {"authorization", auth} + }); + return parseResponse(response); + } + + /** + * @brief Lists all the items information in the directory + * @param repo_id repo id + * @param path path to query + * @param token token + * @param token_type token type + * @return repo entries + */ + tl::expected, SeafileException> + repoDir(const std::string& repo_id, const std::string& path, const std::string& token, + const TokenType token_type) const + { + const auto url = cpr::Url{makeUrl(std::format("/api2/repos/{}/dir/", repo_id))}; + const auto auth = makeAuthToken(token, token_type); + + const auto response = cpr::Get(url, + cpr::Header{ + {"accept", "application/json"}, + {"authorization", auth} + }, + cpr::Parameters{ + {"p", path} + }); + return parseResponse>(response); + } + + tl::expected + accountInfo(const std::string& token, + const TokenType token_type) const + { + const auto url = cpr::Url{makeUrl("/api2/account/info/")}; + const auto auth = makeAuthToken(token, token_type); + + const auto response = cpr::Get(url, + cpr::Header{ + {"accept", "application/json"}, + {"authorization", auth} + }); + return parseResponse(response); + } + + tl::expected + fileDetail(const std::string& repo_id, const std::string& path, const std::string& token, + const TokenType token_type) const + { + const auto url = cpr::Url{makeUrl(std::format("/api2/repos/{}/file/detail/", repo_id))}; + const auto auth = makeAuthToken(token, token_type); + + const auto response = cpr::Get(url, + cpr::Header{ + {"accept", "application/json"}, + {"authorization", auth} + }, cpr::Parameters{ + {"p", path} + }); + return parseResponse(response); + } + + tl::expected + downloadFileUrl(const std::string& repo_id, const std::string& path, const std::string& token, + const TokenType token_type) const + { + 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, + cpr::Header{ + {"accept", "application/json"}, + {"authorization", auth} + }, cpr::Parameters{ + {"p", path} + }); + return parseResponse(response); + } + + + tl::expected + downloadFile(const std::string& repo_id, + const std::string& path, + const std::optional start, + const std::optional end, + const std::string& token, + const TokenType token_type) const + { + if (const auto download_url = downloadFileUrl(repo_id, path, token, token_type); download_url.has_value()) + { + const auto auth = makeAuthToken(token, token_type); + const auto url = cpr::Url{download_url.value()}; + auto header = cpr::Header{ + {"authorization", auth} + }; + const auto range = std::format( + "bytes={}-{}", + start ? std::to_string(*start) : "", + end ? std::to_string(*end) : ""); + header.emplace("range", std::move(range)); + const auto response = cpr::Get(url, header); + return parseResponse(response); + } + else + { + return download_url; + } + } }; } diff --git a/include/seafile/server_info.hpp b/include/seafile/server_info.hpp index 162cdcd..1c49444 100644 --- a/include/seafile/server_info.hpp +++ b/include/seafile/server_info.hpp @@ -25,7 +25,7 @@ namespace seafile { */ std::vector features; - NLOHMANN_DEFINE_TYPE_INTRUSIVE(ServerInfo, version, encrypted_library_version, features); + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ServerInfo, version, encrypted_library_version, features); }; } diff --git a/include/seafile/token_type.hpp b/include/seafile/token_type.hpp new file mode 100644 index 0000000..7899095 --- /dev/null +++ b/include/seafile/token_type.hpp @@ -0,0 +1,15 @@ +// +// Created by Shell on 2026-06-18. +// + +#ifndef SEADISK_TOKEN_TYPE_HPP +#define SEADISK_TOKEN_TYPE_HPP +namespace seafile +{ + enum class TokenType + { + TOKEN, + BEARER + }; +} +#endif //SEADISK_TOKEN_TYPE_HPP diff --git a/src/main.cpp b/src/main.cpp index 44df8c9..fec37df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,52 @@ #include "seafile/seafile_api.hpp" -#include +#include -int main(int argc,char** argv) { - seafile::SeaFileApi api("https://pan.hanwangsoft.com/"); - if (const auto token = api.authToken("asypost@yeah.net","Shell0805@china"); token.has_value()) { - std::cout<().token< file_system = nullptr; + +static NTSTATUS GetVolumeInfo( + FSP_FILE_SYSTEM* FileSystem, + FSP_FSCTL_VOLUME_INFO* VolumeInfo) +{ + const auto fs = static_cast(FileSystem->UserContext); + + return fs->GetVolumeInfo(VolumeInfo); +} + +static FSP_FILE_SYSTEM_INTERFACE seafile_interface = { + .GetVolumeInfo = GetVolumeInfo +}; + +static +NTSTATUS SvcStart(FSP_SERVICE* Service, ULONG argc, PWSTR* argv) // (2) +{ + if (file_system) + { + return file_system->SvcStart(Service, argc, argv); } - if (const auto server = api.serverInfo(); server.has_value()) { - std::cout<().version<SvcStop(Service); } - return 0; -} \ No newline at end of file + return STATUS_NOT_IMPLEMENTED; +} + +int wmain(int argc, wchar_t** argv) +{ + if (!NT_SUCCESS(FspLoad(nullptr))) + return ERROR_DELAY_LOAD_FAILED; + + file_system = std::make_shared(std::wstring("X:")); + + file_system->auth("asypost@yeah.net","Shell0805@china"); + + return FspServiceRun(const_cast(L"SeaDisk"), SvcStart, SvcStop, nullptr); +} diff --git a/src/seafile/fs/seafile_file_system.cpp b/src/seafile/fs/seafile_file_system.cpp new file mode 100644 index 0000000..d1b4c36 --- /dev/null +++ b/src/seafile/fs/seafile_file_system.cpp @@ -0,0 +1,16 @@ +// +// Created by Shell on 2026-06-18. +// + +#include "seafile/fs/seafile_file_system.hpp" + +namespace seafile::fs +{ + void SeafileFileSystem::auth(const std::string& username, const std::string& password) + { + if (const auto authToken = api.authToken(username, password); authToken.has_value()) + { + this->token = authToken.value().token; + } + } +}