|
1 | 1 | #include "Gamelist.h" |
2 | 2 |
|
| 3 | +#include <algorithm> |
3 | 4 | #include <chrono> |
| 5 | +#include <fstream> |
| 6 | +#include <future> |
| 7 | +#include <mutex> |
| 8 | +#include <sstream> |
4 | 9 |
|
5 | 10 | #include "utils/FileSystemUtil.h" |
6 | 11 | #include "FileData.h" |
|
9 | 14 | #include "Settings.h" |
10 | 15 | #include "SystemData.h" |
11 | 16 | #include <pugixml.hpp> |
| 17 | +#include <unordered_map> |
| 18 | + |
| 19 | +// Async gamelist write infrastructure |
| 20 | +static std::mutex sGamelistWriteMutex; |
| 21 | +static std::vector<std::future<void>> sGamelistPendingWrites; |
12 | 22 |
|
13 | 23 | FileData* findOrCreateFile(SystemData* system, const std::string& path, FileType type) |
14 | 24 | { |
@@ -292,6 +302,7 @@ void updateGamelist(SystemData* system) |
292 | 302 | if(!pathNode) |
293 | 303 | { |
294 | 304 | LOG(LogError) << "<" << tag << "> node contains no <path> child!"; |
| 305 | + fileNode = nextNode; |
295 | 306 | continue; |
296 | 307 | } |
297 | 308 |
|
@@ -325,22 +336,59 @@ void updateGamelist(SystemData* system) |
325 | 336 | // now write the file |
326 | 337 |
|
327 | 338 | if (numUpdated > 0) { |
328 | | - const auto startTs = std::chrono::system_clock::now(); |
329 | | - |
330 | 339 | //make sure the folders leading up to this path exist (or the write will fail) |
331 | 340 | std::string xmlWritePath(system->getGamelistPath(true)); |
332 | 341 | Utils::FileSystem::createDirectory(Utils::FileSystem::getParent(xmlWritePath)); |
333 | 342 |
|
334 | 343 | LOG(LogInfo) << "Added/Updated " << numUpdated << " entities in '" << xmlReadPath << "'"; |
335 | 344 |
|
336 | | - if (!doc.save_file(xmlWritePath.c_str())) { |
337 | | - LOG(LogError) << "Error saving gamelist.xml to \"" << xmlWritePath << "\" (for system " << system->getName() << ")!"; |
338 | | - } |
| 345 | + // Serialize the XML document to a string on the main thread, |
| 346 | + // then write the string to file on a background thread to |
| 347 | + // avoid blocking the UI on slow NAS I/O. |
| 348 | + std::stringstream ss; |
| 349 | + doc.save(ss); |
| 350 | + std::string xmlContent = ss.str(); |
| 351 | + std::string sysName = system->getName(); |
| 352 | + |
| 353 | + { |
| 354 | + // Clean up finished futures |
| 355 | + std::lock_guard<std::mutex> lock(sGamelistWriteMutex); |
| 356 | + sGamelistPendingWrites.erase( |
| 357 | + std::remove_if(sGamelistPendingWrites.begin(), sGamelistPendingWrites.end(), |
| 358 | + [](std::future<void>& f) { |
| 359 | + return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; |
| 360 | + }), |
| 361 | + sGamelistPendingWrites.end()); |
| 362 | + |
| 363 | + sGamelistPendingWrites.push_back(std::async(std::launch::async, |
| 364 | + [xmlContent, xmlWritePath, sysName]() { |
| 365 | + const auto startTs = std::chrono::system_clock::now(); |
| 366 | + |
| 367 | + std::ofstream outFile(xmlWritePath, std::ios::out | std::ios::trunc); |
| 368 | + if (outFile.is_open()) { |
| 369 | + outFile << xmlContent; |
| 370 | + outFile.close(); |
| 371 | + } else { |
| 372 | + LOG(LogError) << "Error saving gamelist.xml to \"" << xmlWritePath << "\" (for system " << sysName << ")!"; |
| 373 | + } |
339 | 374 |
|
340 | | - const auto endTs = std::chrono::system_clock::now(); |
341 | | - LOG(LogInfo) << "Saved gamelist.xml for system \"" << system->getName() << "\" in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTs - startTs).count() << " ms"; |
| 375 | + const auto endTs = std::chrono::system_clock::now(); |
| 376 | + LOG(LogInfo) << "Saved gamelist.xml for system \"" << sysName << "\" in " << std::chrono::duration_cast<std::chrono::milliseconds>(endTs - startTs).count() << " ms"; |
| 377 | + })); |
| 378 | + } |
342 | 379 | } |
343 | 380 | }else{ |
344 | 381 | LOG(LogError) << "Found no root folder for system \"" << system->getName() << "\"!"; |
345 | 382 | } |
346 | 383 | } |
| 384 | + |
| 385 | +void waitForGamelistWrites() |
| 386 | +{ |
| 387 | + std::lock_guard<std::mutex> lock(sGamelistWriteMutex); |
| 388 | + for (auto& f : sGamelistPendingWrites) |
| 389 | + { |
| 390 | + if (f.valid()) |
| 391 | + f.wait(); |
| 392 | + } |
| 393 | + sGamelistPendingWrites.clear(); |
| 394 | +} |
0 commit comments