// // Created by Shell on 2026/6/17. // #ifndef SEADISK_SEAFILE_API_HPP #define SEADISK_SEAFILE_API_HPP #include #include #include #include "exception.hpp" #include #include #include #include #include "account_info.hpp" #include "auth_token.hpp" #include "default_repo.hpp" #include "dir_info.hpp" #include "file_info.hpp" #include "repo_entry.hpp" #include "repo_info.hpp" #include "seafile_api_interface.hpp" #include "server_info.hpp" #include "token_type.hpp" #include "seafile/util/version.hpp" 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 { auto url = ada::parse(this->base_url); if (!url) { return {}; } url->set_pathname(path); return std::string(url->get_href()); } 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 && response.status_code != 206) { return tl::make_unexpected( SeafileException( std::format("Http Error: {}", response.status_code))); } try { return nlohmann::json::parse(response.text) .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); } /** * @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) { 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()}, connection_pool); return parseResponse(response); } public: /** * @brief primary constructor * @param base_url base url of seafile server */ 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 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) { this->token = auth_token->token; 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 { return tl::make_unexpected(server_info.error()); } } return auth_token; } tl::expected serverInfo() const override { const auto response = cpr::Get( cpr::Url{makeUrl("/api2/server-info/")}, cpr::Header{ {"accept", "application/json"}, }, connection_pool); return parseResponse(response); } /** * @brief get the default repo of user * @return default repo info */ tl::expected 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 { 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} }, connection_pool); return parseResponse(response); } tl::expected, SeafileException> 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}, cpr::Header{ {"accept", "application/json"}, {"authorization", auth} }, cpr::Parameters{ {"type", type}, {"nameContains", name_contains} }, connection_pool); return parseResponse >(response); } /** * @brief Lists all the items information in the directory * @param repo_id repo id * @param path path to query * @return repo entries */ tl::expected, SeafileException> 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); const auto response = cpr::Get(url, cpr::Header{ {"accept", "application/json"}, {"authorization", auth} }, cpr::Parameters{ {"p", path} }, connection_pool, cpr::ReserveSize(512 * 1024)); return parseResponse >(response); } tl::expected accountInfo() const override { 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} }, connection_pool); return parseResponse(response); } tl::expected 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); const auto response = cpr::Get(url, cpr::Header{ {"accept", "application/json"}, {"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 { 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} }, connection_pool); return parseResponse(response); } tl::expected 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 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", range); size_t content_size = 1024 * 4; if (start.has_value() && end.has_value()) { content_size = std::min(end.value() - start.value(), 4 * 1024 * 1024ULL); } 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 { return response.text; } } else { return download_url; } } tl::expected 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{ {"accept", "application/json"}, {"authorization", auth} }, cpr::Parameters{ {"path", path} }, connection_pool); return parseResponse(response); } }; } #endif //SEADISK_SEAFILE_API_HPP