Skip to content

Commit

Permalink
Make save/load tab safer
Browse files Browse the repository at this point in the history
After writing all new tab data to temporary file (suffix ".new"),
previously saved tab data is renamed (suffix ".old") and kept as backup,
then the new temporary file renamed to the tab data file.

If loading the tab data file fails, temporary and backup files are tried
next.

Downside is that all the tab data take twice as much disk space since
backup files are kept around.
  • Loading branch information
hluk committed Mar 21, 2022
1 parent 831844d commit 11729f1
Showing 1 changed file with 44 additions and 27 deletions.
71 changes: 44 additions & 27 deletions src/item/itemstore.cpp
Expand Up @@ -103,33 +103,38 @@ ItemSaverPtr loadItems(const QString &tabName, QAbstractItemModel &model, ItemFa
return nullptr;

const QString tabFileName = itemFileName(tabName);

ItemSaverPtr saver;
bool tabFileExists = false;

// If tab file doesn't exist, try to restore data from temporary file.
if ( !QFile::exists(tabFileName) ) {
QFile tmpFile(tabFileName + ".tmp");
if ( tmpFile.exists() ) {
log( QString("Tab \"%1\": Restoring items (previous save failed)").arg(tabName), LogWarning );

saver = loadItems(tabName, tmpFile.fileName(), model, itemFactory, maxItems);
if ( saver && !tmpFile.rename(tabFileName) )
printItemFileError("overwrite original file", tabName, tmpFile);
for (const auto &suffix : {QString(), QStringLiteral(".new"), QStringLiteral(".old")}) {
const QString fileName( QStringLiteral("%1%2").arg(tabFileName, suffix) );
if ( !QFile::exists(fileName) )
continue;

tabFileExists = true;
ItemSaverPtr saver = loadItems(tabName, fileName, model, itemFactory, maxItems);
if (saver) {
if (suffix.isEmpty()) {
COPYQ_LOG( QStringLiteral("Tab \"%1\": %2 items loaded")
.arg(tabName, QString::number(model.rowCount())) );
} else {
log( QStringLiteral("Tab \"%1\": %2 items restored from: %3")
.arg(tabName, QString::number(model.rowCount()), fileName), LogWarning );
}
return saver;
}
}

if (!saver) {
saver = QFile::exists(tabFileName)
? loadItems(tabName, tabFileName, model, itemFactory, maxItems)
: createTab(tabName, model, itemFactory, maxItems);
log( QStringLiteral("Tab \"%1\": Failed to load tab file: %2")
.arg(tabName, fileName), LogError );
model.removeRows(0, model.rowCount());
}

if (!saver) {
model.removeRows(0, model.rowCount());
if (tabFileExists)
return nullptr;
}

COPYQ_LOG( QString("Tab \"%1\": %2 items loaded").arg(tabName).arg(model.rowCount()) );
ItemSaverPtr saver = createTab(tabName, model, itemFactory, maxItems);
if (saver)
COPYQ_LOG( QString("Tab \"%1\": Tab file created").arg(tabName) );

return saver;
}
Expand All @@ -141,42 +146,54 @@ bool saveItems(const QString &tabName, const QAbstractItemModel &model, const It
if ( !createItemDirectory() )
return false;

// Save to temp file.
QFile tmpFile( tabFileName + ".tmp" );
// 1. Save to new temporary file.
const QString tmpFileNameNew( QStringLiteral("%1.new").arg(tabFileName) );
QFile tmpFile(tmpFileNameNew);
if ( !tmpFile.open(QIODevice::WriteOnly) ) {
printItemFileError("save tab (open temporary file)", tabName, tmpFile);
return false;
}

COPYQ_LOG( QString("Tab \"%1\": Saving %2 items").arg(tabName).arg(model.rowCount()) );
COPYQ_LOG( QStringLiteral("Tab \"%1\": Saving %2 items")
.arg(tabName, QString::number(model.rowCount())) );

if ( !saver->saveItems(tabName, model, &tmpFile) ) {
printItemFileError("save tab (save items to temporary file)", tabName, tmpFile);
return false;
}

// 1. Safely flush all data to temporary file.
// 2. Safely flush all data to temporary file.
if ( !tmpFile.flush() ) {
printItemFileError("save tab (flush temporary file)", tabName, tmpFile);
return false;
}

// 2. Remove old tab file.
// 3. Remove old temporary file.
const QString tmpFileNameOld( QStringLiteral("%1.old").arg(tabFileName) );
{
QFile oldTmpFile(tmpFileNameOld);
if ( oldTmpFile.exists() && !oldTmpFile.remove() ) {
printItemFileError("save tab (remove old file)", tabName, oldTmpFile);
return false;
}
}

// 4. Move old tab file (keep around as backup).
{
QFile oldTabFile(tabFileName);
if (oldTabFile.exists() && !oldTabFile.remove()) {
if ( oldTabFile.exists() && !oldTabFile.rename(tmpFileNameOld) ) {
printItemFileError("save tab (remove file)", tabName, oldTabFile);
return false;
}
}

// 3. Overwrite previous file.
// 5. Overwrite previous file.
if ( !tmpFile.rename(tabFileName) ) {
printItemFileError("save tab (overwrite original file)", tabName, tmpFile);
return false;
}

COPYQ_LOG( QString("Tab \"%1\": Items saved").arg(tabName) );
COPYQ_LOG( QStringLiteral("Tab \"%1\": Items saved").arg(tabName) );

return true;
}
Expand Down

0 comments on commit 11729f1

Please sign in to comment.