123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <ProjectUtils.h>
- #include <PythonBindingsInterface.h>
- #include <QFileDialog>
- #include <QDir>
- #include <QtMath>
- #include <QMessageBox>
- #include <QFileInfo>
- #include <QProcess>
- #include <QProcessEnvironment>
- #include <QGuiApplication>
- #include <QProgressDialog>
- namespace O3DE::ProjectManager
- {
- namespace ProjectUtils
- {
- static bool WarnDirectoryOverwrite(const QString& path, QWidget* parent)
- {
- if (!QDir(path).isEmpty())
- {
- QMessageBox::StandardButton warningResult = QMessageBox::warning(
- parent, QObject::tr("Overwrite Directory"),
- QObject::tr("Directory is not empty! Are you sure you want to overwrite it?"), QMessageBox::No | QMessageBox::Yes);
- if (warningResult != QMessageBox::Yes)
- {
- return false;
- }
- }
- return true;
- }
- static bool IsDirectoryDescedent(const QString& possibleAncestorPath, const QString& possibleDecedentPath)
- {
- QDir ancestor(possibleAncestorPath);
- QDir descendent(possibleDecedentPath);
- do
- {
- if (ancestor == descendent)
- {
- return true;
- }
- descendent.cdUp();
- } while (!descendent.isRoot());
- return false;
- }
- 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())
- {
- return false;
- }
- 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(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 (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;
- }
- bool AddProjectDialog(QWidget* parent)
- {
- QString path = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(parent, QObject::tr("Select Project Directory")));
- if (!path.isEmpty())
- {
- return RegisterProject(path);
- }
- return false;
- }
- bool RegisterProject(const QString& path)
- {
- return PythonBindingsInterface::Get()->AddProject(path);
- }
- bool UnregisterProject(const QString& path)
- {
- return PythonBindingsInterface::Get()->RemoveProject(path);
- }
- bool CopyProjectDialog(const QString& origPath, QWidget* parent)
- {
- bool copyResult = false;
- QDir parentOrigDir(origPath);
- parentOrigDir.cdUp();
- QString newPath = QDir::toNativeSeparators(
- QFileDialog::getExistingDirectory(parent, QObject::tr("Select New Project Directory"), parentOrigDir.path()));
- if (!newPath.isEmpty())
- {
- if (!WarnDirectoryOverwrite(newPath, parent))
- {
- return false;
- }
- copyResult = CopyProject(origPath, newPath, parent);
- }
- return copyResult;
- }
- bool CopyProject(const QString& origPath, const QString& newPath, QWidget* parent)
- {
- // Disallow copying from or into subdirectory
- if (IsDirectoryDescedent(origPath, newPath) || IsDirectoryDescedent(newPath, origPath))
- {
- return false;
- }
- 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(QObject::tr("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("%1 ... %2 %3, %4 %5.")
- .arg(QObject::tr("Indexing files"))
- .arg(QString::number(fileCount))
- .arg(QObject::tr("files found"))
- .arg(fileSizeString)
- .arg(QObject::tr("to copy")));
- 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)
- {
- // Phase 2: Register project
- success = RegisterProject(newPath);
- }
- if (!success)
- {
- progressDialog->setLabelText(QObject::tr("Duplicating project failed/cancelled, removing already copied files ..."));
- qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
- DeleteProjectFiles(newPath, true);
- }
- progressDialog->deleteLater();
- return success;
- }
- bool DeleteProjectFiles(const QString& path, bool force)
- {
- QDir projectDirectory(path);
- if (projectDirectory.exists())
- {
- // Check if there is an actual project hereor just force it
- if (force || PythonBindingsInterface::Get()->GetProject(path).IsSuccess())
- {
- return projectDirectory.removeRecursively();
- }
- }
- return false;
- }
- bool MoveProject(QString origPath, QString newPath, QWidget* parent, bool ignoreRegister)
- {
- origPath = QDir::toNativeSeparators(origPath);
- newPath = QDir::toNativeSeparators(newPath);
- if (!WarnDirectoryOverwrite(newPath, parent) || (!ignoreRegister && !UnregisterProject(origPath)))
- {
- return false;
- }
- QDir newDirectory(newPath);
- if (!newDirectory.removeRecursively())
- {
- return false;
- }
- if (!newDirectory.rename(origPath, newPath))
- {
- // Likely failed because trying to move to another partition, try copying
- if (!CopyProject(origPath, newPath, parent))
- {
- return false;
- }
- DeleteProjectFiles(origPath, true);
- }
- if (!ignoreRegister && !RegisterProject(newPath))
- {
- return false;
- }
- return true;
- }
- bool ReplaceFile(const QString& origFile, const QString& newFile, QWidget* parent, bool interactive)
- {
- QFileInfo original(origFile);
- if (original.exists())
- {
- if (interactive)
- {
- QMessageBox::StandardButton warningResult = QMessageBox::warning(
- parent,
- QObject::tr("Overwrite File?"),
- QObject::tr("Replacing this will overwrite the current file on disk. Are you sure?"),
- QMessageBox::No | QMessageBox::Yes);
- if (warningResult == QMessageBox::No)
- {
- return false;
- }
- }
- if (!QFile::remove(origFile))
- {
- return false;
- }
- }
- if (!QFile::copy(newFile, origFile))
- {
- return false;
- }
- return true;
- }
- static bool IsVS2019Installed_internal()
- {
- QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
- QString programFilesPath = environment.value("ProgramFiles(x86)");
- QString vsWherePath = programFilesPath + "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
- QFileInfo vsWhereFile(vsWherePath);
- if (vsWhereFile.exists() && vsWhereFile.isFile())
- {
- QProcess vsWhereProcess;
- vsWhereProcess.setProcessChannelMode(QProcess::MergedChannels);
- vsWhereProcess.start(
- vsWherePath,
- QStringList{ "-version", "16.0", "-latest", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
- "-property", "isComplete" });
- if (!vsWhereProcess.waitForStarted())
- {
- return false;
- }
- while (vsWhereProcess.waitForReadyRead())
- {
- }
- QString vsWhereOutput(vsWhereProcess.readAllStandardOutput());
- if (vsWhereOutput.startsWith("1"))
- {
- return true;
- }
- }
- return false;
- }
- bool IsVS2019Installed()
- {
- static bool vs2019Installed = IsVS2019Installed_internal();
- return vs2019Installed;
- }
- ProjectManagerScreen GetProjectManagerScreen(const QString& screen)
- {
- auto iter = s_ProjectManagerStringNames.find(screen);
- if (iter != s_ProjectManagerStringNames.end())
- {
- return iter.value();
- }
- return ProjectManagerScreen::Invalid;
- }
- } // namespace ProjectUtils
- } // namespace O3DE::ProjectManager
|