Use a primitive form of file transactions when copying pk3 files from a CD or Steam.

This commit is contained in:
Jonathan Young 2014-05-16 22:41:57 +10:00
parent fdb1b8144f
commit 5897c21ddc
6 changed files with 105 additions and 20 deletions

View file

@ -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 <time.h>
#include <QFile>
#include <QFileInfo>
#include "filecopy.h"
FileCopyWorker::FileCopyWorker(const QList<FileCopyOperation> &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<FileOperation> &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()

View file

@ -27,18 +27,42 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <QObject>
#include <QMutex>
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<FileCopyOperation> &files);
FileCopyWorker(const QList<FileOperation> &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<FileOperation> renameOperations);
private:
static const int bufferSize = 32 * 1024;
const QList<FileCopyOperation> files;
const QList<FileOperation> files;
char buffer[bufferSize];
bool isCancelled;
QMutex cancelMutex;
QList<FileOperation> renameOperations;
};
#endif // FILECOPY_H

View file

@ -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<FileCopyOperation> &InstallWizard::getFileCopyOperations() const
const QList<FileOperation> &InstallWizard::getFileCopyOperations() const
{
return fileCopyOperations;
}

View file

@ -21,7 +21,7 @@ public:
void clearFileCopyOperations();
void addFileCopyOperation(const QString &source, const QString &dest);
const QList<FileCopyOperation> &getFileCopyOperations() const;
const QList<FileOperation> &getFileCopyOperations() const;
bool getIsQuake3PatchRequired() const;
void setIsQuake3PatchRequired(bool value);
@ -46,7 +46,7 @@ private:
Ui::InstallWizard *ui;
QPushButton *cancelButton;
Settings *settings;
QList<FileCopyOperation> fileCopyOperations;
QList<FileOperation> fileCopyOperations;
bool isQuake3PatchRequired;
QString quakePath;
};

View file

@ -37,6 +37,7 @@ void InstallWizard_Copy::initializePage()
}
// Start copy thread.
qRegisterMetaType<QList<FileOperation> >("QList<FileOperation>");
copyWorker = new FileCopyWorker(((InstallWizard *)wizard())->getFileCopyOperations());
copyWorker->moveToThread(&copyThread);
connect(&copyThread, &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<FileOperation> 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();

View file

@ -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<FileOperation> renameOperations);
signals:
void copy();