GameList: Add cover downloader

This commit is contained in:
Connor McLaughlin
2022-09-09 20:32:21 +10:00
parent dde2f6cd68
commit bf76780f11
10 changed files with 284 additions and 49 deletions

View File

@ -656,6 +656,14 @@ void FullscreenUI::Render()
ImGuiFullscreen::ResetCloseMenuIfNeeded();
}
void FullscreenUI::InvalidateCoverCache()
{
if (!IsInitialized())
return;
Host::RunOnCPUThread([]() { s_cover_image_map.clear(); });
}
void FullscreenUI::ReturnToMainWindow()
{
if (s_pause_menu_was_open)

View File

@ -21,6 +21,7 @@ bool OpenLeaderboardsWindow();
void Shutdown();
void Render();
void InvalidateCoverCache();
// Returns true if the message has been dismissed.
bool DrawErrorWindow(const char* message);

View File

@ -2,6 +2,7 @@
#include "common/assert.h"
#include "common/byte_stream.h"
#include "common/file_system.h"
#include "common/http_downloader.h"
#include "common/log.h"
#include "common/make_array.h"
#include "common/path.h"
@ -18,9 +19,9 @@
#include <array>
#include <cctype>
#include <ctime>
#include <unordered_map>
#include <string_view>
#include <tinyxml2.h>
#include <unordered_map>
#include <utility>
Log_SetChannel(GameList);
@ -688,3 +689,113 @@ size_t GameList::Entry::GetReleaseDateString(char* buffer, size_t buffer_size) c
return std::strftime(buffer, buffer_size, "%d %B %Y", &date_tm);
}
bool GameList::DownloadCovers(const std::vector<std::string>& url_templates, ProgressCallback* progress /*= nullptr*/)
{
if (!progress)
progress = ProgressCallback::NullProgressCallback;
bool has_title = false;
bool has_file_title = false;
bool has_serial = false;
for (const std::string& url_template : url_templates)
{
if (!has_title && url_template.find("${title}") != std::string::npos)
has_title = true;
if (!has_file_title && url_template.find("${filetitle}") != std::string::npos)
has_file_title = true;
if (!has_serial && url_template.find("${serial}") != std::string::npos)
has_serial = true;
}
if (!has_title && !has_file_title && !has_serial)
{
progress->DisplayError("URL template must contain at least one of ${title}, ${filetitle}, or ${serial}.");
return false;
}
std::vector<std::pair<std::string, std::string>> download_urls;
{
std::unique_lock lock(s_mutex);
for (const GameList::Entry& entry : m_entries)
{
const std::string existing_path(GetCoverImagePathForEntry(&entry));
if (!existing_path.empty())
continue;
for (const std::string& url_template : url_templates)
{
std::string url(url_template);
if (has_title)
StringUtil::ReplaceAll(&url, "${title}", Common::HTTPDownloader::URLEncode(entry.title));
if (has_file_title)
{
std::string display_name(FileSystem::GetDisplayNameFromPath(entry.path));
StringUtil::ReplaceAll(&url, "${filetitle}",
Common::HTTPDownloader::URLEncode(Path::GetFileTitle(display_name)));
}
if (has_serial)
StringUtil::ReplaceAll(&url, "${serial}", Common::HTTPDownloader::URLEncode(entry.serial));
download_urls.emplace_back(entry.path, std::move(url));
}
}
}
if (download_urls.empty())
{
progress->DisplayError("No URLs to download enumerated.");
return false;
}
std::unique_ptr<Common::HTTPDownloader> downloader(Common::HTTPDownloader::Create());
if (!downloader)
{
progress->DisplayError("Failed to create HTTP downloader.");
return false;
}
progress->SetCancellable(true);
progress->SetProgressRange(static_cast<u32>(download_urls.size()));
for (auto& [entry_path, url] : download_urls)
{
if (progress->IsCancelled())
break;
// make sure it didn't get done already
{
std::unique_lock lock(s_mutex);
const GameList::Entry* entry = GetEntryForPath(entry_path.c_str());
if (!entry || !GetCoverImagePathForEntry(entry).empty())
{
progress->IncrementProgressValue();
continue;
}
progress->SetFormattedStatusText("Downloading cover for %s...", entry->title.c_str());
}
// we could actually do a few in parallel here...
std::string filename(Common::HTTPDownloader::URLDecode(url));
downloader->CreateRequest(std::move(url), [entry_path = std::move(entry_path), filename = std::move(filename)](
s32 status_code, Common::HTTPDownloader::Request::Data data) {
if (status_code != Common::HTTPDownloader::HTTP_OK || data.empty())
return;
std::unique_lock lock(s_mutex);
const GameList::Entry* entry = GetEntryForPath(entry_path.c_str());
if (!entry || !GetCoverImagePathForEntry(entry).empty())
return;
std::string write_path(GetNewCoverImagePathForEntry(entry, filename.c_str()));
if (write_path.empty())
return;
FileSystem::WriteBinaryFile(write_path.c_str(), data.data(), data.size());
Host::CoversChanged();
});
downloader->WaitForAllRequests();
progress->IncrementProgressValue();
}
return true;
}

View File

@ -75,6 +75,8 @@ void Refresh(bool invalidate_cache, bool only_cache = false, ProgressCallback* p
std::string GetCoverImagePathForEntry(const Entry* entry);
std::string GetCoverImagePath(const std::string& path, const std::string& serial, const std::string& title);
std::string GetNewCoverImagePathForEntry(const Entry* entry, const char* new_filename);
bool DownloadCovers(const std::vector<std::string>& url_templates, ProgressCallback* progress = nullptr);
}; // namespace GameList
namespace Host {
@ -83,4 +85,8 @@ void RefreshGameListAsync(bool invalidate_cache);
/// Cancels game list refresh, if there is one in progress.
void CancelGameListRefresh();
void DownloadCoversAsync(std::vector<std::string> url_templates);
void CancelCoversDownload();
void CoversChanged();
} // namespace Host