|
@@ -10,11 +10,13 @@
|
|
|
|
|
|
#include <QFileDialog>
|
|
|
#include <QDir>
|
|
|
+#include <QtMath>
|
|
|
#include <QMessageBox>
|
|
|
#include <QFileInfo>
|
|
|
#include <QProcess>
|
|
|
#include <QProcessEnvironment>
|
|
|
#include <QGuiApplication>
|
|
|
+#include <QProgressDialog>
|
|
|
|
|
|
namespace O3DE::ProjectManager
|
|
|
{
|
|
@@ -55,7 +57,42 @@ namespace O3DE::ProjectManager
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- static bool CopyDirectory(const QString& origPath, const QString& newPath)
|
|
|
+ typedef AZStd::function<void(/*fileCount=*/int, /*totalSizeInBytes=*/int)> StatusFunction;
|
|
|
+ static void RecursiveGetAllFiles(const QDir& directory, QStringList& outFileList, qint64& outTotalSizeInBytes, StatusFunction statusCallback)
|
|
|
+ {
|
|
|
+ const QStringList entries = directory.entryList(QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot);
|
|
|
+ for (const QString& entryPath : entries)
|
|
|
+ {
|
|
|
+ const QString filePath = QDir::toNativeSeparators(QString("%1/%2").arg(directory.path()).arg(entryPath));
|
|
|
+ QFileInfo fileInfo(filePath);
|
|
|
+
|
|
|
+ if (fileInfo.isDir())
|
|
|
+ {
|
|
|
+ QDir subDirectory(filePath);
|
|
|
+ RecursiveGetAllFiles(subDirectory, outFileList, outTotalSizeInBytes, statusCallback);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ outFileList.push_back(filePath);
|
|
|
+ outTotalSizeInBytes += fileInfo.size();
|
|
|
+
|
|
|
+ const int updateStatusEvery = 64;
|
|
|
+ if (outFileList.size() % updateStatusEvery == 0)
|
|
|
+ {
|
|
|
+ statusCallback(outFileList.size(), outTotalSizeInBytes);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static bool CopyDirectory(QProgressDialog* progressDialog,
|
|
|
+ const QString& origPath,
|
|
|
+ const QString& newPath,
|
|
|
+ QStringList& filesToCopy,
|
|
|
+ int& outNumCopiedFiles,
|
|
|
+ qint64 totalSizeToCopy,
|
|
|
+ qint64& outCopiedFileSize,
|
|
|
+ bool& showIgnoreFileDialog)
|
|
|
{
|
|
|
QDir original(origPath);
|
|
|
if (!original.exists())
|
|
@@ -65,19 +102,91 @@ namespace O3DE::ProjectManager
|
|
|
|
|
|
for (QString directory : original.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
|
|
|
{
|
|
|
+ if (progressDialog->wasCanceled())
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
QString newDirectoryPath = newPath + QDir::separator() + directory;
|
|
|
original.mkpath(newDirectoryPath);
|
|
|
|
|
|
- if (!CopyDirectory(origPath + QDir::separator() + directory, newDirectoryPath))
|
|
|
+ if (!CopyDirectory(progressDialog, origPath + QDir::separator() + directory,
|
|
|
+ newDirectoryPath, filesToCopy, outNumCopiedFiles, totalSizeToCopy, outCopiedFileSize, showIgnoreFileDialog))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ QLocale locale;
|
|
|
+ const float progressDialogRangeHalf = qFabs(progressDialog->maximum() - progressDialog->minimum()) * 0.5f;
|
|
|
for (QString file : original.entryList(QDir::Files))
|
|
|
{
|
|
|
- if (!QFile::copy(origPath + QDir::separator() + file, newPath + QDir::separator() + file))
|
|
|
+ if (progressDialog->wasCanceled())
|
|
|
+ {
|
|
|
return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Progress window update
|
|
|
+ {
|
|
|
+ // Weight in the number of already copied files as well as the copied bytes to get a better progress indication
|
|
|
+ // for cases combining many small files and some really large files.
|
|
|
+ const float normalizedNumFiles = static_cast<float>(outNumCopiedFiles) / filesToCopy.count();
|
|
|
+ const float normalizedFileSize = static_cast<float>(outCopiedFileSize) / totalSizeToCopy;
|
|
|
+ const int progress = normalizedNumFiles * progressDialogRangeHalf + normalizedFileSize * progressDialogRangeHalf;
|
|
|
+ progressDialog->setValue(progress);
|
|
|
+
|
|
|
+ const QString copiedFileSizeString = locale.formattedDataSize(outCopiedFileSize);
|
|
|
+ const QString totalFileSizeString = locale.formattedDataSize(totalSizeToCopy);
|
|
|
+ progressDialog->setLabelText(QString("Coping file %1 of %2 (%3 of %4) ...").arg(QString::number(outNumCopiedFiles),
|
|
|
+ QString::number(filesToCopy.count()),
|
|
|
+ copiedFileSizeString,
|
|
|
+ totalFileSizeString));
|
|
|
+ qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
|
+ }
|
|
|
+
|
|
|
+ const QString toBeCopiedFilePath = origPath + QDir::separator() + file;
|
|
|
+ const QString copyToFilePath = newPath + QDir::separator() + file;
|
|
|
+ if (!QFile::copy(toBeCopiedFilePath, copyToFilePath))
|
|
|
+ {
|
|
|
+ // Let the user decide to ignore files that failed to copy or cancel the whole operation.
|
|
|
+ if (showIgnoreFileDialog)
|
|
|
+ {
|
|
|
+ QMessageBox ignoreFileMessageBox;
|
|
|
+ const QString text = QString("Cannot copy <b>%1</b>.<br><br>"
|
|
|
+ "Source: %2<br>"
|
|
|
+ "Destination: %3<br><br>"
|
|
|
+ "Press <b>Yes</b> to ignore the file, <b>YesToAll</b> to ignore all upcoming non-copyable files or "
|
|
|
+ "<b>Cancel</b> to abort duplicating the project.").arg(file, toBeCopiedFilePath, copyToFilePath);
|
|
|
+
|
|
|
+ ignoreFileMessageBox.setModal(true);
|
|
|
+ ignoreFileMessageBox.setWindowTitle("Cannot copy file");
|
|
|
+ ignoreFileMessageBox.setText(text);
|
|
|
+ ignoreFileMessageBox.setIcon(QMessageBox::Question);
|
|
|
+ ignoreFileMessageBox.setStandardButtons(QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::Cancel);
|
|
|
+
|
|
|
+ int ignoreFile = ignoreFileMessageBox.exec();
|
|
|
+ if (ignoreFile == QMessageBox::YesToAll)
|
|
|
+ {
|
|
|
+ showIgnoreFileDialog = false;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ else if (ignoreFile == QMessageBox::Yes)
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ outNumCopiedFiles++;
|
|
|
+
|
|
|
+ QFileInfo fileInfo(toBeCopiedFilePath);
|
|
|
+ outCopiedFileSize += fileInfo.size();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
return true;
|
|
@@ -119,16 +228,13 @@ namespace O3DE::ProjectManager
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- QGuiApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
|
|
- copyResult = CopyProject(origPath, newPath);
|
|
|
- QGuiApplication::restoreOverrideCursor();
|
|
|
-
|
|
|
+ copyResult = CopyProject(origPath, newPath, parent);
|
|
|
}
|
|
|
|
|
|
return copyResult;
|
|
|
}
|
|
|
|
|
|
- bool CopyProject(const QString& origPath, const QString& newPath)
|
|
|
+ bool CopyProject(const QString& origPath, const QString& newPath, QWidget* parent)
|
|
|
{
|
|
|
// Disallow copying from or into subdirectory
|
|
|
if (IsDirectoryDescedent(origPath, newPath) || IsDirectoryDescedent(newPath, origPath))
|
|
@@ -136,19 +242,49 @@ namespace O3DE::ProjectManager
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- if (!CopyDirectory(origPath, newPath))
|
|
|
+ QStringList filesToCopy;
|
|
|
+ qint64 totalSizeInBytes = 0;
|
|
|
+
|
|
|
+ QProgressDialog* progressDialog = new QProgressDialog(parent);
|
|
|
+ progressDialog->setAutoClose(true);
|
|
|
+ progressDialog->setValue(0);
|
|
|
+ progressDialog->setRange(0, 1000);
|
|
|
+ progressDialog->setModal(true);
|
|
|
+ progressDialog->setWindowTitle("Copying project ...");
|
|
|
+ progressDialog->show();
|
|
|
+
|
|
|
+ QLocale locale;
|
|
|
+ RecursiveGetAllFiles(origPath, filesToCopy, totalSizeInBytes, [=](int fileCount, int sizeInBytes)
|
|
|
+ {
|
|
|
+ // Create a human-readable version of the file size.
|
|
|
+ const QString fileSizeString = locale.formattedDataSize(sizeInBytes);
|
|
|
+
|
|
|
+ progressDialog->setLabelText(QString("Indexing files ... %1 files found, %2 to copy.").arg(fileCount).arg(fileSizeString));
|
|
|
+ qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
|
+ });
|
|
|
+
|
|
|
+ int numFilesCopied = 0;
|
|
|
+ qint64 copiedFileSize = 0;
|
|
|
+
|
|
|
+ // Phase 1: Copy files
|
|
|
+ bool showIgnoreFileDialog = true;
|
|
|
+ bool success = CopyDirectory(progressDialog, origPath, newPath, filesToCopy, numFilesCopied, totalSizeInBytes, copiedFileSize, showIgnoreFileDialog);
|
|
|
+ if (success)
|
|
|
{
|
|
|
- // Cleanup whatever mess was made
|
|
|
- DeleteProjectFiles(newPath, true);
|
|
|
- return false;
|
|
|
+ // Phase 2: Register project
|
|
|
+ success = RegisterProject(newPath);
|
|
|
}
|
|
|
|
|
|
- if (!RegisterProject(newPath))
|
|
|
+ if (!success)
|
|
|
{
|
|
|
+ progressDialog->setLabelText("Duplicating project failed/cancelled, removing already copied files ...");
|
|
|
+ qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
|
|
|
+
|
|
|
DeleteProjectFiles(newPath, true);
|
|
|
}
|
|
|
|
|
|
- return true;
|
|
|
+ progressDialog->deleteLater();
|
|
|
+ return success;
|
|
|
}
|
|
|
|
|
|
bool DeleteProjectFiles(const QString& path, bool force)
|
|
@@ -184,7 +320,7 @@ namespace O3DE::ProjectManager
|
|
|
if (!newDirectory.rename(origPath, newPath))
|
|
|
{
|
|
|
// Likely failed because trying to move to another partition, try copying
|
|
|
- if (!CopyProject(origPath, newPath))
|
|
|
+ if (!CopyProject(origPath, newPath, parent))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
@@ -272,7 +408,6 @@ namespace O3DE::ProjectManager
|
|
|
bool IsVS2019Installed()
|
|
|
{
|
|
|
static bool vs2019Installed = IsVS2019Installed_internal();
|
|
|
-
|
|
|
return vs2019Installed;
|
|
|
}
|
|
|
|