实现应用列表和SpkResource

SpkResource现有已知问题:首次使用不会完全进行所有任务。
This commit is contained in:
RigoLigoRLC 2021-09-03 00:48:24 +08:00
parent 774e347957
commit ca57a7ac86
26 changed files with 1053 additions and 274 deletions

View File

@ -56,6 +56,8 @@ set(SOURCE_FILES
inc/telemetry/collectid.h
inc/spkqsshelper.h
inc/qt/elidedlabel.h gui/qt/elidedlabel.cpp
inc/spkwindow.h gui/spkwindow.cpp
inc/spktitlebar.h gui/spktitlebar.cpp
inc/spkui_general.h gui/spkui_general.cpp
@ -63,11 +65,15 @@ set(SOURCE_FILES
inc/spkmsgbox.h gui/spkmsgbox.cpp
inc/spkdialog.h gui/spkdialog.cpp
inc/spkabout.h gui/spkabout.cpp
inc/spkpageuitest.h gui/spkpageuitest.cpp
inc/spkloading.h gui/spkloading.cpp
inc/spksidebartree.h gui/spksidebartree.cpp
inc/spkappitem.h gui/spkappitem.cpp
inc/spkpopup.h gui/spkpopup.cpp
inc/spkstretchlayout.h gui/spkstretchlayout.cpp
inc/page/spkpagebase.h gui/page/spkpagebase.cpp
inc/page/spkpageuitest.h gui/page/spkpageuitest.cpp
inc/page/spkpageapplist.h gui/page/spkpageapplist.cpp
inc/spkstore.h src/spkstore.cpp
inc/spkuimsg.h src/spkuimsg.cpp

View File

@ -0,0 +1,43 @@
# 项目结构和命名规范
## 文件夹结构
### cmake
主要用于 CMake 配置时需要用到的 CMake 模块脚本。
### gui
主要用于显示界面的 C++ 代码文件(即包含 Qt Widgets 代码),无论包含多少与其他组件的联系,都在这个目录内。
这包括 SpkUi 的自定义界面组件、商店主界面自身的 Page 类等等。
### inc
项目中所有以 `#include "xxx.h"` 方式包含到代码中的标头文件,全部放入此目录。
标头文件并不强求目录结构明确,文件用途或来源特殊的除外。
### src
商店主体逻辑。包含基础的 `SpkStore` 类、`main.cpp` 等核心逻辑。
新加入的逻辑如单个功能多于一个cpp文件则必须分装到一个目录内。
### plugin
适配 Deepin 以及其他平台的平台相关插件。
## 命名规范
### 通用规范
类名、成员、方法名一律采用大驼峰形式。例如,`SpkTitleBar::SetTitle` 方法。
C++ 规范要求的和第三方库有较大差异的除外。例如,`SpkUiMessage::_notify`成员。
临时变量采用小驼峰形式或全小写。
名称要对功能要有基础的描述,例如,`SpkUi::SpkCategorySelector` 类。抽象核心逻辑除外。
### 类名
如非第三方代码,一般以 `Spk` 前缀标明。
实现 UI 的类都应归于 `SpkUi` 命名空间。
<!--TODO-->

View File

@ -0,0 +1,85 @@
#include <page/spkpageapplist.h>
#include "inc/page/spkpageapplist.h"
#include "spkutils.h"
namespace SpkUi
{
SpkPageAppList::SpkPageAppList(QWidget *parent) : SpkPageBase(parent)
{
mItemLay = new SpkStretchLayout(this);
mItemLay->setContentsMargins(6, 6, 6, 6);
}
void SpkPageAppList::AddApplicationEntry(QString name, QString pkgName, QString description,
QString iconUrl, int appId)
{
auto item = new SpkAppItem(appId, this);
auto id = mAppItemList.size();
item->SetTitle(name);
item->SetDescription(description);
item->setProperty("pkg_name", pkgName);
auto iconRes = RES->RequestResource(id, pkgName, SpkResource::ResourceType::AppIcon,
iconUrl, 0);
QPixmap icon;
if(iconRes.status == SpkResource::ResourceStatus::Ready)
{
if(icon.loadFromData(iconRes.data))
item->SetIcon(icon.scaled(SpkAppItem::IconSize_,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
else
{
item->SetIcon(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_));
RES->PurgeCachedResource(pkgName, SpkResource::ResourceType::AppIcon, 0);
}
}
//TODO: prepare icons for loading entries
// else
// item->SetIcon(QPixmap(":/icons/loading_icon.svg").scaled(SpkAppItem::IconSize_));
mAppItemList.append(item);
mItemLay->addWidget(item);
}
void SpkPageAppList::ClearAll()
{
QWidget *itm;
QLayoutItem *layitm;
while((layitm = mItemLay->takeAt(0)))
{
itm = layitm->widget();
itm->hide();
itm->deleteLater();
}
mAppItemList.clear();
}
void SpkPageAppList::ResourceAcquisitionFinished(int id, ResourceResult result)
{
QPixmap icon;
auto item = mAppItemList[id];
if(result.status == SpkResource::ResourceStatus::Ready)
{
if(icon.loadFromData(result.data))
item->SetIcon(icon.scaled(SpkAppItem::IconSize_,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
else
item->SetIcon(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_));
}
else if(result.status == SpkResource::ResourceStatus::Failed)
{
item->SetIcon(QIcon::fromTheme("dialog-error").pixmap(SpkAppItem::IconSize_));
RES->PurgeCachedResource(item->property("pkg_name").toString(),
SpkResource::ResourceType::AppIcon, 0);
}
}
void SpkPageAppList::Activated()
{
RES->Acquire(this, false);
}
}

18
gui/page/spkpagebase.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "page/spkpagebase.h"
SpkPageBase::SpkPageBase(QWidget *parent) : QScrollArea(parent)
{
}
void SpkPageBase::ResourceAcquisitionFinished(int id, ResourceResult result)
{
Q_UNUSED(id);
Q_UNUSED(result);
}
void SpkPageBase::Activated()
{
; // Do nothing
}

View File

@ -1,7 +1,7 @@
#include <QApplication>
#include "spkabout.h"
#include "spkpageuitest.h"
#include "inc/page/spkpageuitest.h"
#include "spkpopup.h"
#include "spkui_general.h"
@ -44,9 +44,9 @@ SpkUi::SpkPageUiTest::SpkPageUiTest(QWidget *parent) : QSplitter(parent)
Loading->setObjectName("spk_pg_qsstest_loading");
Loading->start();
AppItem = new SpkAppItem(this);
AppItem = new SpkAppItem(0, this);
AppItem->setObjectName("spk_pg_qsstest_appitem");
AppItem->SetTitle("Lorem Ipsum");
AppItem->SetTitle("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
AppItem->SetDescription("Nam vehicula lacus vitae leo fermentum efficitur. "
"Phasellus finibus risus id aliquam pulvinar.");
AppItem->SetIcon(QIcon::fromTheme("dialog-information").pixmap(72, 72));

133
gui/qt/elidedlabel.cpp Normal file
View File

@ -0,0 +1,133 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
/*
* Slight modifications has been done to the code to make it fit into the project.
*/
#include "qt/elidedlabel.h"
#include <QPainter>
#include <QSizePolicy>
#include <QTextLayout>
//! [0]
ElidedLabel::ElidedLabel(const QString &text, QWidget *parent)
: QFrame(parent)
, elided(false)
, content(text)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
//! [0]
//! [1]
void ElidedLabel::setText(const QString &newText)
{
content = newText;
update();
}
//! [1]
//! [2]
void ElidedLabel::paintEvent(QPaintEvent *event)
{
QFrame::paintEvent(event);
QPainter painter(this);
QFontMetrics fontMetrics = painter.fontMetrics();
bool didElide = false;
int lineSpacing = fontMetrics.lineSpacing();
int y = 0;
QTextLayout textLayout(content, painter.font());
textLayout.beginLayout();
forever {
QTextLine line = textLayout.createLine();
if (!line.isValid())
break;
line.setLineWidth(width());
int nextLineY = y + lineSpacing;
if (height() >= nextLineY + lineSpacing) {
line.draw(&painter, QPoint(0, y));
y = nextLineY;
//! [2]
//! [3]
} else {
QString lastLine = content.mid(line.textStart());
QString elidedLastLine = fontMetrics.elidedText(lastLine, Qt::ElideRight, width());
painter.drawText(QPoint(0, y + fontMetrics.ascent()), elidedLastLine);
line = textLayout.createLine();
didElide = line.isValid();
break;
}
}
textLayout.endLayout();
//! [3]
//! [4]
if (didElide != elided) {
elided = didElide;
emit elisionChanged(didElide);
}
}
ElidedLabel::ElidedLabel(QWidget *parent)
: QFrame(parent)
, elided(false)
, content("")
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
}
//! [4]

View File

@ -2,9 +2,14 @@
#include <QPainter>
#include <QStyleOption>
#include "spkappitem.h"
#include "qt/elidedlabel.h"
SpkAppItem::SpkAppItem(QWidget *parent) : QWidget(parent)
const QSize SpkAppItem::IconSize_;
SpkAppItem::SpkAppItem(int appId, QWidget *parent) : QWidget(parent)
{
mAppId = appId;
mMainLay = new QHBoxLayout(this);
mLayText = new QVBoxLayout;
@ -12,16 +17,17 @@ SpkAppItem::SpkAppItem(QWidget *parent) : QWidget(parent)
mIcon->setFixedSize(IconSize, IconSize);
mIcon->setAutoFillBackground(false);
// NOTE: Make mTitle ElidedTitle too?
mTitle = new QLabel;
mTitle->setWordWrap(false);
mTitle->setObjectName("styAppItmTitle");
mTitle->setAutoFillBackground(true);
mDescription = new QLabel;
mDescription->setWordWrap(true);
mDescription = new ElidedLabel;
// mDescription->setWordWrap(true); // Commented out since ElidedLabel lacks of these methods
mDescription->setObjectName("styAppItmDesc");
mDescription->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mDescription->setAlignment(Qt::AlignTop | Qt::AlignLeft);
mDescription->setAutoFillBackground(true);
// mDescription->setAlignment(Qt::AlignTop | Qt::AlignLeft);
// mDescription->setAutoFillBackground(true);
mLayText->addWidget(mTitle);
mLayText->addWidget(mDescription);
mLayText->setAlignment(Qt::AlignTop);
@ -32,7 +38,7 @@ SpkAppItem::SpkAppItem(QWidget *parent) : QWidget(parent)
setMinimumHeight(82);
setMaximumHeight(82);
setMinimumWidth(300);
setMaximumWidth(350);
// setMaximumWidth(350);
}
void SpkAppItem::paintEvent(QPaintEvent *e)

View File

@ -11,6 +11,7 @@
SpkMainWindow::SpkMainWindow(QWidget *parent) : SpkWindow(parent)
{
ui = new SpkUi::SpkMainWidget(parent);
Initialize();
SetUseTitleBar(false);
SetCentralWidget(ui);
@ -22,11 +23,23 @@ SpkMainWindow::SpkMainWindow(QWidget *parent) : SpkWindow(parent)
move(size.width(), size.height());
}
void SpkMainWindow::SwitchToPage(SpkUi::SpkStackedPages page)
{
if(mCurrentPage != page)
{
ui->Pager->setCurrentIndex(int(page));
mCurrentPage = page;
// If the page is a SpkPageBase (with a resource context), activate it for resource acquisition
auto tryActivate = qobject_cast<SpkPageBase *>(ui->Pager->currentWidget());
if(tryActivate)
tryActivate->Activated();
}
}
void SpkMainWindow::PopulateCategories(QJsonArray aCategoryData)
{
using SpkUi::SpkSidebarSelector;
QTreeWidgetItem *catg;
auto w = ui->CategoryWidget;
if(ui->CategoryParentItem->childCount()) // Clear all existing children if there is any
foreach(auto &i, ui->CategoryParentItem->takeChildren())
delete i;
@ -71,10 +84,91 @@ void SpkMainWindow::CategoryDataReceived()
{
sErr(tr("Failed to load categories!"));
// TODO: Switch to an error page
return;
}
PopulateCategories(retval.toArray());
}
void SpkMainWindow::EnterCategoryList(int aCategoryId)
{
// Asynchronously call category API
using namespace SpkUtils;
VerifySingleRequest(mCategoryAppListGetReply);
QJsonObject reqData;
QJsonDocument reqDoc;
reqData.insert("type_id", QJsonValue(aCategoryId));
reqDoc.setObject(reqData);
mCategoryAppListGetReply = STORE->SendApiRequest("application/get_application_list", reqDoc);
DeleteReplyLater(mCategoryAppListGetReply);
connect(mCategoryAppListGetReply, &QNetworkReply::finished,
this, &SpkMainWindow::CategoryListDataReceived);
setCursor(Qt::BusyCursor);
}
void SpkMainWindow::CategoryListDataReceived()
{
QJsonValue retval;
if(!SpkUtils::VerifyReplyJson(mCategoryAppListGetReply, retval) || !retval.isObject())
{
sErrPop(tr("Failed to load app list of category! Type of retval: %1.").arg(retval.type()));
return;
}
PopulateAppList(retval.toObject());
setCursor(Qt::ArrowCursor);
SwitchToPage(SpkUi::PgAppList);
}
void SpkMainWindow::PopulateAppList(QJsonObject appData)
{
auto w = ui->PageAppList;
w->ClearAll();
if(!appData.contains("data") || !appData.value("data").isArray())
{
sErrPop(tr("Received invalid application list data!"));
return;
}
auto apps = appData.value("data").toArray();
foreach(auto i, apps)
{
if(i.isObject())
{
auto j = i.toObject();
QString pkgName, displayName, description, iconPath;
int appid;
if(j.contains("package") && j.value("package").isString())
pkgName = j.value("package").toString();
else continue;
if(j.contains("application_name_zh") && j.value("application_name_zh").isString())
displayName = j.value("application_name_zh").toString();
else continue;
if(j.contains("description") && j.value("description").isString())
description = j.value("description").toString();
else continue;
if(j.contains("application_id") && j.value("application_id").isDouble())
appid = j.value("application_id").toInt();
else continue;
if(j.contains("icons") && j.value("icons").isString())
iconPath = j.value("icons").toString();
else continue;
w->AddApplicationEntry(displayName, pkgName, description, iconPath, appid);
}
}
}
// ==================== Main Window Initialization ====================
void SpkMainWindow::Initialize()
{
connect(ui->SidebarMgr, &SpkUi::SpkSidebarSelector::SwitchToCategory,
this, &SpkMainWindow::EnterCategoryList);
}
// ==================== Main Widget Initialization ====================
SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent)
{
setObjectName("spk_mainwidget");
@ -169,9 +263,22 @@ SpkUi::SpkMainWidget::SpkMainWidget(QWidget *parent) : QFrame(parent)
HorizontalDivide->addWidget(SideBarRestrictor);
HorizontalDivide->addLayout(VLayMain);
// Red-Black tree based map will be able to sort things
QMap<SpkStackedPages, QWidget*> sorter;
// Initialize pages
PageAppList = new SpkUi::SpkPageAppList(this);
PageAppList->setProperty("spk_pageid", SpkStackedPages::PgAppList);
sorter[PgAppList] = PageAppList;
#ifndef NDEBUG // If only in debug mode should we initialize QSS test page
PageQssTest = new SpkUi::SpkPageUiTest(this);
Pager->addWidget(PageQssTest);
PageQssTest->setProperty("spk_pageid", SpkStackedPages::PgQssTest);
sorter[PgQssTest] = PageQssTest;
#endif
for(auto i : sorter)
Pager->addWidget(i);
setLayout(HorizontalDivide);
}

84
gui/spkstretchlayout.cpp Normal file
View File

@ -0,0 +1,84 @@
#include "spkstretchlayout.h"
SpkStretchLayout::SpkStretchLayout(QWidget *parent) : QLayout(parent)
{
}
SpkStretchLayout::~SpkStretchLayout()
{
QLayoutItem *item;
while((item = takeAt(0)))
delete item;
}
void SpkStretchLayout::addItem(QLayoutItem *item)
{
mItems.append(item);
}
QSize SpkStretchLayout::sizeHint() const
{
int n = mItems.count();
auto minSize = mItems.isEmpty() ? QSize { 0, 0 } : mItems.first()->minimumSize();
QSize s { 200, 50 }; // s: Hint
if(n)
s = s.expandedTo(QSize { minSize.width(), minSize.height() * n });
return s + n * QSize(spacing(), spacing());
}
QSize SpkStretchLayout::minimumSize() const
{
int n = mItems.count();
auto minSize = mItems.isEmpty() ? QSize { 0, 0 } : mItems.first()->minimumSize();
QSize s(minSize.width(), minSize.height() * n);
return s + n * QSize(spacing(), spacing());
}
int SpkStretchLayout::count() const
{
return mItems.size();
}
QLayoutItem *SpkStretchLayout::itemAt(int i) const
{
return mItems.value(i);
}
QLayoutItem *SpkStretchLayout::takeAt(int i)
{
return i >=0 && i < mItems.size() ? mItems.takeAt(i) : nullptr;
}
void SpkStretchLayout::setGeometry(const QRect &rect)
{
QLayout::setGeometry(rect);
if(mItems.isEmpty())
return;
int spc = spacing(), w = rect.width() - spc;
QSize size;
auto itm = mItems.first();
// All items are considered the same, so we only calculate with the first item.
// Figure out how many at most can we squeeze into one line
int countPerLine = w / (itm->minimumSize().width() + spacing());
if(countPerLine >= mItems.size()) // All items fit in one line
size = itm->minimumSize();
// Won't fit in one line.
else // Stretch items.
size = QSize {(w / countPerLine - spc), itm->maximumSize().height() };
QLayoutItem *o;
for(int i = 0; i < mItems.size(); i++)
{
o = mItems.at(i);
QRect geo;
geo.setSize(size);
geo.moveTo((i % countPerLine) * (size.width() + spc) + spc,
(i / countPerLine) * (size.height() + spacing()) + spc);
o->setGeometry(geo);
}
}

View File

@ -271,5 +271,4 @@ namespace SpkUi
else
SetGlobalStyle(Light, true);
}
}

38
inc/page/spkpageapplist.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <QScrollArea>
#include <QList>
#include "spkresource.h"
#include "spkappitem.h"
#include "page/spkpagebase.h"
#include "spkstretchlayout.h"
namespace SpkUi
{
class SpkPageAppList : public SpkPageBase
{
Q_OBJECT
public:
SpkPageAppList(QWidget *parent = nullptr);
void AddApplicationEntry(QString name, QString pkgName, QString description, QString iconUrl,
int appId);
void ClearAll();
private:
public:
private:
SpkStretchLayout *mItemLay;
QList<SpkAppItem *> mAppItemList;
signals:
void ApplicationClicked(QString name, QString pkgName);
public slots:
void ResourceAcquisitionFinished(int id, ResourceResult result);
void Activated();
};
}

27
inc/page/spkpagebase.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <QScrollArea>
#include <spkresource.h>
class SpkPageBase : public QScrollArea
{
Q_OBJECT
public:
SpkPageBase(QWidget *parent = nullptr);
public slots:
/**
* @brief This signal is emitted by resource manager when a resource acquisition requested
* has finished.
* @param id The request ID
* @param result The data retrieved
*/
virtual void ResourceAcquisitionFinished(int id, ResourceResult result);
/**
* @brief This is an optional signal for Resource Context objects, mainly used for notifying the
* context that it is now activated (therefore it needs to acquire the resource manager).
*/
virtual void Activated();
};

87
inc/qt/elidedlabel.h Normal file
View File

@ -0,0 +1,87 @@
#pragma once
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
/*
* Slight modifications has been done to the code to make it fit into the project.
*/
#include <QFrame>
#include <QString>
//! [0]
class ElidedLabel : public QFrame
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(bool isElided READ isElided)
public:
explicit ElidedLabel(const QString &text, QWidget *parent = nullptr);
explicit ElidedLabel(QWidget *parent = nullptr);
void setText(const QString &text);
const QString & text() const { return content; }
bool isElided() const { return elided; }
protected:
void paintEvent(QPaintEvent *event) override;
signals:
void elisionChanged(bool elided);
private:
bool elided;
QString content;
};
//! [0]

View File

@ -4,13 +4,14 @@
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include "qt/elidedlabel.h"
class SpkAppItem : public QWidget
{
Q_OBJECT
public:
SpkAppItem(QWidget *parent = nullptr);
public:
SpkAppItem(int appId, QWidget *parent = nullptr);
void SetIcon(QPixmap p) { mIcon->setPixmap(p); }
void SetTitle(QString s) { mTitle->setText(s); }
void SetDescription(QString s) { mDescription->setText(s); }
@ -18,14 +19,17 @@ class SpkAppItem : public QWidget
protected:
void paintEvent(QPaintEvent *e);
public:
static constexpr int IconSize = 72;
static constexpr QSize IconSize_ = { IconSize, IconSize };
private:
QLabel *mIcon;
QLabel *mTitle;
QLabel *mDescription;
ElidedLabel *mDescription;
int mAppId;
QVBoxLayout *mLayText;
QHBoxLayout *mMainLay;
static constexpr int IconSize = 72;
};

View File

@ -5,13 +5,15 @@
#pragma once
#include "spkwindow.h"
#include <vector>
#include <QTextEdit>
#include <QStackedWidget>
#include <QButtonGroup>
#include <QJsonObject>
#include "spksidebartree.h" // In place of #include <QTreeWidget>
#include <QPointer>
#include "spkpageuitest.h"
#include "inc/page/spkpageuitest.h"
#include "inc/page/spkpageapplist.h"
class QNetworkReply;
@ -19,9 +21,16 @@ namespace SpkUi
{
enum SpkStackedPages
{
PgInvalid = -1,
PgAppList,
PgQssTest // Must be at last
};
const std::vector<SpkStackedPages> ResourceContexts
{
PgAppList
};
class SpkSidebarSelector : public QObject
{
Q_OBJECT
@ -139,6 +148,7 @@ namespace SpkUi
//Pages
SpkPageUiTest *PageQssTest;
SpkPageAppList *PageAppList;
};
}
@ -154,11 +164,24 @@ class SpkMainWindow : public SpkWindow
void PopulateCategories(QJsonArray);
private:
QPointer<QNetworkReply> mCategoryGetReply;
void Initialize();
private:
QPointer<QNetworkReply> mCategoryGetReply,
mCategoryAppListGetReply;
SpkUi::SpkStackedPages mCurrentPage = SpkUi::PgInvalid;
public slots:
void RefreshCategoryData();
private slots:
void SwitchToPage(SpkUi::SpkStackedPages page);
void CategoryDataReceived();
void EnterCategoryList(int id);
void CategoryListDataReceived();
private:
void PopulateAppList(QJsonObject appData);
};

View File

@ -23,6 +23,7 @@ namespace SpkUi
LightCtrlsGradDarker,
DarkCtrlsGradLight,
DarkCtrlsGradDark,
DarkCtrlsGradDarker,
TextOnSelection, TextOnAccentColor = TextOnSelection,
TextOnGlobalBgnd,
TextOnControlsBgnd,
@ -50,6 +51,7 @@ namespace SpkUi
{ LightCtrlsGradDarker, "LCTL3" },
{ DarkCtrlsGradLight, "DCTL1" },
{ DarkCtrlsGradDark, "DCTL2" },
{ DarkCtrlsGradDarker, "DCTL3" },
{ TextOnSelection, "TXACC" },
{ TextOnGlobalBgnd, "TXGBG" },
{ TextOnControlsBgnd, "TXCBG" },
@ -71,6 +73,7 @@ namespace SpkUi
{ LightCtrlsGradDarker, 0x606060 },
{ DarkCtrlsGradLight, 0x404040 },
{ DarkCtrlsGradDark, 0x383838 },
{ DarkCtrlsGradDarker, 0x323232 },
{ TextOnSelection, ColorTextOnBackground(0x0070ff) },
{ TextOnGlobalBgnd, ColorTextOnBackground(0x282828) },
{ TextOnControlsBgnd, ColorTextOnBackground(0x282828) },
@ -91,12 +94,13 @@ namespace SpkUi
{ LightCtrlsGradDarker, 0xebebeb },
{ DarkCtrlsGradLight, 0xe4e4e4 },
{ DarkCtrlsGradDark, 0xcecece },
{ DarkCtrlsGradDarker, 0xb8b8b8 },
{ TextOnSelection, ColorTextOnBackground(0x0070ff) },
{ TextOnGlobalBgnd, ColorTextOnBackground(0xf8f8f8) },
{ TextOnControlsBgnd, ColorTextOnBackground(0xf8f8f8) },
{ TextLighter, 0x2a2a2a },
{ GlossyEdge, 0x9d9d9d },
{ ShadesEdge, 0xc5c5c5 }
{ GlossyEdge, 0xc5c5c5 },
{ ShadesEdge, 0x9d9d9d }
};
using ColorSet = std::map<Qss::ColorSetIndex, QColor>;

View File

@ -2,13 +2,98 @@
#pragma once
#include <QObject>
#include <QQueue>
#include <QSemaphore>
#include <QMap>
#include <QNetworkReply>
#include <QVariant>
struct ResourceResult;
class SpkResourceContext;
class SpkPageBase;
class SpkResource : public QObject
{
Q_OBJECT
public:
enum class ResourceType { HomeImage, AppIcon, TagIcon, AppScreenshot, };
enum class ResourceStatus
{
Q_OBJECT
public:
SpkResource(QObject *parent = nullptr);
private:
Ready, ///< The resource is ready to be read now and the file link is included
Deferred, ///< The resource is not in cache or outdated, and is being downloaded
Failed, ///< The requested resource can not be downloaded and thus can not be loaded.
};
private:
struct ResourceTask
{
QString pkgName, path;
ResourceType type;
QVariant info;
int id;
};
static const QMap<ResourceType, QString> ResourceName;
public:
SpkResource(QObject *parent = nullptr);
ResourceResult
RequestResource(const int aId, const QString &aPkgName, ResourceType aType, const QString &aPath,
const QVariant &aInfo = 0);
void PurgeCachedResource(const QString &aPkgName, SpkResource::ResourceType aType,
const QVariant &aInfo);
inline QString GetCachePath(const ResourceTask &task);
/**
* @brief When the resource context was changed, the new context needs to acquire the resource
* manager, so the resource manager can download resource for the new context.
*
* A resource context, in Spark Store, is likely to be a "page" of the UI. AppList is
* a page, Homepage is a page, AppDetails is a page. But each category is not a separated
* context; they're all from AppList page.
* @param dest Pass a SpkResourceContext as the resource acquisition destination. Signals will
* be connected in this function internally. When a resource was downloaded, this
* object will be notified.
* @param clearQueue determines if the awaiting tasks needs to be cleared.
* @param stopOngoing determines if ongoing tasks needs to be terminated.
*/
void Acquire(SpkPageBase *dest, bool stopOngoing, bool clearQueue = true);
public:
static SpkResource* Instance;
const int mMaximumConcurrent; ///< Maximum number of concurrent resource downloads
const QString mCacheDirectory;///< Where caches were stored
private slots:
void ResourceDownloaded();
void TryBeginAwaitingTasks();
private:
ResourceResult LocateCachedResource(const ResourceTask &task);
signals:
void AcquisitionFinish(int id, ResourceResult result);
private:
// Operations to mAwaitingRequests and mWorkingRequests must be all made synchronously.
// When connecting signal/slot pairs regarding operations to them, use QueuedConnection.
QQueue<ResourceTask> mAwaitingRequests;
QSemaphore *mRequestSemaphore;
QMap<QNetworkReply*, int> mWorkingRequests;
};
struct ResourceResult
{
SpkResource::ResourceStatus status;
QByteArray data;
};
Q_DECLARE_METATYPE(ResourceResult);
/**
* @brief SpkResourceContext is meant to be used by connecting signals from it. An object inheriting
* it would be theoretically a resource context. A resource context can acquire a SpkResource
* management object for downloading resources. More details at SpkResource::Acquire.
*/

View File

@ -9,6 +9,7 @@
#include "spklogging.h"
#include "spkmainwindow.h"
#include "spkresource.h"
/**
* @brief SpkStore class is the core of the store client side program, it is constructed first and
@ -27,13 +28,19 @@ class SpkStore : public QObject
SpkMainWindow* GetRootWindow() { return mMainWindow; }
void SetApiResuestUrl(QString aUrlStr) { mApiRequestUrl = aUrlStr; }
void SetApiRequestUrl(QString aUrlStr) { mApiRequestUrl = aUrlStr; }
QString GetApiRequestUrl() { return mApiRequestUrl; }
QNetworkReply *SendApiRequest(QString path, QJsonDocument param = QJsonDocument());
QNetworkReply *SendResourceRequest(QString path); ///< WARNING: Only intended for SpkResource!
private:
SpkLogger *mLogger;
SpkMainWindow *mMainWindow = nullptr;
SpkResource *mResMgr = nullptr;
QNetworkAccessManager *mNetMgr = nullptr;
QString mDistroName, mApiRequestUrl, mUserAgentStr, mConfigPath;
QString mDistroName,
mApiRequestUrl,
mResourceRequestUrl,
mUserAgentStr,
mConfigPath;
};

27
inc/spkstretchlayout.h Normal file
View File

@ -0,0 +1,27 @@
#pragma once
#include <QtWidgets>
#include <QVector>
/**
* @brief SpkStretchLayout is meant solely for use with app list, where each layout item is
* considered the same size.
*/
class SpkStretchLayout : public QLayout
{
public:
SpkStretchLayout(QWidget *parent = nullptr);
~SpkStretchLayout();
void addItem(QLayoutItem *item) override;
QSize sizeHint() const override;
QSize minimumSize() const override;
int count() const override;
QLayoutItem* itemAt(int) const override;
QLayoutItem* takeAt(int) override;
void setGeometry(const QRect &rect) override;
private:
QVector<QLayoutItem*> mItems;
};

View File

@ -10,8 +10,9 @@
#include "spkstore.h"
#include "spklogging.h"
#define STORE SpkStore::Instance
#define STORE (SpkStore::Instance)
#define CFG (SpkStore::Instance->mCfg)
#define RES (SpkResource::Instance)
namespace SpkUtils
{
@ -22,4 +23,7 @@ namespace SpkUtils
void DeleteReplyLater(QNetworkReply *aReply);
bool VerifyReplyJson(QNetworkReply *aReply, QJsonValue& aRetDoc);
QString CutFileName(QString);
QString CutPath(QString);
}

View File

@ -1,240 +0,0 @@
/*
* Copyright (C) 2001-2004 Bart Massey and Jamey Sharp.
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the names of the authors or their
* institutions shall not be used in advertising or otherwise to promote the
* sale, use or other dealings in this Software without prior written
* authorization from the authors.
*/
#ifndef __XCBINT_H
#define __XCBINT_H
#include "bigreq.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef GCC_HAS_VISIBILITY
#pragma GCC visibility push(hidden)
#endif
#ifndef __has_attribute
# define __has_attribute(x) 0 /* Compatibility with older compilers. */
#endif
#if __has_attribute(fallthrough)
# define XCB_ALLOW_FALLTHRU __attribute__ ((fallthrough));
#else
# define XCB_ALLOW_FALLTHRU /* FALLTHRU */
#endif
enum workarounds {
WORKAROUND_NONE,
WORKAROUND_GLX_GET_FB_CONFIGS_BUG,
WORKAROUND_EXTERNAL_SOCKET_OWNER
};
enum lazy_reply_tag
{
LAZY_NONE = 0,
LAZY_COOKIE,
LAZY_FORCED
};
#define XCB_PAD(i) (-(i) & 3)
#define XCB_SEQUENCE_COMPARE(a,op,b) ((int64_t) ((a) - (b)) op 0)
#ifndef offsetof
#define offsetof(type,member) ((size_t) &((type *)0)->member)
#endif
#ifndef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#endif
#define container_of(pointer,type,member) ((type *)(((char *)(pointer)) - offsetof(type, member)))
/* xcb_list.c */
typedef void (*xcb_list_free_func_t)(void *);
typedef struct _xcb_map _xcb_map;
_xcb_map *_xcb_map_new(void);
void _xcb_map_delete(_xcb_map *q, xcb_list_free_func_t do_free);
int _xcb_map_put(_xcb_map *q, unsigned int key, void *data);
void *_xcb_map_remove(_xcb_map *q, unsigned int key);
/* xcb_out.c */
#if HAVE_SENDMSG
#define XCB_MAX_PASS_FD 16
typedef struct _xcb_fd {
int fd[XCB_MAX_PASS_FD];
int nfd;
int ifd;
} _xcb_fd;
#endif
typedef struct _xcb_out {
pthread_cond_t cond;
int writing;
pthread_cond_t socket_cond;
void (*return_socket)(void *closure);
void *socket_closure;
int socket_moving;
char queue[XCB_QUEUE_BUFFER_SIZE];
int queue_len;
uint64_t request;
uint64_t request_written;
uint64_t total_written;
pthread_mutex_t reqlenlock;
enum lazy_reply_tag maximum_request_length_tag;
union {
xcb_big_requests_enable_cookie_t cookie;
uint32_t value;
} maximum_request_length;
#if HAVE_SENDMSG
_xcb_fd out_fd;
#endif
} _xcb_out;
int _xcb_out_init(_xcb_out *out);
void _xcb_out_destroy(_xcb_out *out);
int _xcb_out_send(xcb_connection_t *c, struct iovec *vector, int count);
void _xcb_out_send_sync(xcb_connection_t *c);
int _xcb_out_flush_to(xcb_connection_t *c, uint64_t request);
/* xcb_in.c */
typedef struct _xcb_in {
pthread_cond_t event_cond;
int reading;
char queue[4096];
int queue_len;
uint64_t request_expected;
uint64_t request_read;
uint64_t request_completed;
uint64_t total_read;
struct reply_list *current_reply;
struct reply_list **current_reply_tail;
_xcb_map *replies;
struct event_list *events;
struct event_list **events_tail;
struct reader_list *readers;
struct special_list *special_waiters;
struct pending_reply *pending_replies;
struct pending_reply **pending_replies_tail;
#if HAVE_SENDMSG
_xcb_fd in_fd;
#endif
struct xcb_special_event *special_events;
} _xcb_in;
int _xcb_in_init(_xcb_in *in);
void _xcb_in_destroy(_xcb_in *in);
void _xcb_in_wake_up_next_reader(xcb_connection_t *c);
int _xcb_in_expect_reply(xcb_connection_t *c, uint64_t request, enum workarounds workaround, int flags);
void _xcb_in_replies_done(xcb_connection_t *c);
int _xcb_in_read(xcb_connection_t *c);
int _xcb_in_read_block(xcb_connection_t *c, void *buf, int nread);
/* xcb_xid.c */
typedef struct _xcb_xid {
pthread_mutex_t lock;
uint32_t last;
uint32_t base;
uint32_t max;
uint32_t inc;
} _xcb_xid;
int _xcb_xid_init(xcb_connection_t *c);
void _xcb_xid_destroy(xcb_connection_t *c);
/* xcb_ext.c */
typedef struct _xcb_ext {
pthread_mutex_t lock;
struct lazyreply *extensions;
int extensions_size;
} _xcb_ext;
int _xcb_ext_init(xcb_connection_t *c);
void _xcb_ext_destroy(xcb_connection_t *c);
/* xcb_conn.c */
struct xcb_connection_t {
/* This must be the first field; see _xcb_conn_ret_error(). */
int has_error;
/* constant data */
xcb_setup_t *setup;
int fd;
/* I/O data */
pthread_mutex_t iolock;
_xcb_in in;
_xcb_out out;
/* misc data */
_xcb_ext ext;
_xcb_xid xid;
};
void _xcb_conn_shutdown(xcb_connection_t *c, int err);
xcb_connection_t *_xcb_conn_ret_error(int err);
int _xcb_conn_wait(xcb_connection_t *c, pthread_cond_t *cond, struct iovec **vector, int *count);
/* xcb_auth.c */
int _xcb_get_auth_info(int fd, xcb_auth_info_t *info, int display);
#ifdef GCC_HAS_VISIBILITY
#pragma GCC visibility pop
#endif
#endif

View File

@ -56,7 +56,7 @@ QPushButton
font-size: 14px;
font-weight: 300;
border-radius: 8px;
border-color: LCTL2;
border-color: GLS;
border-top-color: SHD;
border-style: solid;
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 LCTL1, stop:1 LCTL2)
@ -90,6 +90,11 @@ QScrollBar::add-line, QScrollBar::sub-line
/* Custom widgets */
ElidedLabel
{
background: transparent;
}
#styAboutDesc
{
color: TXL
@ -104,7 +109,11 @@ SpkAppItem
SpkAppItem::hover
{
background: DCTL1;
border-radius: 11px;
}
SpkAppItem::pressed
{
background: DCTL3;
}
#styAppItmTitle

View File

@ -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" },
};

View File

@ -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

View File

@ -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);
}