mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 22:23:49 +08:00
实现应用列表和SpkResource
SpkResource现有已知问题:首次使用不会完全进行所有任务。
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
|
||||
#include <QDir>
|
||||
#include "page/spkpagebase.h"
|
||||
#include "spkutils.h"
|
||||
#include "spkresource.h"
|
||||
|
||||
SpkResource* SpkResource::Instance = nullptr;
|
||||
|
||||
// If you want to iterate all keys in a QMap, there's no good way to do that, nor does Clazy have.
|
||||
// Clazy here is just a piece of shit nagging you like crazy and never gives you the actual
|
||||
// solution.
|
||||
// clazy:excludeall=container-anti-pattern
|
||||
|
||||
SpkResource::SpkResource(QObject *parent) : QObject(parent),
|
||||
mMaximumConcurrent(CFG->value("download/resource_concurrent_count", 5).toInt()),
|
||||
mCacheDirectory(CFG->value("cache_directory", "%1/.cache/spark-store/res/")
|
||||
.toString()
|
||||
.arg(QDir::homePath()))
|
||||
{
|
||||
Q_ASSERT(!Instance);
|
||||
qRegisterMetaType<ResourceResult>();
|
||||
Instance = this;
|
||||
mRequestSemaphore = new QSemaphore(mMaximumConcurrent);
|
||||
|
||||
QString path = mCacheDirectory.section('/', 1, -2, QString::SectionIncludeLeadingSep);
|
||||
if(!QDir().exists(path))
|
||||
{
|
||||
if(!QDir().mkpath(path))
|
||||
{
|
||||
sErr(tr("Cache directory \"%1\" cannot be created.").arg(path));
|
||||
return;
|
||||
}
|
||||
else
|
||||
sLog(tr("Created cache directory \"%1\".").arg(path));
|
||||
}
|
||||
}
|
||||
|
||||
ResourceResult
|
||||
SpkResource::RequestResource(const int aId, const QString &aPkgName, ResourceType aType,
|
||||
const QString &aPath, const QVariant &aInfo)
|
||||
{
|
||||
auto ret = LocateCachedResource(ResourceTask
|
||||
{ .pkgName = aPkgName, .path = aPath, .type = aType,
|
||||
.info = aInfo, .id = aId });
|
||||
|
||||
if(ret.status == ResourceStatus::Ready)
|
||||
return ret;
|
||||
|
||||
mAwaitingRequests.enqueue(ResourceTask
|
||||
{ .pkgName = aPkgName, .path = aPath, .type = aType, .info = aInfo,
|
||||
.id = aId });
|
||||
TryBeginAwaitingTasks();
|
||||
|
||||
return ResourceResult { .status = ResourceStatus::Deferred, .data = QByteArray() };
|
||||
}
|
||||
|
||||
void SpkResource::ResourceDownloaded()
|
||||
{
|
||||
auto reply = qobject_cast<QNetworkReply*>(sender());
|
||||
auto id = mWorkingRequests.value(reply);
|
||||
mWorkingRequests.remove(reply); // Remove in time
|
||||
mRequestSemaphore->release(); // Release semaphore resource
|
||||
|
||||
ResourceResult ret;
|
||||
ret.status = ResourceStatus::Ready;
|
||||
ret.data = reply->readAll();
|
||||
|
||||
// Save cache to disk
|
||||
auto cacheFile = reply->property("dest_file").toString();
|
||||
QFile writeCache(cacheFile);
|
||||
QString path = cacheFile.section('/', 1, -2, QString::SectionIncludeLeadingSep);
|
||||
if(!QDir().exists(path))
|
||||
{
|
||||
if(!QDir().mkpath(path))
|
||||
{
|
||||
sWarn(tr("Cache directory \"%1\" cannot be created.").arg(path));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(writeCache.open(QFile::WriteOnly))
|
||||
{
|
||||
writeCache.write(ret.data);
|
||||
writeCache.close();
|
||||
}
|
||||
else
|
||||
sWarn("Save cache to \"" + cacheFile + "\" failed! Msg: " + writeCache.errorString());
|
||||
|
||||
// Tell ResourceContext
|
||||
AcquisitionFinish(id, ret);
|
||||
// Start next possible mission
|
||||
TryBeginAwaitingTasks();
|
||||
}
|
||||
|
||||
void SpkResource::Acquire(SpkPageBase *dest, bool stopOngoing, bool clearQueue)
|
||||
{
|
||||
if(stopOngoing)
|
||||
{
|
||||
for(auto &i : mWorkingRequests.keys())
|
||||
{
|
||||
// Don't let forced abort falsely report a finish signal. Disconnect them first.
|
||||
i->disconnect(i, &QNetworkReply::finished, this, &SpkResource::ResourceDownloaded);
|
||||
i->abort();
|
||||
delete i;
|
||||
}
|
||||
mWorkingRequests.clear();
|
||||
|
||||
mRequestSemaphore->release(mMaximumConcurrent); // Release all semaphore users
|
||||
}
|
||||
|
||||
if(clearQueue)
|
||||
mAwaitingRequests.clear();
|
||||
|
||||
disconnect(this, SLOT(AcquisitionFinish(int, ResourceResult)));
|
||||
|
||||
connect(this, &SpkResource::AcquisitionFinish,
|
||||
dest, &SpkPageBase::ResourceAcquisitionFinished,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
ResourceResult SpkResource::LocateCachedResource(const ResourceTask &task)
|
||||
{
|
||||
// TODO: Test overhead of all these?
|
||||
auto dir = QDir(mCacheDirectory + task.pkgName + '/', ResourceName[task.type] + '*');
|
||||
auto list = dir.entryList();
|
||||
|
||||
// If there's not even a file of the desired type, then tell invoker it's deferred
|
||||
if(list.isEmpty())
|
||||
return ResourceResult { .status = ResourceStatus::Deferred, .data = QByteArray() };
|
||||
|
||||
// If there is the desired file, then we return the resource in binary
|
||||
auto cacheFullPath = GetCachePath(task);
|
||||
if(list.contains(SpkUtils::CutFileName(cacheFullPath)))
|
||||
{
|
||||
// qInfo() << "Cache hit:" << GetCachePath(task);
|
||||
QFile cacheFile(cacheFullPath);
|
||||
if(cacheFile.open(QFile::ReadOnly))
|
||||
{
|
||||
ResourceResult ret { .status = ResourceStatus::Ready,
|
||||
.data = cacheFile.readAll()};
|
||||
cacheFile.close();
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cache file is unreadable, error and will be deleted
|
||||
sErr(tr("Desired cache file \"%1\" is unreadable by you!"));
|
||||
}
|
||||
}
|
||||
|
||||
// If above two cases are not met, then we have outdated resources. Remove them.
|
||||
for(auto &i : list)
|
||||
QFile::remove(i);
|
||||
|
||||
// Nothing to give you. Wait for completion.
|
||||
return ResourceResult { .status = ResourceStatus::Deferred, .data = QByteArray() };
|
||||
}
|
||||
|
||||
void SpkResource::TryBeginAwaitingTasks()
|
||||
{
|
||||
if(mAwaitingRequests.isEmpty())
|
||||
return;
|
||||
while(!mAwaitingRequests.isEmpty() && mRequestSemaphore->tryAcquire())
|
||||
{
|
||||
auto task = mAwaitingRequests.dequeue();
|
||||
// If no cached items exist, go download it
|
||||
auto reply = STORE->SendResourceRequest(task.path);
|
||||
// Store the destination cache file location in the property
|
||||
reply->setProperty("dest_file", GetCachePath(task));
|
||||
mWorkingRequests[reply] = task.id;
|
||||
connect(reply, &QNetworkReply::finished, this, &SpkResource::ResourceDownloaded);
|
||||
}
|
||||
}
|
||||
|
||||
void SpkResource::PurgeCachedResource(const QString &aPkgName, SpkResource::ResourceType aType,
|
||||
const QVariant &aInfo)
|
||||
{
|
||||
auto dir = QDir(mCacheDirectory + aPkgName + '/', ResourceName[aType] + '*');
|
||||
auto list = dir.entryList();
|
||||
|
||||
if(list.isEmpty())
|
||||
return;
|
||||
for(auto &i : list)
|
||||
if(!QFile::remove(dir.absolutePath() + '/' + i))
|
||||
qInfo() << "Fail to remove broken cache " << dir.absolutePath() + '/' + i;
|
||||
}
|
||||
|
||||
QString SpkResource::GetCachePath(const ResourceTask &task)
|
||||
{
|
||||
return mCacheDirectory + task.pkgName + '/' + ResourceName.value(task.type) + '.' +
|
||||
task.info.toString() + '.' + SpkUtils::CutFileName(task.path);
|
||||
}
|
||||
|
||||
const QMap<SpkResource::ResourceType, QString> SpkResource::ResourceName
|
||||
{
|
||||
{ SpkResource::ResourceType::AppIcon, "icon" },
|
||||
{ SpkResource::ResourceType::AppScreenshot, "scrshot" },
|
||||
{ SpkResource::ResourceType::HomeImage, "img" },
|
||||
{ SpkResource::ResourceType::TagIcon, "tag" },
|
||||
};
|
||||
|
||||
+15
-1
@@ -56,7 +56,12 @@ SpkStore::SpkStore(bool aCli, QString &aLogPath)
|
||||
mNetMgr = new QNetworkAccessManager(this);
|
||||
mNetMgr->setProxy(QNetworkProxy(QNetworkProxy::NoProxy)); // FIXME
|
||||
mDistroName = SpkUtils::GetDistroName();
|
||||
mApiRequestUrl = "https://store.deepinos.org/api/"; // TODO: CHECK BEFORE 4.0 RELEASE
|
||||
|
||||
// Initialize URL
|
||||
mApiRequestUrl = mCfg->value("url/api", "https://store.deepinos.org/api/").toString();
|
||||
mResourceRequestUrl = mCfg->value("url/res", "http://img.store.deepinos.org.cn/").toString();
|
||||
|
||||
|
||||
mUserAgentStr = QString("Spark-Store/%1 Distro/%2")
|
||||
.arg(GitVer::DescribeTags())
|
||||
.arg(mDistroName);
|
||||
@@ -66,6 +71,7 @@ SpkStore::SpkStore(bool aCli, QString &aLogPath)
|
||||
return;
|
||||
|
||||
// UI Initialization
|
||||
mResMgr = new SpkResource(this); // Resource manager must be created before the windows
|
||||
SpkUi::Initialize();
|
||||
mMainWindow = new SpkMainWindow;
|
||||
SpkUi::Popup = new SpkUi::SpkPopup(mMainWindow);
|
||||
@@ -88,6 +94,14 @@ QNetworkReply *SpkStore::SendApiRequest(QString aPath, QJsonDocument aParam)
|
||||
return mNetMgr->post(request, aParam.isEmpty() ? "{}" : aParam.toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
QNetworkReply *SpkStore::SendResourceRequest(QString aPath)
|
||||
{
|
||||
QNetworkRequest request;
|
||||
request.setUrl(mResourceRequestUrl + aPath);
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, mUserAgentStr);
|
||||
return mNetMgr->get(request);
|
||||
}
|
||||
|
||||
static void InstallDefaultConfigs()
|
||||
{
|
||||
//TODO:STUB
|
||||
|
||||
@@ -78,3 +78,13 @@ void SpkUtils::DeleteReplyLater(QNetworkReply *aReply)
|
||||
{
|
||||
QObject::connect(aReply, &QNetworkReply::finished, aReply, &QObject::deleteLater);
|
||||
}
|
||||
|
||||
QString SpkUtils::CutFileName(QString path)
|
||||
{
|
||||
return path.section('/', -1);
|
||||
}
|
||||
|
||||
QString SpkUtils::CutPath(QString path)
|
||||
{
|
||||
return path.section('/', 1, -2, QString::SectionIncludeLeadingSep);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user