Added featured videos model, paginator and cache

This commit is contained in:
Digital Artifex
2026-06-17 16:07:11 -04:00
parent be089a43f7
commit 26fb04431e
8 changed files with 2657 additions and 345 deletions
+2 -4
View File
@@ -179,16 +179,14 @@ Item {
required property string author required property string author
required property string authorId required property string authorId
required property string authorUrl required property string authorUrl
required property string description
required property string thumbnail required property string thumbnail
required property string original
SearchResultItem SearchResultItem
{ {
anchors.fill: parent anchors.fill: parent
title: "Pexels Photo" title: parent.author
author: parent.author author: parent.author
description: parent.description description: "Video provided by Pexels"
thumbnail: parent.thumbnail thumbnail: parent.thumbnail
uuid: parent.uuid uuid: parent.uuid
} }
+2
View File
@@ -48,6 +48,8 @@ qt_add_qml_module(
SOURCES paginators/featuredvideospaginator.h SOURCES paginators/featuredvideospaginator.h
SOURCES common/imagecache.h SOURCES common/imagecache.h
SOURCES paginators/imagepaginator.h SOURCES paginators/imagepaginator.h
SOURCES common/videocache.h
SOURCES paginators/videopaginator.h
) )
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
#ifndef VIDEOCACHE_H
#define VIDEOCACHE_H
#include "komplex_global.h"
#include <QObject>
struct KOMPLEX_EXPORT VideoEntry
{
QString type;
qint64 fps = std::numeric_limits<qint64>::infinity();
qint64 height = std::numeric_limits<qint64>::infinity();
qint64 width = std::numeric_limits<qint64>::infinity();
qint64 size = std::numeric_limits<qint64>::infinity();
qint64 id = std::numeric_limits<qint64>::infinity();
QString url;
QString quality;
auto operator == (const VideoEntry &other) const -> bool
{
return (
other.fps == fps &&
other.height == height &&
other.id == id &&
other.quality == quality &&
other.size == size &&
other.type == type &&
other.url == url &&
other.width == width
);
}
auto operator != (const VideoEntry &other) const -> bool
{
return !(other == *this);
}
};
Q_DECLARE_METATYPE(VideoEntry)
struct KOMPLEX_EXPORT VideoCache
{
QString averageColor;
qint64 duration = std::numeric_limits<qint64>::infinity();
qint64 height = std::numeric_limits<qint64>::infinity();
qint64 width = std::numeric_limits<qint64>::infinity();
qint64 id = std::numeric_limits<qint64>::infinity();
QString thumbnail;
QString url;
QString author;
QString authorUrl;
qint64 authorId;
QList<VideoEntry> files;
auto operator == (const VideoCache &other) const -> bool
{
return (
other.averageColor == averageColor &&
other.duration == duration &&
other.height == height &&
other.width == width &&
other.id == id &&
other.thumbnail == thumbnail &&
other.url == url &&
other.author == author &&
other.authorUrl == authorUrl &&
other.authorId == authorId &&
other.files == files
);
}
auto operator != (const VideoCache &other) const -> bool
{
return !(other == *this);
}
};
#endif // VIDEOCACHE_H
+18 -24
View File
@@ -1,5 +1,5 @@
#include "featuredvideosmodel.h" #include "featuredvideosmodel.h"
#include "common/wallpapercache.h" #include "common/videocache.h"
FeaturedVideosModel::FeaturedVideosModel(QObject *parent) : QAbstractListModel{parent} FeaturedVideosModel::FeaturedVideosModel(QObject *parent) : QAbstractListModel{parent}
{ {
@@ -59,46 +59,40 @@ auto FeaturedVideosModel::data(const QModelIndex &index, int role) const -> QVar
return {}; return {};
} }
WallpaperCache dataPoint = m_data.at(index.row()); VideoCache dataPoint = m_data.at(index.row());
QVariant data; QVariant data;
switch(static_cast<DataRole>(role)) switch(static_cast<DataRole>(role))
{ {
case UuidRole: case UuidRole:
data = dataPoint.uuid; data = dataPoint.id;
break; break;
case AuthorRole: case AuthorRole:
data = dataPoint.author; data = dataPoint.author;
break; break;
case DescriptionRole: case AuthorIdRole:
data = dataPoint.description; data = dataPoint.authorId;
break; break;
case NameRole: case AuthorUrlRole:
data = dataPoint.name; data = dataPoint.authorUrl;
break;
case DurationRole:
data = dataPoint.duration;
break; break;
case ThumbnailRole: case ThumbnailRole:
data = dataPoint.thumbnail; data = dataPoint.thumbnail;
break; break;
case CreatedDateRole: case UrlRole:
data = dataPoint.createdDate; data = dataPoint.url;
break; break;
case AuthorIdRole: case HeightRole:
data = dataPoint.authorId; data = dataPoint.height;
break; break;
case PriceRole: case WidthRole:
data = dataPoint.price; data = dataPoint.width;
break; break;
case CurrencyRole: }
data = dataPoint.currency;
break;
case DownloadCountRole:
data = dataPoint.downloadCount;
break;
case TypeRole:
data = dataPoint.type;
break;
}
return data; return data;
} }
@@ -160,7 +154,7 @@ auto FeaturedVideosModel::resetDataModel() -> void
beginInsertRows(QModelIndex(), 0, m_paginator->count() - 1); beginInsertRows(QModelIndex(), 0, m_paginator->count() - 1);
auto dataset = m_paginator->data(); auto dataset = m_paginator->data();
for(const WallpaperCache& data : std::as_const(dataset)) for(const VideoCache& data : std::as_const(dataset))
{ {
m_data.append(data); m_data.append(data);
} }
+16 -26
View File
@@ -65,14 +65,12 @@ public:
UuidRole = Qt::UserRole + 1, UuidRole = Qt::UserRole + 1,
AuthorRole, AuthorRole,
AuthorIdRole, AuthorIdRole,
DescriptionRole, AuthorUrlRole,
NameRole, DurationRole,
ThumbnailRole, ThumbnailRole,
CreatedDateRole, UrlRole,
PriceRole, HeightRole,
CurrencyRole, WidthRole
DownloadCountRole,
TypeRole
}; };
Q_ENUM(DataRole) Q_ENUM(DataRole)
@@ -251,36 +249,28 @@ private:
QByteArray("authorId") QByteArray("authorId")
}, },
{ {
static_cast<int>(DescriptionRole), static_cast<int>(AuthorUrlRole),
QByteArray("description") QByteArray("authorUrl")
}, },
{ {
static_cast<int>(NameRole), static_cast<int>(DurationRole),
QByteArray("name") QByteArray("duration")
}, },
{ {
static_cast<int>(ThumbnailRole), static_cast<int>(ThumbnailRole),
QByteArray("thumbnail") QByteArray("thumbnail")
}, },
{ {
static_cast<int>(CreatedDateRole), static_cast<int>(UrlRole),
QByteArray("createdDate") QByteArray("url")
}, },
{ {
static_cast<int>(PriceRole), static_cast<int>(HeightRole),
QByteArray("price") QByteArray("height")
}, },
{ {
static_cast<int>(CurrencyRole), static_cast<int>(WidthRole),
QByteArray("currency") QByteArray("width")
},
{
static_cast<int>(DownloadCountRole),
QByteArray("downloadCount")
},
{
static_cast<int>(TypeRole),
QByteArray("type")
} }
}; };
State m_state = Idle; State m_state = Idle;
@@ -289,7 +279,7 @@ private:
FeaturedVideosPaginator *m_paginator = nullptr; FeaturedVideosPaginator *m_paginator = nullptr;
mutable QList<WallpaperCache> m_data; mutable QList<VideoCache> m_data;
Q_PROPERTY(State state READ state WRITE setState RESET resetState NOTIFY stateChanged FINAL) Q_PROPERTY(State state READ state WRITE setState RESET resetState NOTIFY stateChanged FINAL)
Q_PROPERTY(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL) Q_PROPERTY(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL)
@@ -29,12 +29,12 @@
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include "wallpaperpaginator.h" #include "videopaginator.h"
class KOMPLEX_EXPORT FeaturedVideosPaginator : public WallpaperPaginator class KOMPLEX_EXPORT FeaturedVideosPaginator : public VideoPaginator
{ {
public: public:
explicit FeaturedVideosPaginator(QObject *parent = nullptr) : WallpaperPaginator(parent) explicit FeaturedVideosPaginator(QObject *parent = nullptr) : VideoPaginator(parent)
{ {
setUri( setUri(
QString::fromUtf8(KOMPLEX_ENDPOINT_VIDEOS_FEATURED) QString::fromUtf8(KOMPLEX_ENDPOINT_VIDEOS_FEATURED)
@@ -0,0 +1,335 @@
/*
* Komplex Wallpaper Engine
* Copyright (C) 2026 @DigitalArtifex
* https://digitalartifex.dev - https://github.com/DigitalArtifex
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef VIDEOPAGINATOR_H
#define VIDEOPAGINATOR_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QEventLoop>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonArray>
#include <QJsonObject>
#include "common/logging.h"
#include "common/paginator.h"
#include "common/videocache.h"
#include "common/coreservices.h"
/**
* @brief The VideoPaginator class
* Most of the media endpoints for the Komplex API have the same data structure so
* child classes can make use of this generic controller where possible
*/
class KOMPLEX_EXPORT VideoPaginator : public Paginator<VideoCache>
{
Q_OBJECT
public:
explicit VideoPaginator(QObject *parent = nullptr) : Paginator<VideoCache>(parent)
{
SlidingCacheController<VideoCache> *controller = this->controller();
controller->setFetch(
std::bind(&VideoPaginator::fetch, this, std::placeholders::_1, std::placeholders::_2)
);
}
/**
* @brief uri
* URI of the API Endpoint. This is set by the child class
* @return
*/
[[nodiscard]]
auto uri() const -> QString
{
return m_uri;
}
/**
* @brief query
* Current query string, if the API Endpoint supports queries
* @return
*/
[[nodiscard]]
auto query() const -> QString
{
return m_query;
}
/**
* @brief setQuery
* Sets the new query string and updates the controller
* @param query
*/
auto setQuery(const QString &query) -> void
{
if(query == m_query)
{
return;
}
m_query = query;
Q_EMIT queryChanged();
reset();
setOffset(0);
}
signals:
/**
* @brief uriChanged
*/
auto uriChanged() -> void;
/**
* @brief queryChanged
*/
auto queryChanged() -> void;
protected:
/**
* @brief fetch
* Used to fetch results from the API endpoint
* for the CacheController. It is intended to be passed
* to the Cache Controller not meant to be called directly.
* @param offset Offset index to start the results from
* @param limit Number of results to attempt to fetch
* @return FetchResult<ImageCache>
*/
[[nodiscard]]
auto fetch(
qsizetype offset,
qsizetype limit
) const -> FetchResult<VideoCache>
{
LOG_ERROR_X(m_uri.isEmpty(),
"VideoPaginator::fetch",
"Uri not set",
{}
);
QUrl url(
QString::fromUtf8("%1/%2/%3").arg(
KOMPLEX_API_HOST,
KOMPLEX_API_VERSION,
m_uri
)
);
QNetworkRequest request(url);
request.setHeaders(
QHttpHeaders::fromMultiMap(
{
{
QByteArray("query"),
m_query.toUtf8()
},
{
QByteArray("offset"),
QString::number(offset).toUtf8()
},
{
QByteArray("limit"),
QString::number(limit).toUtf8()
},
{
QByteArray("session_token"),
CoreServices::sessionToken()
}
}
)
);
QNetworkReply *reply = getNetworkReply(request);
if(reply == nullptr)
{
return {};
}
QJsonDocument document = getDocument(reply);
QJsonObject rootObject = document.object();
const QJsonArray resultsArray = rootObject.value(QString::fromUtf8("videos")).toArray();
QList<VideoCache> fetchedResults;
for(const QJsonValue &result : resultsArray)
{
if(!result.isObject())
{
continue;
}
QJsonObject resultObject = result.toObject();
QJsonObject userObject = resultObject.value(QString::fromUtf8("user")).toObject();
QList<VideoEntry> videos;
const QJsonArray videosArray = resultObject.value(QString::fromUtf8("video_files")).toArray();
for(const QJsonValue &entry : videosArray)
{
if(!entry.isObject())
{
continue;
}
QJsonObject videoObject = entry.toObject();
videos.append
(
{
videoObject.value(QString::fromUtf8("file_type")).toString(),
videoObject.value(QString::fromUtf8("fps")).toInteger(),
videoObject.value(QString::fromUtf8("height")).toInteger(),
videoObject.value(QString::fromUtf8("width")).toInteger(),
videoObject.value(QString::fromUtf8("size")).toInteger(),
videoObject.value(QString::fromUtf8("id")).toInteger(),
videoObject.value(QString::fromUtf8("link")).toString(),
videoObject.value(QString::fromUtf8("quality")).toString()
}
);
}
fetchedResults.append
(
{
resultObject.value(QString::fromUtf8("avg_color")).toString(),
resultObject.value(QString::fromUtf8("duration")).toInteger(),
resultObject.value(QString::fromUtf8("height")).toInteger(),
resultObject.value(QString::fromUtf8("width")).toInteger(),
resultObject.value(QString::fromUtf8("id")).toInteger(),
resultObject.value(QString::fromUtf8("image")).toString(),
resultObject.value(QString::fromUtf8("url")).toString(),
userObject.value(QString::fromUtf8("name")).toString(),
userObject.value(QString::fromUtf8("url")).toString(),
userObject.value(QString::fromUtf8("id")).toInteger(),
std::move(videos)
}
);
}
FetchResult<VideoCache> result
{
static_cast<qsizetype>(rootObject.value(QString::fromUtf8("total_results")).toInteger()),
fetchedResults.count(),
std::move(fetchedResults)
};
return std::move(result);
}
/**
* @brief getNetworkReply
* Processes the request and blocks until completion.
* @param request
* @return
*/
[[nodiscard]]
auto getNetworkReply(const QNetworkRequest &request) const -> QNetworkReply*
{
QEventLoop loop;
QWeakPointer<QNetworkAccessManager> managerReference = CoreServices::networkAccessManager();
QSharedPointer<QNetworkAccessManager> manager = managerReference.toStrongRef();
LOG_ERROR_X(!manager,
"NewestPacksPaginator::getNetworkReply",
"Network manager reference has already been deleted",
nullptr
);
QNetworkReply *reply = manager->post(request, nullptr);
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
QObject::connect(reply, &QNetworkReply::errorOccurred, &loop,
[&loop](QNetworkReply::NetworkError error) -> void
{
loop.quit();
}
);
if(!reply->isFinished())
{
loop.exec();
}
LOG_ERROR_X(reply->error() != QNetworkReply::NoError,
"NewestPacksPaginator::getNetworkReply",
reply->errorString().toStdString().c_str(),
nullptr
);
return reply;
}
/**
* @brief getDocument
* Extracts the JSON document from the server reply
* @param reply
* @return
*/
[[nodiscard]]
auto getDocument(QNetworkReply *reply) const -> QJsonDocument
{
QByteArray data = reply->readAll();
QJsonParseError documentError;
QJsonDocument document = QJsonDocument::fromJson(data, &documentError);
LOG_ERROR_X(documentError.error != QJsonParseError::NoError,
"NewestPacksPaginator::getDocument",
documentError.errorString().toStdString().c_str(),
{}
);
return document;
}
/**
* @brief setUri
* Sets the API Endpoint URI to fetch from
* @param uri
*/
auto setUri(const QString &uri) -> void
{
if(uri == m_uri)
{
return;
}
m_uri = uri;
Q_EMIT uriChanged();
}
private:
/**
* @brief m_uri
* API Endpoint URI
*/
QString m_uri;
/**
* @brief m_query
* The query string used in fetch operations
*/
QString m_query;
};
#endif // VIDEOPAGINATOR_H