#include "githubimagehost.h" #include #include #include #include #include #include #include using namespace vnotex; const QString GitHubImageHost::c_apiUrl = "https://api.github.com"; GitHubImageHost::GitHubImageHost(QObject *p_parent) : ImageHost(p_parent) { } bool GitHubImageHost::ready() const { return !m_personalAccessToken.isEmpty() && !m_userName.isEmpty() && !m_repoName.isEmpty(); } ImageHost::Type GitHubImageHost::getType() const { return Type::GitHub; } QJsonObject GitHubImageHost::getConfig() const { QJsonObject obj; obj[QStringLiteral("personal_access_token")] = m_personalAccessToken; obj[QStringLiteral("user_name")] = m_userName; obj[QStringLiteral("repository_name")] = m_repoName; return obj; } void GitHubImageHost::setConfig(const QJsonObject &p_jobj) { parseConfig(p_jobj, m_personalAccessToken, m_userName, m_repoName); // Do not assume the default branch. m_imageUrlPrefix = QString("https://raw.githubusercontent.com/%1/%2/").arg(m_userName, m_repoName); } bool GitHubImageHost::testConfig(const QJsonObject &p_jobj, QString &p_msg) { p_msg.clear(); QString token, userName, repoName; parseConfig(p_jobj, token, userName, repoName); if (token.isEmpty() || userName.isEmpty() || repoName.isEmpty()) { p_msg = tr("PersonalAccessToken/UserName/RepositoryName should not be empty."); return false; } auto reply = getRepoInfo(token, userName, repoName); p_msg = QString::fromUtf8(reply.m_data); return reply.m_error == QNetworkReply::NoError; } QPair GitHubImageHost::authorizationHeader(const QString &p_token) { auto token = "token " + p_token; return qMakePair(QByteArray("Authorization"), token.toUtf8()); } QPair GitHubImageHost::acceptHeader() { return qMakePair(QByteArray("Accept"), QByteArray("application/vnd.github.v3+json")); } vte::NetworkAccess::RawHeaderPairs GitHubImageHost::prepareCommonHeaders(const QString &p_token) { vte::NetworkAccess::RawHeaderPairs rawHeader; rawHeader.push_back(authorizationHeader(p_token)); rawHeader.push_back(acceptHeader()); return rawHeader; } vte::NetworkReply GitHubImageHost::getRepoInfo(const QString &p_token, const QString &p_userName, const QString &p_repoName) const { auto rawHeader = prepareCommonHeaders(p_token); const auto urlStr = QString("%1/repos/%2/%3").arg(c_apiUrl, p_userName, p_repoName); auto reply = vte::NetworkAccess::request(QUrl(urlStr), rawHeader); return reply; } void GitHubImageHost::parseConfig(const QJsonObject &p_jobj, QString &p_token, QString &p_userName, QString &p_repoName) { p_token = p_jobj[QStringLiteral("personal_access_token")].toString(); p_userName = p_jobj[QStringLiteral("user_name")].toString(); p_repoName = p_jobj[QStringLiteral("repository_name")].toString(); } QString GitHubImageHost::create(const QByteArray &p_data, const QString &p_path, QString &p_msg) { QString destUrl; if (p_path.isEmpty()) { p_msg = tr("Failed to create image with empty path."); return destUrl; } if (!ready()) { p_msg = tr("Invalid GitHub image host configuration."); return QString(); } auto rawHeader = prepareCommonHeaders(m_personalAccessToken); const auto urlStr = QString("%1/repos/%2/%3/contents/%4").arg(c_apiUrl, m_userName, m_repoName, p_path); // Check if @p_path already exists. auto reply = vte::NetworkAccess::request(QUrl(urlStr), rawHeader); if (reply.m_error == QNetworkReply::NoError) { p_msg = tr("The resource already exists at the image host (%1).").arg(p_path); return QString(); } else if (reply.m_error != QNetworkReply::ContentNotFoundError) { p_msg = tr("Failed to query the resource at the image host (%1) (%2) (%3).").arg(urlStr, reply.errorStr(), reply.m_data); return QString(); } // Create the content. QJsonObject requestDataObj; requestDataObj[QStringLiteral("message")] = QString("VX_ADD: %1").arg(p_path); requestDataObj[QStringLiteral("content")] = QString::fromUtf8(p_data.toBase64()); auto requestData = Utils::toJsonString(requestDataObj); reply = vte::NetworkAccess::put(QUrl(urlStr), rawHeader, requestData); if (reply.m_error != QNetworkReply::NoError) { p_msg = tr("Failed to create resource at the image host (%1) (%2) (%3).").arg(urlStr, reply.errorStr(), reply.m_data); return QString(); } else { auto replyObj = Utils::fromJsonString(reply.m_data); Q_ASSERT(!replyObj.isEmpty()); auto targetUrl = replyObj[QStringLiteral("content")].toObject().value(QStringLiteral("download_url")).toString(); if (targetUrl.isEmpty()) { p_msg = tr("Failed to create resource at the image host (%1) (%2) (%3).").arg(urlStr, reply.errorStr(), reply.m_data); } else { qDebug() << "created resource" << targetUrl; } return targetUrl; } } bool GitHubImageHost::ownsUrl(const QString &p_url) const { return p_url.startsWith(m_imageUrlPrefix); } QString GitHubImageHost::fetchResourcePath(const QString &p_prefix, const QString &p_url) { auto resourcePath = p_url.mid(p_prefix.size()); // Skip the branch name. resourcePath = resourcePath.mid(resourcePath.indexOf(QLatin1Char('/')) + 1); resourcePath = WebUtils::purifyUrl(resourcePath); return resourcePath; } bool GitHubImageHost::remove(const QString &p_url, QString &p_msg) { Q_ASSERT(ownsUrl(p_url)); if (!ready()) { p_msg = tr("Invalid GitHub image host configuration."); return false; } const auto resourcePath = fetchResourcePath(m_imageUrlPrefix, p_url); auto rawHeader = prepareCommonHeaders(m_personalAccessToken); const auto urlStr = QString("%1/repos/%2/%3/contents/%4").arg(c_apiUrl, m_userName, m_repoName, resourcePath); // Get the SHA of the resource. QString sha = ""; auto reply = vte::NetworkAccess::request(QUrl(urlStr), rawHeader); if (reply.m_error == QNetworkReply::NoError) { auto replyObj = Utils::fromJsonString(reply.m_data); Q_ASSERT(!replyObj.isEmpty()); sha = replyObj[QStringLiteral("sha")].toString(); } else if (reply.m_error == QNetworkReply::ContentAccessDenied) { // File larger than 1M // Get all file information under the warehouse directory const auto fileInfo = QFileInfo(urlStr); const auto urlFilePath = QUrl(fileInfo.path()); const auto fleName = fileInfo.fileName(); reply = vte::NetworkAccess::request(urlFilePath, rawHeader); if (QNetworkReply::NoError == reply.m_error) { const auto jsonArray = QJsonDocument::fromJson(reply.m_data).array(); for (auto i = jsonArray.begin(); i < jsonArray.end(); i++) { if (!i->isObject()) { continue; } const auto jsonObj = i->toObject(); const auto name = jsonObj[QStringLiteral("name")].toString(); if (name == fleName) { sha = jsonObj[QStringLiteral("sha")].toString(); break; } } } } if (sha.isEmpty()) { p_msg = tr("Failed to fetch SHA about the resource (%1) (%2).").arg(resourcePath, QString::fromUtf8(reply.m_data)); return false; } // Delete. QJsonObject requestDataObj; requestDataObj[QStringLiteral("message")] = QString("VX_DEL: %1").arg(resourcePath); requestDataObj[QStringLiteral("sha")] = sha; auto requestData = Utils::toJsonString(requestDataObj); reply = vte::NetworkAccess::deleteResource(QUrl(urlStr), rawHeader, requestData); if (reply.m_error != QNetworkReply::NoError) { p_msg = tr("Failed to delete resource (%1) (%2).").arg(resourcePath, QString::fromUtf8(reply.m_data)); return false; } qDebug() << "deleted resource" << resourcePath; return true; }