Skip to content

Commit 9898d37

Browse files
authored
Merge pull request #65 from oblivioncth/implementation/backup_manager
Move backup facilities out of IInstall to dedicated class
2 parents 06129d1 + 6c1bb09 commit 9898d37

File tree

9 files changed

+323
-194
lines changed

9 files changed

+323
-194
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ endif()
7474

7575
include(OB/FetchQx)
7676
ob_fetch_qx(
77-
REF "d12b1a3dd8445ba3bae1271e4a6fc6fcb0420dfd"
77+
REF "66ccfeff2eddd912fff7e0116539bbfe84e9503c"
7878
COMPONENTS
7979
${FIL_QX_COMPONENTS}
8080
)

app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ add_custom_target(fil_copy_clifp
2323

2424
# ------------------ Setup FIL --------------------------
2525
set(FIL_SOURCE
26+
import/backup.h
27+
import/backup.cpp
2628
import/details.h
2729
import/details.cpp
2830
import/properties.h

app/src/import/backup.cpp

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Unit Includes
2+
#include "backup.h"
3+
4+
// Qt Includes
5+
#include <QFile>
6+
#include <QFileInfo>
7+
8+
namespace Import
9+
{
10+
11+
//===============================================================================================================
12+
// BackupError
13+
//===============================================================================================================
14+
15+
//-Constructor-------------------------------------------------------------
16+
//Private:
17+
BackupError::BackupError(Type t, const QString& s) :
18+
mType(t),
19+
mSpecific(s)
20+
{}
21+
22+
//Public:
23+
BackupError::BackupError() :
24+
mType(NoError)
25+
{}
26+
27+
//-Instance Functions-------------------------------------------------------------
28+
//Public:
29+
bool BackupError::isValid() const { return mType != NoError; }
30+
QString BackupError::specific() const { return mSpecific; }
31+
BackupError::Type BackupError::type() const { return mType; }
32+
33+
//Private:
34+
Qx::Severity BackupError::deriveSeverity() const { return Qx::Err; }
35+
quint32 BackupError::deriveValue() const { return mType; }
36+
QString BackupError::derivePrimary() const { return ERR_STRINGS.value(mType); }
37+
QString BackupError::deriveSecondary() const { return mSpecific; }
38+
QString BackupError::deriveCaption() const { return CAPTION_REVERT_ERR; }
39+
40+
//===============================================================================================================
41+
// BackupManager
42+
//===============================================================================================================
43+
44+
//-Constructor-------------------------------------------------------------
45+
//Private:
46+
BackupManager::BackupManager() {}
47+
48+
//-Class Functions-------------------------------------------------------------
49+
//Private:
50+
QString BackupManager::filePathToBackupPath(const QString& filePath)
51+
{
52+
return filePath + '.' + BACKUP_FILE_EXT;
53+
}
54+
55+
//Public:
56+
BackupManager* BackupManager::instance() { static BackupManager inst; return &inst; }
57+
58+
//-Instance Functions-------------------------------------------------------------
59+
//Private:
60+
BackupError BackupManager::backup(const QString& path, bool (*fn)(const QString& a, const QString& b))
61+
{
62+
// Prevent double+ backups
63+
if(mRevertablePaths.contains(path))
64+
return BackupError();
65+
66+
// Note revertable
67+
mRevertablePaths.insert(path);
68+
69+
// Backup if exists
70+
if(QFile::exists(path))
71+
{
72+
QString backupPath = filePathToBackupPath(path);
73+
74+
if(QFile::exists(backupPath) && QFileInfo(backupPath).isFile())
75+
{
76+
if(!QFile::remove(backupPath))
77+
return BackupError(BackupError::FileWontDelete, backupPath);
78+
}
79+
80+
if(!fn(path, backupPath))
81+
return BackupError(BackupError::FileWontBackup, path);
82+
}
83+
84+
return BackupError();
85+
}
86+
87+
BackupError BackupManager::restore(QSet<QString>::const_iterator pathItr)
88+
{
89+
Q_ASSERT(pathItr != mRevertablePaths.cend());
90+
91+
const QString path = *pathItr;
92+
mRevertablePaths.erase(pathItr);
93+
QString backupPath = filePathToBackupPath(path);
94+
95+
if(QFile::exists(path) && !QFile::remove(path))
96+
return BackupError(BackupError::FileWontDelete, path);
97+
98+
if(!QFile::exists(path) && QFile::exists(backupPath) && !QFile::rename(backupPath, path))
99+
return BackupError(BackupError::FileWontRestore, backupPath);
100+
101+
return BackupError();
102+
}
103+
104+
//Public:
105+
BackupError BackupManager::backupCopy(const QString& path)
106+
{
107+
return backup(path, [](const QString& a, const QString& b){ return QFile::copy(a, b); });
108+
}
109+
110+
BackupError BackupManager::backupRename(const QString& path)
111+
{
112+
return backup(path, [](const QString& a, const QString& b){ return QFile::rename(a, b); });
113+
}
114+
115+
BackupError BackupManager::restore(const QString& path)
116+
{
117+
auto store = mRevertablePaths.constFind(path);
118+
if(store == mRevertablePaths.cend())
119+
return BackupError();
120+
121+
return restore(store);
122+
}
123+
124+
BackupError BackupManager::safeReplace(const QString& src, const QString& dst, bool symlink)
125+
{
126+
// Maybe make sure destination folder exists here?
127+
128+
// Backup
129+
QString backupPath = filePathToBackupPath(dst);
130+
bool dstOccupied = QFile::exists(dst);
131+
if(dstOccupied)
132+
if(!QFile::rename(dst, backupPath)) // Temp backup
133+
return BackupError(BackupError::FileWontBackup, dst);
134+
135+
// Replace
136+
std::error_code replaceError;
137+
if(symlink)
138+
std::filesystem::create_symlink(src.toStdString(), dst.toStdString(), replaceError);
139+
else
140+
replaceError = QFile::copy(src, dst) ? std::error_code() : std::make_error_code(std::io_errc::stream);
141+
142+
// Restore on fail
143+
if(replaceError)
144+
{
145+
if(dstOccupied)
146+
QFile::rename(backupPath, dst);
147+
return BackupError(BackupError::FileWontReplace, src);
148+
}
149+
150+
// Remove backup immediately
151+
if(dstOccupied)
152+
QFile::remove(backupPath);
153+
else // Mark new files (only) as revertible so that existing ones will remain in the event of a revert
154+
mRevertablePaths.insert(dst);
155+
156+
return BackupError();
157+
}
158+
159+
int BackupManager::revertQueueCount() const { return mRevertablePaths.size(); }
160+
161+
int BackupManager::revertNextChange(BackupError& error, bool skipOnFail)
162+
{
163+
// Ensure error message is null
164+
error = BackupError();
165+
166+
// Delete new files and restore backups if present
167+
if(!mRevertablePaths.isEmpty())
168+
{
169+
BackupError rErr = restore(mRevertablePaths.cbegin());
170+
if(rErr && !skipOnFail)
171+
error = rErr;
172+
173+
return mRevertablePaths.size();
174+
}
175+
176+
// Return 0 if all empty (shouldn't be reached if function is used correctly)
177+
qWarning("Reversion function called with no reverts left!");
178+
return 0;
179+
}
180+
181+
}

app/src/import/backup.h

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#ifndef BACKUP_H
2+
#define BACKUP_H
3+
4+
// Qt Includes
5+
#include <QString>
6+
#include <QSet>
7+
8+
// Qx Includes
9+
#include <qx/core/qx-abstracterror.h>
10+
11+
using namespace Qt::StringLiterals;
12+
13+
/* TODO: The approach, or at least the language around doing a full revert (i.e. emptying the revert
14+
* queue) could use some touch-up.
15+
*/
16+
17+
namespace Import
18+
{
19+
20+
class QX_ERROR_TYPE(BackupError, "Lr::BackupError", 1301)
21+
{
22+
friend class BackupManager;
23+
//-Class Enums-------------------------------------------------------------
24+
public:
25+
enum Type
26+
{
27+
NoError,
28+
FileWontDelete,
29+
FileWontRestore,
30+
FileWontBackup,
31+
FileWontReplace
32+
};
33+
34+
//-Class Variables-------------------------------------------------------------
35+
private:
36+
static inline const QHash<Type, QString> ERR_STRINGS{
37+
{NoError, u""_s},
38+
{FileWontDelete, u"Cannot remove a file. It may need to be deleted manually."_s},
39+
{FileWontRestore, u"Cannot restore a file backup. It may need to be renamed manually.."_s},
40+
{FileWontBackup, u"Cannot backup file."_s},
41+
{FileWontReplace, u"A file that was part of a safe replace operation could not be transfered."_s}
42+
};
43+
44+
static inline const QString CAPTION_REVERT_ERR = u"Error managing backups"_s;
45+
46+
//-Instance Variables-------------------------------------------------------------
47+
private:
48+
Type mType;
49+
QString mSpecific;
50+
51+
//-Constructor-------------------------------------------------------------
52+
private:
53+
BackupError(Type t, const QString& s);
54+
55+
public:
56+
BackupError();
57+
58+
//-Instance Functions-------------------------------------------------------------
59+
public:
60+
bool isValid() const;
61+
Type type() const;
62+
QString specific() const;
63+
64+
private:
65+
Qx::Severity deriveSeverity() const override;
66+
quint32 deriveValue() const override;
67+
QString derivePrimary() const override;
68+
QString deriveSecondary() const override;
69+
QString deriveCaption() const override;
70+
};
71+
72+
class BackupManager
73+
{
74+
//-Class Variables-----------------------------------------------------------------------------------------------
75+
private:
76+
// Files
77+
static inline const QString BACKUP_FILE_EXT = u"fbk"_s;
78+
79+
//-Instance Variables-------------------------------------------------------------
80+
private:
81+
QSet<QString> mRevertablePaths;
82+
83+
//-Constructor-------------------------------------------------------------
84+
private:
85+
BackupManager();
86+
87+
//-Class Functions-------------------------------------------------------------
88+
private:
89+
static QString filePathToBackupPath(const QString& filePath);
90+
91+
public:
92+
static BackupManager* instance();
93+
94+
//-Instance Functions-------------------------------------------------------------
95+
private:
96+
BackupError backup(const QString& path, bool (*fn)(const QString& a, const QString& b));
97+
BackupError restore(QSet<QString>::const_iterator pathItr);
98+
99+
public:
100+
BackupError backupCopy(const QString& path);
101+
BackupError backupRename(const QString& path);
102+
BackupError restore(const QString& path);
103+
BackupError safeReplace(const QString& src, const QString& dst, bool symlink);
104+
105+
int revertQueueCount() const;
106+
int revertNextChange(BackupError& error, bool skipOnFail);
107+
};
108+
109+
}
110+
111+
#endif // BACKUP_H

app/src/import/worker.cpp

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// Project Includes
1515
#include "kernel/clifp.h"
1616
#include "import/details.h"
17+
#include "import/backup.h"
1718

1819
namespace Import
1920
{
@@ -148,6 +149,7 @@ ImageTransferError Worker::transferImage(bool symlink, QString sourcePath, QStri
148149
QString sourceChecksum;
149150
QString destinationChecksum;
150151

152+
// TODO: Probably better to just byte-wise compare
151153
if(!Qx::calculateFileChecksum(sourceChecksum, source, QCryptographicHash::Md5).isFailure() &&
152154
!Qx::calculateFileChecksum(destinationChecksum, destination, QCryptographicHash::Md5).isFailure() &&
153155
sourceChecksum.compare(destinationChecksum, Qt::CaseInsensitive) == 0)
@@ -160,42 +162,16 @@ ImageTransferError Worker::transferImage(bool symlink, QString sourcePath, QStri
160162
if(!destinationDir.mkpath(u"."_s))
161163
return ImageTransferError(ImageTransferError::CantCreateDirectory, QString(), destinationDir.absolutePath());
162164

163-
// Determine backup path
164-
QString backupPath = Lr::IInstall::filePathToBackupPath(destinationInfo.absoluteFilePath());
165-
166-
// Temporarily backup image if it already exists (also acts as deletion marking in case images for the title were removed in an update)
167-
if(destinationOccupied)
168-
if(!QFile::rename(destinationPath, backupPath)) // Temp backup
169-
return ImageTransferError(ImageTransferError::ImageWontBackup, QString(), destinationPath);
170-
171-
// Linking error tracker
172-
std::error_code linkError;
173-
174-
// Handle transfer
175-
if(symlink)
176-
{
177-
std::filesystem::create_symlink(sourcePath.toStdString(), destinationPath.toStdString(), linkError);
178-
if(linkError)
179-
{
180-
QFile::rename(backupPath, destinationPath); // Restore Backup
181-
return ImageTransferError(ImageTransferError::ImageWontLink, sourcePath, destinationPath);
182-
}
183-
else if(QFile::exists(backupPath))
184-
QFile::remove(backupPath);
185-
else
186-
mLauncherInstall->addRevertableFile(destinationPath); // Only queue image to be removed on failure if its new, so existing images aren't deleted on revert
187-
}
188-
else
165+
// Transfer image
166+
BackupError bErr = BackupManager::instance()->safeReplace(sourcePath, destinationPath, symlink);
167+
if(bErr)
189168
{
190-
if(!QFile::copy(sourcePath, destinationPath))
191-
{
192-
QFile::rename(backupPath, destinationPath); // Restore Backup
193-
return ImageTransferError(ImageTransferError::ImageWontCopy, sourcePath, destinationPath);
194-
}
195-
else if(QFile::exists(backupPath))
196-
QFile::remove(backupPath);
169+
if(bErr.type() == BackupError::FileWontBackup)
170+
return ImageTransferError(ImageTransferError::ImageWontBackup, QString(), destinationPath);
171+
else if(bErr.type() == BackupError::FileWontReplace)
172+
return ImageTransferError(symlink ? ImageTransferError::ImageWontLink : ImageTransferError::ImageWontCopy, QString(), destinationPath);
197173
else
198-
mLauncherInstall->addRevertableFile(destinationPath); // Only queue image to be removed on failure if its new, so existing images aren't deleted on revert
174+
qFatal("Unhandled image transfer error type.");
199175
}
200176

201177
// Return null error on success

0 commit comments

Comments
 (0)