diff --git a/cockatrice/src/interface/deck_loader/deck_loader.cpp b/cockatrice/src/interface/deck_loader/deck_loader.cpp index e616c5eb5..9a58cf3f9 100644 --- a/cockatrice/src/interface/deck_loader/deck_loader.cpp +++ b/cockatrice/src/interface/deck_loader/deck_loader.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -129,7 +130,10 @@ std::optional DeckLoader::loadFromRemote(const QString &nativeString std::optional DeckLoader::saveToFile(const DeckList &deck, const QString &fileName, DeckFileFormat::Format fmt) { - QFile file(fileName); + // Use QSaveFile so that a failed write (e.g. a full disk) leaves the existing deck untouched + // instead of truncating it to a 0-byte file. The target is only replaced once every byte has + // been flushed successfully in commit(). + QSaveFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(DeckLoaderLog) << "Could not create or open file:" << fileName; return std::nullopt; @@ -145,15 +149,19 @@ DeckLoader::saveToFile(const DeckList &deck, const QString &fileName, DeckFileFo break; } - file.flush(); - file.close(); - - qCInfo(DeckLoaderLog) << "Saved deck to " << fileName << "with format" << fmt << "-" << success; - if (!success) { + file.cancelWriting(); + qCWarning(DeckLoaderLog) << "Failed to serialize deck for file:" << fileName; return std::nullopt; } + if (!file.commit()) { + qCWarning(DeckLoaderLog) << "Failed to save deck to " << fileName << ":" << file.errorString(); + return std::nullopt; + } + + qCInfo(DeckLoaderLog) << "Saved deck to " << fileName << "with format" << fmt; + LoadedDeck::LoadInfo lastLoadInfo = {fileName, fmt}; return lastLoadInfo; } @@ -196,38 +204,44 @@ bool DeckLoader::updateLastLoadedTimestamp(LoadedDeck &deck) QDateTime originalTimestamp = fileInfo.lastModified(); - // Open the file for writing - QFile file(fileName); + // Use QSaveFile so that a failed write (e.g. a full disk) cannot truncate an existing deck to a + // 0-byte file while merely bumping its timestamp. + QSaveFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << fileName; return false; } - bool result = false; - // Perform file modifications deck.deckList.setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); - result = deck.deckList.saveToFile_Native(&file); - file.close(); // Close the file to ensure changes are flushed - - if (result) { - // Re-open the file and set the original timestamp - if (!file.open(QIODevice::ReadWrite)) { - qCWarning(DeckLoaderLog) << "Failed to re-open file to set timestamp:" << fileName; - return false; - } - - if (!file.setFileTime(originalTimestamp, QFileDevice::FileModificationTime)) { - qCWarning(DeckLoaderLog) << "Failed to set modification time for file:" << fileName; - file.close(); - return false; - } - - file.close(); + if (!deck.deckList.saveToFile_Native(&file)) { + file.cancelWriting(); + qCWarning(DeckLoaderLog) << "Failed to serialize deck for file:" << fileName; + return false; } - return result; + if (!file.commit()) { + qCWarning(DeckLoaderLog) << "Failed to update timestamp for file:" << fileName << ":" << file.errorString(); + return false; + } + + // Re-open the file and restore the original timestamp, so that updating the lastLoadedTimestamp + // does not change the file's modification time. + QFile timestampFile(fileName); + if (!timestampFile.open(QIODevice::ReadWrite)) { + qCWarning(DeckLoaderLog) << "Failed to re-open file to set timestamp:" << fileName; + return false; + } + + if (!timestampFile.setFileTime(originalTimestamp, QFileDevice::FileModificationTime)) { + qCWarning(DeckLoaderLog) << "Failed to set modification time for file:" << fileName; + timestampFile.close(); + return false; + } + + timestampFile.close(); + return true; } static QString getDomainForWebsite(DeckLoader::DecklistWebsite website)