Skip to content

Commit

Permalink
Merge pull request #79 from oblivioncth/feature/download_command
Browse files Browse the repository at this point in the history
Add 'download' command. Allows bulk download of games from a playlist
  • Loading branch information
oblivioncth authored Nov 9, 2023
2 parents 0f48e0f + 014374f commit 5f11711
Show file tree
Hide file tree
Showing 11 changed files with 330 additions and 118 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ endif()

include(OB/FetchQx)
ob_fetch_qx(
REF "v0.5.5.1"
REF "7acb9d0f3e65de2f0a6e91c979b1216e6be7de7d"
COMPONENTS
${CLIFP_QX_COMPONENTS}
)

# Fetch libfp (build and import from source)
include(OB/Fetchlibfp)
ob_fetch_libfp("v0.5.1.1")
ob_fetch_libfp("d27276305defe2fbd98946b23ec9d135ce3ae1bd")

# Fetch QI-QMP (build and import from source)
include(OB/FetchQI-QMP)
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ The **-title-strict** and **-subtitle-strict** options only consider exact match
Tip: You can use **-subtitle** with an empty string (i.e. `-s ""`) to see all of the additional-apps for a given title.

### Command List:

**download** - Downloads data packs for games that require them in bulk

Options:
- **-p | --playlist** Name of the playlist to download games for.

--------------------------------------------------------------------------------

**link** - Creates a shortcut to a Flashpoint title

Options:
Expand Down
2 changes: 2 additions & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ set(CLIFP_SOURCE
kernel/errorstatus.cpp
command/command.h
command/command.cpp
command/c-download.h
command/c-download.cpp
command/c-link.h
command/c-link.cpp
command/c-play.h
Expand Down
114 changes: 114 additions & 0 deletions app/src/command/c-download.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Unit Include
#include "c-download.h"

// Project Includes
#include "task/t-download.h"
#include "task/t-generic.h"

//===============================================================================================================
// CDownloadError
//===============================================================================================================

//-Constructor-------------------------------------------------------------
//Private:
CDownloadError::CDownloadError(Type t, const QString& s) :
mType(t),
mSpecific(s)
{}

//-Instance Functions-------------------------------------------------------------
//Public:
bool CDownloadError::isValid() const { return mType != NoError; }
QString CDownloadError::specific() const { return mSpecific; }
CDownloadError::Type CDownloadError::type() const { return mType; }

//Private:
Qx::Severity CDownloadError::deriveSeverity() const { return Qx::Critical; }
quint32 CDownloadError::deriveValue() const { return mType; }
QString CDownloadError::derivePrimary() const { return ERR_STRINGS.value(mType); }
QString CDownloadError::deriveSecondary() const { return mSpecific; }

//===============================================================================================================
// CDownload
//===============================================================================================================

//-Constructor-------------------------------------------------------------
//Public:
CDownload::CDownload(Core& coreRef) : Command(coreRef) {}

//-Instance Functions-------------------------------------------------------------
//Protected:
QList<const QCommandLineOption*> CDownload::options() { return CL_OPTIONS_SPECIFIC + Command::options(); }
QSet<const QCommandLineOption*> CDownload::requiredOptions() { return CL_OPTIONS_REQUIRED + Command::requiredOptions(); }
QString CDownload::name() { return NAME; }

Qx::Error CDownload::perform()
{
QString playlistName = mParser.value(CL_OPTION_PLAYLIST).trimmed();
mCore.setStatus(STATUS_DOWNLOAD, playlistName);

Fp::Db* db = mCore.fpInstall().database();
Fp::PlaylistManager* pm = mCore.fpInstall().playlistManager();
if(Qx::Error pError = pm->populate(); pError.isValid())
return pError;

// Find playlist
QList<Fp::Playlist> playlists = pm->playlists();
auto pItr = std::find_if(playlists.cbegin(), playlists.cend(), [&playlistName](auto p){
return p.title() == playlistName || p.title().trimmed() == playlistName; // Some playlists have spaces for sorting purposes
});

if(pItr == playlists.cend())
{
CDownloadError err(CDownloadError::InvalidPlaylist, playlistName);
mCore.postError(NAME, err);
return err;
}
mCore.logEvent(NAME, LOG_PLAYLIST_MATCH.arg(pItr->id().toString(QUuid::WithoutBraces)));

// Queue downloads for each game
TDownload* downloadTask = new TDownload(&mCore);
downloadTask->setStage(Task::Stage::Primary);
downloadTask->setDescription(u"playlist data packs"_s);
QList<int> dataIds;

for(const auto& pg : pItr->playlistGames())
{
// Get data
Fp::GameData gameData;
if(Fp::DbError gdErr = db->getGameData(gameData, pg.gameId()); gdErr.isValid())
{
mCore.postError(NAME, gdErr);
return gdErr;
}

if(gameData.isNull())
{
mCore.logEvent(NAME, LOG_NON_DATAPACK.arg(pg.gameId().toString(QUuid::WithoutBraces)));
continue;
}

// Queue download
QString filename = gameData.path();
downloadTask->addFile({.target = mCore.datapackUrl(filename), .dest = mCore.datapackPath(filename), .checksum = gameData.sha256()});

// Note data id
dataIds.append(gameData.id());
}

// Enqueue download task
mCore.enqueueSingleTask(downloadTask);

// Enqueue onDiskState update task
Core* corePtr = &mCore; // Safe, will outlive task
TGeneric* onDiskUpdateTask = new TGeneric(corePtr);
onDiskUpdateTask->setStage(Task::Stage::Primary);
onDiskUpdateTask->setDescription(u"Update GameData onDisk state."_s);
onDiskUpdateTask->setAction([dataIds, corePtr]{
return corePtr->fpInstall().database()->updateGameDataOnDiskState(dataIds, true);
});
mCore.enqueueSingleTask(onDiskUpdateTask);

// Return success
return CDownloadError();
}
89 changes: 89 additions & 0 deletions app/src/command/c-download.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#ifndef CDOWNLOAD_H
#define CDOWNLOAD_H

// Qx Includes
#include <qx/utility/qx-macros.h>

// Project Includes
#include "command/command.h"

class QX_ERROR_TYPE(CDownloadError, "CDownloadError", 1217)
{
friend class CDownload;
//-Class Enums-------------------------------------------------------------
public:
enum Type
{
NoError,
InvalidPlaylist
};

//-Class Variables-------------------------------------------------------------
private:
static inline const QHash<Type, QString> ERR_STRINGS{
{NoError, u""_s},
{InvalidPlaylist, u""_s}
};

//-Instance Variables-------------------------------------------------------------
private:
Type mType;
QString mSpecific;

//-Constructor-------------------------------------------------------------
private:
CDownloadError(Type t = NoError, const QString& s = {});

//-Instance Functions-------------------------------------------------------------
public:
bool isValid() const;
Type type() const;
QString specific() const;

private:
Qx::Severity deriveSeverity() const override;
quint32 deriveValue() const override;
QString derivePrimary() const override;
QString deriveSecondary() const override;
};

class CDownload : public Command
{
//-Class Variables------------------------------------------------------------------------------------------------------
private:
// Status
static inline const QString STATUS_DOWNLOAD = u"Downloading data packs"_s;

// Logging
static inline const QString LOG_PLAYLIST_MATCH = u"Playlist matches ID: %1"_s;
static inline const QString LOG_NON_DATAPACK = u"Game %1 does not use a data pack."_s;

// Command line option strings
static inline const QString CL_OPT_PLAYLIST_S_NAME = u"p"_s;
static inline const QString CL_OPT_PLAYLIST_L_NAME = u"playlist"_s;
static inline const QString CL_OPT_PLAYLIST_DESC = u"Name of the playlist to download games for."_s;

// Command line options
static inline const QCommandLineOption CL_OPTION_PLAYLIST{{CL_OPT_PLAYLIST_S_NAME, CL_OPT_PLAYLIST_L_NAME}, CL_OPT_PLAYLIST_DESC, u"playlist"_s}; // Takes value
static inline const QList<const QCommandLineOption*> CL_OPTIONS_SPECIFIC{&CL_OPTION_PLAYLIST};
static inline const QSet<const QCommandLineOption*> CL_OPTIONS_REQUIRED{&CL_OPTION_PLAYLIST};

public:
// Meta
static inline const QString NAME = u"download"_s;
static inline const QString DESCRIPTION = u"Download game data packs in bulk"_s;

//-Constructor----------------------------------------------------------------------------------------------------------
public:
CDownload(Core& coreRef);

//-Instance Functions------------------------------------------------------------------------------------------------------
protected:
QList<const QCommandLineOption*> options() override;
QSet<const QCommandLineOption*> requiredOptions() override;
QString name() override;
Qx::Error perform() override;
};
REGISTER_COMMAND(CDownload::NAME, CDownload, CDownload::DESCRIPTION);

#endif // CDOWNLOAD_H
2 changes: 1 addition & 1 deletion app/src/command/c-prepare.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ CPrepare::CPrepare(Core& coreRef) : TitleCommand(coreRef) {}

//-Instance Functions-------------------------------------------------------------
//Protected:
QList<const QCommandLineOption*> CPrepare::options() { return {&CL_OPTION_ID, &CL_OPTION_TITLE, &CL_OPTION_TITLE_STRICT}; }
QList<const QCommandLineOption*> CPrepare::options() { return TitleCommand::options(); }
QString CPrepare::name() { return NAME; }

Qx::Error CPrepare::perform()
Expand Down
5 changes: 2 additions & 3 deletions app/src/command/c-update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,8 @@ CUpdateError CUpdate::checkAndPrepareUpdate() const
QString tempName = u"clifp_update.zip"_s;
TDownload* downloadTask = new TDownload(&mCore);
downloadTask->setStage(Task::Stage::Primary);
downloadTask->setTargetFile(aItr->browser_download_url);
downloadTask->setDestinationPath(uDownloadDir.absolutePath());
downloadTask->setDestinationFilename(tempName);
downloadTask->setDescription(u"update"_s);
downloadTask->addFile({.target = aItr->browser_download_url, .dest = uDownloadDir.absoluteFilePath(tempName)});
mCore.enqueueSingleTask(downloadTask);

TExtract* extractTask = new TExtract(&mCore);
Expand Down
Loading

0 comments on commit 5f11711

Please sign in to comment.