!17 多线程下载合并

* Update README.md
* Improve Features
* 修改获取线路的域名
* 修改默认源
* 修改多线程下载域名和图片服务器
* 修正取消下载的闪退问题
* 将图片下载由 curl 转为 QtNetworkService
* 切换多域名下载,提高下载速度
* 完成并发请求下载
This commit is contained in:
Jerry 2021-04-17 01:35:51 +08:00 committed by zty199
parent 1a4b1176fb
commit f7ced7739c
13 changed files with 584 additions and 441 deletions

View File

@ -21,9 +21,8 @@ web页面部分正在开发当中详情请见[web仓库](https://gitee.com/de
当前服务器线路列表(项目中包含):
```
http://sucdn.jerrywang.top/
http://store.jerrywang.top/
http://dcstore.spark-app.store/
https://d.store.deepinos.org.cn/
https://store.deepinos.org.cn/
```
#### 调用参数(spk规则)

View File

@ -4,4 +4,4 @@ CONFIG += ordered
SUBDIRS = third-party/QtNetworkService \
src/spark-store.pro
spark-store.depends = third-party/QtNetworkService
spark-store.depends = third-party/QtNetworkService

259
src/downloadworker.cpp Normal file
View File

@ -0,0 +1,259 @@
#include "downloadworker.h"
#include <QIODevice>
#include <QEventLoop>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDebug>
#include <QThread>
#include <QRegularExpression>
#include <QFileInfo>
#include <QDir>
DownloadWorker::DownloadWorker(QObject *parent)
{
}
void DownloadWorker::setIdentifier(int identifier)
{
this->identifier = identifier;
}
void DownloadWorker::setParamter(const QString &url, QPair<qint64, qint64> range, QFile *file)
{
this->url = url;
this->startPos = range.first;
this->endPos = range.second;
this->file = file;
}
qint64 DownloadWorker::getReceivedPos()
{
return receivedPos;
}
void DownloadWorker::doWork()
{
mgr = new QNetworkAccessManager(this);
QNetworkRequest request;
request.setUrl(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setRawHeader("Range", QString("bytes=%1-%2").arg(startPos)
.arg(endPos).toLocal8Bit());
reply = mgr->get(request);
qDebug() << "开始下载数据:" << QString(" %1~%2 -> writePos Start %3")
.arg(startPos).arg(endPos).arg(receivedPos);
connect(reply, static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
[this](QNetworkReply::NetworkError error){
if (error != QNetworkReply::NoError) {
qDebug() << "出错了:" << reply->errorString();
}
});
connect(reply, &QNetworkReply::finished, mgr, &QNetworkAccessManager::deleteLater);
connect(reply, &QNetworkReply::readyRead, this, &DownloadWorker::dataReady);
connect(reply, &QNetworkReply::finished, this, &DownloadWorker::slotFinish);
connect(reply, &QNetworkReply::downloadProgress, this, &DownloadWorker::handleProcess);
// connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, this, &DownloadWorker::doStop);
}
void DownloadWorker::doStop()
{
if (reply) {
reply->disconnect();
reply->aboutToClose();
reply->deleteLater();
reply = nullptr;
}
}
void DownloadWorker::dataReady()
{
QByteArray data = reply->readAll();
file->seek(startPos + receivedPos);
file->write(data);
receivedPos += data.size();
}
void DownloadWorker::slotFinish()
{
file->flush();
qDebug() << "数据块下载完毕:" << QString(" %1~%2 -> writePos Start %3")
.arg(startPos).arg(endPos).arg(receivedPos);
emit workFinished();
}
void DownloadWorker::handleProcess(qint64, qint64)
{
emit this->downloadProcess();
}
DownloadController::DownloadController(QObject *parent)
{
domains = {
"d1.store.deepinos.org.cn",
"d2.store.deepinos.org.cn",
"d3.store.deepinos.org.cn",
"d4.store.deepinos.org.cn",
"d5.store.deepinos.org.cn"
};
this->threadNum = domains.size() > 4 ? 4 : domains.size();
}
DownloadController::~DownloadController()
{
if (workers.size() > 0) {
for(int i = 0; i < workers.size(); i++) {
workers.at(i)->doStop();
workers.at(i)->disconnect();
workers.at(i)->deleteLater();
}
workers.clear();
}
}
void DownloadController::setFilename(QString filename)
{
this->filename = filename;
}
void DownloadController::setThreadNum(int threadNum)
{
this->threadNum = threadNum;
}
/**
* @brief
*/
void DownloadController::startDownload(const QString &url)
{
finish = 0;
// 下载任务等分,计算每个线程的下载数据
fileSize = getFileSize(url);
if (fileSize == 0) {
emit errorOccur("文件大小获取失败");
return;
}
qint64 segmentSize = fileSize / threadNum;
ranges.resize(threadNum);
QVector<qint64> receivedBytes;
receivedBytes.resize(threadNum);
for (int i = 0; i < threadNum; i++) {
ranges[i].first = i * segmentSize;
ranges[i].second = i * segmentSize + segmentSize - 1;
receivedBytes[i] = 0;
}
ranges[threadNum-1].second = fileSize; // 余数部分加入最后一个
// 打开文件
QDir tmpdir("/tmp/spark-store");
file = new QFile;
file->setFileName(tmpdir.absoluteFilePath(filename));
if (file->exists())
file->remove();
if (!file->open(QIODevice::WriteOnly)) {
delete file;
file = nullptr;
emit errorOccur(file->errorString());
return;
}
file->resize(fileSize);
// 创建下载线程
workers.clear();
for(int i = 0; i < ranges.size(); i++) {
qDebug() << QString("第%1个下载请求%2-%3").arg(i).arg(ranges.at(i).first).arg(ranges.at(i).second);
auto worker = new DownloadWorker(this);
auto range = ranges.at(i);
QString chunkUrl = replaceDomain(url, domains.at(i));
worker->setIdentifier(i);
worker->setParamter(chunkUrl, range, file);
workers.append(worker);
connect(worker, &DownloadWorker::downloadProcess, this, &DownloadController::handleProcess);
connect(worker, &DownloadWorker::workFinished, this, &DownloadController::chunkDownloadFinish);
worker->doWork();
}
}
/**
* @brief
*/
void DownloadController::stopDownload()
{
for(int i = 0; i < workers.size(); i++) {
workers.at(i)->doStop();
workers.at(i)->disconnect();
workers.at(i)->deleteLater();
}
workers.clear();
qDebug() << "文件下载路径:" << QFileInfo(file->fileName()).absoluteFilePath();
file->flush();
file->close();
delete file;
file = nullptr;
}
void DownloadController::handleProcess()
{
qint64 bytesReceived = 0;
for(int i = 0; i < workers.size(); i++) {
bytesReceived += workers.at(i)->getReceivedPos();
}
qDebug() << QString("下载进度 %1-%2").arg(bytesReceived).arg(fileSize);
emit downloadProcess(bytesReceived, fileSize);
}
void DownloadController::chunkDownloadFinish()
{
finish++;
qDebug() << QString("已下载了%1块共%2块").arg(finish).arg(threadNum);
if (finish == threadNum) {
stopDownload();
emit downloadFinished();
}
}
qint64 DownloadController::getFileSize(const QString& url)
{
QEventLoop event;
QNetworkAccessManager requestManager;
QNetworkRequest request;
request.setUrl(QUrl(url));
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
QNetworkReply *reply = requestManager.head(request);
connect(reply, static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
[this, reply](QNetworkReply::NetworkError error){
if (error != QNetworkReply::NoError) {
emit errorOccur(reply->errorString());
}
});
connect(reply, &QNetworkReply::finished, &event, &QEventLoop::quit);
event.exec();
qint64 fileSize = 0;
if (reply->rawHeader("Accept-Ranges") == QByteArrayLiteral("bytes")
&& reply->hasRawHeader(QString("Content-Length").toLocal8Bit())) {
fileSize = reply->header(QNetworkRequest::ContentLengthHeader).toUInt();
}
qDebug() << "文件大小为:" << fileSize;
reply->deleteLater();
return fileSize;
}
QString DownloadController::replaceDomain(const QString& url, const QString domain)
{
QRegularExpression regex(R"((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9])");
if (regex.match(url).hasMatch()) {
return QString(url).replace(regex.match(url).captured(), domain);
}
return url;
}

75
src/downloadworker.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef DOWNLOADWORKER_H
#define DOWNLOADWORKER_H
#include <QObject>
#include <QList>
#include <QFile>
#include <QNetworkReply>
class DownloadWorker : public QObject
{
Q_OBJECT
public:
explicit DownloadWorker(QObject *parent = nullptr);
void setIdentifier(int identifier);
void setParamter(const QString &url, QPair<qint64, qint64> range, QFile *flle);
qint64 getReceivedPos();
public slots:
void doWork();
void doStop();
void dataReady();
void slotFinish();
void handleProcess(qint64, qint64);
signals:
void resultReady(int identifier, QByteArray data);
void testSignals();
void workFinished();
void downloadProcess();
private:
int identifier;
QString url;
qint64 startPos;
qint64 endPos;
qint64 receivedPos = 0;
QNetworkReply *reply;
QNetworkAccessManager *mgr;
QFile *file;
};
class DownloadController : public QObject
{
Q_OBJECT
public:
explicit DownloadController(QObject *parent = nullptr);
~DownloadController();
void setFilename(QString filename);
void setThreadNum(int threadNum);
void startDownload(const QString &url);
void stopDownload();
qint64 getFileSize(const QString& url);
QString replaceDomain(const QString& url, const QString domain);
public slots:
void handleProcess();
void chunkDownloadFinish();
signals:
void errorOccur(const QString& msg);
void downloadProcess(qint64, qint64);
void downloadFinished();
private:
int threadNum;
QString filename;
qint64 fileSize;
QVector<QPair<qint64, qint64>> ranges;
QFile *file;
QList<DownloadWorker*> workers;
int finish = 0;
QVector<QString> domains;
};
#endif // FILEDOWNLOADWORKER_H

View File

@ -25,8 +25,12 @@ DEFINES += QT_DEPRECATED_WARNINGS
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
# 禁止输出 qWarning / qDebug 信息
CONFIG(release, debug|release): DEFINES += QT_NO_WARNING_OUTPUT QT_NO_DEBUG_OUTPUT
SOURCES += main.cpp\
appitem.cpp \
downloadworker.cpp \
widget.cpp \
downloadlist.cpp \
image_show.cpp \
@ -37,6 +41,7 @@ SOURCES += main.cpp\
HEADERS += \
appitem.h \
downloadworker.h \
widget.h \
downloadlist.h \
image_show.h \

View File

@ -33,6 +33,7 @@
#include "HttpClient.h"
#include "appitem.h"
#include "flowlayout.h"
#include "downloadworker.h"
DWIDGET_USE_NAMESPACE
@ -48,6 +49,8 @@ Widget::Widget(DBlurEffectWidget *parent) :
m_loadweb->show();
httpClient = new AeaQt::HttpClient;
// 并发下载
downloadController = new DownloadController(this);
connect(ui->menu_main,&QPushButton::clicked,[=](){Widget::chooseLeftMenu(0);});
connect(ui->menu_network,&QPushButton::clicked,[=](){Widget::chooseLeftMenu(1);});
@ -72,6 +75,7 @@ Widget::Widget(DBlurEffectWidget *parent) :
connect(&appinfoLoadThread, &SpkAppInfoLoaderThread::finishedScreenshotLoad, this, &Widget::sltAppinfoScreenshot, Qt::ConnectionType::BlockingQueuedConnection);
connect(&appinfoLoadThread, &SpkAppInfoLoaderThread::finishAllLoading, this, &Widget::sltAppinfoFinish, Qt::ConnectionType::BlockingQueuedConnection);
// 搜索事件
connect(searchEdit, &DSearchEdit::returnPressed, this, [=]()
{
@ -127,6 +131,7 @@ Widget::~Widget()
{
notify_uninit();
// delete httpFinished;
delete ui;
qDebug()<<"exit";
DApplication::quit();
@ -231,8 +236,15 @@ void Widget::initConfig()
while (getline(serverList,lineTmp)) {
ui->comboBox_server->addItem(QString::fromStdString(lineTmp));
}
for(int i = 0; i < ui->comboBox_server->count(); i++)
{
if(ui->comboBox_server->itemText(i) == "开发者模式 Dev only")
{
ui->comboBox_server->model()->setData(ui->comboBox_server->model()->index(i, 0), QVariant(0), Qt::UserRole - 1);
}
}
}else {
ui->comboBox_server->addItem("http://sucdn.jerrywang.top/");
ui->comboBox_server->addItem("https://d.store.deepinos.org.cn/");
}
// 读取服务器URL并初始化菜单项的链接
@ -241,7 +253,7 @@ void Widget::initConfig()
ui->comboBox_server->setCurrentText(readConfig.value("server/choose").toString());
appinfoLoadThread.setServer(serverUrl=readConfig.value("server/choose").toString());
}else {
appinfoLoadThread.setServer(serverUrl="http://sucdn.jerrywang.top/"); // 默认URL
appinfoLoadThread.setServer(serverUrl="https://d.store.deenos.org.cn/"); // 默认URL
}
configCanSave=true; // 防止触发保存配置信号
menuUrl[0]=serverUrl + "store/#/";
@ -266,8 +278,8 @@ void Widget::initConfig()
ui->webfoot->hide();
//初始化首页
ui->webEngineView->setUrl(menuUrl[0]);
// ui->webEngineView->setUrl(menuUrl[1]);
chooseLeftMenu(0);
// ui->webEngineView->setUrl(menuUrl[0]);
//给下载列表赋值到数组,方便调用
for (int i =0; i<LIST_MAX;i++){
@ -512,174 +524,6 @@ void Widget::updatefoot()
ui->webfoot->setFixedHeight(allh-foot);
}
int Widget::loadappinfo(QUrl arg1)
{
if(arg1.isEmpty()){
return 1;
}
// 先隐藏详情页负责显示截图的label
ui->screen_0->hide();
ui->screen_1->hide();
ui->screen_2->hide();
ui->screen_3->hide();
ui->screen_4->hide();
ui->label_appicon->clear();
ui->tag_community->hide();
ui->tag_ubuntu->hide();
ui->tag_deepin->hide();
ui->tag_uos->hide();
ui->tag_dtk5->hide();
ui->tag_dwine2->hide();
ui->tag_dwine5->hide();
ui->tag_a2d->hide();
// 重置UI状态
ui->pushButton_uninstall->hide();
ui->pushButton_website->setEnabled(false);
ui->pushButton->setEnabled(false);
ui->pushButton_translate->setEnabled(false);
ui->label_show->setText("Loading...");
ui->label_show->show();
QProcess get_json;
QDir dir("/tmp");
dir.mkdir("spark-store");
QDir::setCurrent("/tmp/spark-store");
get_json.start("curl -o app.json "+arg1.toString());
get_json.waitForFinished();
if(get_json.exitCode())
{
sendNotification(tr("Failed to download app info. Please check internet connection."));
}
QFile app_json("app.json");
if(app_json.open(QIODevice::ReadOnly)){
// 成功得到json文件
QByteArray json_array=app_json.readAll();
// 将路径转化为相应源的下载路径
urladdress=arg1.toString().left(arg1.toString().length()-8);
QStringList downloadurl=urladdress.split("/");
urladdress=ui->comboBox_server->currentText();
QString deburl=urladdress;
deburl=deburl.left(urladdress.length()-1);
urladdress="https://cdn.jsdelivr.net/gh/Jerrywang959/jsonpng@master/"; // 使用图片专用服务器请保留这行,删除后将使用源服务器
urladdress=urladdress.left(urladdress.length()-1);
for (int i=3;i<downloadurl.size();i++) {
urladdress+="/"+downloadurl[i];
deburl+="/"+downloadurl[i];
}
// 路径转化完成
QJsonObject json= QJsonDocument::fromJson(json_array).object();
appName = json["Name"].toString();
url=deburl + json["Filename"].toString();
qDebug()<<url;
ui->label_appname->setText(appName);
system("rm -r *.png");
ui->label_show->show();
// 软件信息加载
QString info;
info= tr("PkgName: ")+json["Pkgname"].toString()+"\n";
info+=tr("Version: ")+json["Version"].toString()+"\n";
if(json["Author"].toString()!="" && json["Author"].toString()!=" "){
info+=tr("Author: ")+json["Author"].toString()+"\n";
}
if(json["Website"].toString()!="" && json["Website"].toString()!=" "){
info+=tr("Official Site: ")+json["Website"].toString()+"\n";
ui->pushButton_website->show();
appweb=json["Website"].toString();
}
info+=tr("Contributor: ")+json["Contributor"].toString()+"\n";
info+=tr("Update Time: ")+json["Update"].toString()+"\n";
info+=tr("Installed Size: ")+json["Size"].toString()+"\n";
ui->label_info->setText(info);
ui->label_more->setText(json["More"].toString());
QProcess isInstall;
pkgName=json["Pkgname"].toString();
isInstall.start("dpkg -s "+json["Pkgname"].toString());
isInstall.waitForFinished();
int error=QString::fromStdString(isInstall.readAllStandardError().toStdString()).length();
if(error==0){
ui->pushButton_download->setText(tr("Reinstall"));
ui->pushButton_uninstall->show();
}else {
ui->pushButton_download->setText(tr("Install"));
}
//tag加载
QString tags=json["Tags"].toString();
QStringList tagList=tags.split(";");
for (int i=0;i<tagList.size();i++) {
if(tagList[i]=="community")
ui->tag_community->show();
if(tagList[i]=="ubuntu")
ui->tag_ubuntu->show();
if(tagList[i]=="deepin")
ui->tag_deepin->show();
if(tagList[i]=="uos")
ui->tag_uos->show();
if(tagList[i]=="dtk5")
ui->tag_dtk5->show();
if(tagList[i]=="dwine2")
ui->tag_dwine2->show();
if(tagList[i]=="dwine5")
ui->tag_dwine5->show();
if(tagList[i]=="a2d")
ui->tag_a2d->show();
}
// 图标加载
get_json.start("curl -o icon.png "+urladdress+"icon.png");
get_json.waitForFinished();
if(!get_json.exitCode()) {
QPixmap appicon(QString::fromUtf8(TMP_PATH)+"/icon.png");
ui->label_appicon->setPixmap(appicon);
ui->pushButton_download->setEnabled(true);
ui->pushButton->setEnabled(true);
ui->pushButton_translate->setEnabled(true);
ui->pushButton_website->setEnabled(true);
}
else
sendNotification(tr("Failed to load application icon."));
// 截图展示加载
QList<image_show*> label_screen;
label_screen << ui->screen_0 << ui->screen_1 << ui->screen_2 << ui->screen_3 << ui->screen_4;
for (int i = 0; i < 5; i++) {
QString cmd = "curl -o screen_"+QString::number(i+1)+".png "+urladdress+"screen_"+QString::number(i+1)+".png";
get_json.terminate();
get_json.start(cmd);
get_json.waitForFinished();
bool s = screen[i].load("screen_"+QString::number(i+1)+".png");
if(s){
label_screen[i]->setImage(screen[i]);
label_screen[i]->show();
/*
switch(i){ // 故意为之,为了清除多余截图
case 0:
label_screen[1]->hide();
case 1:
label_screen[2]->hide();
case 2:
label_screen[3]->hide();
case 3:
label_screen[4]->hide();
}*/
}else{
QFile::remove("screen_"+QString::number(i+1)+".png");
break;
}
}
ui->label_show->setText("");
ui->label_show->hide();
}
return 0;
}
void Widget::on_pushButton_download_clicked()
{
chooseLeftMenu(13);
@ -705,31 +549,37 @@ void Widget::on_pushButton_download_clicked()
system("cp icon.png icon_"+QString::number(allDownload-1).toUtf8()+".png");
download_list[allDownload-1].seticon(icon);
if(!isBusy){
file = new QFile(fileName);
if(!file->open(QIODevice::WriteOnly)){
delete file;
file = nullptr;
return ;
}
// file = new QFile(fileName);
// if(!file->open(QIODevice::WriteOnly)){
// delete file;
// file = nullptr;
// return ;
// }
nowDownload+=1;
startRequest(urList.at(nowDownload-1)); // 进行链接请求
startRequest(urList.at(nowDownload-1), fileName); // 进行链接请求
}
if(ui->pushButton_download->text()==tr("Reinstall")){
download_list[allDownload-1].reinstall=true;
}
}
void Widget::startRequest(QUrl url)
void Widget::startRequest(QUrl url, QString fileName)
{
ui->listWidget->show();
ui->label->hide();
isBusy=true;
isdownload=true;
download_list[allDownload-1].free=false;
reply = manager->get(QNetworkRequest(url));
connect(reply,SIGNAL(finished()),this,SLOT(httpFinished()));
connect(reply,SIGNAL(readyRead()),this,SLOT(httpReadyRead()));
connect(reply,SIGNAL(downloadProgress(qint64,qint64)),this,SLOT(updateDataReadProgress(qint64,qint64)));
connect(downloadController, &DownloadController::downloadProcess, this, &Widget::updateDataReadProgress);
connect(downloadController, &DownloadController::downloadFinished, this, &Widget::httpFinished);
connect(downloadController, &DownloadController::errorOccur, [this](QString msg){
this->sendNotification(msg);
});
downloadController->setFilename(fileName);
downloadController->startDownload(url.toString());
}
void Widget::searchApp(QString text)
@ -846,6 +696,8 @@ void Widget::updateDataReadProgress(qint64 bytesRead, qint64 totalBytes)
download_list[nowDownload-1].setValue((bytesRead*10000)/totalBytes); // 当前值
download_size=bytesRead;
if(download_list[nowDownload-1].close){ // 随时检测下载是否被取消
downloadController->disconnect();
downloadController->stopDownload();
download_list[nowDownload-1].closeDownload();
httpFinished();
}
@ -958,12 +810,6 @@ void Widget::sltAppinfoFinish()
void Widget::httpFinished() // 完成下载
{
file->flush();
file->close();
reply->deleteLater();
reply = nullptr;
delete file;
file = nullptr;
isdownload=false;
isBusy=false;
download_list[nowDownload-1].readyInstall();
@ -974,14 +820,7 @@ void Widget::httpFinished() // 完成下载
nowDownload+=1;
}
QString fileName=download_list[nowDownload-1].getName();
file = new QFile(fileName);
if(!file->open(QIODevice::WriteOnly))
{
delete file;
file = nullptr;
return ;
}
startRequest(urList.at(nowDownload-1));
startRequest(urList.at(nowDownload-1), fileName);
}
}
@ -1049,7 +888,7 @@ void Widget::on_pushButton_updateServer_clicked()
ui->pushButton_updateServer->setEnabled(false);
ui->comboBox_server->clear();
QFile::remove(QDir::homePath().toUtf8()+"/.config/spark-store/server.list");
system("curl -o "+QDir::homePath().toUtf8()+"/.config/spark-store/server.list http://dcstore.shenmo.tech/store/server.list");
system("curl -o "+QDir::homePath().toUtf8()+"/.config/spark-store/server.list https://d.store.deepinos.org.cn/store/server.list");
std::fstream server;
server.open(QDir::homePath().toUtf8()+"/.config/spark-store/server.list",std::ios::in);
std::string lineTmp;
@ -1058,10 +897,18 @@ void Widget::on_pushButton_updateServer_clicked()
ui->comboBox_server->addItem(QString::fromStdString(lineTmp));
}
}else {
ui->comboBox_server->addItem("http://sucdn.jerrywang.top/");
ui->comboBox_server->addItem("https://d.store.deepinos.org.cn/");
}
ui->pushButton_updateServer->setEnabled(true);
ui->comboBox_server->setCurrentIndex(0);
for(int i = 0; i < ui->comboBox_server->count(); i++)
{
if(ui->comboBox_server->itemText(i) == "开发者模式 Dev only")
{
ui->comboBox_server->model()->setData(ui->comboBox_server->model()->index(i, 0), QVariant(0), Qt::UserRole - 1);
}
}
});
}
@ -1269,6 +1116,7 @@ void Widget::on_webEngineView_urlChanged(const QUrl &arg1)
ui->pushButton_download->setEnabled(false);
ui->stackedWidget->setCurrentIndex(2);
qDebug()<<"https://demo-one-vert.vercel.app/"+type_name+"/"+pname;
qDebug()<< "链接地址:" << arg1;
/*
load.cancel();//打开并发加载线程前关闭正在执行的线程
load = QtConcurrent::run([=](){

View File

@ -39,6 +39,7 @@ class Widget;
class FlowLayout;
class DownloadController;
namespace AeaQt {
class HttpClient;
@ -51,7 +52,7 @@ class Widget : public DBlurEffectWidget
public:
explicit Widget(DBlurEffectWidget *parent = nullptr);
~Widget();
void startRequest(QUrl url);
void startRequest(QUrl url, QString fileName);
void searchApp(QString);
int nowDownload=0;
int allDownload=0;
@ -122,7 +123,6 @@ public:
private:
void initUI();
void initConfig();
int loadappinfo(QUrl);
void chooseLeftMenu(int index);
void setfoot(int);
void updatefoot();
@ -163,6 +163,7 @@ private:
AeaQt::HttpClient *httpClient;
FlowLayout *applist_grid;
QHBoxLayout *main;
DownloadController *downloadController;
};
#endif // WIDGET_H

View File

@ -5,117 +5,124 @@
#include <QJsonDocument>
#include "workerthreads.h"
#include "widget.h"
#include "HttpClient.h"
void SpkAppInfoLoaderThread::run()
{
emit requestResetUi();
QProcess get_json;
QString urladdress, deatils, more, packagename, appweb;
QDir dir("/tmp");
bool isInstalled;
dir.mkdir("spark-store");
QDir::setCurrent("/tmp/spark-store");
httpClient = new AeaQt::HttpClient;
get_json.start("curl -o app.json " + targetUrl.toString());
if(waitDownload(get_json) == -1)
return;
if(get_json.exitCode())
{
Widget::sendNotification(tr("Failed to download app info. Please check internet connection."));
}
httpClient->get(targetUrl.toString())
.header("content-type", "application/json")
.onResponse([this](QByteArray json_array) {
qDebug() << "请求应用信息 " << json_array;
QString urladdress, deatils, more, packagename, appweb;
bool isInstalled;
QFile app_json("app.json");
if(app_json.open(QIODevice::ReadOnly)){
// 成功得到json文件
QByteArray json_array = app_json.readAll();
// 将路径转化为相应源的下载路径
urladdress = targetUrl.toString().left(targetUrl.toString().length()-8);
QStringList downloadurl=urladdress.split("/");
// 将路径转化为相应源的下载路径
urladdress = targetUrl.toString().left(targetUrl.toString().length()-8);
QStringList downloadurl=urladdress.split("/");
QString deburl = serverUrl;
deburl = deburl.left(urladdress.length()-1);
urladdress = "https://cdn.jsdelivr.net/gh/Jerrywang959/jsonpng@master/"; // 使用图片专用服务器请保留这行,删除后将使用源服务器
urladdress = urladdress.left(urladdress.length()-1);
QString deburl = serverUrl;
deburl = deburl.left(urladdress.length()-1);
urladdress = "https://img.jerrywang.top/"; // 使用图片专用服务器请保留这行,删除后将使用源服务器
urladdress = urladdress.left(urladdress.length()-1);
for (int i=3;i<downloadurl.size();i++) {
urladdress+="/"+downloadurl[i];
deburl+="/"+downloadurl[i];
}
// 路径转化完成
QJsonObject json= QJsonDocument::fromJson(json_array).object();
QString appName = json["Name"].toString();
QUrl fileUrl = deburl + json["Filename"].toString();
// 软件信息加载
QString details;
details = tr("PkgName: ") + json["Pkgname"].toString()+"\n";
details += tr("Version: ") + json["Version"].toString()+"\n";
if(json["Author"].toString() != "" && json["Author"].toString() != " "){
details += tr("Author: ") + json["Author"].toString() + "\n";
}
if(json["Website"].toString() != "" && json["Website"].toString() != " "){
details += tr("Official Site: ") + json["Website"].toString() + "\n";
//ui->pushButton_website->show(); move to setinfo slot
appweb=json["Website"].toString();
}
details+=tr("Contributor: ")+json["Contributor"].toString()+"\n";
details+=tr("Update Time: ")+json["Update"].toString()+"\n";
details+=tr("Installed Size: ")+json["Size"].toString()+"\n";
more = json["More"].toString();
QProcess isInstall;
packagename = json["Pkgname"].toString();
isInstall.start("dpkg -s "+json["Pkgname"].toString());
isInstall.waitForFinished();
int error=QString::fromStdString(isInstall.readAllStandardError().toStdString()).length();
if(error==0)
isInstalled = true;
else
isInstalled = false;
emit requestSetAppInformation(&appName, &details, &more, &appweb, &packagename, &fileUrl, isInstalled);
//tag加载
QString tags=json["Tags"].toString();
QStringList tagList=tags.split(";");
emit requestSetTags(&tagList);
// 图标加载
get_json.start("curl -o icon.png "+urladdress+"icon.png");
if(waitDownload(get_json) == -1)
return;
if(!get_json.exitCode()) {
QPixmap appicon("icon.png");
emit finishedIconLoad(&appicon);
}
else
Widget::sendNotification(tr("Failed to load application icon."));
// 截图展示加载
QPixmap screenshotCache[5];
for (int i = 0; i < 5; i++) {
QString cmd = "curl -o screen_"+QString::number(i+1)+".png "+urladdress+"screen_"+QString::number(i+1)+".png";
get_json.start(cmd);
if(waitDownload(get_json) == -1)
return;
bool s = screenshotCache[i].load(QString(TMP_PATH) + "/screen_"+QString::number(i+1)+".png");
if(s){
emit finishedScreenshotLoad(&screenshotCache[i], i);
}else{
emit finishedScreenshotLoad(nullptr, i);
QFile::remove("screen_"+QString::number(i+1)+".png");
break;
for (int i=3;i<downloadurl.size();i++) {
urladdress+="/"+downloadurl[i];
deburl+="/"+downloadurl[i];
}
}
emit finishAllLoading();
}
// 路径转化完成
QJsonObject json= QJsonDocument::fromJson(json_array).object();
QString appName = json["Name"].toString();
QUrl fileUrl = deburl + json["Filename"].toString();
// 软件信息加载
QString details;
details = tr("PkgName: ") + json["Pkgname"].toString()+"\n";
details += tr("Version: ") + json["Version"].toString()+"\n";
if(json["Author"].toString() != "" && json["Author"].toString() != " "){
details += tr("Author: ") + json["Author"].toString() + "\n";
}
if(json["Website"].toString() != "" && json["Website"].toString() != " "){
details += tr("Official Site: ") + json["Website"].toString() + "\n";
//ui->pushButton_website->show(); move to setinfo slot
appweb=json["Website"].toString();
}
details+=tr("Contributor: ")+json["Contributor"].toString()+"\n";
details+=tr("Update Time: ")+json["Update"].toString()+"\n";
details+=tr("Installed Size: ")+json["Size"].toString()+"\n";
more = json["More"].toString();
QProcess isInstall;
packagename = json["Pkgname"].toString();
isInstall.start("dpkg -s "+json["Pkgname"].toString());
isInstall.waitForFinished();
int error=QString::fromStdString(isInstall.readAllStandardError().toStdString()).length();
if(error==0)
isInstalled = true;
else
isInstalled = false;
emit requestSetAppInformation(&appName, &details, &more, &appweb, &packagename, &fileUrl, isInstalled);
//tag加载
QString tags=json["Tags"].toString();
QStringList tagList=tags.split(";");
emit requestSetTags(&tagList);
// 图标加载
httpClient->get(urladdress+"icon.png")
.onResponse([this](QByteArray imgData){
QPixmap appicon;
appicon.loadFromData(imgData);
emit finishedIconLoad(&appicon);
})
.onError([this](QString errorStr) {
Widget::sendNotification(tr("Failed to load application icon."));
})
.block()
.timeout(5 * 100)
.exec();
// 截图展示加载
QPixmap screenshotCache[5];
for (int i = 0; i < 5; i++) {
httpClient->get(urladdress+"screen_"+QString::number(i+1)+".png")
.onResponse([this, i, &screenshotCache](QByteArray imgData){
bool s = screenshotCache[i].loadFromData(imgData);
if(s){
emit finishedScreenshotLoad(&screenshotCache[i], i);
}else{
emit finishedScreenshotLoad(nullptr, i);
}
})
.onError([this](QString errorStr) {
qDebug() << "截图下载失败";
// Widget::sendNotification(tr("Failed to load application screenshot."));
})
.block()
.timeout(4 * 100)
.exec();
}
emit finishAllLoading();
httpClient->deleteLater();
})
.onError([](QString errorStr) {
Widget::sendNotification(tr("Failed to download app info. Please check internet connection."));
})
.timeout(5 * 100)
.block()
.exec();
}
void SpkAppInfoLoaderThread::setUrl(const QUrl &url)
{
targetUrl = url;

View File

@ -6,6 +6,11 @@
#include <QUrl>
#include <QProcess>
namespace AeaQt {
class HttpClient;
}
class SpkAppInfoLoaderThread Q_DECL_FINAL : public QThread
{
Q_OBJECT
@ -31,6 +36,8 @@ private:
QString serverUrl;
bool finishedDownload = false;
int downloaderRetval = 0;
AeaQt::HttpClient *httpClient;
};
#endif // WORKERTHREADS_H

Binary file not shown.

View File

@ -28,47 +28,47 @@
<context>
<name>SpkAppInfoLoaderThread</name>
<message>
<location filename="../src/workerthreads.cpp" line="25"/>
<location filename="../src/workerthreads.cpp" line="117"/>
<source>Failed to download app info. Please check internet connection.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="53"/>
<location filename="../src/workerthreads.cpp" line="44"/>
<source>PkgName: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="54"/>
<location filename="../src/workerthreads.cpp" line="45"/>
<source>Version: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="56"/>
<location filename="../src/workerthreads.cpp" line="47"/>
<source>Author: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="60"/>
<location filename="../src/workerthreads.cpp" line="51"/>
<source>Official Site: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="64"/>
<location filename="../src/workerthreads.cpp" line="55"/>
<source>Contributor: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="65"/>
<location filename="../src/workerthreads.cpp" line="56"/>
<source>Update Time: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="66"/>
<location filename="../src/workerthreads.cpp" line="57"/>
<source>Installed Size: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="95"/>
<location filename="../src/workerthreads.cpp" line="85"/>
<source>Failed to load application icon.</source>
<translation type="unfinished"></translation>
</message>
@ -127,8 +127,7 @@
</message>
<message>
<location filename="../src/widget.ui" line="547"/>
<location filename="../src/widget.cpp" line="602"/>
<location filename="../src/widget.cpp" line="901"/>
<location filename="../src/widget.cpp" line="772"/>
<source>Install</source>
<translation type="unfinished"></translation>
</message>
@ -411,128 +410,82 @@
<translation type="unfinished">0B</translation>
</message>
<message>
<location filename="../src/widget.cpp" line="165"/>
<location filename="../src/widget.cpp" line="172"/>
<source>Spark Store</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="166"/>
<location filename="../src/widget.cpp" line="173"/>
<source>Search or enter spk://</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="172"/>
<location filename="../src/widget.cpp" line="179"/>
<source>Submit App</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="173"/>
<location filename="../src/widget.cpp" line="180"/>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="275"/>
<location filename="../src/widget.cpp" line="295"/>
<source>Not Exist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="121"/>
<location filename="../src/widget.cpp" line="329"/>
<location filename="../src/widget.cpp" line="333"/>
<location filename="../src/widget.cpp" line="342"/>
<location filename="../src/widget.cpp" line="346"/>
<location filename="../src/widget.cpp" line="127"/>
<location filename="../src/widget.cpp" line="349"/>
<location filename="../src/widget.cpp" line="353"/>
<location filename="../src/widget.cpp" line="362"/>
<location filename="../src/widget.cpp" line="366"/>
<source>Spark\ Store</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="546"/>
<source>Failed to download app info. Please check internet connection.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="576"/>
<source>PkgName: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="577"/>
<source>Version: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="579"/>
<source>Author: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="583"/>
<source>Official Site: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="587"/>
<source>Contributor: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="588"/>
<source>Update Time: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="589"/>
<source>Installed Size: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="598"/>
<location filename="../src/widget.cpp" line="709"/>
<location filename="../src/widget.cpp" line="898"/>
<location filename="../src/widget.cpp" line="563"/>
<location filename="../src/widget.cpp" line="769"/>
<source>Reinstall</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="637"/>
<source>Failed to load application icon.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="684"/>
<location filename="../src/widget.cpp" line="536"/>
<source>Failed to get the name to the file to be downloaded.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1047"/>
<location filename="../src/widget.cpp" line="919"/>
<source>Updating, please wait...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1100"/>
<location filename="../src/widget.cpp" line="972"/>
<source>Apt has reported an error. Please use apt update in terminal to locate the problem.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1106"/>
<location filename="../src/widget.cpp" line="978"/>
<source>Unknown error!</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1289"/>
<location filename="../src/widget.cpp" line="1164"/>
<source>Yes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1289"/>
<location filename="../src/widget.cpp" line="1164"/>
<source>No</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1290"/>
<location filename="../src/widget.cpp" line="1165"/>
<source>Information for Contributors</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1291"/>
<location filename="../src/widget.cpp" line="1166"/>
<source>Currently the translation contribution is limited to English,
and you will be redirected to our Gitee repository at which you are
supposed to be creating pull requests to contribute app info
@ -543,27 +496,27 @@ Click yes to continue.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1126"/>
<location filename="../src/widget.cpp" line="998"/>
<source>Uninstall succeeded</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="749"/>
<location filename="../src/widget.cpp" line="607"/>
<source>Relative apps Not Found!</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="757"/>
<location filename="../src/widget.cpp" line="615"/>
<source>Request Error: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1140"/>
<location filename="../src/widget.cpp" line="1012"/>
<source>Temporary cache was cleaned</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1208"/>
<location filename="../src/widget.cpp" line="1081"/>
<source>The URL has been copied to the clipboard</source>
<translation type="unfinished"></translation>
</message>

Binary file not shown.

View File

@ -48,47 +48,47 @@
<context>
<name>SpkAppInfoLoaderThread</name>
<message>
<location filename="../src/workerthreads.cpp" line="25"/>
<location filename="../src/workerthreads.cpp" line="117"/>
<source>Failed to download app info. Please check internet connection.</source>
<translation></translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="53"/>
<location filename="../src/workerthreads.cpp" line="44"/>
<source>PkgName: </source>
<translation> </translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="54"/>
<location filename="../src/workerthreads.cpp" line="45"/>
<source>Version: </source>
<translation> </translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="56"/>
<location filename="../src/workerthreads.cpp" line="47"/>
<source>Author: </source>
<translation> </translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="60"/>
<location filename="../src/workerthreads.cpp" line="51"/>
<source>Official Site: </source>
<translation> </translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="64"/>
<location filename="../src/workerthreads.cpp" line="55"/>
<source>Contributor: </source>
<translation>稿 </translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="65"/>
<location filename="../src/workerthreads.cpp" line="56"/>
<source>Update Time: </source>
<translation> </translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="66"/>
<location filename="../src/workerthreads.cpp" line="57"/>
<source>Installed Size: </source>
<translation> </translation>
</message>
<message>
<location filename="../src/workerthreads.cpp" line="95"/>
<location filename="../src/workerthreads.cpp" line="85"/>
<source>Failed to load application icon.</source>
<translation></translation>
</message>
@ -190,8 +190,7 @@
</message>
<message>
<location filename="../src/widget.ui" line="547"/>
<location filename="../src/widget.cpp" line="602"/>
<location filename="../src/widget.cpp" line="901"/>
<location filename="../src/widget.cpp" line="772"/>
<source>Install</source>
<translation></translation>
</message>
@ -471,103 +470,93 @@
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="165"/>
<location filename="../src/widget.cpp" line="172"/>
<source>Spark Store</source>
<translation>Spark </translation>
</message>
<message>
<location filename="../src/widget.cpp" line="166"/>
<location filename="../src/widget.cpp" line="173"/>
<source>Search or enter spk://</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="172"/>
<location filename="../src/widget.cpp" line="179"/>
<source>Submit App</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="173"/>
<location filename="../src/widget.cpp" line="180"/>
<source>Settings</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="275"/>
<location filename="../src/widget.cpp" line="295"/>
<source>Not Exist</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="121"/>
<location filename="../src/widget.cpp" line="329"/>
<location filename="../src/widget.cpp" line="333"/>
<location filename="../src/widget.cpp" line="342"/>
<location filename="../src/widget.cpp" line="346"/>
<location filename="../src/widget.cpp" line="127"/>
<location filename="../src/widget.cpp" line="349"/>
<location filename="../src/widget.cpp" line="353"/>
<location filename="../src/widget.cpp" line="362"/>
<location filename="../src/widget.cpp" line="366"/>
<source>Spark\ Store</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="546"/>
<source>Failed to download app info. Please check internet connection.</source>
<translation></translation>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="576"/>
<source>PkgName: </source>
<translation> </translation>
<translation type="vanished"> </translation>
</message>
<message>
<location filename="../src/widget.cpp" line="577"/>
<source>Version: </source>
<translation> </translation>
<translation type="vanished"> </translation>
</message>
<message>
<location filename="../src/widget.cpp" line="579"/>
<source>Author: </source>
<translation> </translation>
<translation type="vanished"> </translation>
</message>
<message>
<location filename="../src/widget.cpp" line="583"/>
<source>Official Site: </source>
<translation> </translation>
<translation type="vanished"> </translation>
</message>
<message>
<location filename="../src/widget.cpp" line="587"/>
<source>Contributor: </source>
<translation>稿 </translation>
<translation type="vanished">稿 </translation>
</message>
<message>
<location filename="../src/widget.cpp" line="588"/>
<source>Update Time: </source>
<translation> </translation>
<translation type="vanished"> </translation>
</message>
<message>
<location filename="../src/widget.cpp" line="589"/>
<source>Installed Size: </source>
<translation> </translation>
<translation type="vanished"> </translation>
</message>
<message>
<location filename="../src/widget.cpp" line="598"/>
<location filename="../src/widget.cpp" line="709"/>
<location filename="../src/widget.cpp" line="898"/>
<location filename="../src/widget.cpp" line="563"/>
<location filename="../src/widget.cpp" line="769"/>
<source>Reinstall</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="637"/>
<source>Failed to load application icon.</source>
<translation></translation>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="684"/>
<location filename="../src/widget.cpp" line="536"/>
<source>Failed to get the name to the file to be downloaded.</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="749"/>
<location filename="../src/widget.cpp" line="607"/>
<source>Relative apps Not Found!</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="757"/>
<location filename="../src/widget.cpp" line="615"/>
<source>Request Error: %1</source>
<translation>%1</translation>
</message>
@ -576,37 +565,37 @@
<translation type="vanished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1047"/>
<location filename="../src/widget.cpp" line="919"/>
<source>Updating, please wait...</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1100"/>
<location filename="../src/widget.cpp" line="972"/>
<source>Apt has reported an error. Please use apt update in terminal to locate the problem.</source>
<translation>使apt update来查看错误原因</translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1106"/>
<location filename="../src/widget.cpp" line="978"/>
<source>Unknown error!</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1289"/>
<location filename="../src/widget.cpp" line="1164"/>
<source>Yes</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1289"/>
<location filename="../src/widget.cpp" line="1164"/>
<source>No</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1290"/>
<location filename="../src/widget.cpp" line="1165"/>
<source>Information for Contributors</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1291"/>
<location filename="../src/widget.cpp" line="1166"/>
<source>Currently the translation contribution is limited to English,
and you will be redirected to our Gitee repository at which you are
supposed to be creating pull requests to contribute app info
@ -642,17 +631,17 @@ Click yes to continue.</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1126"/>
<location filename="../src/widget.cpp" line="998"/>
<source>Uninstall succeeded</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1140"/>
<location filename="../src/widget.cpp" line="1012"/>
<source>Temporary cache was cleaned</source>
<translation></translation>
</message>
<message>
<location filename="../src/widget.cpp" line="1208"/>
<location filename="../src/widget.cpp" line="1081"/>
<source>The URL has been copied to the clipboard</source>
<translation></translation>
</message>