Kaynağa Gözat

Project Manager: Showing a progress bar when copying projects (#2025)

Signed-off-by: Benjamin Jillich <[email protected]>
Benjamin Jillich 4 yıl önce
ebeveyn
işleme
76cef6e91f

+ 0 - 2
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 {

+ 151 - 16
Code/Tools/ProjectManager/Source/ProjectUtils.cpp

@@ -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;
         }
 

+ 1 - 1
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);