mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-05 21:13:55 -07:00
Fix #6952: prevent deck loss when saving to a full disk
DeckLoader::saveToFile() opened the target with QFile in WriteOnly mode, which truncates the existing file to 0 bytes the moment it is opened. The serializers (DeckList::saveToFile_Native/_Plain) always return true and the result of flush() was ignored, so a write that failed part-way -- e.g. because the disk was full -- left a 0-byte file behind yet was still reported (and logged) as a successful save. The same truncate-then-write pattern in updateLastLoadedTimestamp() could destroy a deck on load. Switch both paths to QSaveFile, which writes to a temporary file and only atomically replaces the target if commit() succeeds. On any write or flush failure commit() returns false, the original deck is left untouched, and the failure is logged instead of being reported as success.
This commit is contained in:
parent
c14a008080
commit
6ff8783265
1 changed files with 42 additions and 28 deletions
|
|
@ -9,6 +9,7 @@
|
|||
#include <QFutureWatcher>
|
||||
#include <QPrinter>
|
||||
#include <QRegularExpression>
|
||||
#include <QSaveFile>
|
||||
#include <QStringList>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocument>
|
||||
|
|
@ -129,7 +130,10 @@ std::optional<LoadedDeck> DeckLoader::loadFromRemote(const QString &nativeString
|
|||
std::optional<LoadedDeck::LoadInfo>
|
||||
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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue