Skip to content

Commit

Permalink
GameList: Add cover downloader
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek committed Sep 13, 2022
1 parent dde2f6c commit bf76780
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 49 deletions.
6 changes: 3 additions & 3 deletions CMakeLists.txt
Expand Up @@ -95,6 +95,9 @@ if(NOT ANDROID)
if(NOT WIN32 AND USE_SDL2)
find_package(SDL2 REQUIRED)
endif()
if(NOT WIN32)
find_package(CURL REQUIRED)
endif()
if(BUILD_QT_FRONTEND)
find_package(Qt6 COMPONENTS Core Gui Widgets Network LinguistTools REQUIRED)
endif()
Expand Down Expand Up @@ -129,9 +132,6 @@ if(USE_EVDEV)
endif()
if(ENABLE_CHEEVOS)
message(STATUS "RetroAchievements support enabled")
if(NOT WIN32 AND NOT ANDROID)
find_package(CURL REQUIRED)
endif()
endif()


Expand Down
37 changes: 14 additions & 23 deletions src/common/CMakeLists.txt
Expand Up @@ -20,6 +20,8 @@ add_library(common
hash_combine.h
heap_array.h
heterogeneous_containers.h
http_downloader.cpp
http_downloader.h
layered_settings_interface.cpp
layered_settings_interface.h
log.cpp
Expand Down Expand Up @@ -86,6 +88,8 @@ if(WIN32)
d3d11/stream_buffer.h
d3d11/texture.cpp
d3d11/texture.h
http_downloader_winhttp.cpp
http_downloader_winhttp.h
thirdparty/StackWalker.cpp
thirdparty/StackWalker.h
win32_progress_callback.cpp
Expand All @@ -95,6 +99,16 @@ if(WIN32)
target_link_libraries(common PRIVATE d3dcompiler.lib)
endif()

if(NOT WIN32 AND NOT ANDROID)
target_sources(common PRIVATE
http_downloader_curl.cpp
http_downloader_curl.h
)
target_link_libraries(common PRIVATE
CURL::libcurl
)
endif()

if(ANDROID)
target_link_libraries(common PRIVATE log)
endif()
Expand Down Expand Up @@ -242,29 +256,6 @@ if(ENABLE_VULKAN)
endif()
endif()



if(ENABLE_CHEEVOS)
target_sources(common PRIVATE
http_downloader.cpp
http_downloader.h
)
if(WIN32)
target_sources(common PRIVATE
http_downloader_winhttp.cpp
http_downloader_winhttp.h
)
elseif(NOT ANDROID)
target_sources(common PRIVATE
http_downloader_curl.cpp
http_downloader_curl.h
)
target_link_libraries(common PRIVATE
CURL::libcurl
)
endif()
endif()

if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
# We need -lrt for shm_unlink
target_link_libraries(common PRIVATE rt)
Expand Down
72 changes: 71 additions & 1 deletion src/common/http_downloader.cpp
Expand Up @@ -183,4 +183,74 @@ u32 HTTPDownloader::LockedGetActiveRequestCount()
return count;
}

} // namespace FrontendCommon
std::string HTTPDownloader::URLEncode(const std::string_view& str)
{
std::string ret;
ret.reserve(str.length() + ((str.length() + 3) / 4) * 3);

for (size_t i = 0, l = str.size(); i < l; i++)
{
const char c = str[i];
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '-' || c == '_' ||
c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')')
{
ret.push_back(c);
}
else
{
ret.push_back('%');

const unsigned char n1 = static_cast<unsigned char>(c) >> 4;
const unsigned char n2 = static_cast<unsigned char>(c) & 0x0F;
ret.push_back((n1 >= 10) ? ('a' + (n1 - 10)) : ('0' + n1));
ret.push_back((n2 >= 10) ? ('a' + (n2 - 10)) : ('0' + n2));
}
}

return ret;
}

std::string HTTPDownloader::URLDecode(const std::string_view& str)
{
std::string ret;
ret.reserve(str.length());

for (size_t i = 0, l = str.size(); i < l; i++)
{
const char c = str[i];
if (c == '+')
{
ret.push_back(c);
}
else if (c == '%')
{
if ((i + 2) >= str.length())
break;

const char clower = str[i + 1];
const char cupper = str[i + 2];
const unsigned char lower =
(clower >= '0' && clower <= '9') ?
static_cast<unsigned char>(clower - '0') :
((clower >= 'a' && clower <= 'f') ?
static_cast<unsigned char>(clower - 'a') :
((clower >= 'A' && clower <= 'F') ? static_cast<unsigned char>(clower - 'A') : 0));
const unsigned char upper =
(cupper >= '0' && cupper <= '9') ?
static_cast<unsigned char>(cupper - '0') :
((cupper >= 'a' && cupper <= 'f') ?
static_cast<unsigned char>(cupper - 'a') :
((cupper >= 'A' && cupper <= 'F') ? static_cast<unsigned char>(cupper - 'A') : 0));
const char dch = static_cast<char>(lower | (upper << 4));
ret.push_back(dch);
}
else
{
ret.push_back(c);
}
}

return std::string(str);
}

} // namespace Common
3 changes: 3 additions & 0 deletions src/common/http_downloader.h
Expand Up @@ -5,6 +5,7 @@
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <vector>

namespace Common {
Expand Down Expand Up @@ -53,6 +54,8 @@ class HTTPDownloader
virtual ~HTTPDownloader();

static std::unique_ptr<HTTPDownloader> Create(const char* user_agent = DEFAULT_USER_AGENT);
static std::string URLEncode(const std::string_view& str);
static std::string URLDecode(const std::string_view& str);

void SetTimeout(float timeout);
void SetMaxActiveRequests(u32 max_active_requests);
Expand Down
72 changes: 51 additions & 21 deletions src/duckstation-nogui/nogui_host.cpp
Expand Up @@ -76,7 +76,9 @@ static void CPUThreadMainLoop();
static std::unique_ptr<NoGUIPlatform> CreatePlatform();
static std::string GetWindowTitle(const std::string& game_title);
static void UpdateWindowTitle(const std::string& game_title);
static void GameListRefreshThreadEntryPoint(bool invalidate_cache);
static void CancelAsyncOp();
static void StartAsyncOp(std::function<void(ProgressCallback*)> callback);
static void AsyncOpThreadEntryPoint(std::function<void(ProgressCallback*)> callback);
static bool AcquireHostDisplay(RenderAPI api);
static void ReleaseHostDisplay();
} // namespace NoGUIHost
Expand All @@ -99,9 +101,9 @@ static std::condition_variable s_cpu_thread_event_posted;
static std::deque<std::pair<std::function<void()>, bool>> s_cpu_thread_events;
static u32 s_blocking_cpu_events_pending = 0; // TODO: Token system would work better here.

static std::mutex s_game_list_refresh_lock;
static std::thread s_game_list_refresh_thread;
static FullscreenUI::ProgressCallback* s_game_list_refresh_progress = nullptr;
static std::mutex s_async_op_mutex;
static std::thread s_async_op_thread;
static FullscreenUI::ProgressCallback* s_async_op_progress = nullptr;

//////////////////////////////////////////////////////////////////////////
// Initialization/Shutdown
Expand Down Expand Up @@ -961,39 +963,66 @@ void Host::RunOnCPUThread(std::function<void()> function, bool block /* = false
s_cpu_thread_event_done.wait(lock, []() { return s_blocking_cpu_events_pending == 0; });
}

void NoGUIHost::GameListRefreshThreadEntryPoint(bool invalidate_cache)
void NoGUIHost::StartAsyncOp(std::function<void(ProgressCallback*)> callback)
{
Threading::SetNameOfCurrentThread("Game List Refresh");
CancelAsyncOp();
s_async_op_thread = std::thread(AsyncOpThreadEntryPoint, std::move(callback));
}

void NoGUIHost::CancelAsyncOp()
{
std::unique_lock lock(s_async_op_mutex);
if (!s_async_op_thread.joinable())
return;

FullscreenUI::ProgressCallback callback("game_list_refresh");
std::unique_lock lock(s_game_list_refresh_lock);
s_game_list_refresh_progress = &callback;
if (s_async_op_progress)
s_async_op_progress->SetCancelled();

lock.unlock();
GameList::Refresh(invalidate_cache, false, &callback);
s_async_op_thread.join();
}

void NoGUIHost::AsyncOpThreadEntryPoint(std::function<void(ProgressCallback*)> callback)
{
Threading::SetNameOfCurrentThread("Async Op");

FullscreenUI::ProgressCallback fs_callback("async_op");
std::unique_lock lock(s_async_op_mutex);
s_async_op_progress = &fs_callback;

lock.unlock();
callback(&fs_callback);
lock.lock();

s_game_list_refresh_progress = nullptr;
s_async_op_progress = nullptr;
}

void Host::RefreshGameListAsync(bool invalidate_cache)
{
CancelGameListRefresh();

s_game_list_refresh_thread = std::thread(NoGUIHost::GameListRefreshThreadEntryPoint, invalidate_cache);
NoGUIHost::StartAsyncOp(
[invalidate_cache](ProgressCallback* progress) { GameList::Refresh(invalidate_cache, false, progress); });
}

void Host::CancelGameListRefresh()
{
std::unique_lock lock(s_game_list_refresh_lock);
if (!s_game_list_refresh_thread.joinable())
return;
NoGUIHost::CancelAsyncOp();
}

void Host::DownloadCoversAsync(std::vector<std::string> url_templates)
{
NoGUIHost::StartAsyncOp([url_templates = std::move(url_templates)](ProgressCallback* progress) {
GameList::DownloadCovers(url_templates, progress);
});
}

if (s_game_list_refresh_progress)
s_game_list_refresh_progress->SetCancelled();
void Host::CancelCoversDownload()
{
NoGUIHost::CancelAsyncOp();
}

lock.unlock();
s_game_list_refresh_thread.join();
void Host::CoversChanged()
{
Host::RunOnCPUThread([]() { FullscreenUI::InvalidateCoverCache(); });
}

bool Host::IsFullscreen()
Expand Down Expand Up @@ -1340,6 +1369,7 @@ int main(int argc, char* argv[])

g_nogui_window->RunMessageLoop();

NoGUIHost::CancelAsyncOp();
NoGUIHost::StopCPUThread();

// Ensure log is flushed.
Expand Down
15 changes: 15 additions & 0 deletions src/duckstation-qt/qthost.cpp
Expand Up @@ -1078,6 +1078,21 @@ void Host::CancelGameListRefresh()
QMetaObject::invokeMethod(g_main_window, "cancelGameListRefresh", Qt::BlockingQueuedConnection);
}

void Host::DownloadCoversAsync(std::vector<std::string> url_templates)
{
//
}

void Host::CancelCoversDownload()
{
//
}

void Host::CoversChanged()
{
//
}

void EmuThread::loadState(const QString& filename)
{
if (!isOnThread())
Expand Down
8 changes: 8 additions & 0 deletions src/frontend-common/fullscreen_ui.cpp
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/frontend-common/fullscreen_ui.h
Expand Up @@ -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);
Expand Down

0 comments on commit bf76780

Please sign in to comment.