vnote/src/vimagehosting.cpp
2019-11-28 20:32:52 +08:00

1617 lines
59 KiB
C++

#include "vimagehosting.h"
#include "utils/vutils.h"
#include "veditor.h"
#include "vfile.h"
#include "utils/vclipboardutils.h"
extern VConfigManager *g_config;
VGithubImageHosting::VGithubImageHosting(VFile *p_file, QObject *p_parent)
:QObject(p_parent),
m_file(p_file)
{
reply = Q_NULLPTR;
imageUploaded = false;
}
void VGithubImageHosting::handleUploadImageToGithubRequested()
{
qDebug() << "Start processing the image upload request to GitHub";
if(g_config->getGithubPersonalAccessToken().isEmpty() ||
g_config->getGithubReposName().isEmpty() ||
g_config->getGithubUserName().isEmpty())
{
qDebug() << "Please configure the GitHub image hosting first!";
QMessageBox::warning(nullptr,
tr("GitHub Image Hosting"),
tr("Please configure the GitHub image hosting first!"));
return;
}
authenticateGithubImageHosting(g_config->getGithubPersonalAccessToken());
}
void VGithubImageHosting::authenticateGithubImageHosting(QString p_token)
{
qDebug() << "start the authentication process ";
QApplication::setOverrideCursor(Qt::WaitCursor);
QNetworkRequest request;
QUrl url = QUrl("https://api.github.com");
QString ptoken = "token " + p_token;
request.setRawHeader("Authorization", ptoken.toLocal8Bit());
request.setUrl(url);
if(reply != Q_NULLPTR)
{
reply->deleteLater();
}
reply = manager.get(request);
connect(reply, &QNetworkReply::finished, this, &VGithubImageHosting::githubImageBedAuthFinished);
}
void VGithubImageHosting::githubImageBedAuthFinished()
{
switch (reply->error()) {
case QNetworkReply::NoError:
{
QByteArray bytes = reply->readAll();
if(bytes.contains("Bad credentials"))
{
qDebug() << "Authentication failed";
QApplication::restoreOverrideCursor(); // Recovery pointer
QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), tr("Bad credentials! ") +
tr("Please check your GitHub Image Hosting parameters!"));
return;
}
else
{
qDebug() << "Authentication completed";
qDebug() << "The current article path is: " << m_file->fetchPath();
imageBasePath = m_file->fetchBasePath();
newFileContent = m_file->getContent();
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file,ImageLink::LocalRelativeInternal);
QApplication::restoreOverrideCursor(); // Recovery pointer
if(images.size() > 0)
{
proDlg = new QProgressDialog(tr("Uploading images to GitHub..."),
tr("Abort"),
0,
images.size(),
nullptr);
proDlg->setWindowModality(Qt::WindowModal);
proDlg->setWindowTitle(tr("Uploading Images To Github"));
proDlg->setMinimumDuration(1);
uploadImageCount = images.size();
uploadImageCountIndex = uploadImageCount;
for(int i=0;i<images.size() ;i++)
{
if(images[i].m_url.contains(".png") ||
images[i].m_url.contains(".jpg") ||
images[i].m_url.contains(".gif"))
{
imageUrlMap.insert(images[i].m_url, "");
}
else
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Unsupported type...";
QFileInfo fileInfo(images[i].m_path.toLocal8Bit());
QString fileSuffix = fileInfo.suffix();
QString info = tr("Unsupported type: ") + fileSuffix;
QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info);
return;
}
}
githubImageBedUploadManager();
}
else
{
qDebug() << m_file->getName() << " No local images to upload";
QString info = tr("No local images to upload: %1").arg(m_file->getName());
QMessageBox::information(nullptr, tr("GitHub Image Hosting"), info);
}
}
break;
}
default:
{
QApplication::restoreOverrideCursor(); // Recovery pointer
qDebug() << "Network error: " << reply->errorString() << " error " << reply->error();
QString info = tr("Network error: ") + reply->errorString() + tr("\n\nPlease check your network!");
QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info);
}
}
}
void VGithubImageHosting::githubImageBedUploadManager()
{
uploadImageCountIndex--;
QString imageToUpload;
QMapIterator<QString, QString> it(imageUrlMap);
while(it.hasNext())
{
it.next();
if(it.value() == "")
{
imageToUpload = it.key();
proDlg->setValue(uploadImageCount - 1 - uploadImageCountIndex);
proDlg->setLabelText(tr("Uploading image: %1").arg(imageToUpload));
break;
}
}
if(imageToUpload == "")
{
qDebug() << "All images have been uploaded";
githubImageBedReplaceLink(newFileContent, m_file->fetchPath());
return;
}
if(g_config->getGithubPersonalAccessToken().isEmpty() ||
g_config->getGithubReposName().isEmpty() ||
g_config->getGithubUserName().isEmpty())
{
qDebug() << "Please configure the GitHub image hosting first!";
QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), tr("Please configure the GitHub image hosting first!"));
imageUrlMap.clear();
return;
}
QString path = imageBasePath + QDir::separator();
path += imageToUpload;
githubImageBedUploadImage(g_config->getGithubUserName(),
g_config->getGithubReposName(),
path,
g_config->getGithubPersonalAccessToken());
}
void VGithubImageHosting::githubImageBedUploadImage(const QString &p_username,
const QString &p_repository,
const QString &p_imagePath,
const QString &p_token)
{
QFileInfo fileInfo(p_imagePath.toLocal8Bit());
if(!fileInfo.exists())
{
qDebug() << "The picture does not exist in this path: " << p_imagePath.toLocal8Bit();
QString info = tr("The picture does not exist in this path: ") + p_imagePath.toLocal8Bit();
QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info);
imageUrlMap.clear();
if(imageUploaded)
{
githubImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
return;
}
QString fileSuffix = fileInfo.suffix(); // file extension
QString fileName = fileInfo.fileName(); // filename
QString uploadUrl; // Image upload URL
uploadUrl = "https://api.github.com/repos/" +
p_username +
"/" +
p_repository +
"/contents/" +
QString::number(QDateTime::currentDateTime().toTime_t()) +
"_" +
fileName;
if(fileSuffix != QString::fromLocal8Bit("jpg") &&
fileSuffix != QString::fromLocal8Bit("png") &&
fileSuffix != QString::fromLocal8Bit("gif"))
{
qDebug() << "Unsupported type...";
QString info = tr("Unsupported type: ") + fileSuffix;
QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info);
imageUrlMap.clear();
if(imageUploaded)
{
githubImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
return;
}
QNetworkRequest request;
QUrl url = QUrl(uploadUrl);
QString ptoken = "token " + p_token;
request.setRawHeader("Authorization", ptoken.toLocal8Bit());
request.setUrl(url);
if(reply != Q_NULLPTR)
{
reply->deleteLater();
}
QString param = githubImageBedGenerateParam(p_imagePath);
QByteArray postData;
postData.append(param);
reply = manager.put(request, postData);
qDebug() << "Start uploading images: " + p_imagePath + " Waiting for upload to complete";
uploadImageStatus = true;
currentUploadImage = p_imagePath;
connect(reply, &QNetworkReply::finished, this, &VGithubImageHosting::githubImageBedUploadFinished);
}
void VGithubImageHosting::githubImageBedUploadFinished()
{
if (proDlg->wasCanceled())
{
qDebug() << "User stops uploading";
reply->abort(); // Stop network request
imageUrlMap.clear();
// The ones that have been uploaded successfully before still need to stay
if(imageUploaded)
{
githubImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
return;
}
switch (reply->error())
{
case QNetworkReply::NoError:
{
QByteArray bytes = reply->readAll();
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(httpStatus == 201)
{
qDebug() << "Upload success";
QString downloadUrl;
QString imageName;
QJsonDocument doucment = QJsonDocument::fromJson(bytes);
if (!doucment.isNull())
{
if (doucment.isObject())
{
QJsonObject object = doucment.object();
if (object.contains("content"))
{
QJsonValue value = object.value("content");
if (value.isObject())
{
QJsonObject obj = value.toObject();
if (obj.contains("download_url"))
{
QJsonValue value = obj.value("download_url");
if (value.isString())
{
downloadUrl = value.toString();
qDebug() << "JSON decode: download_url : " << downloadUrl;
imageUploaded = true; // On behalf of successfully uploaded images
proDlg->setValue(uploadImageCount);
}
}
if(obj.contains("name"))
{
QJsonValue value = obj.value("name");
if(value.isString())
{
imageName = value.toString();
}
}
// Traverse key in imageurlmap
QList<QString> klist = imageUrlMap.keys();
QString temp;
for(int i=0;i<klist.count();i++)
{
temp = klist[i].split("/")[1];
if(imageName.contains(temp))
{
// You can assign values in the map
imageUrlMap.insert(klist[i], downloadUrl);
// Replace the link in the original
newFileContent.replace(klist[i], downloadUrl);
break;
}
}
// Start calling the method.
// Whether the value in the map is empty determines whether to stop.
githubImageBedUploadManager();
}
}
}
}
else
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Resolution failure!";
qDebug() << "Resolution failure's JSON: " << bytes;
if(imageUploaded)
{
githubImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
QString info = tr("JSON decode error. Please contact the developer.");
QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info);
}
}
else
{
// If status is not 201, it means there is a problem.
delete proDlg;
imageUrlMap.clear();
qDebug() << "Upload failure";
if(imageUploaded)
{
githubImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
QString info = tr("GitHub status code != 201. Please contact the developer.");
QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info);
}
break;
}
default:
{
delete proDlg;
imageUrlMap.clear();
qDebug()<<"network error: " << reply->errorString() << " error " << reply->error();
QByteArray bytes = reply->readAll();
qDebug() << bytes;
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << "status: " << httpStatus;
if(imageUploaded)
{
githubImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
QString info = tr("Uploading ") +
currentUploadImage +
tr(" \n\nNetwork error: ") +
reply->errorString() +
tr("\n\nPlease check the network or image size");
QMessageBox::warning(nullptr, tr("GitHub Image Hosting"), info);
}
}
}
void VGithubImageHosting::githubImageBedReplaceLink(QString p_fileContent, const QString p_filePath)
{
Q_UNUSED(p_filePath);
// This function must be executed when the upload is completed or fails in the middle.
if(!g_config->getGithubKeepImgScale())
{
// delete image scale
p_fileContent.replace(QRegExp("\\s+=\\d+x"),"");
}
if(!g_config->getGithubDoNotReplaceLink()) {
// Write content to file.
m_editor->setContent(p_fileContent, true);
} else {
VClipboardUtils::setTextToClipboard(QApplication::clipboard(), p_fileContent);
emit m_editor->object()->statusMessage(tr("Copied contents with new image links"));
}
// Reset.
imageUrlMap.clear();
imageUploaded = false;
}
QString VGithubImageHosting::githubImageBedGenerateParam(const QString p_imagePath)
{
// According to the requirements of GitHub interface, pictures must be in Base64 format.
// Image to base64.
QByteArray hexed;
QFile imgFile(p_imagePath);
imgFile.open(QIODevice::ReadOnly);
hexed = imgFile.readAll().toBase64();
QString imgBase64 = hexed;
QJsonObject json;
json.insert("message", QString("updatetest"));
json.insert("content", imgBase64);
QJsonDocument document;
document.setObject(json);
QByteArray byteArray = document.toJson(QJsonDocument::Compact);
QString jsonStr(byteArray);
return jsonStr;
}
void VGithubImageHosting::setEditor(VEditor *p_editor)
{
m_editor = p_editor;
}
VGiteeImageHosting::VGiteeImageHosting(VFile *p_file, QObject *p_parent)
:QObject(p_parent),
m_file(p_file)
{
reply = Q_NULLPTR;
imageUploaded = false;
}
void VGiteeImageHosting::handleUploadImageToGiteeRequested()
{
qDebug() << "Start processing the image upload request to Gitee";
if(g_config->getGiteePersonalAccessToken().isEmpty() ||
g_config->getGiteeReposName().isEmpty() ||
g_config->getGiteeUserName().isEmpty())
{
qDebug() << "Please configure the Gitee image hosting first!";
QMessageBox::warning(nullptr,
tr("Gitee Image Hosting"),
tr("Please configure the Gitee image hosting first!"));
return;
}
authenticateGiteeImageHosting(g_config->getGiteeUserName(),
g_config->getGiteeReposName(),
g_config->getGiteePersonalAccessToken());
}
void VGiteeImageHosting::authenticateGiteeImageHosting(const QString &p_username,
const QString &p_repository,
const QString &p_token)
{
qDebug() << "start the authentication process ";
QApplication::setOverrideCursor(Qt::WaitCursor);
QNetworkRequest request;
QString url = "https://gitee.com/api/v5/repos/";
url += p_username + "/";
url += p_repository + "/branches/master?access_token=";
url += p_token;
qDebug() << "gitee url: " << url;
QUrl qurl = QUrl(url);
request.setUrl(qurl);
if(reply != Q_NULLPTR)
{
reply->deleteLater();
}
reply = manager.get(request);
connect(reply, &QNetworkReply::finished, this, &VGiteeImageHosting::giteeImageBedAuthFinished);
}
void VGiteeImageHosting::giteeImageBedAuthFinished()
{
switch (reply->error())
{
case QNetworkReply::NoError:
{
QByteArray bytes = reply->readAll();
qDebug() << "Authentication completed";
qDebug() << "The current article path is: " << m_file->fetchPath();
imageBasePath = m_file->fetchBasePath();
newFileContent = m_file->getContent();
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file, ImageLink::LocalRelativeInternal);
QApplication::restoreOverrideCursor(); // Recovery pointer
if(images.size() > 0)
{
proDlg = new QProgressDialog(tr("Uploading images to Gitee..."),
tr("Abort"),
0,
images.size(),
nullptr);
proDlg->setWindowModality(Qt::WindowModal);
proDlg->setWindowTitle(tr("Uploading Images To Gitee"));
proDlg->setMinimumDuration(1);
uploadImageCount = images.size();
uploadImageCountIndex = uploadImageCount;
for(int i=0;i<images.size() ;i++)
{
if(images[i].m_url.contains(".png") ||
images[i].m_url.contains(".jpg") ||
images[i].m_url.contains(".gif"))
{
imageUrlMap.insert(images[i].m_url, "");
}
else
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Unsupported type...";
QFileInfo fileInfo(images[i].m_path.toLocal8Bit());
QString fileSuffix = fileInfo.suffix();
QString info = tr("Unsupported type: ") + fileSuffix;
QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info);
return;
}
}
giteeImageBedUploadManager();
}
else
{
qDebug() << m_file->getName() << " No images to upload";
QString info = tr("No local images to upload: %1").arg(m_file->getName());
QMessageBox::information(nullptr, tr("Gitee Image Hosting"), info);
}
break;
}
default:
{
QApplication::restoreOverrideCursor(); // Recovery pointer
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(httpStatus == 401 || httpStatus == 404)
{
qDebug() << "Authentication failed: " << reply->errorString() << " error " << reply->error();
QString info = tr("Authentication failed: ") +
reply->errorString() +
tr("\n\nPlease check your Gitee Image Hosting parameters!");
QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info);
}
else
{
qDebug() << "Network error: " << reply->errorString() << " error " << reply->error();
QString info = tr("Network error: ") + reply->errorString() + tr("\n\nPlease check your network!");
QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info);
}
}
}
}
void VGiteeImageHosting::giteeImageBedUploadManager()
{
uploadImageCountIndex--;
QString imageToUpload;
QMapIterator<QString, QString> it(imageUrlMap);
while(it.hasNext())
{
it.next();
if(it.value() == "")
{
imageToUpload = it.key();
proDlg->setValue(uploadImageCount - 1 - uploadImageCountIndex);
proDlg->setLabelText(tr("Uploading image: %1").arg(imageToUpload));
break;
}
}
if(imageToUpload == "")
{
qDebug() << "All images have been uploaded";
giteeImageBedReplaceLink(newFileContent, m_file->fetchPath());
return;
}
if(g_config->getGiteePersonalAccessToken().isEmpty() ||
g_config->getGiteeReposName().isEmpty() ||
g_config->getGiteeUserName().isEmpty())
{
qDebug() << "Please configure the Gitee image hosting first!";
QMessageBox::warning(nullptr,
tr("Gitee Image Hosting"),
tr("Please configure the Gitee image hosting first!"));
imageUrlMap.clear();
return;
}
QString path = imageBasePath + QDir::separator();
path += imageToUpload;
giteeImageBedUploadImage(g_config->getGiteeUserName(),
g_config->getGiteeReposName(),
path,
g_config->getGiteePersonalAccessToken());
}
void VGiteeImageHosting::giteeImageBedUploadImage(const QString &p_username,
const QString &p_repository,
const QString &p_imagePath,
const QString &p_token)
{
QFileInfo fileInfo(p_imagePath.toLocal8Bit());
if(!fileInfo.exists())
{
qDebug() << "The picture does not exist in this path: " << p_imagePath.toLocal8Bit();
QString info = tr("The picture does not exist in this path: ") + p_imagePath.toLocal8Bit();
QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info);
imageUrlMap.clear();
if(imageUploaded)
{
giteeImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
return;
}
QString fileSuffix = fileInfo.suffix(); // file extension
QString fileName = fileInfo.fileName(); // filename
QString uploadUrl; // Image upload URL
uploadUrl = "https://gitee.com/api/v5/repos/" +
p_username +
"/" +
p_repository +
"/contents/" +
QString::number(QDateTime::currentDateTime().toTime_t()) +
"_" +
fileName;
if(fileSuffix != QString::fromLocal8Bit("jpg") &&
fileSuffix != QString::fromLocal8Bit("png") &&
fileSuffix != QString::fromLocal8Bit("gif"))
{
qDebug() << "Unsupported type...";
QString info = tr("Unsupported type: ") + fileSuffix;
QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info);
imageUrlMap.clear();
if(imageUploaded)
{
giteeImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
return;
}
QNetworkRequest request;
QUrl url = QUrl(uploadUrl);
request.setUrl(url);
request.setRawHeader("Content-Type", "application/json;charset=UTF-8");
if(reply != Q_NULLPTR)
{
reply->deleteLater();
}
QString param = giteeImageBedGenerateParam(p_imagePath, p_token);
QByteArray postData;
postData.append(param);
reply = manager.post(request, postData);
qDebug() << "Start uploading images: " + p_imagePath + " Waiting for upload to complete";
uploadImageStatus = true;
currentUploadImage = p_imagePath;
connect(reply, &QNetworkReply::finished, this, &VGiteeImageHosting::giteeImageBedUploadFinished);
}
void VGiteeImageHosting::giteeImageBedUploadFinished()
{
if (proDlg->wasCanceled())
{
qDebug() << "User stops uploading";
reply->abort(); // Stop network request
imageUrlMap.clear();
// The ones that have been uploaded successfully before still need to stay
if(imageUploaded)
{
giteeImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
return;
}
switch (reply->error())
{
case QNetworkReply::NoError:
{
QByteArray bytes = reply->readAll();
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(httpStatus == 201)
{
qDebug() << "Upload success";
QString downloadUrl;
QString imageName;
QJsonDocument doucment = QJsonDocument::fromJson(bytes);
if (!doucment.isNull())
{
if (doucment.isObject())
{
QJsonObject object = doucment.object();
if (object.contains("content"))
{
QJsonValue value = object.value("content");
if (value.isObject()) {
QJsonObject obj = value.toObject();
if (obj.contains("download_url"))
{
QJsonValue value = obj.value("download_url");
if (value.isString())
{
downloadUrl = value.toString();
qDebug() << "json decode: download_url : " << downloadUrl;
imageUploaded = true; // On behalf of successfully uploaded images
proDlg->setValue(uploadImageCount);
}
}
if(obj.contains("name"))
{
QJsonValue value = obj.value("name");
if(value.isString())
{
imageName = value.toString();
}
}
// Traverse key in imageurlmap
QList<QString> klist = imageUrlMap.keys();
QString temp;
for(int i=0;i<klist.count();i++)
{
temp = klist[i].split("/")[1];
if(imageName.contains(temp))
{
// You can assign values in the map
imageUrlMap.insert(klist[i], downloadUrl);
// Replace the link in the original
newFileContent.replace(klist[i], downloadUrl);
break;
}
}
// Start calling the method.
// Whether the value in the map is empty determines whether to stop.
giteeImageBedUploadManager();
}
}
}
}
else
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Resolution failure!";
qDebug() << "Resolution failure's json: " << bytes;
if(imageUploaded)
{
giteeImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
QString info = tr("JSON decode error. Please contact the developer.");
QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info);
}
}else{
// If status is not 201, it means there is a problem.
delete proDlg;
imageUrlMap.clear();
qDebug() << "Upload failure";
if(imageUploaded)
{
giteeImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
QString info = tr("Gitee status code != 201. Please contact the developer.");
QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info);
}
break;
}
default:
{
delete proDlg;
imageUrlMap.clear();
qDebug()<<"network error: " << reply->errorString() << " error " << reply->error();
QByteArray bytes = reply->readAll();
qDebug() << bytes;
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << "status: " << httpStatus;
if(imageUploaded)
{
giteeImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
QString info = tr("Uploading ") +
currentUploadImage +
tr(" \n\nNetwork error: ") +
reply->errorString() +
tr("\n\nPlease check the network or image size");
QMessageBox::warning(nullptr, tr("Gitee Image Hosting"), info);
}
}
}
void VGiteeImageHosting::giteeImageBedReplaceLink(QString p_fileContent, const QString p_filePath)
{
Q_UNUSED(p_filePath);
// This function must be executed when the upload is completed or fails in the middle.
if(!g_config->getGiteeKeepImgScale())
{
// delete image scale
p_fileContent.replace(QRegExp("\\s+=\\d+x"),"");
}
if(!g_config->getGiteeDoNotReplaceLink())
{
// Write content to file.
m_editor->setContent(p_fileContent, true);
} else {
VClipboardUtils::setTextToClipboard(QApplication::clipboard(), p_fileContent);
emit m_editor->object()->statusMessage(tr("Copied contents with new image links"));
}
// Reset.
imageUrlMap.clear();
imageUploaded = false;
}
QString VGiteeImageHosting::giteeImageBedGenerateParam(const QString &p_imagePath, const QString &p_token)
{
// According to the requirements of GitHub interface, pictures must be in Base64 format.
// Image to base64.
QByteArray hexed;
QFile imgFile(p_imagePath);
imgFile.open(QIODevice::ReadOnly);
hexed = imgFile.readAll().toBase64();
QString imgBase64 = hexed;
QJsonObject json;
json.insert("access_token", p_token);
json.insert("message", QString("updatetest"));
json.insert("content", imgBase64);
QJsonDocument document;
document.setObject(json);
QByteArray byteArray = document.toJson(QJsonDocument::Compact);
QString jsonStr(byteArray);
return jsonStr;
}
void VGiteeImageHosting::setEditor(VEditor *p_editor)
{
m_editor = p_editor;
}
VWechatImageHosting::VWechatImageHosting(VFile *p_file, QObject *p_parent)
:QObject(p_parent),
m_file(p_file)
{
reply = Q_NULLPTR;
imageUploaded = false;
}
void VWechatImageHosting::handleUploadImageToWechatRequested()
{
qDebug() << "Start processing image upload request to Wechat";
QString appid = g_config->getWechatAppid();
QString secret = g_config->getWechatSecret();
if(appid.isEmpty() || secret.isEmpty())
{
qDebug() << "Please configure the Wechat image hosting first!";
QMessageBox::warning(nullptr,
tr("Wechat Image Hosting"),
tr("Please configure the Wechat image hosting first!"));
return;
}
authenticateWechatImageHosting(appid, secret);
}
void VWechatImageHosting::authenticateWechatImageHosting(const QString p_appid, const QString p_secret)
{
qDebug() << "Start certification";
// Set the mouse to wait
QApplication::setOverrideCursor(Qt::WaitCursor);
QNetworkRequest request;
QString auth_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" +
p_appid.toLocal8Bit() +
"&secret=" +
p_secret.toLocal8Bit();
QUrl url = QUrl(auth_url);
request.setUrl(url);
if(reply != Q_NULLPTR)
{
reply->deleteLater();
}
reply = manager.get(request);
connect(reply, &QNetworkReply::finished, this, &VWechatImageHosting::wechatImageBedAuthFinished);
}
void VWechatImageHosting::wechatImageBedAuthFinished()
{
switch (reply->error())
{
case QNetworkReply::NoError:
{
QByteArray bytes = reply->readAll();
QJsonDocument document = QJsonDocument::fromJson(bytes);
if(!document.isNull())
{
if(document.isObject()){
QJsonObject object = document.object();
if(object.contains("access_token"))
{
QJsonValue value = object.value("access_token");
if(value.isString())
{
qDebug() << "Authentication successful, get token";
// Parsing token.
wechatAccessToken = value.toString();
qDebug() << "The current article path is: " << m_file->fetchPath();
imageBasePath = m_file->fetchBasePath();
newFileContent = m_file->getContent();
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file,ImageLink::LocalRelativeInternal);
QApplication::restoreOverrideCursor(); // Recovery pointer
if(images.size() > 0)
{
proDlg = new QProgressDialog(tr("Uploading images to Wechat..."),
tr("Abort"),
0,
images.size(),
nullptr);
proDlg->setWindowModality(Qt::WindowModal);
proDlg->setWindowTitle(tr("Uploading Images To Wechat"));
proDlg->setMinimumDuration(1);
uploadImageCount = images.size();
uploadImageCountIndex = uploadImageCount;
for(int i=0;i<images.size() ;i++)
{
if(images[i].m_url.contains(".png") || images[i].m_url.contains(".jpg"))
{
imageUrlMap.insert(images[i].m_url, "");
}
else
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Unsupported type...";
QFileInfo file_info(images[i].m_path.toLocal8Bit());
QString file_suffix = file_info.suffix();
QString info = tr("Unsupported type: ") + file_suffix;
QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info);
return;
}
}
wechatImageBedUploadManager();
}
else
{
qDebug() << m_file->getName() << " No pictures to upload";
QString info = tr("No local images to upload: %1").arg(m_file->getName());
QMessageBox::information(nullptr, tr("Wechat Image Hosting"), info);
}
}
}
else
{
qDebug() << "Authentication failed";
QString string = bytes;
qDebug() << string;
// You can refine the error here.
QApplication::restoreOverrideCursor();
if(string.contains("invalid ip"))
{
QString ip = string.split(" ")[2];
QClipboard *board = QApplication::clipboard();
board->setText(ip);
QMessageBox::warning(nullptr,
tr("Wechat Image Hosting"),
tr("Your ip address was set to the Clipboard!") +
tr("\nPlease add the IP address: ") +
ip +
tr(" to the Wechat ip whitelist!"));
}
else
{
QMessageBox::warning(nullptr,
tr("Wechat Image Hosting"),
tr("Please check your Wechat Image Hosting parameters!\n") +
string);
}
return;
}
}
}
else
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Resolution failure!";
qDebug() << "Resolution failure's json: " << bytes;
QString info = tr("JSON decode error. Please contact the developer.");
QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info);
}
break;
}
default:
{
QApplication::restoreOverrideCursor();
qDebug() << "Network error: " << reply->errorString() << " error " << reply->error();
QString info = tr("Network error: ") + reply->errorString() + tr("\n\nPlease check your network!");
QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info);
}
}
}
void VWechatImageHosting::wechatImageBedUploadManager()
{
uploadImageCountIndex--;
QString image_to_upload = "";
QMapIterator<QString, QString> it(imageUrlMap);
while(it.hasNext())
{
it.next();
if(it.value() == "")
{
image_to_upload = it.key();
proDlg->setValue(uploadImageCount - 1 - uploadImageCountIndex);
proDlg->setLabelText(tr("Uploading image: %1").arg(image_to_upload));
break;
}
}
if(image_to_upload == "")
{
qDebug() << "All pictures have been uploaded";
// Copy content to clipboard.
wechatImageBedReplaceLink(newFileContent, m_file->fetchPath());
return;
}
QString path = imageBasePath + QDir::separator();
path += image_to_upload;
currentUploadRelativeImagePah = image_to_upload;
wechatImageBedUploadImage(path, wechatAccessToken);
}
void VWechatImageHosting::wechatImageBedUploadImage(const QString p_imagePath, const QString p_token)
{
qDebug() << "To deal with: " << p_imagePath;
QFileInfo fileInfo(p_imagePath.toLocal8Bit());
if(!fileInfo.exists())
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "The picture does not exist in this path: " << p_imagePath.toLocal8Bit();
QString info = tr("The picture does not exist in this path: ") + p_imagePath.toLocal8Bit();
QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info);
return;
}
QString file_suffix = fileInfo.suffix(); // File extension.
QString file_name = fileInfo.fileName(); // Filename.
if(file_suffix != QString::fromLocal8Bit("jpg") && file_suffix != QString::fromLocal8Bit("png"))
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Unsupported type...";
QString info = tr("Unsupported type: ") + file_suffix;
QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info);
return;
}
qint64 file_size = fileInfo.size(); // Unit is byte.
qDebug() << "Image size: " << file_size;
if(file_size > 1024*1024)
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "The size of the picture is more than 1M";
QString info = tr("The size of the picture is more than 1M! Wechat API does not support!");
QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info);
return;
}
QString upload_img_url = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=" + p_token;
QNetworkRequest request;
request.setUrl(upload_img_url);
if(reply != Q_NULLPTR)
{
reply->deleteLater();
}
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart imagePart;
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
QString filename = p_imagePath.split(QDir::separator()).last();
QString contentVariant = QString("form-data; name=\"media\"; filename=\"%1\";").arg(filename);
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(contentVariant));
QFile *file = new QFile(p_imagePath);
if(!file->open(QIODevice::ReadOnly))
{
qDebug() << "File open failed";
}
imagePart.setBodyDevice(file);
file->setParent(multiPart);
multiPart->append(imagePart);
// Set boundary
// Because boundary is quoted by QNetworkAccessManager, the wechat api is not recognized...
QByteArray m_boundary;
m_boundary.append("multipart/form-data; boundary=");
m_boundary.append(multiPart->boundary());
request.setRawHeader(QByteArray("Content-Type"), m_boundary);
reply = manager.post(request, multiPart);
multiPart->setParent(reply);
qDebug() << "Start uploading images: " + p_imagePath + " Waiting for upload to complete";
uploadImageStatus=true;
currentUploadImage = p_imagePath;
connect(reply, &QNetworkReply::finished, this, &VWechatImageHosting::wechatImageBedUploadFinished);
}
void VWechatImageHosting::wechatImageBedUploadFinished()
{
if(proDlg->wasCanceled())
{
qDebug() << "User stops uploading";
reply->abort();
// If the upload was successful, don't use it!!!
imageUrlMap.clear();
return;
}
switch (reply->error())
{
case QNetworkReply::NoError:
{
QByteArray bytes = reply->readAll();
QJsonDocument document = QJsonDocument::fromJson(bytes);
if(!document.isNull())
{
if(document.isObject())
{
QJsonObject object = document.object();
if(object.contains("url"))
{
QJsonValue value = object.value("url");
if(value.isString())
{
qDebug() << "Authentication successful, get online link";
imageUploaded = true;
proDlg->setValue(uploadImageCount);
imageUrlMap.insert(currentUploadRelativeImagePah, value.toString());
newFileContent.replace(currentUploadRelativeImagePah, value.toString());
// Start calling the method.
// Whether the value in the map is empty determines whether to stop
wechatImageBedUploadManager();
}
}
else
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Upload failure: ";
QString error = bytes;
qDebug() << bytes;
QString info = tr("Upload failed! Please contact the developer.");
QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info);
}
}
}
else
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Resolution failure!";
qDebug() << "Resolution failure's json: " << bytes;
QString info = tr("JSON decode error. Please contact the developer.");
QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info);
}
break;
}
default:
{
delete proDlg;
qDebug()<<"Network error: " << reply->errorString() << " error " << reply->error();
QString info = tr("Uploading ") +
currentUploadImage +
tr(" \n\nNetwork error: ") +
reply->errorString() +
tr("\n\nPlease check the network or image size");
QMessageBox::warning(nullptr, tr("Wechat Image Hosting"), info);
}
}
}
void VWechatImageHosting::wechatImageBedReplaceLink(QString &p_fileContent, const QString &p_filePath)
{
Q_UNUSED(p_filePath);
if(!g_config->getWechatKeepImgScale())
{
// delete image scale
p_fileContent.replace(QRegExp("\\s+=\\d+x"),"");
}
if(!g_config->getWechatDoNotReplaceLink())
{
// Write content to file.
m_editor->setContent(p_fileContent, true);
}
VClipboardUtils::setTextToClipboard(QApplication::clipboard(), p_fileContent);
emit m_editor->object()->statusMessage(tr("Copied contents with new image links"));
QString url = g_config->getMarkdown2WechatToolUrl();
if(!url.isEmpty()) {
QMessageBox::StandardButton result;
result = QMessageBox::question(nullptr,
tr("Wechat Image Hosting"),
tr("Contents with new image links are copied. ") +
tr("Do you want to open the link of Markdown2Wechat tool?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes);
if(result == QMessageBox::Yes)
{
QDesktopServices::openUrl(QUrl(url));
}
}
// Reset.
imageUrlMap.clear();
imageUploaded = false;
}
void VWechatImageHosting::setEditor(VEditor *p_editor)
{
m_editor = p_editor;
}
VTencentImageHosting::VTencentImageHosting(VFile *p_file, QObject *p_parent)
:QObject(p_parent),
m_file(p_file)
{
reply = Q_NULLPTR;
imageUploaded = false;
}
void VTencentImageHosting::handleUploadImageToTencentRequested()
{
qDebug() << "Start processing the image upload request to Tencent Cloud";
if(g_config->getTencentAccessDomainName().isEmpty() ||
g_config->getTencentSecretId().isEmpty() ||
g_config->getTencentSecretKey().isEmpty())
{
qDebug() << "Please configure the Tencent Cloud image hosting first!";
QMessageBox::warning(nullptr,
tr("Tencent Cloud Image Hosting"),
tr("Please configure the Tencent Cloud image hosting first!"));
return;
}
findAndStartUploadImage();
}
void VTencentImageHosting::findAndStartUploadImage()
{
qDebug() << "The current article path is: " << m_file->fetchPath();
imageBasePath = m_file->fetchBasePath();
newFileContent = m_file->getContent();
QVector<ImageLink> images = VUtils::fetchImagesFromMarkdownFile(m_file,ImageLink::LocalRelativeInternal);
if(images.size() > 0)
{
proDlg = new QProgressDialog(tr("Uploading images to Tencent Cloud..."),
tr("Abort"),
0,
images.size(),
nullptr);
proDlg->setWindowModality(Qt::WindowModal);
proDlg->setWindowTitle(tr("Uploading Images To Tencent Cloud"));
proDlg->setMinimumDuration(1);
uploadImageCount = images.size();
uploadImageCountIndex = uploadImageCount;
for(int i=0;i<images.size() ;i++)
{
if(images[i].m_url.contains(".png") || images[i].m_url.contains(".jpg"))
{
imageUrlMap.insert(images[i].m_url, "");
}
else
{
delete proDlg;
imageUrlMap.clear();
qDebug() << "Unsupported type...";
QFileInfo fileInfo(images[i].m_path.toLocal8Bit());
QString fileSuffix = fileInfo.suffix();
QString info = tr("Unsupported type: ") + fileSuffix;
QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), info);
return;
}
}
tencentImageBedUploadManager();
}
else
{
qDebug() << m_file->getName() << " No images to upload";
QString info = tr("No local images to upload: %1").arg(m_file->getName());
QMessageBox::information(nullptr, tr("Tencent Cloud Image Hosting"), info);
}
}
void VTencentImageHosting::tencentImageBedUploadManager()
{
uploadImageCountIndex--;
QString image_to_upload = "";
QMapIterator<QString, QString> it(imageUrlMap);
while(it.hasNext())
{
it.next();
if(it.value() == "")
{
image_to_upload = it.key();
proDlg->setValue(uploadImageCount - 1 - uploadImageCountIndex);
proDlg->setLabelText(tr("Uploading image: %1").arg(image_to_upload));
break;
}
}
if(image_to_upload == "")
{
qDebug() << "All pictures have been uploaded";
tencentImageBedReplaceLink(newFileContent, m_file->fetchPath());
return;
}
QString path = imageBasePath + QDir::separator();
path += image_to_upload;
currentUploadRelativeImagePah = image_to_upload;
tencentImageBedUploadImage(path,
g_config->getTencentAccessDomainName(),
g_config->getTencentSecretId(),
g_config->getTencentSecretKey());
}
void VTencentImageHosting::tencentImageBedUploadImage(const QString &p_imagePath,
const QString &p_accessDomainName,
const QString &p_secretId,
const QString &p_secretKey)
{
QFileInfo fileInfo(p_imagePath.toLocal8Bit());
if(!fileInfo.exists())
{
qDebug() << "The picture does not exist in this path: " << p_imagePath.toLocal8Bit();
QString info = tr("The picture does not exist in this path: ") + p_imagePath.toLocal8Bit();
QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), info);
imageUrlMap.clear();
if(imageUploaded)
{
tencentImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
return;
}
QString fileSuffix = fileInfo.suffix(); // file extension
QString fileName = fileInfo.fileName(); // filename
QString uploadUrl; // Image upload URL
new_file_name = QString::number(QDateTime::currentDateTime().toTime_t()) +"_" + fileName;
if(fileSuffix != QString::fromLocal8Bit("jpg") && fileSuffix != QString::fromLocal8Bit("png"))
{
qDebug() << "Unsupported type...";
QString info = tr("Unsupported type: ") + fileSuffix;
QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), info);
imageUrlMap.clear();
if(imageUploaded)
{
tencentImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
return;
}
QByteArray postData = getImgContent(p_imagePath);
QNetworkRequest request;
uploadUrl = "https://" + p_accessDomainName + "/" + new_file_name;
request.setRawHeader(QByteArray("Host"), p_accessDomainName.toUtf8());
if(fileSuffix == QString::fromLocal8Bit("jpg"))
{
request.setHeader(QNetworkRequest::ContentTypeHeader, "image/jpeg");
}
else if(fileSuffix == QString::fromLocal8Bit("png"))
{
request.setHeader(QNetworkRequest::ContentTypeHeader, "image/png");
}
request.setHeader(QNetworkRequest::ContentLengthHeader, postData.size());
QString str = getAuthorizationString(p_secretId, p_secretKey, new_file_name);
request.setRawHeader(QByteArray("Authorization"), str.toUtf8());
request.setRawHeader(QByteArray("Connection"), QByteArray("close"));
request.setUrl(QUrl(uploadUrl));
if(reply != Q_NULLPTR)
{
reply->deleteLater();
}
reply = manager.put(request, postData);
qDebug() << "Start uploading images: " + p_imagePath + " Waiting for upload to complete";
uploadImageStatus = true;
currentUploadImage = p_imagePath;
connect(reply, &QNetworkReply::finished, this, &VTencentImageHosting::tencentImageBedUploadFinished);
}
void VTencentImageHosting::tencentImageBedUploadFinished()
{
if(proDlg->wasCanceled())
{
qDebug() << "User stops uploading";
reply->abort(); // Stop network request
imageUrlMap.clear();
// The ones that have been uploaded successfully before still need to stay
if(imageUploaded)
{
tencentImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
return;
}
switch (reply->error())
{
case QNetworkReply::NoError:
{
QByteArray bytes = reply->readAll();
QString replyContent = bytes;
if(replyContent.size() > 0)
{
// If there is a value returned, it means there is a problem.
qDebug() << "Upload failure";
qDebug() << replyContent;
delete proDlg;
imageUrlMap.clear();
if(imageUploaded)
{
tencentImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), replyContent);
}
else
{
qDebug() << "Upload success";
imageUploaded = true; // On behalf of successfully uploaded images
proDlg->setValue(uploadImageCount);
QString downloadUrl = "https://" +
g_config->getTencentAccessDomainName() +
"/" +
new_file_name;
imageUrlMap.insert(currentUploadRelativeImagePah, downloadUrl);
newFileContent.replace(currentUploadRelativeImagePah, downloadUrl);
// Start calling the method.
// Whether the value in the map is empty determines whether to stop.
tencentImageBedUploadManager();
}
break;
}
default:
{
delete proDlg;
imageUrlMap.clear();
if(imageUploaded)
{
tencentImageBedReplaceLink(newFileContent, m_file->fetchPath());
}
qDebug()<<"network error: " << reply->error();
if(reply->error() == 3) // HostNotFoundError
{
QString info = tr(" \n\nNetwork error: HostNotFoundError") +
tr("\n\nPlease check your network");
QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), info);
}
else
{
QByteArray bytes = reply->readAll();
QString replyContent = bytes;
qDebug() << replyContent;
QString errorCode;
QString errorMessage;
QRegExp rc("<Code>([a-zA-Z0-9 ]+)</Code>");
QRegExp rx("<Message>([a-zA-Z0-9 ]+)</Message>");
if(rc.indexIn(replyContent) != -1)
{
qDebug() << "Error Code: " + rc.cap(1);
errorCode = rc.cap(1);
}
if(rx.indexIn(replyContent) != -1)
{
qDebug() << "Error Message: " + rx.cap(1);
errorMessage = rx.cap(1);
}
QString info = tr("Uploading ") +
currentUploadImage +
"\n\n" +
errorCode +
"\n\n" +
errorMessage;
QMessageBox::warning(nullptr, tr("Tencent Cloud Image Hosting"), info);
}
}
}
}
void VTencentImageHosting::tencentImageBedReplaceLink(QString &p_fileContent, const QString &p_filePath)
{
Q_UNUSED(p_filePath);
if(!g_config->getTencentKeepImgScale())
{
// delete image scale
p_fileContent.replace(QRegExp("\\s+=\\d+x"),"");
}
if(!g_config->getTencentDoNotReplaceLink())
{
// Write content to file.
m_editor->setContent(p_fileContent, true);
} else {
VClipboardUtils::setTextToClipboard(QApplication::clipboard(), p_fileContent);
emit m_editor->object()->statusMessage(tr("Copied contents with new image links"));
}
// Reset.
imageUrlMap.clear();
imageUploaded = false;
}
QByteArray VTencentImageHosting::hmacSha1(const QByteArray &p_key, const QByteArray &p_baseString)
{
int blockSize = 64;
QByteArray key = p_key;
if (key.length() > blockSize)
{
key = QCryptographicHash::hash(key, QCryptographicHash::Sha1);
}
QByteArray innerPadding(blockSize, char(0x36));
QByteArray outerPadding(blockSize, char(0x5c));
for (int i = 0; i < key.length(); i++)
{
innerPadding[i] = innerPadding[i] ^ key.at(i);
outerPadding[i] = outerPadding[i] ^ key.at(i);
}
QByteArray total = outerPadding;
QByteArray part = innerPadding;
part.append(p_baseString);
total.append(QCryptographicHash::hash(part, QCryptographicHash::Sha1));
QByteArray hashed = QCryptographicHash::hash(total, QCryptographicHash::Sha1);
return hashed.toHex();
}
QString VTencentImageHosting::getAuthorizationString(const QString &p_secretId,
const QString &p_secretKey,
const QString &p_imgName)
{
unsigned int now=QDateTime::currentDateTime().toTime_t();
QString keyTime=QString::number(now) + ";" + QString::number(now+3600);
QString SignKey=hmacSha1(p_secretKey.toUtf8(), keyTime.toUtf8());
QString HttpString="put\n/" + p_imgName + "\n\n\n";
QString StringToSign = "sha1\n" +
keyTime +
"\n" +
QCryptographicHash::hash(HttpString.toUtf8(), QCryptographicHash::Sha1).toHex() +
"\n";
QString signature=hmacSha1(SignKey.toUtf8(), StringToSign.toUtf8());
QString Authorization;
Authorization = "q-sign-algorithm=sha1";
Authorization += "&q-ak=";
Authorization += p_secretId;
Authorization += "&q-sign-time=";
Authorization += keyTime;
Authorization += "&q-key-time=";
Authorization += keyTime;
Authorization += "&q-header-list=";
Authorization += "&q-url-param-list=";
Authorization += "&q-signature=";
Authorization += signature;
return Authorization;
}
QByteArray VTencentImageHosting::getImgContent(const QString &p_imagePath)
{
QFile file(p_imagePath);
file.open(QIODevice::ReadOnly);
QByteArray fdata = file.readAll();
file.close();
return fdata;
}
void VTencentImageHosting::setEditor(VEditor *p_editor)
{
m_editor = p_editor;
}