From d7d11635a0430d32c380857d99662b018e5a637a Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Wed, 27 Jun 2018 22:02:29 -0700 Subject: [PATCH] Add BoxArtManager for loading box art with caching --- app/app.pro | 6 ++- app/gui/mainwindow.h | 8 +++- app/http/boxartmanager.cpp | 77 ++++++++++++++++++++++++++++++++++++++ app/http/boxartmanager.h | 73 ++++++++++++++++++++++++++++++++++++ app/http/nvhttp.h | 2 + app/main.cpp | 9 +++-- 6 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 app/http/boxartmanager.cpp create mode 100644 app/http/boxartmanager.h diff --git a/app/app.pro b/app/app.pro index 7f547b48..513eac59 100644 --- a/app/app.pro +++ b/app/app.pro @@ -61,7 +61,8 @@ SOURCES += \ http/nvpairingmanager.cpp \ streaming/video.c \ streaming/connection.cpp \ - http/computermanager.cpp + http/computermanager.cpp \ + http/boxartmanager.cpp HEADERS += \ utils.h \ @@ -71,7 +72,8 @@ HEADERS += \ http/nvhttp.h \ http/nvpairingmanager.h \ streaming/streaming.h \ - http/computermanager.h + http/computermanager.h \ + http/boxartmanager.h FORMS += \ gui/mainwindow.ui diff --git a/app/gui/mainwindow.h b/app/gui/mainwindow.h index bbec0e04..61e83957 100644 --- a/app/gui/mainwindow.h +++ b/app/gui/mainwindow.h @@ -1,6 +1,9 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "http/computermanager.h" +#include "http/boxartmanager.h" + #include #include @@ -21,10 +24,13 @@ private slots: void on_newHostBtn_clicked(); void addHostToDisplay(QMap); void on_selectHostComboBox_activated(const QString &); + void computerStateChanged(NvComputer* computer); + void boxArtLoadComplete(NvComputer* computer, NvApp app, QImage image); private: Ui::MainWindow *ui; - + BoxArtManager m_BoxArtManager; + ComputerManager m_ComputerManager; }; #endif // MAINWINDOW_H diff --git a/app/http/boxartmanager.cpp b/app/http/boxartmanager.cpp new file mode 100644 index 00000000..2a4b1de1 --- /dev/null +++ b/app/http/boxartmanager.cpp @@ -0,0 +1,77 @@ +#include "boxartmanager.h" + +#include +#include +#include + +BoxArtManager::BoxArtManager(QObject *parent) : + QObject(parent), + m_BoxArtDir( + QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/boxart"), + m_PlaceholderImage(":/res/no_app_image.png") +{ + if (!m_BoxArtDir.exists()) { + m_BoxArtDir.mkpath("."); + } +} + +QString +BoxArtManager::getFilePathForBoxArt(NvComputer* computer, int appId) +{ + QDir dir = m_BoxArtDir; + + // Create the cache directory if it did not already exist + if (!dir.exists(computer->uuid)) { + dir.mkdir(computer->uuid); + } + + // Change to this computer's box art cache folder + dir.cd(computer->uuid); + + // Try to open the cached file + return dir.filePath(QString::number(appId) + ".png"); +} + +QImage BoxArtManager::loadBoxArt(NvComputer* computer, NvApp& app) +{ + // Try to open the cached file + QFile cacheFile(getFilePathForBoxArt(computer, app.id)); + if (cacheFile.open(QFile::ReadOnly)) { + // Return what we have if it's a valid image + QImage image = QImageReader(&cacheFile).read(); + if (!image.isNull()) { + return image; + } + } + + // If we get here, we need to fetch asynchronously. + // Kick off a worker on our thread pool to do just that. + NetworkBoxArtLoadTask* netLoadTask = new NetworkBoxArtLoadTask(this, computer, app); + QThreadPool::globalInstance()->start(netLoadTask); + + // Return the placeholder then we can notify the caller + // later when the real image is ready. + return m_PlaceholderImage; +} + +void BoxArtManager::handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QImage image) +{ + emit boxArtLoadComplete(computer, app, image); +} + +QImage BoxArtManager::loadBoxArtFromNetwork(NvComputer* computer, int appId) +{ + NvHTTP http(computer->activeAddress); + + QImage image; + try { + image = http.getBoxArt(appId); + } catch (...) {} + + // Cache the box art on disk if it loaded + if (!image.isNull()) { + image.save(getFilePathForBoxArt(computer, appId)); + } + + return image; +} diff --git a/app/http/boxartmanager.h b/app/http/boxartmanager.h new file mode 100644 index 00000000..5515987a --- /dev/null +++ b/app/http/boxartmanager.h @@ -0,0 +1,73 @@ +#pragma once + +#include "computermanager.h" +#include +#include +#include +#include + +class BoxArtManager : public QObject +{ + Q_OBJECT + + friend class NetworkBoxArtLoadTask; + +public: + explicit BoxArtManager(QObject *parent = nullptr); + + QImage + loadBoxArt(NvComputer* computer, NvApp& app); + +signals: + void + boxArtLoadComplete(NvComputer* computer, NvApp app, QImage image); + +public slots: + +private slots: + void + handleBoxArtLoadComplete(NvComputer* computer, NvApp app, QImage image); + +private: + QImage + loadBoxArtFromNetwork(NvComputer* computer, int appId); + + QString + getFilePathForBoxArt(NvComputer* computer, int appId); + + QDir m_BoxArtDir; + QImage m_PlaceholderImage; +}; + +class NetworkBoxArtLoadTask : public QObject, public QRunnable +{ + Q_OBJECT + +public: + NetworkBoxArtLoadTask(BoxArtManager* boxArtManager, NvComputer* computer, NvApp& app) + : m_Bam(boxArtManager), + m_Computer(computer), + m_App(app) + { + connect(this, SIGNAL(boxArtFetchCompleted(NvComputer*,NvApp,QImage)), + boxArtManager, SLOT(handleBoxArtLoadComplete(NvComputer*,NvApp,QImage))); + } + +signals: + void boxArtFetchCompleted(NvComputer* computer, NvApp app, QImage image); + +private: + void run() + { + QImage image = m_Bam->loadBoxArtFromNetwork(m_Computer, m_App.id); + if (image.isNull()) { + // Give it another shot if it fails once + image = m_Bam->loadBoxArtFromNetwork(m_Computer, m_App.id); + } + emit boxArtFetchCompleted(m_Computer, m_App, image); + } + + BoxArtManager* m_Bam; + NvComputer* m_Computer; + NvApp m_App; +}; diff --git a/app/http/nvhttp.h b/app/http/nvhttp.h index 3c1a1aee..717cf728 100644 --- a/app/http/nvhttp.h +++ b/app/http/nvhttp.h @@ -25,6 +25,8 @@ public: bool hdrSupported; }; +Q_DECLARE_METATYPE(NvApp) + class GfeHttpResponseException : public std::exception { public: diff --git a/app/main.cpp b/app/main.cpp index b54e4f51..4aec510c 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,6 +1,8 @@ #include "gui/mainwindow.h" #include +#include "http/nvhttp.h" + // Don't let SDL hook our main function, since Qt is already // doing the same thing #define SDL_MAIN_HANDLED @@ -8,10 +10,6 @@ int main(int argc, char *argv[]) { - // MacOS directive to prevent the menu bar from being merged into the native bar - // i.e. it's in the window, and not the top left of the screen - QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); - // This avoids using the default keychain for SSL, which may cause // password prompts on macOS. qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1")); @@ -21,6 +19,9 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationDomain("moonlight-stream.com"); QCoreApplication::setApplicationName("Moonlight"); + // Register custom metatypes for use in signals + qRegisterMetaType("NvApp"); + QApplication a(argc, argv); MainWindow w; w.show();