2023-01-23 14:03:55 +00:00
|
|
|
// SPDX-FileCopyrightText: 2023 flowln <flowlnlnln@gmail.com>
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
|
2022-11-25 12:23:46 +00:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <optional>
|
|
|
|
|
|
|
|
#include <QAbstractListModel>
|
|
|
|
|
|
|
|
#include "QObjectPtr.h"
|
2022-12-20 15:15:17 +00:00
|
|
|
|
2022-11-25 12:23:46 +00:00
|
|
|
#include "modplatform/ResourceAPI.h"
|
2022-12-20 15:15:17 +00:00
|
|
|
|
2022-11-25 12:23:46 +00:00
|
|
|
#include "tasks/ConcurrentTask.h"
|
|
|
|
|
|
|
|
class NetJob;
|
|
|
|
class ResourceAPI;
|
|
|
|
|
|
|
|
namespace ModPlatform {
|
|
|
|
struct IndexedPack;
|
|
|
|
}
|
|
|
|
|
2022-12-16 22:03:52 +00:00
|
|
|
namespace ResourceDownload {
|
|
|
|
|
2022-11-25 12:23:46 +00:00
|
|
|
class ResourceModel : public QAbstractListModel {
|
|
|
|
Q_OBJECT
|
|
|
|
|
2022-12-18 19:55:09 +00:00
|
|
|
Q_PROPERTY(QString search_term MEMBER m_search_term WRITE setSearchTerm)
|
|
|
|
|
2022-11-25 12:23:46 +00:00
|
|
|
public:
|
2023-01-03 15:48:22 +00:00
|
|
|
ResourceModel(ResourceAPI* api);
|
2022-11-25 12:23:46 +00:00
|
|
|
~ResourceModel() override;
|
|
|
|
|
|
|
|
[[nodiscard]] auto data(const QModelIndex&, int role) const -> QVariant override;
|
2022-12-18 19:55:09 +00:00
|
|
|
[[nodiscard]] auto roleNames() const -> QHash<int, QByteArray> override;
|
2022-11-25 12:23:46 +00:00
|
|
|
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
|
|
|
|
|
2022-12-18 18:41:46 +00:00
|
|
|
[[nodiscard]] virtual auto debugName() const -> QString;
|
|
|
|
[[nodiscard]] virtual auto metaEntryBase() const -> QString = 0;
|
2022-11-25 12:23:46 +00:00
|
|
|
|
|
|
|
[[nodiscard]] inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : m_packs.size(); }
|
2022-12-18 20:03:39 +00:00
|
|
|
[[nodiscard]] inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; }
|
|
|
|
[[nodiscard]] inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }
|
2022-11-25 12:23:46 +00:00
|
|
|
|
2022-12-20 20:14:17 +00:00
|
|
|
[[nodiscard]] bool hasActiveSearchJob() const { return m_current_search_job && m_current_search_job->isRunning(); }
|
|
|
|
[[nodiscard]] bool hasActiveInfoJob() const { return m_current_info_job.isRunning(); }
|
|
|
|
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? m_current_search_job : nullptr; }
|
2022-11-25 12:23:46 +00:00
|
|
|
|
2022-12-20 15:15:17 +00:00
|
|
|
[[nodiscard]] auto getSortingMethods() const { return m_api->getSortingMethods(); }
|
|
|
|
|
2022-11-25 12:23:46 +00:00
|
|
|
public slots:
|
|
|
|
void fetchMore(const QModelIndex& parent) override;
|
2022-12-18 20:03:39 +00:00
|
|
|
// NOTE: Can't use [[nodiscard]] here because of https://bugreports.qt.io/browse/QTBUG-58628 on Qt 5.12
|
|
|
|
inline bool canFetchMore(const QModelIndex& parent) const override
|
2022-11-25 12:23:46 +00:00
|
|
|
{
|
|
|
|
return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setSearchTerm(QString term) { m_search_term = term; }
|
|
|
|
|
|
|
|
virtual ResourceAPI::SearchArgs createSearchArguments() = 0;
|
2022-12-23 21:18:20 +00:00
|
|
|
virtual ResourceAPI::SearchCallbacks createSearchCallbacks() { return {}; }
|
2022-11-25 12:23:46 +00:00
|
|
|
|
|
|
|
virtual ResourceAPI::VersionSearchArgs createVersionsArguments(QModelIndex&) = 0;
|
2022-12-23 21:18:20 +00:00
|
|
|
virtual ResourceAPI::VersionSearchCallbacks createVersionsCallbacks(QModelIndex&) { return {}; }
|
2022-11-25 12:23:46 +00:00
|
|
|
|
|
|
|
virtual ResourceAPI::ProjectInfoArgs createInfoArguments(QModelIndex&) = 0;
|
2022-12-23 21:18:20 +00:00
|
|
|
virtual ResourceAPI::ProjectInfoCallbacks createInfoCallbacks(QModelIndex&) { return {}; }
|
2022-11-25 12:23:46 +00:00
|
|
|
|
|
|
|
/** Requests the API for more entries. */
|
|
|
|
virtual void search();
|
|
|
|
|
|
|
|
/** Applies any processing / extra requests needed to fully load the specified entry's information. */
|
|
|
|
virtual void loadEntry(QModelIndex&);
|
|
|
|
|
|
|
|
/** Schedule a refresh, clearing the current state. */
|
|
|
|
void refresh();
|
|
|
|
|
|
|
|
/** Gets the icon at the URL for the given index. If it's not fetched yet, fetch it and update when fisinhed. */
|
|
|
|
std::optional<QIcon> getIcon(QModelIndex&, const QUrl&);
|
|
|
|
|
|
|
|
protected:
|
|
|
|
/** Resets the model's data. */
|
|
|
|
void clearData();
|
|
|
|
|
2023-01-03 16:58:27 +00:00
|
|
|
void runSearchJob(Task::Ptr);
|
2022-12-20 20:14:17 +00:00
|
|
|
void runInfoJob(Task::Ptr);
|
|
|
|
|
2022-12-23 20:28:42 +00:00
|
|
|
[[nodiscard]] auto getCurrentSortingMethodByIndex() const -> std::optional<ResourceAPI::SortingMethod>;
|
|
|
|
|
2022-12-23 21:18:20 +00:00
|
|
|
/** Converts a JSON document to a common array format.
|
|
|
|
*
|
|
|
|
* This is needed so that different providers, with different JSON structures, can be parsed
|
|
|
|
* uniformally. You NEED to re-implement this if you intend on using the default callbacks.
|
|
|
|
*/
|
|
|
|
[[nodiscard]] virtual auto documentToArray(QJsonDocument&) const -> QJsonArray;
|
|
|
|
|
|
|
|
/** Functions to load data into a pack.
|
|
|
|
*
|
|
|
|
* Those are needed for the same reason as ddocumentToArray, and NEED to be re-implemented in the same way.
|
|
|
|
*/
|
|
|
|
|
|
|
|
virtual void loadIndexedPack(ModPlatform::IndexedPack&, QJsonObject&);
|
|
|
|
virtual void loadExtraPackInfo(ModPlatform::IndexedPack&, QJsonObject&);
|
|
|
|
virtual void loadIndexedPackVersions(ModPlatform::IndexedPack&, QJsonArray&);
|
|
|
|
|
2022-11-25 12:23:46 +00:00
|
|
|
protected:
|
|
|
|
/* Basic search parameters */
|
|
|
|
enum class SearchState { None, CanFetchMore, ResetRequested, Finished } m_search_state = SearchState::None;
|
|
|
|
int m_next_search_offset = 0;
|
|
|
|
QString m_search_term;
|
2022-12-20 15:15:17 +00:00
|
|
|
unsigned int m_current_sort_index = 0;
|
2022-11-25 12:23:46 +00:00
|
|
|
|
|
|
|
std::unique_ptr<ResourceAPI> m_api;
|
|
|
|
|
2022-12-20 20:14:17 +00:00
|
|
|
// Job for searching for new entries
|
2023-01-03 16:58:27 +00:00
|
|
|
shared_qobject_ptr<Task> m_current_search_job;
|
2022-12-20 20:14:17 +00:00
|
|
|
// Job for fetching versions and extra info on existing entries
|
|
|
|
ConcurrentTask m_current_info_job;
|
2022-11-25 12:23:46 +00:00
|
|
|
|
|
|
|
shared_qobject_ptr<NetJob> m_current_icon_job;
|
|
|
|
QSet<QUrl> m_currently_running_icon_actions;
|
|
|
|
QSet<QUrl> m_failed_icon_actions;
|
|
|
|
|
|
|
|
QList<ModPlatform::IndexedPack> m_packs;
|
|
|
|
|
|
|
|
// HACK: We need this to prevent callbacks from calling the model after it has already been deleted.
|
|
|
|
// This leaks a tiny bit of memory per time the user has opened a resource dialog. How to make this better?
|
|
|
|
static QHash<ResourceModel*, bool> s_running_models;
|
|
|
|
|
|
|
|
private:
|
|
|
|
/* Default search request callbacks */
|
2022-12-23 21:18:20 +00:00
|
|
|
void searchRequestSucceeded(QJsonDocument&);
|
2022-11-25 12:23:46 +00:00
|
|
|
void searchRequestFailed(QString reason, int network_error_code);
|
|
|
|
void searchRequestAborted();
|
2022-12-18 20:03:39 +00:00
|
|
|
|
2022-12-23 21:18:20 +00:00
|
|
|
void versionRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&);
|
|
|
|
|
|
|
|
void infoRequestSucceeded(QJsonDocument&, ModPlatform::IndexedPack&, const QModelIndex&);
|
|
|
|
|
2022-12-18 20:03:39 +00:00
|
|
|
signals:
|
|
|
|
void versionListUpdated();
|
|
|
|
void projectInfoUpdated();
|
2022-11-25 12:23:46 +00:00
|
|
|
};
|
2022-12-16 22:03:52 +00:00
|
|
|
|
|
|
|
} // namespace ResourceDownload
|