mason/src2/download.cpp

217 lines
6.5 KiB
C++

#include "download.h"
#include <regex>
#include "collection.h"
constexpr std::string_view downloads("downloads");
constexpr std::string_view headerAcceptJson("accept: application/json");
static const std::regex repo("(^https?):\\/\\/([\\w\\d\\.\\-\\_]+)\\/([\\w\\d\\-\\_]+)\\/([\\w\\d\\-\\_]+)");
unsigned int Download::instances(0);
AtomicMutex Download::amx{};
void Download::CurlDeleter::operator()(CURL* curl) const {
curl_easy_cleanup(curl);
}
void Download::FileDeleter::operator()(FILE* file) const {
fclose(file);
}
Download::Download(
const std::string& url,
const std::filesystem::path& destination,
const std::shared_ptr<Collection>& collection,
const std::shared_ptr<Logger>& logger
) :
Loggable(logger),
curl(),
collection(collection),
url(url),
destination(destination)
{
std::lock_guard lock(amx);
if (instances == 0) {
CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
if (res != CURLE_OK)
throw CurlError(std::string("Error initializing curl global ") + curl_easy_strerror(res));
}
++instances;
createCurl();
}
Download::~Download() {
std::lock_guard lock(amx);
--instances;
if (instances == 0)
curl_global_cleanup();
}
void Download::proceed() {
bool success = false;
std::optional<RepoInfo> repo = testRepo();
if (repo.has_value()) {
std::optional<std::filesystem::path> path = downloadAsRepo(repo.value());
}
}
std::optional<nlohmann::json> Download::repoInfoGiteaApi1(const RepoInfo& repo) {
std::string url = repo.origin() + "/api/v1/repos/" + repo.project();
std::string data;
CURLcode code = httpGet(url, data, {headerAcceptJson});
if (code == CURLE_OK) {
try {
return nlohmann::json::parse(data);
} catch (const nlohmann::json::exception& e) {
warn(e.what());
}
}
return std::nullopt;
}
std::optional<std::filesystem::path> Download::downloadAsRepo(const RepoInfo& repo) {
info("Trying Gitea v1 API");
std::optional<nlohmann::json> repoInfo = repoInfoGiteaApi1(repo);
if (repoInfo.has_value()) {
nlohmann::json::const_iterator itr = repoInfo.value().find("default_branch");
if (itr != repoInfo.value().end() && itr->is_string()) {
std::string branchName = *itr;
info("Gitea v1 API seem to have worked");
info("Default branch is " + branchName);
std::optional<std::filesystem::path> path = downloadRepoGiteaApi1(repo, branchName);
}
}
return std::nullopt;
}
std::optional<std::filesystem::path> Download::downloadRepoGiteaApi1(const RepoInfo& repo, const std::string& branch) {
std::string fileName = branch + ".tar.gz";
std::string url = repo.origin() + "/api/v1/repos/" + repo.project() + "/archive/" + fileName;
std::filesystem::path path = destination/downloads/fileName;
CURLcode code = httpDownload(url, path);
if (code == CURLE_OK)
return path;
warn("Couldn't download file " + url + ": " + curl_easy_strerror(code));
minor("Removing " + path.string());
if (std::filesystem::exists(path))
std::filesystem::remove_all(path);
return std::nullopt;
}
CURLcode Download::httpGet(
const std::string& url,
std::string& result,
const std::vector<std::string_view>& headers)
{
setHeaders(headers);
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, writeString);
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &result);
return curl_easy_perform(curl.get());
}
CURLcode Download::httpDownload(const std::string& url, const std::filesystem::path& path, const std::vector<std::string_view>& headers) {
std::filesystem::create_directories(path.parent_path());
if (std::filesystem::exists(path)) {
minor("File " + path.string() + " already exists, will be overwritten");
if (std::filesystem::is_directory(path))
std::filesystem::remove_all(path);
}
std::unique_ptr<FILE, FileDeleter> filePtr(fopen(path.c_str(), "wb"));
if (!filePtr)
throw FileError("Could not open file " + path.string() + " to write");
setHeaders(headers);
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, writeFile);
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, filePtr.get());
return curl_easy_perform(curl.get());
// return res;
// if (res != CURLE_OK) {
// Project::warn("Couldn't download file " + url + ": " + curl_easy_strerror(res));
// } else {
// uint32_t code;
// curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
// if (code == 200)
// result = true;
// else
// Project::warn("Couldn't download file " + url + ": response code " + std::to_string(code));
// }
// } else {
// Project::error(std::string("Couldn't open output file ") + destination.c_str());
// }
}
void Download::setHeaders(const std::vector<std::string_view>& headers) {
struct curl_slist* curlHeaders = nullptr;
for (const std::string_view& header : headers)
curlHeaders = curl_slist_append(curlHeaders, header.data());
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, curlHeaders);
}
void Download::createCurl() {
CURL* crl = curl_easy_init();
if (!crl)
throw CurlError("Error creating curl instalce");
curl_easy_setopt(crl, CURLOPT_USERAGENT, "libcurl-agent/1.0" );
curl_easy_setopt(crl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(crl, CURLOPT_DEBUGFUNCTION, trace);
curl_easy_setopt(crl, CURLOPT_DEBUGDATA, this);
curl_easy_setopt(crl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(crl, CURLOPT_NOPROGRESS, 1L);
curl = std::unique_ptr<CURL, CurlDeleter>(crl);
}
std::optional<Download::RepoInfo> Download::testRepo() const {
std::smatch results;
if (std::regex_search(url, results, repo)) {
info(url + " appears to be a git repository");
return RepoInfo {
results[1],
results[2],
results[3],
results[4]
};
}
return std::nullopt;
}
Download::CurlError::CurlError(const std::string& message) :
std::runtime_error(message) {}
Download::FileError::FileError(const std::string& message) :
std::runtime_error(message) {}
std::string Download::RepoInfo::origin() const {
return protocol + "://" + host;
}
std::string Download::RepoInfo::project() const {
return owner + "/" + name;
}