diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index 0684ec1310..74acc1c7ef 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -435,8 +435,6 @@ QProgressBar { border: none; background-color: transparent; padding: 0px; - min-height: 14px; - font-size: 2px; } QProgressBar::chunk { diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.cpp b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp index f7a90f5e0c..82944aa41a 100644 --- a/Code/Tools/ProjectManager/Source/ProjectUtils.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.cpp @@ -10,11 +10,13 @@ #include #include +#include #include #include #include #include #include +#include namespace O3DE::ProjectManager { @@ -55,7 +57,42 @@ namespace O3DE::ProjectManager return false; } - static bool CopyDirectory(const QString& origPath, const QString& newPath) + typedef AZStd::function 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(outNumCopiedFiles) / filesToCopy.count(); + const float normalizedFileSize = static_cast(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 %1.

" + "Source: %2
" + "Destination: %3

" + "Press Yes to ignore the file, YesToAll to ignore all upcoming non-copyable files or " + "Cancel 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; } diff --git a/Code/Tools/ProjectManager/Source/ProjectUtils.h b/Code/Tools/ProjectManager/Source/ProjectUtils.h index 786ff34b88..2829a45180 100644 --- a/Code/Tools/ProjectManager/Source/ProjectUtils.h +++ b/Code/Tools/ProjectManager/Source/ProjectUtils.h @@ -17,7 +17,7 @@ namespace O3DE::ProjectManager bool RegisterProject(const QString& path); bool UnregisterProject(const QString& path); bool CopyProjectDialog(const QString& origPath, QWidget* parent = nullptr); - bool CopyProject(const QString& origPath, const QString& newPath); + bool CopyProject(const QString& origPath, const QString& newPath, QWidget* parent); bool DeleteProjectFiles(const QString& path, bool force = false); bool MoveProject(QString origPath, QString newPath, QWidget* parent = nullptr, bool ignoreRegister = false);