mirror of
https://gitee.com/spark-store-project/spark-store
synced 2025-12-13 12:22:05 +08:00
Temporary commit
This commit is contained in:
@@ -56,7 +56,12 @@ namespace SpkUi
|
|||||||
|
|
||||||
void SpkPageAppDetails::SetWebsiteLink(QString url)
|
void SpkPageAppDetails::SetWebsiteLink(QString url)
|
||||||
{
|
{
|
||||||
mWebsite->setText(QString("<a href=\"%1\">%1</a>").arg(url));
|
mWebsite->setText(QString("<a href=\"%1\">%2</a>").arg(url, tr("Website link")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpkPageAppDetails::SetPackagePath(QString url)
|
||||||
|
{
|
||||||
|
mPkgPath = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpkPageAppDetails::SpkPageAppDetails(QWidget *parent) : SpkPageBase(parent)
|
SpkPageAppDetails::SpkPageAppDetails(QWidget *parent) : SpkPageBase(parent)
|
||||||
@@ -65,16 +70,20 @@ namespace SpkUi
|
|||||||
mMainArea->setWidgetResizable(true);
|
mMainArea->setWidgetResizable(true);
|
||||||
mMainArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
mMainArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
|
||||||
mLay4MainArea = new QVBoxLayout(this);
|
mMainLay = new QVBoxLayout(this);
|
||||||
mLay4MainArea->addWidget(mMainArea);
|
mMainLay->addWidget(mMainArea);
|
||||||
|
|
||||||
mMainLay = new QVBoxLayout(mMainArea);
|
mBottomBar = new QWidget;
|
||||||
mMainLay->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
mMainLay->addWidget(mBottomBar);
|
||||||
|
|
||||||
|
mDetailsLay = new QVBoxLayout(mMainArea);
|
||||||
|
mDetailsLay->setSizeConstraint(QLayout::SetMinAndMaxSize);
|
||||||
|
|
||||||
mAppIcon = new QLabel;
|
mAppIcon = new QLabel;
|
||||||
|
|
||||||
mAppTitle = new QLabel;
|
mAppTitle = new QLabel;
|
||||||
mAppTitle->setObjectName("styDetTitle");
|
mAppTitle->setObjectName("styDetTitle");
|
||||||
|
mAppTitle->setWordWrap(true);
|
||||||
|
|
||||||
mAppDescription = new QLabel;
|
mAppDescription = new QLabel;
|
||||||
mAppDescription->setObjectName("styDetDesc");
|
mAppDescription->setObjectName("styDetDesc");
|
||||||
@@ -87,9 +96,11 @@ namespace SpkUi
|
|||||||
mAppShortDesc->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
|
mAppShortDesc->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
|
||||||
mAppShortDesc->setMinimumWidth(100);
|
mAppShortDesc->setMinimumWidth(100);
|
||||||
mVersion = new QLabel;
|
mVersion = new QLabel;
|
||||||
|
mVersion->setWordWrap(true);
|
||||||
mWebsite = new QLabel;
|
mWebsite = new QLabel;
|
||||||
mPkgName = new QLabel;
|
mPkgName = new QLabel;
|
||||||
mPkgName->setObjectName("styDetPkg");
|
mPkgName->setObjectName("styDetPkg");
|
||||||
|
mPkgName->setWordWrap(true);
|
||||||
|
|
||||||
mTitleLay = new QVBoxLayout;
|
mTitleLay = new QVBoxLayout;
|
||||||
mTitleLay->setAlignment(Qt::AlignTop);
|
mTitleLay->setAlignment(Qt::AlignTop);
|
||||||
@@ -132,18 +143,49 @@ namespace SpkUi
|
|||||||
// mDetailWidget->setLayout(mDetailLay);
|
// mDetailWidget->setLayout(mDetailLay);
|
||||||
// mDetailWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
// mDetailWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||||
|
|
||||||
mMainLay->setAlignment(Qt::AlignTop);
|
mDetailsLay->setAlignment(Qt::AlignTop);
|
||||||
mMainLay->addWidget(mIconTitleWidget);
|
mDetailsLay->addWidget(mIconTitleWidget);
|
||||||
mMainLay->addLayout(mDetailLay);
|
mDetailsLay->addLayout(mDetailLay);
|
||||||
mMainLay->addWidget(mAppDescription);
|
mDetailsLay->addWidget(mAppDescription);
|
||||||
// mMainLay->addStretch();
|
// mMainLay->addStretch();
|
||||||
mWid4MainArea = new QWidget;
|
mWid4MainArea = new QWidget;
|
||||||
mWid4MainArea->setLayout(mMainLay);
|
mWid4MainArea->setLayout(mDetailsLay);
|
||||||
|
|
||||||
mMainArea->setWidget(mWid4MainArea);
|
mMainArea->setWidget(mWid4MainArea);
|
||||||
|
|
||||||
mWebsite->setTextFormat(Qt::RichText);
|
mWebsite->setTextFormat(Qt::RichText);
|
||||||
mWebsite->setOpenExternalLinks(true);
|
mWebsite->setOpenExternalLinks(true);
|
||||||
|
|
||||||
|
// Bottom bar buttons
|
||||||
|
mBottomBarLay = new QHBoxLayout;
|
||||||
|
mBottomBar->setLayout(mBottomBarLay);
|
||||||
|
|
||||||
|
mBtnDownload = new QPushButton;
|
||||||
|
mBtnDownload->setText(tr("Download"));
|
||||||
|
|
||||||
|
mBtnInstall = new QPushButton;
|
||||||
|
mBtnInstall->setText(tr("Install"));
|
||||||
|
|
||||||
|
mBtnUninstall = new QPushButton;
|
||||||
|
mBtnUninstall->setText(tr("Uninstall"));
|
||||||
|
|
||||||
|
mBtnRequestUpdate = new QPushButton;
|
||||||
|
mBtnRequestUpdate->setText(tr("Request Update"));
|
||||||
|
|
||||||
|
mBtnReport = new QPushButton;
|
||||||
|
mBtnReport->setText(tr("Report"));
|
||||||
|
|
||||||
|
mBottomBarLay->addStretch();
|
||||||
|
mBottomBarLay->addWidget(mBtnDownload);
|
||||||
|
mBottomBarLay->addWidget(mBtnInstall);
|
||||||
|
mBottomBarLay->addWidget(mBtnUninstall);
|
||||||
|
mBottomBarLay->addWidget(mBtnRequestUpdate);
|
||||||
|
mBottomBarLay->addWidget(mBtnReport);
|
||||||
|
|
||||||
|
connect(mBtnDownload, &QPushButton::clicked,
|
||||||
|
[=](){ emit RequestDownload(mAppTitle->text(), mPkgName->text(),
|
||||||
|
"/store/chat/icalingua/icalingua_2.4.4-Deus-non-vult_amd64.deb");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpkPageAppDetails::ResourceAcquisitionFinished(int id, ResourceResult result)
|
void SpkPageAppDetails::ResourceAcquisitionFinished(int id, ResourceResult result)
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ namespace SpkUi
|
|||||||
|
|
||||||
auto iconRes = RES->RequestResource(id, pkgName, SpkResource::ResourceType::AppIcon,
|
auto iconRes = RES->RequestResource(id, pkgName, SpkResource::ResourceType::AppIcon,
|
||||||
iconUrl, 0);
|
iconUrl, 0);
|
||||||
// TODO: cache scaled icons
|
|
||||||
QPixmap icon;
|
QPixmap icon;
|
||||||
if(iconRes.status == SpkResource::ResourceStatus::Ready)
|
if(iconRes.status == SpkResource::ResourceStatus::Ready)
|
||||||
{
|
{
|
||||||
@@ -80,7 +79,6 @@ namespace SpkUi
|
|||||||
RES->PurgeCachedResource(pkgName, SpkResource::ResourceType::AppIcon, 0);
|
RES->PurgeCachedResource(pkgName, SpkResource::ResourceType::AppIcon, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: [TEST] prepare icons for loading entries
|
|
||||||
else
|
else
|
||||||
item->SetIcon(*mLoadingIcon);
|
item->SetIcon(*mLoadingIcon);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,18 @@ SpkUi::SpkPageDownloads::SpkPageDownloads(QWidget *parent) :
|
|||||||
mScrollArea->setWidget(mScrollWidget);
|
mScrollArea->setWidget(mScrollWidget);
|
||||||
mScrollArea->setWidgetResizable(true);
|
mScrollArea->setWidgetResizable(true);
|
||||||
mMainLay->addWidget(mScrollArea);
|
mMainLay->addWidget(mScrollArea);
|
||||||
|
mMainLay->addStretch();
|
||||||
setLayout(mMainLay);
|
setLayout(mMainLay);
|
||||||
|
|
||||||
|
mDownloadMgr = new SpkDownloadMgr(this);
|
||||||
|
connect(mDownloadMgr, &SpkDownloadMgr::DownloadProgressed,
|
||||||
|
this, &SpkPageDownloads::DownloadProgress);
|
||||||
|
|
||||||
|
mNextDownloadId = 0;
|
||||||
|
mCurrentStatus = Idle;
|
||||||
|
|
||||||
|
connect(mDownloadMgr, &SpkDownloadMgr::DownloadStopped,
|
||||||
|
this, &SpkPageDownloads::DownloadStopped);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpkUi::SpkPageDownloads::~SpkPageDownloads()
|
SpkUi::SpkPageDownloads::~SpkPageDownloads()
|
||||||
@@ -25,13 +36,80 @@ SpkUi::SpkPageDownloads::~SpkPageDownloads()
|
|||||||
|
|
||||||
void SpkUi::SpkPageDownloads::DownloadProgress(qint64 downloadedBytes, qint64 totalBytes, int id)
|
void SpkUi::SpkPageDownloads::DownloadProgress(qint64 downloadedBytes, qint64 totalBytes, int id)
|
||||||
{
|
{
|
||||||
// TODO
|
if(!totalBytes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(mCurrentStatus == Waiting && totalBytes)
|
||||||
|
{
|
||||||
|
mCurrentStatus = Downloading;
|
||||||
|
mEntries[id]->SetTotalBytes(totalBytes);
|
||||||
|
mEntries[id]->SetStatus(SpkDownloadEntry::Downloading);
|
||||||
|
}
|
||||||
|
mEntries[id]->Progress(downloadedBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpkUi::SpkPageDownloads::AddDownloadTask(QString name, QString pkgName, QString path)
|
void SpkUi::SpkPageDownloads::AddDownloadTask(QString name, QString pkgName, QString path)
|
||||||
{
|
{
|
||||||
|
// Add a new download entry into the UI
|
||||||
|
auto entry = new SpkDownloadEntry;
|
||||||
|
auto iconData = RES->CacheLookup(pkgName, SpkResource::ResourceType::AppIcon, 0);
|
||||||
|
QPixmap icon;
|
||||||
|
if(iconData.status != SpkResource::ResourceStatus::Ready || !icon.loadFromData(iconData.data))
|
||||||
|
icon.load(":/icons/broken-icon.svg");
|
||||||
|
entry->SetBasicInfo(name, icon);
|
||||||
|
entry->SetStatus(SpkDownloadEntry::Waiting);
|
||||||
|
|
||||||
|
mEntries[mNextDownloadId] = entry;
|
||||||
|
mLayEntries->addWidget(entry);
|
||||||
|
|
||||||
|
if(mCurrentStatus != Idle)
|
||||||
|
mWaitingDownloads.enqueue({ mNextDownloadId, path }); // Queue download task for future
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!mDownloadMgr->StartNewDownload(path, mNextDownloadId)) // Initiate a download task when idle
|
||||||
|
emit mDownloadMgr->DownloadStopped(SpkDownloadMgr::FailNoVaibleServer, mNextDownloadId);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mCurrentStatus = Waiting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mNextDownloadId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpkUi::SpkPageDownloads::DownloadStopped(SpkDownloadMgr::TaskResult status, int id)
|
void SpkUi::SpkPageDownloads::DownloadStopped(SpkDownloadMgr::TaskResult status, int id)
|
||||||
{
|
{
|
||||||
|
switch(status)
|
||||||
|
{
|
||||||
|
case SpkDownloadMgr::Success:
|
||||||
|
mEntries[id]->SetStatus(SpkDownloadEntry::ToBeInstalled);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SpkDownloadMgr::FailCannotCreateFile:
|
||||||
|
mEntries[id]->SetStatus(SpkDownloadEntry::Failed,
|
||||||
|
tr("Cannot create download file. Download failed."));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SpkDownloadMgr::FailNoVaibleServer:
|
||||||
|
mEntries[id]->SetStatus(SpkDownloadEntry::Failed,
|
||||||
|
tr("Connection unstable or server failure. Download failed."));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SpkDownloadMgr::Fail:
|
||||||
|
mEntries[id]->SetStatus(SpkDownloadEntry::Failed,
|
||||||
|
tr("Unknown error. Download failed."));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue next download task
|
||||||
|
if(!mWaitingDownloads.isEmpty())
|
||||||
|
{
|
||||||
|
auto nextTask = mWaitingDownloads.dequeue();
|
||||||
|
mDownloadMgr->StartNewDownload(nextTask.second, nextTask.first);
|
||||||
|
mCurrentStatus = Waiting;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mCurrentStatus = Idle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
#include "spkdownloadentry.h"
|
#include "spkdownloadentry.h"
|
||||||
#include "spklogging.h"
|
#include "spklogging.h"
|
||||||
|
#include "spkutils.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
constexpr QSize SpkDownloadEntry::IconSize;
|
constexpr QSize SpkDownloadEntry::IconSize;
|
||||||
|
|
||||||
@@ -37,6 +39,8 @@ SpkDownloadEntry::SpkDownloadEntry(QWidget *parent)
|
|||||||
mLayMain->addWidget(mBtnDelete);
|
mLayMain->addWidget(mBtnDelete);
|
||||||
|
|
||||||
setLayout(mLayMain);
|
setLayout(mLayMain);
|
||||||
|
|
||||||
|
mLastReportTime = QTime::currentTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,13 +49,20 @@ SpkDownloadEntry::~SpkDownloadEntry()
|
|||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpkDownloadEntry::SetTotalBytes(qint64 total)
|
||||||
|
{
|
||||||
|
mTotalBytes = total;
|
||||||
|
mReadableTotalSize = SpkUtils::BytesToSize(total);
|
||||||
|
mLastReportTime = QTime::currentTime();
|
||||||
|
}
|
||||||
|
|
||||||
void SpkDownloadEntry::SetBasicInfo(QString name, QPixmap icon)
|
void SpkDownloadEntry::SetBasicInfo(QString name, QPixmap icon)
|
||||||
{
|
{
|
||||||
mAppName->setText(name);
|
mAppName->setText(name);
|
||||||
mIcon->setPixmap(icon.scaled(IconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
mIcon->setPixmap(icon.scaled(IconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpkDownloadEntry::SetStatus(DownloadEntryStatus status)
|
void SpkDownloadEntry::SetStatus(DownloadEntryStatus status, QString msg)
|
||||||
{
|
{
|
||||||
switch(status)
|
switch(status)
|
||||||
{
|
{
|
||||||
@@ -73,7 +84,7 @@ void SpkDownloadEntry::SetStatus(DownloadEntryStatus status)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case Failed:
|
case Failed:
|
||||||
mMessage->setText(tr("Download Failed"));
|
mMessage->setText(msg);
|
||||||
mProgress->setVisible(false);
|
mProgress->setVisible(false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -95,7 +106,7 @@ void SpkDownloadEntry::SetStatus(DownloadEntryStatus status)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case InstallFailed:
|
case InstallFailed:
|
||||||
mMessage->setText(tr("Install Failed"));
|
mMessage->setText(msg.isEmpty() ? tr("Install Failed") : msg);
|
||||||
mLoading->End();
|
mLoading->End();
|
||||||
mLoading->setVisible(false);
|
mLoading->setVisible(false);
|
||||||
break;
|
break;
|
||||||
@@ -105,7 +116,22 @@ void SpkDownloadEntry::SetStatus(DownloadEntryStatus status)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpkDownloadEntry::SetProgress(int p)
|
void SpkDownloadEntry::Progress(qint64 bytes)
|
||||||
{
|
{
|
||||||
mProgress->setValue(p);
|
auto now = QTime::currentTime();
|
||||||
|
auto msecDiff = mLastReportTime.msecsTo(now);
|
||||||
|
|
||||||
|
if(msecDiff != 0)
|
||||||
|
{
|
||||||
|
auto bytesPerSec = (bytes - mDownloadedBytes) / (msecDiff / 1000.0);
|
||||||
|
qDebug() << "Bytes" << bytes - mDownloadedBytes
|
||||||
|
<< "MsDiff" << msecDiff / 1000.0
|
||||||
|
<< "Bytes-Per-Seg" << bytesPerSec;
|
||||||
|
auto speedSize = SpkUtils::BytesToSize(static_cast<size_t>(bytesPerSec));
|
||||||
|
mMessage->setText(QString("%1/%2(%3/s)")
|
||||||
|
.arg(SpkUtils::BytesToSize(bytes), mReadableTotalSize, speedSize));
|
||||||
|
}
|
||||||
|
mDownloadedBytes = bytes;
|
||||||
|
mProgress->setValue(static_cast<int>(((double)bytes) / mTotalBytes * 1000));
|
||||||
|
mLastReportTime = now;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ void SpkMainWindow::SwitchToPage(SpkUi::SpkStackedPages page)
|
|||||||
auto tryActivate = qobject_cast<SpkPageBase *>(ui->Pager->currentWidget());
|
auto tryActivate = qobject_cast<SpkPageBase *>(ui->Pager->currentWidget());
|
||||||
if(tryActivate)
|
if(tryActivate)
|
||||||
tryActivate->Activated();
|
tryActivate->Activated();
|
||||||
|
|
||||||
|
ui->BtnBack->setVisible(page == SpkUi::SpkStackedPages::PgAppDetails);
|
||||||
|
ui->BtnBack->setEnabled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,9 +92,11 @@ void SpkMainWindow::RefreshCategoryData()
|
|||||||
void SpkMainWindow::CategoryDataReceived()
|
void SpkMainWindow::CategoryDataReceived()
|
||||||
{
|
{
|
||||||
QJsonValue retval;
|
QJsonValue retval;
|
||||||
if(!SpkUtils::VerifyReplyJson(mCategoryGetReply, retval) || !retval.isArray())
|
auto verify = SpkUtils::VerifyReplyJson(mCategoryGetReply, retval);
|
||||||
|
if(verify || !retval.isArray())
|
||||||
{
|
{
|
||||||
sErr(tr("Failed to load categories!"));
|
sErr(tr("Failed to load categories! Type=%1 Code=%2").arg(retval.type()).arg(verify));
|
||||||
|
sNotify(tr("Cannot load categories! Type: %1 Code: %2").arg(retval.type()).arg(verify));
|
||||||
// TODO: Switch to an error page
|
// TODO: Switch to an error page
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -119,9 +124,11 @@ void SpkMainWindow::EnterCategoryList(int aCategoryId, int aPage)
|
|||||||
void SpkMainWindow::CategoryListDataReceived()
|
void SpkMainWindow::CategoryListDataReceived()
|
||||||
{
|
{
|
||||||
QJsonValue retval;
|
QJsonValue retval;
|
||||||
if(!SpkUtils::VerifyReplyJson(mCategoryAppListGetReply, retval) || !retval.isObject())
|
int verify = SpkUtils::VerifyReplyJson(mCategoryAppListGetReply, retval);
|
||||||
|
if(verify || !retval.isObject())
|
||||||
{
|
{
|
||||||
sErrPop(tr("Failed to load app list of category! Type of retval: %1.").arg(retval.type()));
|
sErr(tr("Failed to load app list of category! Type=%1 Code=%2").arg(retval.type()).arg(verify));
|
||||||
|
sNotify(tr("Failed to load app list of category! Type: %1 Code: %2").arg(retval.type()).arg(verify));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SwitchToPage(SpkUi::PgAppList);
|
SwitchToPage(SpkUi::PgAppList);
|
||||||
@@ -149,9 +156,11 @@ void SpkMainWindow::SearchKeyword(QString aKeyword, int aPage)
|
|||||||
void SpkMainWindow::SearchDataReceived()
|
void SpkMainWindow::SearchDataReceived()
|
||||||
{
|
{
|
||||||
QJsonValue retval;
|
QJsonValue retval;
|
||||||
if(!SpkUtils::VerifyReplyJson(mCategoryAppListGetReply, retval) || !retval.isObject())
|
auto verify = SpkUtils::VerifyReplyJson(mCategoryAppListGetReply, retval);
|
||||||
|
if(verify || !retval.isObject())
|
||||||
{
|
{
|
||||||
sErrPop(tr("Failed to search keyword! Type of retval: %1.").arg(retval.type()));
|
sErr(tr("Failed to search keyword! Type=%1 Code=%2").arg(retval.type()).arg(verify));
|
||||||
|
sNotify(tr("Failed to search keyword! Type: %1 Code: %2").arg(retval.type()).arg(verify));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SwitchToPage(SpkUi::PgAppList);
|
SwitchToPage(SpkUi::PgAppList);
|
||||||
@@ -233,9 +242,11 @@ void SpkMainWindow::EnterAppDetails(int aAppId)
|
|||||||
void SpkMainWindow::AppDetailsDataReceived()
|
void SpkMainWindow::AppDetailsDataReceived()
|
||||||
{
|
{
|
||||||
QJsonValue retval;
|
QJsonValue retval;
|
||||||
if(!SpkUtils::VerifyReplyJson(mAppDetailsGetReply, retval) || !retval.isObject())
|
auto verify = SpkUtils::VerifyReplyJson(mAppDetailsGetReply, retval);
|
||||||
|
if(verify || !retval.isObject())
|
||||||
{
|
{
|
||||||
sErrPop(tr("Failed to open app details page! Type of retval: %1.").arg(retval.type()));
|
sErr(tr("Failed to open app details page! Type=%1 Code=%2").arg(retval.type()).arg(verify));
|
||||||
|
sNotify(tr("Failed to open app details page! Type: %1 Code: %2").arg(retval.type()).arg(verify));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SwitchToPage(SpkUi::PgAppList);
|
SwitchToPage(SpkUi::PgAppList);
|
||||||
@@ -245,7 +256,8 @@ void SpkMainWindow::AppDetailsDataReceived()
|
|||||||
|
|
||||||
void SpkMainWindow::PopulateAppDetails(QJsonObject appDetails)
|
void SpkMainWindow::PopulateAppDetails(QJsonObject appDetails)
|
||||||
{
|
{
|
||||||
QString pkgName, author, contributor, site, iconPath, arch, version, details, shortDesc, name;
|
QString pkgName, author, contributor, site, iconPath, arch, version, details, shortDesc, name,
|
||||||
|
pkgPath;
|
||||||
QStringList screenshots, tags;
|
QStringList screenshots, tags;
|
||||||
int packageSize;
|
int packageSize;
|
||||||
static auto err =
|
static auto err =
|
||||||
@@ -280,6 +292,8 @@ void SpkMainWindow::PopulateAppDetails(QJsonObject appDetails)
|
|||||||
arch = appDetails.value("arch").toString();
|
arch = appDetails.value("arch").toString();
|
||||||
if(appDetails.contains("size") && appDetails.value("size").isDouble())
|
if(appDetails.contains("size") && appDetails.value("size").isDouble())
|
||||||
packageSize = appDetails.value("size").toInt();
|
packageSize = appDetails.value("size").toInt();
|
||||||
|
if(appDetails.contains("deb_url") && appDetails.value("deb_url").isString())
|
||||||
|
pkgPath = appDetails.value("deb_url").toString();
|
||||||
|
|
||||||
QJsonArray imgs;
|
QJsonArray imgs;
|
||||||
if(appDetails.contains("img_urls") && appDetails.value("img_urls").isArray())
|
if(appDetails.contains("img_urls") && appDetails.value("img_urls").isArray())
|
||||||
@@ -310,6 +324,7 @@ void SpkMainWindow::PopulateAppDetails(QJsonObject appDetails)
|
|||||||
w->mArch->SetValue(arch);
|
w->mArch->SetValue(arch);
|
||||||
w->mSize->SetValue(SpkUtils::BytesToSize(packageSize));
|
w->mSize->SetValue(SpkUtils::BytesToSize(packageSize));
|
||||||
w->mVersion->setText(version);
|
w->mVersion->setText(version);
|
||||||
|
w->SetPackagePath(pkgPath);
|
||||||
SwitchToPage(SpkUi::PgAppDetails);
|
SwitchToPage(SpkUi::PgAppDetails);
|
||||||
ui->AppDetailsItem->setHidden(false);
|
ui->AppDetailsItem->setHidden(false);
|
||||||
ui->CategoryWidget->setCurrentItem(ui->AppDetailsItem);
|
ui->CategoryWidget->setCurrentItem(ui->AppDetailsItem);
|
||||||
@@ -345,6 +360,8 @@ void SpkMainWindow::Initialize()
|
|||||||
connect(SpkUi::DtkPlugin, &SpkDtkPlugin::DarkLightThemeChanged,
|
connect(SpkUi::DtkPlugin, &SpkDtkPlugin::DarkLightThemeChanged,
|
||||||
this, &SpkMainWindow::ReloadThemedUiIcons);
|
this, &SpkMainWindow::ReloadThemedUiIcons);
|
||||||
}
|
}
|
||||||
|
connect(ui->PageAppDetails, &SpkUi::SpkPageAppDetails::RequestDownload,
|
||||||
|
ui->PageDownloads, &SpkUi::SpkPageDownloads::AddDownloadTask);
|
||||||
|
|
||||||
// Register themed button icons
|
// Register themed button icons
|
||||||
// mThemedUiIconReferences.append({ ui->BtnSettings, "settings" });
|
// mThemedUiIconReferences.append({ ui->BtnSettings, "settings" });
|
||||||
@@ -417,6 +434,11 @@ SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent)
|
|||||||
BtnDayNight->setFixedSize({ 40, 40 });
|
BtnDayNight->setFixedSize({ 40, 40 });
|
||||||
BtnDayNight->SetIcon(QIcon(":/icons/daynight.svg"), QSize(20, 20));
|
BtnDayNight->SetIcon(QIcon(":/icons/daynight.svg"), QSize(20, 20));
|
||||||
|
|
||||||
|
BtnBack = new SpkIconButton(this);
|
||||||
|
BtnBack->setFixedSize({ 40, 40 });
|
||||||
|
BtnBack->SetIcon(QIcon(":/icons/back.svg"), QSize(20, 20));
|
||||||
|
BtnBack->setVisible(false);
|
||||||
|
|
||||||
HLaySideTop->addWidget(StoreIcon);
|
HLaySideTop->addWidget(StoreIcon);
|
||||||
HLaySideTop->addStretch();
|
HLaySideTop->addStretch();
|
||||||
HLaySideTop->addWidget(BtnDayNight);
|
HLaySideTop->addWidget(BtnDayNight);
|
||||||
@@ -510,8 +532,14 @@ SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent)
|
|||||||
SearchEdit->setFixedWidth(static_cast<int>(250 * v) + 30);
|
SearchEdit->setFixedWidth(static_cast<int>(250 * v) + 30);
|
||||||
});
|
});
|
||||||
connect(ActClearSearchBar, &QAction::triggered, [=](){ SearchEdit->clear(); });
|
connect(ActClearSearchBar, &QAction::triggered, [=](){ SearchEdit->clear(); });
|
||||||
|
connect(BtnBack, &QPushButton::clicked,
|
||||||
|
[=](){
|
||||||
|
SidebarMgr->GoBack();
|
||||||
|
BtnBack->setEnabled(false);
|
||||||
|
});
|
||||||
|
|
||||||
auto space = TitleBar->GetUserSpace();
|
auto space = TitleBar->GetUserSpace();
|
||||||
|
space->addWidget(BtnBack);
|
||||||
space->addWidget(SearchEdit);
|
space->addWidget(SearchEdit);
|
||||||
space->addStretch();
|
space->addStretch();
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ namespace SpkUi
|
|||||||
|
|
||||||
void LoadAppResources(QString pkgName, QString icon, QStringList screenshots, QStringList tags);
|
void LoadAppResources(QString pkgName, QString icon, QStringList screenshots, QStringList tags);
|
||||||
void SetWebsiteLink(QString url);
|
void SetWebsiteLink(QString url);
|
||||||
|
void SetPackagePath(QString url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QString mPkgPath;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void ResourceAcquisitionFinished(int id, ResourceResult result);
|
void ResourceAcquisitionFinished(int id, ResourceResult result);
|
||||||
@@ -38,12 +40,16 @@ namespace SpkUi
|
|||||||
*mWebsite;
|
*mWebsite;
|
||||||
SpkDetailEntry *mAuthor, *mContributor, *mSite, *mArch, *mSize;
|
SpkDetailEntry *mAuthor, *mContributor, *mSite, *mArch, *mSize;
|
||||||
SpkStretchLayout *mDetailLay;
|
SpkStretchLayout *mDetailLay;
|
||||||
QVBoxLayout *mMainLay, *mTitleLay, *mLay4MainArea;
|
QVBoxLayout *mDetailsLay, *mTitleLay, *mMainLay;
|
||||||
QHBoxLayout *mIconTitleLay;
|
QHBoxLayout *mIconTitleLay;
|
||||||
|
|
||||||
// Bottom bar
|
// Bottom bar
|
||||||
QWidget *mBottomBar;
|
QWidget *mBottomBar;
|
||||||
QPushButton *mBtnInstall, *mBtnDownload, *mBtnUninstall, *mBtnRequestUpdate, *mBtnReport;
|
QPushButton *mBtnInstall, *mBtnDownload, *mBtnUninstall, *mBtnRequestUpdate, *mBtnReport;
|
||||||
|
QHBoxLayout *mBottomBarLay;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void RequestDownload(QString name, QString pkgName, QString path);
|
||||||
};
|
};
|
||||||
|
|
||||||
class SpkDetailEntry : public QWidget
|
class SpkDetailEntry : public QWidget
|
||||||
|
|||||||
@@ -91,9 +91,11 @@ class SpkDownloadMgr : public QObject
|
|||||||
private slots:
|
private slots:
|
||||||
void WorkerFinish();
|
void WorkerFinish();
|
||||||
void WorkerDownloadProgress(); ///< Be connected to ***QNetworkReply::readyRead***
|
void WorkerDownloadProgress(); ///< Be connected to ***QNetworkReply::readyRead***
|
||||||
|
void WorkerError(QNetworkReply::NetworkError);
|
||||||
void ProgressTimer();
|
void ProgressTimer();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void ProcessWorkerError(DownloadWorker &, int id);
|
||||||
void LinkReplyWithMe(QNetworkReply*);
|
void LinkReplyWithMe(QNetworkReply*);
|
||||||
void TryScheduleFailureRetries();
|
void TryScheduleFailureRetries();
|
||||||
void TryScheduleFailureRetries(int i); ///< Try schedule on a specific task slot.
|
void TryScheduleFailureRetries(int i); ///< Try schedule on a specific task slot.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <QProgressBar>
|
#include <QProgressBar>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
#include <QTime>
|
||||||
|
|
||||||
class SpkDownloadEntry : public QWidget
|
class SpkDownloadEntry : public QWidget
|
||||||
{
|
{
|
||||||
@@ -33,10 +34,10 @@ class SpkDownloadEntry : public QWidget
|
|||||||
InstallFailed
|
InstallFailed
|
||||||
};
|
};
|
||||||
|
|
||||||
public slots:
|
void SetTotalBytes(qint64 total);
|
||||||
void SetBasicInfo(QString name, QPixmap icon);
|
void SetBasicInfo(QString name, QPixmap icon);
|
||||||
void SetStatus(DownloadEntryStatus status);
|
void SetStatus(DownloadEntryStatus status, QString msg = "");
|
||||||
void SetProgress(int);
|
void Progress(qint64 bytes);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QLabel *mIcon, *mMessage;
|
QLabel *mIcon, *mMessage;
|
||||||
@@ -49,4 +50,8 @@ class SpkDownloadEntry : public QWidget
|
|||||||
QHBoxLayout *mLayMsgs, *mLayMain;
|
QHBoxLayout *mLayMsgs, *mLayMain;
|
||||||
QVBoxLayout *mLayInfo;
|
QVBoxLayout *mLayInfo;
|
||||||
|
|
||||||
|
// Download status data
|
||||||
|
qint64 mTotalBytes, mDownloadedBytes;
|
||||||
|
QTime mLastReportTime;
|
||||||
|
QString mReadableTotalSize;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "spksidebartree.h" // In place of #include <QTreeWidget>
|
#include "spksidebartree.h" // In place of #include <QTreeWidget>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QTimeLine>
|
#include <QTimeLine>
|
||||||
|
#include <QQueue>
|
||||||
#include "spkfocuslineedit.h"
|
#include "spkfocuslineedit.h"
|
||||||
#include "spkiconbutton.h"
|
#include "spkiconbutton.h"
|
||||||
#include "page/spkpageuitest.h"
|
#include "page/spkpageuitest.h"
|
||||||
@@ -47,6 +48,9 @@ namespace SpkUi
|
|||||||
QTreeWidget *mCategoryWidget;
|
QTreeWidget *mCategoryWidget;
|
||||||
QVector<QTreeWidgetItem *> mUnusableItems; // Unselectable top level items; never changes
|
QVector<QTreeWidgetItem *> mUnusableItems; // Unselectable top level items; never changes
|
||||||
|
|
||||||
|
QTreeWidgetItem* mLastCategoryItem;
|
||||||
|
int mLastCategoryPage;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SpkSidebarSelector(QObject *parent = nullptr) : QObject(parent)
|
SpkSidebarSelector(QObject *parent = nullptr) : QObject(parent)
|
||||||
{
|
{
|
||||||
@@ -69,6 +73,11 @@ namespace SpkUi
|
|||||||
&SpkSidebarSelector::TreeItemSelected);
|
&SpkSidebarSelector::TreeItemSelected);
|
||||||
}
|
}
|
||||||
void AddUnusableItem(QTreeWidgetItem *i) { mUnusableItems.append(i); }
|
void AddUnusableItem(QTreeWidgetItem *i) { mUnusableItems.append(i); }
|
||||||
|
void GoBack()
|
||||||
|
{
|
||||||
|
emit SwitchToCategory(mLastCategoryPage, 0);
|
||||||
|
mLastCategoryItem->setSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
// We assume the objects in interest all have the correct properties
|
// We assume the objects in interest all have the correct properties
|
||||||
@@ -91,7 +100,8 @@ namespace SpkUi
|
|||||||
mLastSelectedItem = nullptr;
|
mLastSelectedItem = nullptr;
|
||||||
}
|
}
|
||||||
mLastCheckedBtn = b;
|
mLastCheckedBtn = b;
|
||||||
emit SwitchToPage(static_cast<SpkStackedPages>(b->property("spk_pageno").toInt()));
|
auto id = b->property("spk_pageno").toInt();
|
||||||
|
emit SwitchToPage(static_cast<SpkStackedPages>(id));
|
||||||
}
|
}
|
||||||
void TreeItemSelected(QTreeWidgetItem *item, int column)
|
void TreeItemSelected(QTreeWidgetItem *item, int column)
|
||||||
{
|
{
|
||||||
@@ -105,11 +115,11 @@ namespace SpkUi
|
|||||||
mLastCheckedBtn = nullptr;
|
mLastCheckedBtn = nullptr;
|
||||||
}
|
}
|
||||||
mLastSelectedItem = item;
|
mLastSelectedItem = item;
|
||||||
|
auto id = item->data(column, RoleItemCategoryPageId).toInt();
|
||||||
if(item->data(column, RoleItemIsCategory).toBool())
|
if(item->data(column, RoleItemIsCategory).toBool())
|
||||||
emit SwitchToCategory(item->data(column, RoleItemCategoryPageId).toInt(), 0);
|
emit SwitchToCategory(id, 0), mLastCategoryPage = id, mLastCategoryItem = item;
|
||||||
else
|
else
|
||||||
emit SwitchToPage(static_cast<SpkStackedPages>(
|
emit SwitchToPage(static_cast<SpkStackedPages>(id));
|
||||||
item->data(column, RoleItemCategoryPageId).toInt()));
|
|
||||||
}
|
}
|
||||||
void UnusableItemSelected(QTreeWidgetItem *i)
|
void UnusableItemSelected(QTreeWidgetItem *i)
|
||||||
{
|
{
|
||||||
@@ -147,7 +157,7 @@ namespace SpkUi
|
|||||||
QVBoxLayout *VLaySidebar;
|
QVBoxLayout *VLaySidebar;
|
||||||
QHBoxLayout *HLaySideTop;
|
QHBoxLayout *HLaySideTop;
|
||||||
QLabel *StoreIcon;
|
QLabel *StoreIcon;
|
||||||
SpkIconButton *BtnSettings, *BtnFeedback, *BtnLogs, *BtnDayNight;
|
SpkIconButton *BtnSettings, *BtnFeedback, *BtnLogs, *BtnDayNight, *BtnBack;
|
||||||
SpkSidebarTree *CategoryWidget;
|
SpkSidebarTree *CategoryWidget;
|
||||||
QMap<int, QTreeWidgetItem> *CategoryItemMap;
|
QMap<int, QTreeWidgetItem> *CategoryItemMap;
|
||||||
SpkSidebarSelector *SidebarMgr;
|
SpkSidebarSelector *SidebarMgr;
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ namespace SpkUtils
|
|||||||
|
|
||||||
void DeleteReplyLater(QNetworkReply *aReply);
|
void DeleteReplyLater(QNetworkReply *aReply);
|
||||||
|
|
||||||
bool VerifyReplyJson(QNetworkReply *aReply, QJsonValue& aRetDoc);
|
int VerifyReplyJson(QNetworkReply *aReply, QJsonValue& aRetDoc);
|
||||||
|
|
||||||
QString CutFileName(QString);
|
QString CutFileName(QString);
|
||||||
QString CutPath(QString);
|
QString CutPath(QString);
|
||||||
QString BytesToSize(size_t s, int prec = 2);
|
QString BytesToSize(size_t s, int prec = 2);
|
||||||
|
bool EnsureDirExists(QString path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
|
||||||
[Apis]
|
[url]
|
||||||
MasterApiUrl=https://store.deepinos.org/api
|
api=https://store.deepinos.org/api/
|
||||||
|
res=http://img.store.deepinos.org.cn/
|
||||||
|
|
||||||
|
[dirs]
|
||||||
|
cache=%1/.cache/spark-store/res/
|
||||||
|
download="%1/.local/spark-store/downloads/"
|
||||||
|
|
||||||
|
[download]
|
||||||
|
servers=https://d1.store.deepinos.org.cn/;;https://d2.store.deepinos.org.cn/;;https://d3.store.deepinos.org.cn/;;https://d4.store.deepinos.org.cn/;;https://d5.store.deepinos.org.cn/
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
concurrent=5
|
||||||
|
|||||||
60
resource/icons/back.svg
Normal file
60
resource/icons/back.svg
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.7 12.7"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
|
||||||
|
sodipodi:docname="back.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#505050"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:pageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
inkscape:zoom="14.285166"
|
||||||
|
inkscape:cx="25.095963"
|
||||||
|
inkscape:cy="22.505864"
|
||||||
|
inkscape:window-width="1852"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="68"
|
||||||
|
inkscape:window-y="30"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid9" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="6.35,6.35"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide11" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="6.35,6.35"
|
||||||
|
orientation="0,1"
|
||||||
|
id="guide13" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="图层 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#393939;stroke-width:1.38924;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:45.3543;stroke-opacity:1;paint-order:markers stroke fill;fill-opacity:1"
|
||||||
|
d="M 9.1126934,1.5875 3.8488777,6.3499999 9.1126934,11.1125"
|
||||||
|
id="path945" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -16,5 +16,6 @@
|
|||||||
<file>icons/daynight-dark.svg</file>
|
<file>icons/daynight-dark.svg</file>
|
||||||
<file>icons/daynight.svg</file>
|
<file>icons/daynight.svg</file>
|
||||||
<file>icons/loading-icon.svg</file>
|
<file>icons/loading-icon.svg</file>
|
||||||
|
<file>icons/back.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ QLineEdit /*We can't apply gradient to TextEdit cuz it messes with scroll bar*/
|
|||||||
QPushButton
|
QPushButton
|
||||||
{
|
{
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
padding: 2px;
|
padding: 2px 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -249,7 +249,7 @@ SpkTitleBarDefaultButton:pressed
|
|||||||
|
|
||||||
#styMwCateg::item
|
#styMwCateg::item
|
||||||
{
|
{
|
||||||
height: 35px;
|
/*height: 35px;*/
|
||||||
border: none;
|
border: none;
|
||||||
color: TXCBG;
|
color: TXCBG;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,19 @@
|
|||||||
|
|
||||||
SpkDownloadMgr::SpkDownloadMgr(QObject *parent)
|
SpkDownloadMgr::SpkDownloadMgr(QObject *parent)
|
||||||
{
|
{
|
||||||
mDestFolder = CFG->value("download/dir", QDir::homePath() + "/.local/spark-store/downloads")
|
mDestFolder = CFG->value("dirs/download", "%1/.local/spark-store/downloads")
|
||||||
.toString();
|
.toString().arg(QDir::homePath());
|
||||||
|
|
||||||
QDir dest(mDestFolder);
|
QDir dest(mDestFolder);
|
||||||
if(!dest.exists())
|
if(!dest.exists())
|
||||||
QDir().mkdir(mDestFolder);
|
QDir().mkdir(mDestFolder);
|
||||||
|
|
||||||
// Distribution servers
|
// Distribution servers
|
||||||
QString srvPaths = CFG->value("download/servers", "https://d.store.deepinos.org/").toString();
|
QString srvPaths = CFG->value("download/servers", "https://d1.store.deepinos.org.cn/;;"
|
||||||
|
"https://d2.store.deepinos.org.cn/;;"
|
||||||
|
"https://d3.store.deepinos.org.cn/;;"
|
||||||
|
"https://d4.store.deepinos.org.cn/;;"
|
||||||
|
"https://d5.store.deepinos.org.cn/").toString();
|
||||||
mServers = srvPaths.split(";;");
|
mServers = srvPaths.split(";;");
|
||||||
|
|
||||||
mCurrentDownloadId = -1;
|
mCurrentDownloadId = -1;
|
||||||
@@ -76,7 +80,7 @@ void SpkDownloadMgr::SetDestinationFolder(QString path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SpkDownloadMgr::StartNewDownload(QString path, int downloadId)
|
bool SpkDownloadMgr::StartNewDownload(QString path, int downloadId)
|
||||||
{
|
{
|
||||||
if(mCurrentDownloadId != -1)
|
if(mCurrentDownloadId != -1)
|
||||||
return false; // Already downloading something
|
return false; // Already downloading something
|
||||||
|
|
||||||
@@ -87,16 +91,34 @@ bool SpkDownloadMgr::StartNewDownload(QString path, int downloadId)
|
|||||||
info = GetRemoteFileInfo(mServers[i] + path);
|
info = GetRemoteFileInfo(mServers[i] + path);
|
||||||
// TODO: Mark dead servers as unusable so they don't get scheduled first?
|
// TODO: Mark dead servers as unusable so they don't get scheduled first?
|
||||||
}
|
}
|
||||||
if(info.Size == -1) return false; // If all servers failed then we say it's a failure
|
if(info.Size == -1)
|
||||||
|
{
|
||||||
|
sNotify(tr("Server request failure, %1 cannot be downloaded.").arg(SpkUtils::CutFileName(path)));
|
||||||
|
sErr(tr("SpkDownloadMgr: all start requests failed for %1.").arg(path));
|
||||||
|
return false; // If all servers failed then we say it's a failure
|
||||||
|
}
|
||||||
|
|
||||||
mCurrentRemoteFileInfo = info;
|
mCurrentRemoteFileInfo = info;
|
||||||
mActiveWorkerCount = 0;
|
mActiveWorkerCount = 0;
|
||||||
|
|
||||||
// Create the destination file.
|
// Create the destination file.
|
||||||
mDestFile.close();
|
if(mDestFile.isOpen())
|
||||||
|
mDestFile.close();
|
||||||
|
if(!SpkUtils::EnsureDirExists(mDestFolder))
|
||||||
|
{
|
||||||
|
sNotify(tr("Cannot create download destination folder, download cannot start."));
|
||||||
|
sErr(tr("SpkDownloadMgr: Download directory %1 cannot be created.").arg(mDestFolder));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
mDestFile.setFileName(mDestFolder + '/' + SpkUtils::CutFileName(path));
|
mDestFile.setFileName(mDestFolder + '/' + SpkUtils::CutFileName(path));
|
||||||
if(!mDestFile.open(QFile::ReadWrite))
|
if(!mDestFile.open(QFile::ReadWrite))
|
||||||
|
{
|
||||||
|
sNotify(tr("Cannot write to destination file, download cannot start."));
|
||||||
|
sErr(tr("SpkDownloadMgr: Download destination file %1 cannot be opened with mode %2.")
|
||||||
|
.arg(mDestFile.fileName())
|
||||||
|
.arg(mDestFile.openMode()));
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mCurrentRemotePath = path;
|
mCurrentRemotePath = path;
|
||||||
|
|
||||||
@@ -150,6 +172,8 @@ bool SpkDownloadMgr::StartNewDownload(QString path, int downloadId)
|
|||||||
|
|
||||||
mProgressEmitterTimer.start();
|
mProgressEmitterTimer.start();
|
||||||
|
|
||||||
|
mCurrentDownloadId = downloadId;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,9 +204,9 @@ bool SpkDownloadMgr::CancelCurrentDownload()
|
|||||||
{
|
{
|
||||||
sErr(tr("SpkDownloadMgr: Cannot remove destination file %1 of a cancelled task")
|
sErr(tr("SpkDownloadMgr: Cannot remove destination file %1 of a cancelled task")
|
||||||
.arg(mDestFile.fileName()));
|
.arg(mDestFile.fileName()));
|
||||||
SpkUi::Popup->Show(tr("The destination file of the cancelled task can't be deleted!"));
|
sNotify(tr("The destination file of the cancelled task can't be deleted!"));
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpkDownloadMgr::WorkerFinish()
|
void SpkDownloadMgr::WorkerFinish()
|
||||||
@@ -216,29 +240,7 @@ void SpkDownloadMgr::WorkerFinish()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Failed here! Update our offset and required bytes count etc.
|
ProcessWorkerError(worker, id);
|
||||||
worker.BeginOffset += worker.BytesRecvd;
|
|
||||||
worker.BytesNeeded -= worker.BytesRecvd;
|
|
||||||
worker.BytesRecvd = 0;
|
|
||||||
|
|
||||||
if(reply->property("failCount").toInt() > MaximumThreadRetryCount)
|
|
||||||
{
|
|
||||||
// Failed too many times, this server is probably down or really bad condition.
|
|
||||||
// Schedule it on other servers.
|
|
||||||
reply->deleteLater();
|
|
||||||
worker.Reply = nullptr;
|
|
||||||
mFailureRetryQueue.enqueue(worker);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can still retry.
|
|
||||||
worker.Reply =
|
|
||||||
STORE->SendDownloadRequest(mServers[id] + mCurrentRemotePath,
|
|
||||||
worker.BeginOffset,
|
|
||||||
worker.BeginOffset + worker.BytesNeeded);
|
|
||||||
LinkReplyWithMe(worker.Reply);
|
|
||||||
worker.Reply->setProperty("failCount", reply->property("failCount").toInt() + 1);
|
|
||||||
reply->deleteLater();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,11 +256,49 @@ void SpkDownloadMgr::WorkerDownloadProgress()
|
|||||||
mDownloadedBytes += replyData.size();
|
mDownloadedBytes += replyData.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpkDownloadMgr::WorkerError(QNetworkReply::NetworkError)
|
||||||
|
{
|
||||||
|
QNetworkReply *reply = static_cast<QNetworkReply*>(sender());
|
||||||
|
int id = reply->property("workerId").toInt();
|
||||||
|
DownloadWorker &worker = mScheduledWorkers[id];
|
||||||
|
|
||||||
|
ProcessWorkerError(worker, id);
|
||||||
|
}
|
||||||
|
|
||||||
void SpkDownloadMgr::ProgressTimer()
|
void SpkDownloadMgr::ProgressTimer()
|
||||||
{
|
{
|
||||||
emit DownloadProgressed(mDownloadedBytes, mCurrentRemoteFileInfo.Size, mCurrentDownloadId);
|
emit DownloadProgressed(mDownloadedBytes, mCurrentRemoteFileInfo.Size, mCurrentDownloadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpkDownloadMgr::ProcessWorkerError(DownloadWorker &worker, int id)
|
||||||
|
{
|
||||||
|
auto reply = worker.Reply;
|
||||||
|
|
||||||
|
// Update our offset and required bytes count etc.
|
||||||
|
worker.BeginOffset += worker.BytesRecvd;
|
||||||
|
worker.BytesNeeded -= worker.BytesRecvd;
|
||||||
|
worker.BytesRecvd = 0;
|
||||||
|
|
||||||
|
if(reply->property("failCount").toInt() > MaximumThreadRetryCount)
|
||||||
|
{
|
||||||
|
// Failed too many times, this server is probably down or really bad condition.
|
||||||
|
// Schedule it on other servers.
|
||||||
|
reply->deleteLater();
|
||||||
|
worker.Reply = nullptr;
|
||||||
|
mFailureRetryQueue.enqueue(worker);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can still retry.
|
||||||
|
worker.Reply =
|
||||||
|
STORE->SendDownloadRequest(reply->url(),
|
||||||
|
worker.BeginOffset,
|
||||||
|
worker.BeginOffset + worker.BytesNeeded);
|
||||||
|
LinkReplyWithMe(worker.Reply);
|
||||||
|
worker.Reply->setProperty("failCount", reply->property("failCount").toInt() + 1);
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
void SpkDownloadMgr::LinkReplyWithMe(QNetworkReply *reply)
|
void SpkDownloadMgr::LinkReplyWithMe(QNetworkReply *reply)
|
||||||
{
|
{
|
||||||
mActiveWorkerCount++; // Each time you spin up a request you must do this so it's ok to do it here
|
mActiveWorkerCount++; // Each time you spin up a request you must do this so it's ok to do it here
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "spklogging.h"
|
#include "spklogging.h"
|
||||||
#include "spkmsgbox.h"
|
#include "spkmsgbox.h"
|
||||||
#include "spkuimsg.h"
|
#include "spkuimsg.h"
|
||||||
|
#include "spkutils.h"
|
||||||
|
|
||||||
SpkLogger *SpkLogger::Instance = nullptr;
|
SpkLogger *SpkLogger::Instance = nullptr;
|
||||||
|
|
||||||
@@ -42,12 +43,11 @@ void SpkLogger::Initialize(QString suggestPath)
|
|||||||
}
|
}
|
||||||
mLogPath = QDir::homePath() + "/.local/share/spark-store/logs/default.log";
|
mLogPath = QDir::homePath() + "/.local/share/spark-store/logs/default.log";
|
||||||
QString path = mLogPath.section('/', 1, -2, QString::SectionIncludeLeadingSep);
|
QString path = mLogPath.section('/', 1, -2, QString::SectionIncludeLeadingSep);
|
||||||
if(!QDir().exists(path))
|
if(!SpkUtils::EnsureDirExists(path))
|
||||||
if(!QDir().mkpath(path))
|
{
|
||||||
{
|
SpkLogger::Error(QObject::tr("Log directory \"%1\" cannot be created.").arg(path));
|
||||||
SpkLogger::Error(QObject::tr("Log directory \"%1\" cannot be created.").arg(path));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
mLogFile.setFileName(mLogPath);
|
mLogFile.setFileName(mLogPath);
|
||||||
mLogFile.open(QFile::WriteOnly | QFile::Append);
|
mLogFile.open(QFile::WriteOnly | QFile::Append);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ SpkResource* SpkResource::Instance = nullptr;
|
|||||||
// clazy:excludeall=container-anti-pattern
|
// clazy:excludeall=container-anti-pattern
|
||||||
|
|
||||||
SpkResource::SpkResource(QObject *parent) : QObject(parent),
|
SpkResource::SpkResource(QObject *parent) : QObject(parent),
|
||||||
mMaximumConcurrent(CFG->value("download/resource_concurrent_count", 5).toInt()),
|
mMaximumConcurrent(CFG->value("resource/concurrent", 5).toInt()),
|
||||||
mCacheDirectory(CFG->value("cache_directory", "%1/.cache/spark-store/res/")
|
mCacheDirectory(CFG->value("dirs/cache", "%1/.cache/spark-store/res/")
|
||||||
.toString()
|
.toString()
|
||||||
.arg(QDir::homePath()))
|
.arg(QDir::homePath()))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ SpkStore::SpkStore(bool aCli, QString &aLogPath)
|
|||||||
|
|
||||||
// Initialize URL
|
// Initialize URL
|
||||||
mApiRequestUrl = mCfg->value("url/api", "https://store.deepinos.org/api/").toString();
|
mApiRequestUrl = mCfg->value("url/api", "https://store.deepinos.org/api/").toString();
|
||||||
mResourceRequestUrl = mCfg->value("url/res", "http://d.deepinos.org.cn/").toString();
|
mResourceRequestUrl = mCfg->value("url/res", "http://img.store.deepinos.org.cn/").toString();
|
||||||
|
|
||||||
|
|
||||||
mUserAgentStr = QString("Spark-Store/%1 Distro/%2")
|
mUserAgentStr = QString("Spark-Store/%1 Distro/%2")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QDir>
|
||||||
#include "spkutils.h"
|
#include "spkutils.h"
|
||||||
|
|
||||||
void SpkUtils::VerifySingleRequest(QPointer<QNetworkReply> aReply)
|
void SpkUtils::VerifySingleRequest(QPointer<QNetworkReply> aReply)
|
||||||
@@ -20,7 +21,7 @@ QString SpkUtils::GetDistroName()
|
|||||||
osRelease.value("BUILD_ID", "Unknown Build").toString();
|
osRelease.value("BUILD_ID", "Unknown Build").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpkUtils::VerifyReplyJson(QNetworkReply *aReply, QJsonValue &aRetDoc)
|
int SpkUtils::VerifyReplyJson(QNetworkReply *aReply, QJsonValue &aRetDoc)
|
||||||
{
|
{
|
||||||
QJsonParseError err;
|
QJsonParseError err;
|
||||||
QByteArray rawjson = aReply->readAll();
|
QByteArray rawjson = aReply->readAll();
|
||||||
@@ -29,17 +30,15 @@ bool SpkUtils::VerifyReplyJson(QNetworkReply *aReply, QJsonValue &aRetDoc)
|
|||||||
QJsonObject replyObject;
|
QJsonObject replyObject;
|
||||||
if(err.error != QJsonParseError::NoError)
|
if(err.error != QJsonParseError::NoError)
|
||||||
{
|
{
|
||||||
sNotify(QObject::tr("Failed to parse server reply! Error %1.").arg(err.error));
|
|
||||||
sErr(QObject::tr("VerifyReplyJson: returned JSON of request to %1 is unreadable.")
|
sErr(QObject::tr("VerifyReplyJson: returned JSON of request to %1 is unreadable.")
|
||||||
.arg(aReply->url().toString()));
|
.arg(aReply->url().toString()));
|
||||||
return false;
|
return err.error;
|
||||||
}
|
}
|
||||||
if(!ret.isObject())
|
if(!ret.isObject())
|
||||||
{
|
{
|
||||||
sNotify(QObject::tr("Server sent back an invalid response."));
|
|
||||||
sErr(QObject::tr("VerifyReplyJson: returned JSON of request to %1 is not an Object.")
|
sErr(QObject::tr("VerifyReplyJson: returned JSON of request to %1 is not an Object.")
|
||||||
.arg(aReply->url().toString()));
|
.arg(aReply->url().toString()));
|
||||||
return false;
|
return -1;
|
||||||
}
|
}
|
||||||
replyObject = ret.object();
|
replyObject = ret.object();
|
||||||
if(!replyObject.contains("code"))
|
if(!replyObject.contains("code"))
|
||||||
@@ -57,22 +56,22 @@ bool SpkUtils::VerifyReplyJson(QNetworkReply *aReply, QJsonValue &aRetDoc)
|
|||||||
}
|
}
|
||||||
else if(OpRetCode.toInt() != 0)
|
else if(OpRetCode.toInt() != 0)
|
||||||
{
|
{
|
||||||
sNotify(QObject::tr("Server sent back an failure message; code: %1.")
|
sNotify(QObject::tr("VerifyReplyJson: Server sent back an failure message; code: %1.")
|
||||||
.arg(OpRetCode.toInt()));
|
.arg(OpRetCode.toInt()));
|
||||||
sErr(QObject::tr("VerifyReplyJson: Request to %1 failed with code %2.")
|
sErr(QObject::tr("VerifyReplyJson: Request to %1 failed with code %2.")
|
||||||
.arg(aReply->url().toString()).arg(OpRetCode.toInt()));
|
.arg(aReply->url().toString()).arg(OpRetCode.toInt()));
|
||||||
return false;
|
return OpRetCode.toInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!replyObject.contains("data"))
|
if(!replyObject.contains("data"))
|
||||||
{
|
{
|
||||||
sNotify(QObject::tr("Server did not reply with any data."));
|
sNotify(QObject::tr("VerifyReplyJson: Server did not reply with any data."));
|
||||||
sErr(QObject::tr("VerifyReplyJson: Reply of request to %1 didn't include any data.")
|
sErr(QObject::tr("VerifyReplyJson: Reply of request to %1 didn't include any data.")
|
||||||
.arg(aReply->url().toString()));
|
.arg(aReply->url().toString()));
|
||||||
return false;
|
return -2;
|
||||||
}
|
}
|
||||||
aRetDoc = replyObject.value("data");
|
aRetDoc = replyObject.value("data");
|
||||||
return true;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpkUtils::DeleteReplyLater(QNetworkReply *aReply)
|
void SpkUtils::DeleteReplyLater(QNetworkReply *aReply)
|
||||||
@@ -100,3 +99,14 @@ QString SpkUtils::BytesToSize(size_t s, int prec)
|
|||||||
return QString::number(double (s) / (1 << 10), 'f', prec) + " KB";
|
return QString::number(double (s) / (1 << 10), 'f', prec) + " KB";
|
||||||
return QString::number(s) + " B";
|
return QString::number(s) + " B";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SpkUtils::EnsureDirExists(QString path)
|
||||||
|
{
|
||||||
|
QDir dir;
|
||||||
|
|
||||||
|
if(!dir.exists(path))
|
||||||
|
if(!dir.mkpath(path))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user