diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/yuzu/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | src/yuzu/game_list.cpp | 8 | ||||
| -rw-r--r-- | src/yuzu/game_list.h | 5 | ||||
| -rw-r--r-- | src/yuzu/game_list_p.h | 2 | ||||
| -rw-r--r-- | src/yuzu/game_list_worker.cpp | 25 | ||||
| -rw-r--r-- | src/yuzu/game_list_worker.h | 6 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 11 | ||||
| -rw-r--r-- | src/yuzu/play_time.cpp | 177 | ||||
| -rw-r--r-- | src/yuzu/play_time.h | 68 | ||||
| -rw-r--r-- | src/yuzu/play_time_manager.cpp | 179 | ||||
| -rw-r--r-- | src/yuzu/play_time_manager.h | 44 |
11 files changed, 259 insertions, 270 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 89763f64f..9ebece907 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -195,8 +195,8 @@ add_executable(yuzu | |||
| 195 | multiplayer/state.cpp | 195 | multiplayer/state.cpp |
| 196 | multiplayer/state.h | 196 | multiplayer/state.h |
| 197 | multiplayer/validation.h | 197 | multiplayer/validation.h |
| 198 | play_time.cpp | 198 | play_time_manager.cpp |
| 199 | play_time.h | 199 | play_time_manager.h |
| 200 | precompiled_headers.h | 200 | precompiled_headers.h |
| 201 | qt_common.cpp | 201 | qt_common.cpp |
| 202 | qt_common.h | 202 | qt_common.h |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 98e410e0f..0b7415526 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() { | |||
| 312 | } | 312 | } |
| 313 | 313 | ||
| 314 | GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, | 314 | GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, |
| 315 | Core::System& system_, GMainWindow* parent) | 315 | PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, |
| 316 | : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} { | 316 | GMainWindow* parent) |
| 317 | : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, | ||
| 318 | play_time_manager{play_time_manager_}, system{system_} { | ||
| 317 | watcher = new QFileSystemWatcher(this); | 319 | watcher = new QFileSystemWatcher(this); |
| 318 | connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); | 320 | connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); |
| 319 | 321 | ||
| @@ -826,7 +828,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) { | |||
| 826 | emit ShouldCancelWorker(); | 828 | emit ShouldCancelWorker(); |
| 827 | 829 | ||
| 828 | GameListWorker* worker = | 830 | GameListWorker* worker = |
| 829 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); | 831 | new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system); |
| 830 | 832 | ||
| 831 | connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); | 833 | connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); |
| 832 | connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, | 834 | connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index cde6f1e1f..6e8382c0f 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -18,6 +18,7 @@ | |||
| 18 | #include "core/core.h" | 18 | #include "core/core.h" |
| 19 | #include "uisettings.h" | 19 | #include "uisettings.h" |
| 20 | #include "yuzu/compatibility_list.h" | 20 | #include "yuzu/compatibility_list.h" |
| 21 | #include "yuzu/play_time_manager.h" | ||
| 21 | 22 | ||
| 22 | namespace Core { | 23 | namespace Core { |
| 23 | class System; | 24 | class System; |
| @@ -79,7 +80,8 @@ public: | |||
| 79 | }; | 80 | }; |
| 80 | 81 | ||
| 81 | explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, | 82 | explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, |
| 82 | FileSys::ManualContentProvider* provider_, Core::System& system_, | 83 | FileSys::ManualContentProvider* provider_, |
| 84 | PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_, | ||
| 83 | GMainWindow* parent = nullptr); | 85 | GMainWindow* parent = nullptr); |
| 84 | ~GameList() override; | 86 | ~GameList() override; |
| 85 | 87 | ||
| @@ -168,6 +170,7 @@ private: | |||
| 168 | 170 | ||
| 169 | friend class GameListSearchField; | 171 | friend class GameListSearchField; |
| 170 | 172 | ||
| 173 | const PlayTime::PlayTimeManager& play_time_manager; | ||
| 171 | Core::System& system; | 174 | Core::System& system; |
| 172 | }; | 175 | }; |
| 173 | 176 | ||
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 33a929aae..86a0c41d9 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h | |||
| @@ -18,7 +18,7 @@ | |||
| 18 | #include "common/common_types.h" | 18 | #include "common/common_types.h" |
| 19 | #include "common/logging/log.h" | 19 | #include "common/logging/log.h" |
| 20 | #include "common/string_util.h" | 20 | #include "common/string_util.h" |
| 21 | #include "yuzu/play_time.h" | 21 | #include "yuzu/play_time_manager.h" |
| 22 | #include "yuzu/uisettings.h" | 22 | #include "yuzu/uisettings.h" |
| 23 | #include "yuzu/util/util.h" | 23 | #include "yuzu/util/util.h" |
| 24 | 24 | ||
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index b15ed730e..588f1dd6e 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp | |||
| @@ -27,7 +27,6 @@ | |||
| 27 | #include "yuzu/game_list.h" | 27 | #include "yuzu/game_list.h" |
| 28 | #include "yuzu/game_list_p.h" | 28 | #include "yuzu/game_list_p.h" |
| 29 | #include "yuzu/game_list_worker.h" | 29 | #include "yuzu/game_list_worker.h" |
| 30 | #include "yuzu/play_time.h" | ||
| 31 | #include "yuzu/uisettings.h" | 30 | #include "yuzu/uisettings.h" |
| 32 | 31 | ||
| 33 | namespace { | 32 | namespace { |
| @@ -195,6 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 195 | const std::size_t size, const std::vector<u8>& icon, | 194 | const std::size_t size, const std::vector<u8>& icon, |
| 196 | Loader::AppLoader& loader, u64 program_id, | 195 | Loader::AppLoader& loader, u64 program_id, |
| 197 | const CompatibilityList& compatibility_list, | 196 | const CompatibilityList& compatibility_list, |
| 197 | const PlayTime::PlayTimeManager& play_time_manager, | ||
| 198 | const FileSys::PatchManager& patch) { | 198 | const FileSys::PatchManager& patch) { |
| 199 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); | 199 | const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); |
| 200 | 200 | ||
| @@ -213,7 +213,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 213 | new GameListItemCompat(compatibility), | 213 | new GameListItemCompat(compatibility), |
| 214 | new GameListItem(file_type_string), | 214 | new GameListItem(file_type_string), |
| 215 | new GameListItemSize(size), | 215 | new GameListItemSize(size), |
| 216 | new GameListItemPlayTime(PlayTime::GetPlayTime(program_id)), | 216 | new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)), |
| 217 | }; | 217 | }; |
| 218 | 218 | ||
| 219 | const auto patch_versions = GetGameListCachedObject( | 219 | const auto patch_versions = GetGameListCachedObject( |
| @@ -229,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri | |||
| 229 | GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, | 229 | GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, |
| 230 | FileSys::ManualContentProvider* provider_, | 230 | FileSys::ManualContentProvider* provider_, |
| 231 | QVector<UISettings::GameDir>& game_dirs_, | 231 | QVector<UISettings::GameDir>& game_dirs_, |
| 232 | const CompatibilityList& compatibility_list_, Core::System& system_) | 232 | const CompatibilityList& compatibility_list_, |
| 233 | const PlayTime::PlayTimeManager& play_time_manager_, | ||
| 234 | Core::System& system_) | ||
| 233 | : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, | 235 | : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, |
| 234 | compatibility_list{compatibility_list_}, system{system_} {} | 236 | compatibility_list{compatibility_list_}, |
| 237 | play_time_manager{play_time_manager_}, system{system_} {} | ||
| 235 | 238 | ||
| 236 | GameListWorker::~GameListWorker() = default; | 239 | GameListWorker::~GameListWorker() = default; |
| 237 | 240 | ||
| @@ -282,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) { | |||
| 282 | } | 285 | } |
| 283 | 286 | ||
| 284 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, | 287 | emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, |
| 285 | program_id, compatibility_list, patch), | 288 | program_id, compatibility_list, play_time_manager, patch), |
| 286 | parent_dir); | 289 | parent_dir); |
| 287 | } | 290 | } |
| 288 | } | 291 | } |
| @@ -359,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 359 | 362 | ||
| 360 | emit EntryReady(MakeGameListEntry(physical_name, name, | 363 | emit EntryReady(MakeGameListEntry(physical_name, name, |
| 361 | Common::FS::GetSize(physical_name), icon, | 364 | Common::FS::GetSize(physical_name), icon, |
| 362 | *loader, id, compatibility_list, patch), | 365 | *loader, id, compatibility_list, |
| 366 | play_time_manager, patch), | ||
| 363 | parent_dir); | 367 | parent_dir); |
| 364 | } | 368 | } |
| 365 | } else { | 369 | } else { |
| @@ -372,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa | |||
| 372 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), | 376 | const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), |
| 373 | system.GetContentProvider()}; | 377 | system.GetContentProvider()}; |
| 374 | 378 | ||
| 375 | emit EntryReady( | 379 | emit EntryReady(MakeGameListEntry(physical_name, name, |
| 376 | MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), | 380 | Common::FS::GetSize(physical_name), icon, |
| 377 | icon, *loader, program_id, compatibility_list, patch), | 381 | *loader, program_id, compatibility_list, |
| 378 | parent_dir); | 382 | play_time_manager, patch), |
| 383 | parent_dir); | ||
| 379 | } | 384 | } |
| 380 | } | 385 | } |
| 381 | } else if (is_dir) { | 386 | } else if (is_dir) { |
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h index 24a4e92c3..2bb0a0cb6 100644 --- a/src/yuzu/game_list_worker.h +++ b/src/yuzu/game_list_worker.h | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include <QString> | 13 | #include <QString> |
| 14 | 14 | ||
| 15 | #include "yuzu/compatibility_list.h" | 15 | #include "yuzu/compatibility_list.h" |
| 16 | #include "yuzu/play_time_manager.h" | ||
| 16 | 17 | ||
| 17 | namespace Core { | 18 | namespace Core { |
| 18 | class System; | 19 | class System; |
| @@ -36,7 +37,9 @@ public: | |||
| 36 | explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, | 37 | explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, |
| 37 | FileSys::ManualContentProvider* provider_, | 38 | FileSys::ManualContentProvider* provider_, |
| 38 | QVector<UISettings::GameDir>& game_dirs_, | 39 | QVector<UISettings::GameDir>& game_dirs_, |
| 39 | const CompatibilityList& compatibility_list_, Core::System& system_); | 40 | const CompatibilityList& compatibility_list_, |
| 41 | const PlayTime::PlayTimeManager& play_time_manager_, | ||
| 42 | Core::System& system_); | ||
| 40 | ~GameListWorker() override; | 43 | ~GameListWorker() override; |
| 41 | 44 | ||
| 42 | /// Starts the processing of directory tree information. | 45 | /// Starts the processing of directory tree information. |
| @@ -76,6 +79,7 @@ private: | |||
| 76 | FileSys::ManualContentProvider* provider; | 79 | FileSys::ManualContentProvider* provider; |
| 77 | QVector<UISettings::GameDir>& game_dirs; | 80 | QVector<UISettings::GameDir>& game_dirs; |
| 78 | const CompatibilityList& compatibility_list; | 81 | const CompatibilityList& compatibility_list; |
| 82 | const PlayTime::PlayTimeManager& play_time_manager; | ||
| 79 | 83 | ||
| 80 | QStringList watch_list; | 84 | QStringList watch_list; |
| 81 | std::atomic_bool stop_processing; | 85 | std::atomic_bool stop_processing; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 53ab7ada9..bfa4787e1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -146,7 +146,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 146 | #include "yuzu/install_dialog.h" | 146 | #include "yuzu/install_dialog.h" |
| 147 | #include "yuzu/loading_screen.h" | 147 | #include "yuzu/loading_screen.h" |
| 148 | #include "yuzu/main.h" | 148 | #include "yuzu/main.h" |
| 149 | #include "yuzu/play_time.h" | 149 | #include "yuzu/play_time_manager.h" |
| 150 | #include "yuzu/startup_checks.h" | 150 | #include "yuzu/startup_checks.h" |
| 151 | #include "yuzu/uisettings.h" | 151 | #include "yuzu/uisettings.h" |
| 152 | #include "yuzu/util/clickable_label.h" | 152 | #include "yuzu/util/clickable_label.h" |
| @@ -980,7 +980,7 @@ void GMainWindow::InitializeWidgets() { | |||
| 980 | render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); | 980 | render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); |
| 981 | render_window->hide(); | 981 | render_window->hide(); |
| 982 | 982 | ||
| 983 | game_list = new GameList(vfs, provider.get(), *system, this); | 983 | game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this); |
| 984 | ui->horizontalLayout->addWidget(game_list); | 984 | ui->horizontalLayout->addWidget(game_list); |
| 985 | 985 | ||
| 986 | game_list_placeholder = new GameListPlaceholder(this); | 986 | game_list_placeholder = new GameListPlaceholder(this); |
| @@ -2469,11 +2469,8 @@ void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) { | |||
| 2469 | QMessageBox::No) != QMessageBox::Yes) { | 2469 | QMessageBox::No) != QMessageBox::Yes) { |
| 2470 | return; | 2470 | return; |
| 2471 | } | 2471 | } |
| 2472 | if (!play_time_manager->ResetProgramPlayTime(program_id)) { | 2472 | |
| 2473 | QMessageBox::warning(this, tr("Error Resetting Play Time Data"), | 2473 | play_time_manager->ResetProgramPlayTime(program_id); |
| 2474 | tr("Play time couldn't be cleared")); | ||
| 2475 | return; | ||
| 2476 | } | ||
| 2477 | game_list->PopulateAsync(UISettings::values.game_dirs); | 2474 | game_list->PopulateAsync(UISettings::values.game_dirs); |
| 2478 | } | 2475 | } |
| 2479 | 2476 | ||
diff --git a/src/yuzu/play_time.cpp b/src/yuzu/play_time.cpp deleted file mode 100644 index 6be0327b2..000000000 --- a/src/yuzu/play_time.cpp +++ /dev/null | |||
| @@ -1,177 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/fs/file.h" | ||
| 5 | #include "common/fs/path_util.h" | ||
| 6 | #include "common/logging/log.h" | ||
| 7 | #include "common/settings.h" | ||
| 8 | #include "common/thread.h" | ||
| 9 | #include "core/hle/service/acc/profile_manager.h" | ||
| 10 | |||
| 11 | #include "yuzu/play_time.h" | ||
| 12 | |||
| 13 | namespace PlayTime { | ||
| 14 | |||
| 15 | void PlayTimeManager::SetProgramId(u64 program_id) { | ||
| 16 | this->running_program_id = program_id; | ||
| 17 | } | ||
| 18 | |||
| 19 | inline void PlayTimeManager::UpdateTimestamp() { | ||
| 20 | this->last_timestamp = std::chrono::steady_clock::now(); | ||
| 21 | } | ||
| 22 | |||
| 23 | void PlayTimeManager::Start() { | ||
| 24 | UpdateTimestamp(); | ||
| 25 | play_time_thread = | ||
| 26 | std::jthread([&](std::stop_token stop_token) { this->AutoTimestamp(stop_token); }); | ||
| 27 | } | ||
| 28 | |||
| 29 | void PlayTimeManager::Stop() { | ||
| 30 | play_time_thread.request_stop(); | ||
| 31 | } | ||
| 32 | |||
| 33 | void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) { | ||
| 34 | Common::SetCurrentThreadName("PlayTimeReport"); | ||
| 35 | |||
| 36 | using namespace std::literals::chrono_literals; | ||
| 37 | |||
| 38 | const auto duration = 30s; | ||
| 39 | while (Common::StoppableTimedWait(stop_token, duration)) { | ||
| 40 | Save(); | ||
| 41 | } | ||
| 42 | |||
| 43 | Save(); | ||
| 44 | } | ||
| 45 | |||
| 46 | void PlayTimeManager::Save() { | ||
| 47 | const auto now = std::chrono::steady_clock::now(); | ||
| 48 | const auto duration = | ||
| 49 | static_cast<u64>(std::chrono::duration_cast<std::chrono::seconds>( | ||
| 50 | std::chrono::steady_clock::duration(now - this->last_timestamp)) | ||
| 51 | .count()); | ||
| 52 | UpdateTimestamp(); | ||
| 53 | if (!UpdatePlayTime(running_program_id, duration)) { | ||
| 54 | LOG_ERROR(Common, "Failed to update play time"); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | bool UpdatePlayTime(u64 program_id, u64 add_play_time) { | ||
| 59 | std::vector<PlayTimeElement> play_time_elements; | ||
| 60 | if (!ReadPlayTimeFile(play_time_elements)) { | ||
| 61 | return false; | ||
| 62 | } | ||
| 63 | const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id); | ||
| 64 | |||
| 65 | if (it == play_time_elements.end()) { | ||
| 66 | play_time_elements.push_back({.program_id = program_id, .play_time = add_play_time}); | ||
| 67 | } else { | ||
| 68 | play_time_elements.at(it - play_time_elements.begin()).play_time += add_play_time; | ||
| 69 | } | ||
| 70 | if (!WritePlayTimeFile(play_time_elements)) { | ||
| 71 | return false; | ||
| 72 | } | ||
| 73 | return true; | ||
| 74 | } | ||
| 75 | |||
| 76 | u64 GetPlayTime(u64 program_id) { | ||
| 77 | std::vector<PlayTimeElement> play_time_elements; | ||
| 78 | |||
| 79 | if (!ReadPlayTimeFile(play_time_elements)) { | ||
| 80 | return 0; | ||
| 81 | } | ||
| 82 | const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id); | ||
| 83 | if (it == play_time_elements.end()) { | ||
| 84 | return 0; | ||
| 85 | } | ||
| 86 | return play_time_elements.at(it - play_time_elements.begin()).play_time; | ||
| 87 | } | ||
| 88 | |||
| 89 | bool PlayTimeManager::ResetProgramPlayTime(u64 program_id) { | ||
| 90 | std::vector<PlayTimeElement> play_time_elements; | ||
| 91 | |||
| 92 | if (!ReadPlayTimeFile(play_time_elements)) { | ||
| 93 | return false; | ||
| 94 | } | ||
| 95 | const auto it = std::find(play_time_elements.begin(), play_time_elements.end(), program_id); | ||
| 96 | if (it == play_time_elements.end()) { | ||
| 97 | return false; | ||
| 98 | } | ||
| 99 | play_time_elements.erase(it); | ||
| 100 | if (!WritePlayTimeFile(play_time_elements)) { | ||
| 101 | return false; | ||
| 102 | } | ||
| 103 | return true; | ||
| 104 | } | ||
| 105 | |||
| 106 | std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() { | ||
| 107 | const Service::Account::ProfileManager manager; | ||
| 108 | const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user)); | ||
| 109 | if (!uuid.has_value()) { | ||
| 110 | return std::nullopt; | ||
| 111 | } | ||
| 112 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) / | ||
| 113 | uuid->RawString().append(".bin"); | ||
| 114 | } | ||
| 115 | |||
| 116 | [[nodiscard]] bool ReadPlayTimeFile(std::vector<PlayTimeElement>& out_play_time_elements) { | ||
| 117 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 118 | if (!filename.has_value()) { | ||
| 119 | LOG_ERROR(Common, "Failed to get current user path"); | ||
| 120 | return false; | ||
| 121 | } | ||
| 122 | |||
| 123 | if (Common::FS::Exists(filename.value())) { | ||
| 124 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read, | ||
| 125 | Common::FS::FileType::BinaryFile}; | ||
| 126 | if (!file.IsOpen()) { | ||
| 127 | LOG_ERROR(Common, "Failed to open play time file: {}", | ||
| 128 | Common::FS::PathToUTF8String(filename.value())); | ||
| 129 | return false; | ||
| 130 | } | ||
| 131 | const size_t elem_num = file.GetSize() / sizeof(PlayTimeElement); | ||
| 132 | out_play_time_elements.resize(elem_num); | ||
| 133 | const bool success = file.ReadSpan<PlayTimeElement>(out_play_time_elements) == elem_num; | ||
| 134 | file.Close(); | ||
| 135 | return success; | ||
| 136 | } else { | ||
| 137 | out_play_time_elements.clear(); | ||
| 138 | return true; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | [[nodiscard]] bool WritePlayTimeFile(const std::vector<PlayTimeElement>& play_time_elements) { | ||
| 143 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 144 | if (!filename.has_value()) { | ||
| 145 | LOG_ERROR(Common, "Failed to get current user path"); | ||
| 146 | return false; | ||
| 147 | } | ||
| 148 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write, | ||
| 149 | Common::FS::FileType::BinaryFile}; | ||
| 150 | |||
| 151 | if (!file.IsOpen()) { | ||
| 152 | LOG_ERROR(Common, "Failed to open play time file: {}", | ||
| 153 | Common::FS::PathToUTF8String(filename.value())); | ||
| 154 | return false; | ||
| 155 | } | ||
| 156 | const bool success = | ||
| 157 | file.WriteSpan<PlayTimeElement>(play_time_elements) == play_time_elements.size(); | ||
| 158 | file.Close(); | ||
| 159 | return success; | ||
| 160 | } | ||
| 161 | |||
| 162 | QString ReadablePlayTime(qulonglong time_seconds) { | ||
| 163 | static constexpr std::array units{"m", "h"}; | ||
| 164 | if (time_seconds == 0) { | ||
| 165 | return QLatin1String(""); | ||
| 166 | } | ||
| 167 | const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0); | ||
| 168 | const auto time_hours = static_cast<double>(time_seconds) / 3600; | ||
| 169 | const int unit = time_minutes < 60 ? 0 : 1; | ||
| 170 | const auto value = unit == 0 ? time_minutes : time_hours; | ||
| 171 | |||
| 172 | return QStringLiteral("%L1 %2") | ||
| 173 | .arg(value, 0, 'f', unit && time_seconds % 60 != 0) | ||
| 174 | .arg(QString::fromUtf8(units[unit])); | ||
| 175 | } | ||
| 176 | |||
| 177 | } // namespace PlayTime | ||
diff --git a/src/yuzu/play_time.h b/src/yuzu/play_time.h deleted file mode 100644 index 68e40955c..000000000 --- a/src/yuzu/play_time.h +++ /dev/null | |||
| @@ -1,68 +0,0 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <QString> | ||
| 7 | |||
| 8 | #include <atomic> | ||
| 9 | #include <condition_variable> | ||
| 10 | #include <mutex> | ||
| 11 | #include <optional> | ||
| 12 | #include <thread> | ||
| 13 | |||
| 14 | #include "common/common_types.h" | ||
| 15 | #include "common/fs/fs.h" | ||
| 16 | #include "common/polyfill_thread.h" | ||
| 17 | #include "core/core.h" | ||
| 18 | |||
| 19 | namespace PlayTime { | ||
| 20 | struct PlayTimeElement { | ||
| 21 | u64 program_id; | ||
| 22 | u64 play_time; | ||
| 23 | |||
| 24 | inline bool operator==(const PlayTimeElement& other) const { | ||
| 25 | return program_id == other.program_id; | ||
| 26 | } | ||
| 27 | |||
| 28 | inline bool operator==(const u64 _program_id) const { | ||
| 29 | return program_id == _program_id; | ||
| 30 | } | ||
| 31 | }; | ||
| 32 | |||
| 33 | class PlayTimeManager { | ||
| 34 | public: | ||
| 35 | explicit PlayTimeManager() = default; | ||
| 36 | ~PlayTimeManager() = default; | ||
| 37 | |||
| 38 | public: | ||
| 39 | YUZU_NON_COPYABLE(PlayTimeManager); | ||
| 40 | YUZU_NON_MOVEABLE(PlayTimeManager); | ||
| 41 | |||
| 42 | public: | ||
| 43 | bool ResetProgramPlayTime(u64 program_id); | ||
| 44 | void SetProgramId(u64 program_id); | ||
| 45 | inline void UpdateTimestamp(); | ||
| 46 | void Start(); | ||
| 47 | void Stop(); | ||
| 48 | |||
| 49 | private: | ||
| 50 | u64 running_program_id; | ||
| 51 | std::chrono::steady_clock::time_point last_timestamp; | ||
| 52 | std::jthread play_time_thread; | ||
| 53 | void AutoTimestamp(std::stop_token stop_token); | ||
| 54 | void Save(); | ||
| 55 | }; | ||
| 56 | |||
| 57 | std::optional<std::filesystem::path> GetCurrentUserPlayTimePath(); | ||
| 58 | |||
| 59 | bool UpdatePlayTime(u64 program_id, u64 add_play_time); | ||
| 60 | |||
| 61 | [[nodiscard]] bool ReadPlayTimeFile(std::vector<PlayTimeElement>& out_play_time_elements); | ||
| 62 | [[nodiscard]] bool WritePlayTimeFile(const std::vector<PlayTimeElement>& play_time_elements); | ||
| 63 | |||
| 64 | u64 GetPlayTime(u64 program_id); | ||
| 65 | |||
| 66 | QString ReadablePlayTime(qulonglong time_seconds); | ||
| 67 | |||
| 68 | } // namespace PlayTime | ||
diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp new file mode 100644 index 000000000..155c36b7d --- /dev/null +++ b/src/yuzu/play_time_manager.cpp | |||
| @@ -0,0 +1,179 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/alignment.h" | ||
| 5 | #include "common/fs/file.h" | ||
| 6 | #include "common/fs/fs.h" | ||
| 7 | #include "common/fs/path_util.h" | ||
| 8 | #include "common/logging/log.h" | ||
| 9 | #include "common/settings.h" | ||
| 10 | #include "common/thread.h" | ||
| 11 | #include "core/hle/service/acc/profile_manager.h" | ||
| 12 | #include "yuzu/play_time_manager.h" | ||
| 13 | |||
| 14 | namespace PlayTime { | ||
| 15 | |||
| 16 | namespace { | ||
| 17 | |||
| 18 | struct PlayTimeElement { | ||
| 19 | ProgramId program_id; | ||
| 20 | PlayTime play_time; | ||
| 21 | }; | ||
| 22 | |||
| 23 | std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() { | ||
| 24 | const Service::Account::ProfileManager manager; | ||
| 25 | const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user)); | ||
| 26 | if (!uuid.has_value()) { | ||
| 27 | return std::nullopt; | ||
| 28 | } | ||
| 29 | return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) / | ||
| 30 | uuid->RawString().append(".bin"); | ||
| 31 | } | ||
| 32 | |||
| 33 | [[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) { | ||
| 34 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 35 | |||
| 36 | if (!filename.has_value()) { | ||
| 37 | LOG_ERROR(Frontend, "Failed to get current user path"); | ||
| 38 | return false; | ||
| 39 | } | ||
| 40 | |||
| 41 | out_play_time_db.clear(); | ||
| 42 | |||
| 43 | if (Common::FS::Exists(filename.value())) { | ||
| 44 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read, | ||
| 45 | Common::FS::FileType::BinaryFile}; | ||
| 46 | if (!file.IsOpen()) { | ||
| 47 | LOG_ERROR(Frontend, "Failed to open play time file: {}", | ||
| 48 | Common::FS::PathToUTF8String(filename.value())); | ||
| 49 | return false; | ||
| 50 | } | ||
| 51 | |||
| 52 | const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement); | ||
| 53 | std::vector<PlayTimeElement> elements(num_elements); | ||
| 54 | |||
| 55 | if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) { | ||
| 56 | return false; | ||
| 57 | } | ||
| 58 | |||
| 59 | for (const auto& [program_id, play_time] : elements) { | ||
| 60 | if (program_id != 0) { | ||
| 61 | out_play_time_db[program_id] = play_time; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | return true; | ||
| 67 | } | ||
| 68 | |||
| 69 | [[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) { | ||
| 70 | const auto filename = GetCurrentUserPlayTimePath(); | ||
| 71 | |||
| 72 | if (!filename.has_value()) { | ||
| 73 | LOG_ERROR(Frontend, "Failed to get current user path"); | ||
| 74 | return false; | ||
| 75 | } | ||
| 76 | |||
| 77 | Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write, | ||
| 78 | Common::FS::FileType::BinaryFile}; | ||
| 79 | if (!file.IsOpen()) { | ||
| 80 | LOG_ERROR(Frontend, "Failed to open play time file: {}", | ||
| 81 | Common::FS::PathToUTF8String(filename.value())); | ||
| 82 | return false; | ||
| 83 | } | ||
| 84 | |||
| 85 | std::vector<PlayTimeElement> elements; | ||
| 86 | elements.reserve(play_time_db.size()); | ||
| 87 | |||
| 88 | for (auto& [program_id, play_time] : play_time_db) { | ||
| 89 | if (program_id != 0) { | ||
| 90 | elements.push_back(PlayTimeElement{program_id, play_time}); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | return file.WriteSpan<PlayTimeElement>(elements) == elements.size(); | ||
| 95 | } | ||
| 96 | |||
| 97 | } // namespace | ||
| 98 | |||
| 99 | PlayTimeManager::PlayTimeManager() { | ||
| 100 | if (!ReadPlayTimeFile(database)) { | ||
| 101 | LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default."); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | PlayTimeManager::~PlayTimeManager() { | ||
| 106 | Save(); | ||
| 107 | } | ||
| 108 | |||
| 109 | void PlayTimeManager::SetProgramId(u64 program_id) { | ||
| 110 | running_program_id = program_id; | ||
| 111 | } | ||
| 112 | |||
| 113 | void PlayTimeManager::Start() { | ||
| 114 | play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); }); | ||
| 115 | } | ||
| 116 | |||
| 117 | void PlayTimeManager::Stop() { | ||
| 118 | play_time_thread = {}; | ||
| 119 | } | ||
| 120 | |||
| 121 | void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) { | ||
| 122 | Common::SetCurrentThreadName("PlayTimeReport"); | ||
| 123 | |||
| 124 | using namespace std::literals::chrono_literals; | ||
| 125 | using std::chrono::seconds; | ||
| 126 | using std::chrono::steady_clock; | ||
| 127 | |||
| 128 | auto timestamp = steady_clock::now(); | ||
| 129 | |||
| 130 | const auto GetDuration = [&]() -> u64 { | ||
| 131 | const auto last_timestamp = std::exchange(timestamp, steady_clock::now()); | ||
| 132 | const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp); | ||
| 133 | return static_cast<u64>(duration.count()); | ||
| 134 | }; | ||
| 135 | |||
| 136 | while (!stop_token.stop_requested()) { | ||
| 137 | Common::StoppableTimedWait(stop_token, 30s); | ||
| 138 | |||
| 139 | database[running_program_id] += GetDuration(); | ||
| 140 | Save(); | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | void PlayTimeManager::Save() { | ||
| 145 | if (!WritePlayTimeFile(database)) { | ||
| 146 | LOG_ERROR(Frontend, "Failed to update play time database!"); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | u64 PlayTimeManager::GetPlayTime(u64 program_id) const { | ||
| 151 | auto it = database.find(program_id); | ||
| 152 | if (it != database.end()) { | ||
| 153 | return it->second; | ||
| 154 | } else { | ||
| 155 | return 0; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | void PlayTimeManager::ResetProgramPlayTime(u64 program_id) { | ||
| 160 | database.erase(program_id); | ||
| 161 | Save(); | ||
| 162 | } | ||
| 163 | |||
| 164 | QString ReadablePlayTime(qulonglong time_seconds) { | ||
| 165 | if (time_seconds == 0) { | ||
| 166 | return {}; | ||
| 167 | } | ||
| 168 | const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0); | ||
| 169 | const auto time_hours = static_cast<double>(time_seconds) / 3600; | ||
| 170 | const bool is_minutes = time_minutes < 60; | ||
| 171 | const char* unit = is_minutes ? "m" : "h"; | ||
| 172 | const auto value = is_minutes ? time_minutes : time_hours; | ||
| 173 | |||
| 174 | return QStringLiteral("%L1 %2") | ||
| 175 | .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0) | ||
| 176 | .arg(QString::fromUtf8(unit)); | ||
| 177 | } | ||
| 178 | |||
| 179 | } // namespace PlayTime | ||
diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h new file mode 100644 index 000000000..5f96f3447 --- /dev/null +++ b/src/yuzu/play_time_manager.h | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <QString> | ||
| 7 | |||
| 8 | #include <map> | ||
| 9 | |||
| 10 | #include "common/common_funcs.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/polyfill_thread.h" | ||
| 13 | |||
| 14 | namespace PlayTime { | ||
| 15 | |||
| 16 | using ProgramId = u64; | ||
| 17 | using PlayTime = u64; | ||
| 18 | using PlayTimeDatabase = std::map<ProgramId, PlayTime>; | ||
| 19 | |||
| 20 | class PlayTimeManager { | ||
| 21 | public: | ||
| 22 | explicit PlayTimeManager(); | ||
| 23 | ~PlayTimeManager(); | ||
| 24 | |||
| 25 | YUZU_NON_COPYABLE(PlayTimeManager); | ||
| 26 | YUZU_NON_MOVEABLE(PlayTimeManager); | ||
| 27 | |||
| 28 | u64 GetPlayTime(u64 program_id) const; | ||
| 29 | void ResetProgramPlayTime(u64 program_id); | ||
| 30 | void SetProgramId(u64 program_id); | ||
| 31 | void Start(); | ||
| 32 | void Stop(); | ||
| 33 | |||
| 34 | private: | ||
| 35 | PlayTimeDatabase database; | ||
| 36 | u64 running_program_id; | ||
| 37 | std::jthread play_time_thread; | ||
| 38 | void AutoTimestamp(std::stop_token stop_token); | ||
| 39 | void Save(); | ||
| 40 | }; | ||
| 41 | |||
| 42 | QString ReadablePlayTime(qulonglong time_seconds); | ||
| 43 | |||
| 44 | } // namespace PlayTime | ||