From 5897c21ddcc492b4ec6dd5412a7cb00039a30a2d Mon Sep 17 00:00:00 2001 From: Jonathan Young Date: Fri, 16 May 2014 22:41:57 +1000 Subject: [PATCH] Use a primitive form of file transactions when copying pk3 files from a CD or Steam. --- filecopy.cpp | 48 ++++++++++++++++++++++++++++++++++++------ filecopy.h | 33 +++++++++++++++++++++++++---- installwizard.cpp | 10 ++++----- installwizard.h | 4 ++-- installwizard_copy.cpp | 28 +++++++++++++++++++++++- installwizard_copy.h | 2 +- 6 files changed, 105 insertions(+), 20 deletions(-) diff --git a/filecopy.cpp b/filecopy.cpp index 4e9a00e..314575e 100644 --- a/filecopy.cpp +++ b/filecopy.cpp @@ -20,16 +20,35 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include #include #include #include "filecopy.h" -FileCopyWorker::FileCopyWorker(const QList &files) : files(files), isCancelled(false) +QString FileUtils::uniqueFilename(const QString &filename) +{ + QString unique; + + for (;;) + { + QFileInfo fi(filename); + unique = fi.path() + QString("/") + QString::number(qrand(), 16) + QString("_") + fi.fileName(); + + if (!QFile::exists(unique)) + break; + } + + return unique; +} + +FileCopyWorker::FileCopyWorker(const QList &files) : files(files), isCancelled(false) { } void FileCopyWorker::copy() { + qsrand(time(NULL)); + for (int i = 0; i < files.size(); i++) { QFile sourceFile(files.at(i).source); @@ -37,17 +56,23 @@ void FileCopyWorker::copy() if (!sourceFile.open(QIODevice::ReadOnly)) { emit errorMessage(QString("'%1': %2").arg(files.at(i).source).arg(sourceFile.errorString())); - return; + goto cleanup; } - QFile destinationFile(files.at(i).dest); + const QString tempDestFilename = FileUtils::uniqueFilename(files.at(i).dest); + QFile destinationFile(tempDestFilename); if (!destinationFile.open(QIODevice::WriteOnly)) { - emit errorMessage(QString("'%1': %2").arg(files.at(i).dest).arg(destinationFile.errorString())); - return; + emit errorMessage(QString("'%1': %2").arg(tempDestFilename).arg(destinationFile.errorString())); + goto cleanup; } + FileOperation fo; + fo.source = tempDestFilename; + fo.dest = files.at(i).dest; + renameOperations.append(fo); + emit fileChanged(QFileInfo(sourceFile.fileName()).fileName()); const qint64 totalBytes = sourceFile.size(); qint64 totalBytesWritten = 0; @@ -66,11 +91,20 @@ void FileCopyWorker::copy() QMutexLocker locker(&cancelMutex); if (isCancelled) - break; + goto cleanup; } + } - emit copyFinished(); + emit copyFinished(renameOperations); + return; + + // Delete all the destination files. +cleanup: + for (int i = 0; i < renameOperations.size(); i++) + { + QFile::remove(renameOperations.at(i).source); + } } void FileCopyWorker::cancel() diff --git a/filecopy.h b/filecopy.h index 9bc3496..5846d1e 100644 --- a/filecopy.h +++ b/filecopy.h @@ -27,18 +27,42 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -struct FileCopyOperation +struct FileOperation { QString source; QString dest; }; +class FileUtils +{ +public: + static QString uniqueFilename(const QString &filename); +}; + +/* +Input: list of files to copy (ctor). +Output: list of files to rename in order to complete the transaction (copyFinished signal). + +Example (using a single file): + +Input source: F:\QUAKE3\baseq3\pak0.pk3 +Input dest: C:\Program Files (x86)\Quake III Arena\baseq3\pak0.pk3 + +Source is copied to dest, but the destination file is renamed by prepending a random prefix, e.g. 34d8f_pak0.pk3. + +Output source: C:\Program Files (x86)\Quake III Arena\baseq3\34d8f_pak0.pk3 +Output dest: C:\Program Files (x86)\Quake III Arena\baseq3\pak0.pk3 + +The transaction is completed by deleting output dest and renaming output source to output dest. + +If the worker is cancelled, all created files are deleted. +*/ class FileCopyWorker : public QObject { Q_OBJECT public: - FileCopyWorker(const QList &files); + FileCopyWorker(const QList &files); void cancel(); public slots: @@ -48,14 +72,15 @@ signals: void fileChanged(const QString &filename); void progressChanged(qint64 bytesWritten, qint64 bytesTotal); void errorMessage(const QString &message); - void copyFinished(); + void copyFinished(QList renameOperations); private: static const int bufferSize = 32 * 1024; - const QList files; + const QList files; char buffer[bufferSize]; bool isCancelled; QMutex cancelMutex; + QList renameOperations; }; #endif // FILECOPY_H diff --git a/installwizard.cpp b/installwizard.cpp index 12ea91f..90defb5 100644 --- a/installwizard.cpp +++ b/installwizard.cpp @@ -44,13 +44,13 @@ void InstallWizard::clearFileCopyOperations() void InstallWizard::addFileCopyOperation(const QString &source, const QString &dest) { - FileCopyOperation fco; - fco.source = source; - fco.dest = dest; - fileCopyOperations.append(fco); + FileOperation fo; + fo.source = source; + fo.dest = dest; + fileCopyOperations.append(fo); } -const QList &InstallWizard::getFileCopyOperations() const +const QList &InstallWizard::getFileCopyOperations() const { return fileCopyOperations; } diff --git a/installwizard.h b/installwizard.h index 86cb780..562404c 100644 --- a/installwizard.h +++ b/installwizard.h @@ -21,7 +21,7 @@ public: void clearFileCopyOperations(); void addFileCopyOperation(const QString &source, const QString &dest); - const QList &getFileCopyOperations() const; + const QList &getFileCopyOperations() const; bool getIsQuake3PatchRequired() const; void setIsQuake3PatchRequired(bool value); @@ -46,7 +46,7 @@ private: Ui::InstallWizard *ui; QPushButton *cancelButton; Settings *settings; - QList fileCopyOperations; + QList fileCopyOperations; bool isQuake3PatchRequired; QString quakePath; }; diff --git a/installwizard_copy.cpp b/installwizard_copy.cpp index 51bffb9..97965b9 100644 --- a/installwizard_copy.cpp +++ b/installwizard_copy.cpp @@ -37,6 +37,7 @@ void InstallWizard_Copy::initializePage() } // Start copy thread. + qRegisterMetaType >("QList"); copyWorker = new FileCopyWorker(((InstallWizard *)wizard())->getFileCopyOperations()); copyWorker->moveToThread(©Thread); connect(©Thread, &QThread::finished, copyWorker, &QObject::deleteLater); @@ -90,10 +91,35 @@ void InstallWizard_Copy::setCopyErrorMessage(const QString &message) ui->lblStatus->setText(message); } -void InstallWizard_Copy::finishCopy() +void InstallWizard_Copy::finishCopy(QList renameOperations) { copyThread.quit(); copyThread.wait(); + + // Complete the transaction. + for (int i = 0; i < renameOperations.size(); i++) + { + const FileOperation &fo = renameOperations.at(i); + const QString randomDest = FileUtils::uniqueFilename(fo.dest); + + // Rename dest to random. + if (!QFile::rename(fo.dest, randomDest)) + { + ui->lblStatus->setText(QString("Transaction error renaming '%1' to '%2'").arg(fo.dest).arg(randomDest)); + return; + } + + // Rename source to dest. + if (!QFile::rename(fo.source, fo.dest)) + { + ui->lblStatus->setText(QString("Transaction error renaming '%1' to '%2'").arg(fo.source).arg(fo.dest)); + return; + } + + // Delete random. + QFile::remove(randomDest); + } + isCopyFinished = true; emit completeChanged(); wizard()->next(); diff --git a/installwizard_copy.h b/installwizard_copy.h index e288845..fd12cf6 100644 --- a/installwizard_copy.h +++ b/installwizard_copy.h @@ -26,7 +26,7 @@ private slots: void setCopyFilename(const QString &filename); void setCopyProgress(qint64 bytesWritten, qint64 bytesTotal); void setCopyErrorMessage(const QString &message); - void finishCopy(); + void finishCopy(QList renameOperations); signals: void copy();