支持图标跟随主题,改进详情页,加入更好的多线程下载

新的下载会重试一个线程上的错误,一个线程崩溃次数过多会转移到队列里等待重新安排,其他的暂时没写
This commit is contained in:
RigoLigoRLC
2021-11-28 02:11:54 +08:00
parent acf013d8ab
commit 6491b19f6e
17 changed files with 722 additions and 14 deletions

View File

@@ -20,6 +20,7 @@ namespace SpkUi
SpkPageAppDetails(QWidget *parent = nullptr);
void LoadAppResources(QString pkgName, QString icon, QStringList screenshots, QStringList tags);
void SetWebsiteLink(QString url);
private:
@@ -30,14 +31,19 @@ namespace SpkUi
public:
static constexpr QSize IconSize { 144, 144 };
// Main Area
QScrollArea *mMainArea;
QWidget *mDetailWidget, *mIconTitleWidget, *mWid4MainArea;
QLabel *mAppTitle, *mAppIcon, *mAppDescription, *mAppShortDesc, *mPkgName, *mVersion;
QLabel *mAppTitle, *mAppIcon, *mAppDescription, *mAppShortDesc, *mPkgName, *mVersion,
*mWebsite;
SpkDetailEntry *mAuthor, *mContributor, *mSite, *mArch, *mSize;
SpkStretchLayout *mDetailLay;
QVBoxLayout *mMainLay, *mTitleLay, *mLay4MainArea;
QHBoxLayout *mIconTitleLay;
// Bottom bar
QWidget *mBottomBar;
QPushButton *mBtnInstall, *mBtnDownload, *mBtnUninstall, *mBtnRequestUpdate, *mBtnReport;
};
class SpkDetailEntry : public QWidget
@@ -52,4 +58,4 @@ namespace SpkUi
QLabel mTitle, mField;
QHBoxLayout mLay;
};
}
}

102
inc/spkdownload.h Normal file
View File

@@ -0,0 +1,102 @@
#pragma once
#include "spkstore.h"
/**
* @note SpkDownloadMgr does NOT do download scheduling and other things; it's only a multithreaded
* downloader; it manages the threads that are downloading stuff from the Internet.
*/
class SpkDownloadMgr : public QObject
{
Q_OBJECT
public:
SpkDownloadMgr(QObject *parent = nullptr);
~SpkDownloadMgr();
static constexpr int MaximumThreadRetryCount = 3;
enum TaskResult
{
Success = 0,
FailCannotCreateFile, ///< Failed because destination file cannot be created
FailNoVaibleServer, ///< Failed because no server provides file size or download stalled on
///< all of them
Fail
};
/**
* @brief DownloadWorker is not a real worker but more of a worker-like structure. It holds
* information about one download thread, such as offset, total bytes and received bytes.
* Workers' scheduling is still done by SpkDownloadMgr synchronously, avoiding
* unnecessary race conditions and data safety problems.
* DownloadWorker is also used in mFailureRetryQueue to indicate the blocks that needed
* to be retried on other servers.
*/
struct DownloadWorker
{
QNetworkReply *Reply; ///< Reply from the network
qint64 BeginOffset; ///< Where should a worker start downloading
qint64 BytesNeeded; ///< How many bytes a worker should fetch in total
qint64 BytesRecvd; ///< How many bytes a worker has received till now
};
struct RemoteFileInfo
{
qint64 Size = -1;
bool SupportPartialDownload = false; ///< Whether this file can be downloaded multithreaded
QByteArray Md5;
};
void SetNewServers(QList<QString> servers);
/**
* @note This function uses BLOCKING IO!
*/
static RemoteFileInfo GetRemoteFileInfo(QUrl url);
private:
QList<QString> mServers; ///< Multithreaded download
QList<DownloadWorker> mScheduledWorkers;
// If one scheduled task fails a few times in a row, we must give it up on that server and put
// its responsible block onto this queue so we can try downloading the block from other servers
QQueue<DownloadWorker> mFailureRetryQueue;
QFile mDestFile;
QString mDestFolder, mCurrentRemotePath;
RemoteFileInfo mCurrentRemoteFileInfo;
int mCurrentDownloadId;
int mActiveWorkerCount;
public slots:
void SetDestinationFolder(QString path);
/**
* @brief StartNewDownload try to start new download task.
* @param path File path. Domain name excluded. No leading slashes.
* @param downloadId Emitted with progress, finish and error so the UI know whose status it is.
* @return true for success and false for failure.
*/
bool StartNewDownload(QString path, int downloadId);
bool PauseCurrentDownload();
bool CancelCurrentDownload();
private slots:
void WorkerFinish();
void WorkerDownloadProgress(); ///< Be connected to ***QNetworkReply::readyRead***
private:
void LinkReplyWithMe(QNetworkReply*);
void TryScheduleFailureRetries();
void TryScheduleFailureRetries(int i); ///< Try schedule on a specific task slot.
signals:
void DownloadProgressed(qint64 bytes, qint64 total);
void DownloadStopped(TaskResult status, int id);
};

View File

@@ -144,7 +144,7 @@ namespace SpkUi
QVBoxLayout *VLaySidebar;
QHBoxLayout *HLaySideTop;
QLabel *StoreIcon;
QPushButton *BtnSettings, *BtnFeedback, *BtnLogs;
QPushButton *BtnSettings, *BtnFeedback, *BtnLogs, *BtnDayNight;
SpkSidebarTree *CategoryWidget;
QMap<int, QTreeWidgetItem> *CategoryItemMap;
SpkSidebarSelector *SidebarMgr;
@@ -184,11 +184,15 @@ class SpkMainWindow : public SpkWindow
mCategoryAppListGetReply,
mAppDetailsGetReply;
SpkUi::SpkStackedPages mCurrentPage = SpkUi::PgInvalid;
QList<QPair<QPushButton*, QString>> mThemedUiIconReferences;
public slots:
void ReloadThemedUiIcons();
void RefreshCategoryData();
private slots:
void SwitchDayNightTheme();
void SwitchToPage(SpkUi::SpkStackedPages page);
void CategoryDataReceived();

View File

@@ -32,6 +32,9 @@ class SpkStore : public QObject
QString GetApiRequestUrl() { return mApiRequestUrl; }
QNetworkReply *SendApiRequest(QString path, QJsonDocument param = QJsonDocument());
QNetworkReply *SendResourceRequest(QString path); ///< WARNING: Only intended for SpkResource!
QNetworkReply *SendDownloadRequest(QUrl file, qint64 fromByte = -1, qint64 toByte = -1);
QNetworkReply *SendCustomHeadRequest(QNetworkRequest);
private:
SpkLogger *mLogger;