summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Yuri Kunde Schlesner2017-05-10 18:44:06 -0700
committerGravatar GitHub2017-05-10 18:44:06 -0700
commitdb22b88feab5840a1e4ac474f72cd1626006db1c (patch)
treeeee29b8879414d8bbb1c2e8f933e78d0cb2c4354 /src
parentMerge pull request #2676 from wwylele/irrst (diff)
parentFrontend: Prevent FileSystemWatcher from blocking UI thread (diff)
downloadyuzu-db22b88feab5840a1e4ac474f72cd1626006db1c.tar.gz
yuzu-db22b88feab5840a1e4ac474f72cd1626006db1c.tar.xz
yuzu-db22b88feab5840a1e4ac474f72cd1626006db1c.zip
Merge pull request #2669 from jroweboy/async_file_watcher
Frontend: Prevent FileSystemWatcher from blocking UI thread
Diffstat (limited to 'src')
-rw-r--r--src/citra_qt/game_list.cpp68
-rw-r--r--src/citra_qt/game_list.h5
-rw-r--r--src/citra_qt/game_list_p.h8
3 files changed, 35 insertions, 46 deletions
diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp
index 51257520b..a8e3541cd 100644
--- a/src/citra_qt/game_list.cpp
+++ b/src/citra_qt/game_list.cpp
@@ -2,6 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <QApplication>
5#include <QFileInfo> 6#include <QFileInfo>
6#include <QHeaderView> 7#include <QHeaderView>
7#include <QKeyEvent> 8#include <QKeyEvent>
@@ -194,6 +195,9 @@ void GameList::onFilterCloseClicked() {
194} 195}
195 196
196GameList::GameList(GMainWindow* parent) : QWidget{parent} { 197GameList::GameList(GMainWindow* parent) : QWidget{parent} {
198 watcher = new QFileSystemWatcher(this);
199 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
200
197 this->main_window = parent; 201 this->main_window = parent;
198 layout = new QVBoxLayout; 202 layout = new QVBoxLayout;
199 tree_view = new QTreeView; 203 tree_view = new QTreeView;
@@ -218,7 +222,6 @@ GameList::GameList(GMainWindow* parent) : QWidget{parent} {
218 222
219 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); 223 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
220 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); 224 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
221 connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
222 225
223 // We must register all custom types with the Qt Automoc system so that we are able to use it 226 // We must register all custom types with the Qt Automoc system so that we are able to use it
224 // with signals/slots. In this case, QList falls under the umbrells of custom types. 227 // with signals/slots. In this case, QList falls under the umbrells of custom types.
@@ -269,7 +272,22 @@ void GameList::ValidateEntry(const QModelIndex& item) {
269 emit GameChosen(file_path); 272 emit GameChosen(file_path);
270} 273}
271 274
272void GameList::DonePopulating() { 275void GameList::DonePopulating(QStringList watch_list) {
276 // Clear out the old directories to watch for changes and add the new ones
277 auto watch_dirs = watcher->directories();
278 if (!watch_dirs.isEmpty()) {
279 watcher->removePaths(watch_dirs);
280 }
281 // Workaround: Add the watch paths in chunks to allow the gui to refresh
282 // This prevents the UI from stalling when a large number of watch paths are added
283 // Also artificially caps the watcher to a certain number of directories
284 constexpr int LIMIT_WATCH_DIRECTORIES = 5000;
285 constexpr int SLICE_SIZE = 25;
286 int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES);
287 for (int i = 0; i < len; i += SLICE_SIZE) {
288 watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE));
289 QCoreApplication::processEvents();
290 }
273 tree_view->setEnabled(true); 291 tree_view->setEnabled(true);
274 int rowCount = tree_view->model()->rowCount(); 292 int rowCount = tree_view->model()->rowCount();
275 search_field->setFilterResult(rowCount, rowCount); 293 search_field->setFilterResult(rowCount, rowCount);
@@ -309,11 +327,6 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
309 327
310 emit ShouldCancelWorker(); 328 emit ShouldCancelWorker();
311 329
312 auto watch_dirs = watcher.directories();
313 if (!watch_dirs.isEmpty()) {
314 watcher.removePaths(watch_dirs);
315 }
316 UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0);
317 GameListWorker* worker = new GameListWorker(dir_path, deep_scan); 330 GameListWorker* worker = new GameListWorker(dir_path, deep_scan);
318 331
319 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 332 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
@@ -359,38 +372,6 @@ void GameList::RefreshGameDirectory() {
359 } 372 }
360} 373}
361 374
362/**
363 * Adds the game list folder to the QFileSystemWatcher to check for updates.
364 *
365 * The file watcher will fire off an update to the game list when a change is detected in the game
366 * list folder.
367 *
368 * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and
369 * this function is fast enough to not stall the UI thread. If performance is an issue, it should
370 * be moved to another thread and properly locked to prevent concurrency issues.
371 *
372 * @param dir folder to check for changes in
373 * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each
374 * directory recursively to the watcher and will update the file list if any of the folders
375 * change. The number determines how deep the recursion should traverse.
376 */
377void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) {
378 const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory,
379 const std::string& virtual_name) -> bool {
380 std::string physical_name = directory + DIR_SEP + virtual_name;
381
382 if (FileUtil::IsDirectory(physical_name)) {
383 UpdateWatcherList(physical_name, recursion - 1);
384 }
385 return true;
386 };
387
388 watcher.addPath(QString::fromStdString(dir));
389 if (recursion > 0) {
390 FileUtil::ForeachDirectoryEntry(nullptr, dir, callback);
391 }
392}
393
394void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { 375void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
395 const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, 376 const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory,
396 const std::string& virtual_name) -> bool { 377 const std::string& virtual_name) -> bool {
@@ -399,7 +380,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
399 if (stop_processing) 380 if (stop_processing)
400 return false; // Breaks the callback loop. 381 return false; // Breaks the callback loop.
401 382
402 if (!FileUtil::IsDirectory(physical_name) && HasSupportedFileExtension(physical_name)) { 383 bool is_dir = FileUtil::IsDirectory(physical_name);
384 if (!is_dir && HasSupportedFileExtension(physical_name)) {
403 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name); 385 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
404 if (!loader) 386 if (!loader)
405 return true; 387 return true;
@@ -416,7 +398,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
416 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), 398 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
417 new GameListItemSize(FileUtil::GetSize(physical_name)), 399 new GameListItemSize(FileUtil::GetSize(physical_name)),
418 }); 400 });
419 } else if (recursion > 0) { 401 } else if (is_dir && recursion > 0) {
402 watch_list.append(QString::fromStdString(physical_name));
420 AddFstEntriesToGameList(physical_name, recursion - 1); 403 AddFstEntriesToGameList(physical_name, recursion - 1);
421 } 404 }
422 405
@@ -428,8 +411,9 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
428 411
429void GameListWorker::run() { 412void GameListWorker::run() {
430 stop_processing = false; 413 stop_processing = false;
414 watch_list.append(dir_path);
431 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); 415 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
432 emit Finished(); 416 emit Finished(watch_list);
433} 417}
434 418
435void GameListWorker::Cancel() { 419void GameListWorker::Cancel() {
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h
index d8f8bc5b6..4823a1296 100644
--- a/src/citra_qt/game_list.h
+++ b/src/citra_qt/game_list.h
@@ -85,10 +85,9 @@ private slots:
85private: 85private:
86 void AddEntry(const QList<QStandardItem*>& entry_items); 86 void AddEntry(const QList<QStandardItem*>& entry_items);
87 void ValidateEntry(const QModelIndex& item); 87 void ValidateEntry(const QModelIndex& item);
88 void DonePopulating(); 88 void DonePopulating(QStringList watch_list);
89 89
90 void PopupContextMenu(const QPoint& menu_location); 90 void PopupContextMenu(const QPoint& menu_location);
91 void UpdateWatcherList(const std::string& path, unsigned int recursion);
92 void RefreshGameDirectory(); 91 void RefreshGameDirectory();
93 bool containsAllWords(QString haystack, QString userinput); 92 bool containsAllWords(QString haystack, QString userinput);
94 93
@@ -98,5 +97,5 @@ private:
98 QTreeView* tree_view = nullptr; 97 QTreeView* tree_view = nullptr;
99 QStandardItemModel* item_model = nullptr; 98 QStandardItemModel* item_model = nullptr;
100 GameListWorker* current_worker = nullptr; 99 GameListWorker* current_worker = nullptr;
101 QFileSystemWatcher watcher; 100 QFileSystemWatcher* watcher = nullptr;
102}; 101};
diff --git a/src/citra_qt/game_list_p.h b/src/citra_qt/game_list_p.h
index 3c11b6dd1..d1118ff7f 100644
--- a/src/citra_qt/game_list_p.h
+++ b/src/citra_qt/game_list_p.h
@@ -170,9 +170,15 @@ signals:
170 * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. 170 * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry.
171 */ 171 */
172 void EntryReady(QList<QStandardItem*> entry_items); 172 void EntryReady(QList<QStandardItem*> entry_items);
173 void Finished(); 173
174 /**
175 * After the worker has traversed the game directory looking for entries, this signal is emmited
176 * with a list of folders that should be watched for changes as well.
177 */
178 void Finished(QStringList watch_list);
174 179
175private: 180private:
181 QStringList watch_list;
176 QString dir_path; 182 QString dir_path;
177 bool deep_scan; 183 bool deep_scan;
178 std::atomic_bool stop_processing; 184 std::atomic_bool stop_processing;