/// <summary> /// Creates a symlink between destinationPath and sourcePath (Windows version). /// </summary> /// <param name="sourcePath"></param> /// <param name="destinationPath"></param> private static void CreateLinkWin(string sourcePath, string destinationPath) { string cmd = $"/C mklink /J \"{destinationPath}\" \"{sourcePath}\""; Debug.Log($"Windows junction: {cmd}"); ProjectCloner.StartHiddenConsoleProcess("cmd.exe", cmd); }
/// <summary> /// Creates a symlink between destinationPath and sourcePath (Linux version). /// </summary> /// <param name="sourcePath"></param> /// <param name="destinationPath"></param> private static void CreateLinkLunux(string sourcePath, string destinationPath) { string cmd = string.Format("-c \"ln -s {0} {1}\"", sourcePath, destinationPath); Debug.Log("Linux junction: " + cmd); ProjectCloner.StartHiddenConsoleProcess("/bin/bash", cmd); }
/// <summary> /// Returns the path to the original project. /// If currently open project is the original, returns its own path. /// If the original project folder cannot be found, retuns an empty string. /// </summary> /// <returns></returns> public static string GetOriginalProjectPath() { if (IsClone()) { /// If this is a clone... /// Original project path can be deduced by removing the suffix from the clone's path. string cloneProjectPath = ProjectCloner.GetCurrentProject().projectPath; int index = cloneProjectPath.LastIndexOf(ProjectCloner.CloneNameSuffix); if (index > 0) { string originalProjectPath = cloneProjectPath.Substring(0, index); if (Directory.Exists(originalProjectPath)) { return(originalProjectPath); } } return(string.Empty); } else { /// If this is the original, we return its own path. return(ProjectCloner.GetCurrentProjectPath()); } }
/// <summary> /// Calculates the size of the given directory. Displays a progress bar. /// </summary> /// <param name="directory">Directory, which size has to be calculated.</param> /// <param name="includeNested">If true, size will include all nested directories.</param> /// <param name="progressBarPrefix">Optional string added to the beginning of the progress bar window header.</param> /// <returns>Size of the directory in bytes.</returns> private static long GetDirectorySize(DirectoryInfo directory, bool includeNested = false, string progressBarPrefix = "") { EditorUtility.DisplayProgressBar($"{progressBarPrefix}Calculating size of directories...", $"Scanning '{directory.FullName}'...", 0f); // Calculate size of all files in directory. var enumeratedFiles = directory.EnumerateFiles(); long filesSize = enumeratedFiles.Sum((FileInfo file) => { // Fix which allows to exceed MAX_PATH when working with files in Windows. // Without it, files with full path longer than 259 characters will throw a FileNotFoundException. if (Application.platform == RuntimePlatform.WindowsEditor) { file = new FileInfo($"\\\\?\\{file.FullName}"); } return(file.Length); }); // Calculate size of all nested directories. long directoriesSize = 0; if (includeNested) { IEnumerable <DirectoryInfo> nestedDirectories = directory.EnumerateDirectories(); foreach (DirectoryInfo nestedDir in nestedDirectories) { directoriesSize += ProjectCloner.GetDirectorySize(nestedDir, true, progressBarPrefix); } } return(filesSize + directoriesSize); }
/// <summary> /// Creates a symlink between destinationPath and sourcePath (Windows version). /// </summary> /// <param name="sourcePath"></param> /// <param name="destinationPath"></param> private static void CreateLinkWin(string sourcePath, string destinationPath) { string cmd = "/C mklink /J " + string.Format("\"{0}\" \"{1}\"", destinationPath, sourcePath); Debug.Log("Windows junction: " + cmd); ProjectCloner.StartHiddenConsoleProcess("cmd.exe", cmd); }
/// <summary> /// Returns true is the project currently open in Unity Editor is a clone. /// </summary> /// <returns></returns> public static bool IsClone() { /// The project is a clone if its root directory contains an empty file named ".clone". string cloneFilePath = Path.Combine(ProjectCloner.GetCurrentProjectPath(), ProjectCloner.CloneFileName); bool isClone = File.Exists(cloneFilePath); return(isClone); }
/// <summary> /// Copies directory located at sourcePath to destinationPath. Displays a progress bar. /// Same as the previous method, but uses recursion to copy all nested folders as well. /// </summary> /// <param name="source">Directory to be copied.</param> /// <param name="destination">Destination directory (created automatically if needed).</param> /// <param name="totalBytes">Total bytes to be copied. Calculated automatically, initialize at 0.</param> /// <param name="copiedBytes">To track already copied bytes. Calculated automatically, initialize at 0.</param> /// <param name="progressBarPrefix">Optional string added to the beginning of the progress bar window header.</param> private static void CopyDirectoryWithProgressBarRecursive(DirectoryInfo source, DirectoryInfo destination, ref long totalBytes, ref long copiedBytes, string progressBarPrefix = "") { /// Directory cannot be copied into itself. if (source.FullName.ToLower() == destination.FullName.ToLower()) { Debug.LogError("Cannot copy directory into itself."); return; } /// Calculate total bytes, if required. if (totalBytes == 0) { totalBytes = ProjectCloner.GetDirectorySize(source, true, progressBarPrefix); } /// Create destination directory, if required. if (!Directory.Exists(destination.FullName)) { Directory.CreateDirectory(destination.FullName); } /// Copy all files from the source. foreach (FileInfo file in source.GetFiles()) { try { file.CopyTo(Path.Combine(destination.ToString(), file.Name), true); } catch (IOException) { /// Some files may throw IOException if they are currently open in Unity editor. /// Just ignore them in such case. } /// Account the copied file size. copiedBytes += file.Length; /// Display the progress bar. float progress = (float)copiedBytes / (float)totalBytes; bool cancelCopy = EditorUtility.DisplayCancelableProgressBar( progressBarPrefix + "Copying '" + source.FullName + "' to '" + destination.FullName + "'...", "(" + (progress * 100f).ToString("F2") + "%) Copying file '" + file.Name + "'...", progress); if (cancelCopy) { return; } } /// Copy all nested directories from the source. foreach (DirectoryInfo sourceNestedDir in source.GetDirectories()) { DirectoryInfo nextDestingationNestedDir = destination.CreateSubdirectory(sourceNestedDir.Name); ProjectCloner.CopyDirectoryWithProgressBarRecursive(sourceNestedDir, nextDestingationNestedDir, ref totalBytes, ref copiedBytes, progressBarPrefix); } }
/// <summary> /// Creates a symlink between destinationPath and sourcePath (Mac version). /// </summary> /// <param name="sourcePath"></param> /// <param name="destinationPath"></param> private static void CreateLinkMac(string sourcePath, string destinationPath) { Debug.LogWarning("This hasn't been tested yet! I am mac-less :( Please chime in on the github if it works for you."); string cmd = "ln " + string.Format("\"{0}\" \"{1}\"", destinationPath, sourcePath); Debug.Log("Mac hard link " + cmd); ProjectCloner.StartHiddenConsoleProcess("/bin/bash", cmd); }
/// <summary> /// Copies directory located at sourcePath to destinationPath. Displays a progress bar. /// </summary> /// <param name="source">Directory to be copied.</param> /// <param name="destination">Destination directory (created automatically if needed).</param> /// <param name="progressBarPrefix">Optional string added to the beginning of the progress bar window header.</param> public static void CopyDirectoryWithProgressBar(string sourcePath, string destinationPath, string progressBarPrefix = "") { var source = new DirectoryInfo(sourcePath); var destination = new DirectoryInfo(destinationPath); long totalBytes = 0; long copiedBytes = 0; ProjectCloner.CopyDirectoryWithProgressBarRecursive(source, destination, ref totalBytes, ref copiedBytes, progressBarPrefix); EditorUtility.ClearProgressBar(); }
/// <summary> /// Copies the full contents of the unity library. We want to do this to avoid the lengthy reserialization of the whole project when it opens up the clone. /// </summary> /// <param name="sourceProject"></param> /// <param name="destinationProject"></param> public static void CopyLibraryFolder(Project sourceProject, Project destinationProject) { if (Directory.Exists(destinationProject.libraryPath)) { Debug.LogWarning("Library copy: destination path already exists! "); return; } Debug.Log("Library copy: " + destinationProject.libraryPath); ProjectCloner.CopyDirectoryWithProgressBar(sourceProject.libraryPath, destinationProject.libraryPath, "Cloning project '" + sourceProject.name + "'. "); }
/// <summary> /// Copies the full contents of the unity library. We want to do this to avoid the lengthy reserialization of the whole project when it opens up the clone. /// </summary> /// <remarks> /// Excludes PackageCache directory, as it can cause long path exceptions on Windows if packages with long names are present. /// </remarks> /// <param name="sourceProject"></param> /// <param name="destinationProject"></param> public static void CopyLibraryFolder(Project sourceProject, Project destinationProject) { if (Directory.Exists(destinationProject.libraryPath)) { Debug.LogWarning("Will not copy Library folder: destination path already exists."); return; } Debug.Log($"Copying Library folder '{destinationProject.libraryPath}'..."); ProjectCloner.CopyDirectoryWithProgressBar(sourceProject.libraryPath, destinationProject.libraryPath, $"Cloning project '{sourceProject.name}'. "); Debug.Log("Copied Library folder."); }
/// <summary> /// Creates clone from the project currently open in Unity Editor. /// </summary> /// <returns></returns> public static Project CreateCloneFromCurrent() { if (IsClone()) { Debug.LogError("This project is already a clone. Cannot clone it."); return(null); } string currentProjectPath = ProjectCloner.GetCurrentProjectPath(); return(ProjectCloner.CreateCloneFromPath(currentProjectPath)); }
/// <summary> /// Returns all clone projects path. /// </summary> /// <returns></returns> public static List <string> GetCloneProjectsPath() { List <string> projectsPath = new List <string>(); for (int i = 0; i < MaxCloneProjectCount; i++) { string originalProjectPath = ProjectCloner.GetCurrentProject().projectPath; string cloneProjectPath = originalProjectPath + ProjectCloner.CloneNameSuffix + "_" + i; if (Directory.Exists(cloneProjectPath)) { projectsPath.Add(cloneProjectPath); } } return(projectsPath); }
/// <summary> /// Deletes the clone of the currently open project, if such exists. /// </summary> public static void DeleteClone() { /// Clone won't be able to delete itself. if (ProjectCloner.IsClone()) { return; } string cloneProjectPath = ProjectCloner.GetCloneProjectPath(); ///Extra precautions. if (cloneProjectPath == string.Empty) { return; } if (cloneProjectPath == ProjectCloner.GetOriginalProjectPath()) { return; } if (!cloneProjectPath.EndsWith(ProjectCloner.CloneNameSuffix)) { return; } //Check what OS is switch (Application.platform) { case (RuntimePlatform.WindowsEditor): string args = "/c " + @"rmdir /s/q " + string.Format("\"{0}\"", cloneProjectPath); StartHiddenConsoleProcess("cmd.exe", args); Debug.Log("Clone deleted"); break; case (RuntimePlatform.OSXEditor): throw new System.NotImplementedException("No Mac function implement yet :("); break; case (RuntimePlatform.LinuxEditor): throw new System.NotImplementedException("No linux support yet :("); break; default: Debug.LogWarning("Not in a known editor. Where are you!?"); break; } }
/// <summary> /// Deletes the clone of the currently open project, if such exists. /// </summary> public static void DeleteClone() { /// Clone won't be able to delete itself. if (ProjectCloner.IsClone()) { return; } string cloneProjectPath = ProjectCloner.GetCloneProjectPath(); ///Extra precautions. if (cloneProjectPath == string.Empty) { return; } if (cloneProjectPath == ProjectCloner.GetOriginalProjectPath()) { return; } if (cloneProjectPath.EndsWith(ProjectCloner.CloneNameSuffix)) { return; } /// Delete the clone project folder. throw new System.NotImplementedException(); // TODO: implement proper project deletion; // appears that using FileUtil.DeleteFileOrDirectory(...) on symlinks affects the contents of linked folders // (because this script self-deleted itself and half of the Assets folder when I tested it :D) // there must be another, safe method to delete the clone folder and symlinks without touching the original { /* * EditorUtility.DisplayProgressBar("Deleting clone...", "Deleting '" + ProjectCloner.GetCloneProjectPath() + "'", 0f); * try * { * FileUtil.DeleteFileOrDirectory(cloneProjectPath); * } * catch (IOException) * { * EditorUtility.DisplayDialog("Could not delete clone", "'" + ProjectCloner.GetCurrentProject().name + "_clone' may be currently open in another unity Editor. Please close it and try again.", "OK"); * } * EditorUtility.ClearProgressBar(); */ } }
public static void initGUI() { string projectPath = ProjectCloner.FindCurrentProjectPath(); Project currentProject = new Project(projectPath); Project nextProject = new Project(projectPath + "_clone"); Debug.Log("Start project:\n" + currentProject); Debug.Log("Clone project:\n" + nextProject); ProjectCloner.CreateProjectFolder(nextProject); ProjectCloner.CopyLibrary(currentProject, nextProject); ProjectCloner.linkFolders(currentProject.assetPath, nextProject.assetPath); ProjectCloner.linkFolders(currentProject.projectSettingsPath, nextProject.projectSettingsPath); ProjectCloner.linkFolders(currentProject.packagesPath, nextProject.packagesPath); ProjectCloner.linkFolders(currentProject.autoBuildPath, nextProject.autoBuildPath); }
/// <summary> /// Opens a project located at the given path (if one exists). /// </summary> /// <param name="projectPath"></param> public static void OpenProject(string projectPath) { if (!Directory.Exists(projectPath)) { Debug.LogError("Cannot open the project - provided folder (" + projectPath + ") does not exist."); return; } if (projectPath == ProjectCloner.GetCurrentProjectPath()) { Debug.LogError("Cannot open the project - it is already open."); return; } string fileName = EditorApplication.applicationPath; string args = "-projectPath \"" + projectPath + "\""; Debug.Log("Opening project \"" + fileName + " " + args + "\""); ProjectCloner.StartHiddenConsoleProcess(fileName, args); }
/// <summary> /// Creates clone of the project located at the given path. /// </summary> /// <param name="sourceProjectPath"></param> /// <returns></returns> public static Project CreateCloneFromPath(string sourceProjectPath) { Project sourceProject = new Project(sourceProjectPath); Project cloneProject = new Project(sourceProjectPath + ProjectCloner.CloneNameSuffix); Debug.Log("Start project name: " + sourceProject); Debug.Log("Clone project name: " + cloneProject); ProjectCloner.CreateProjectFolder(cloneProject); ProjectCloner.CopyLibraryFolder(sourceProject, cloneProject); ProjectCloner.LinkFolders(sourceProject.assetPath, cloneProject.assetPath); ProjectCloner.LinkFolders(sourceProject.projectSettingsPath, cloneProject.projectSettingsPath); ProjectCloner.LinkFolders(sourceProject.packagesPath, cloneProject.packagesPath); ProjectCloner.LinkFolders(sourceProject.autoBuildPath, cloneProject.autoBuildPath); ProjectCloner.RegisterClone(cloneProject); return(cloneProject); }
/// <summary> /// Creates clone of the project located at the given path. /// </summary> /// <param name="sourceProjectPath"></param> /// <returns></returns> public static Project CreateCloneFromPath(string sourceProjectPath) { Project sourceProject = new Project(sourceProjectPath); string cloneProjectPath = null; //Find available clone suffix id for (int i = 0; i < MaxCloneProjectCount; i++) { string originalProjectPath = ProjectCloner.GetCurrentProject().projectPath; string possibleCloneProjectPath = originalProjectPath + ProjectCloner.CloneNameSuffix + "_" + i; if (!Directory.Exists(possibleCloneProjectPath)) { cloneProjectPath = possibleCloneProjectPath; break; } } if (string.IsNullOrEmpty(cloneProjectPath)) { Debug.LogError("The number of cloned projects has reach its limit. Limit: " + MaxCloneProjectCount); return(null); } Project cloneProject = new Project(cloneProjectPath); Debug.Log("Start project name: " + sourceProject); Debug.Log("Clone project name: " + cloneProject); ProjectCloner.CreateProjectFolder(cloneProject); ProjectCloner.CopyLibraryFolder(sourceProject, cloneProject); ProjectCloner.LinkFolders(sourceProject.assetPath, cloneProject.assetPath); ProjectCloner.LinkFolders(sourceProject.projectSettingsPath, cloneProject.projectSettingsPath); ProjectCloner.LinkFolders(sourceProject.packagesPath, cloneProject.packagesPath); ProjectCloner.LinkFolders(sourceProject.autoBuildPath, cloneProject.autoBuildPath); ProjectCloner.RegisterClone(cloneProject); return(cloneProject); }
/// <summary> /// Calculates the size of the given directory. Displays a progress bar. /// </summary> /// <param name="directory">Directory, which size has to be calculated.</param> /// <param name="includeNested">If true, size will include all nested directories.</param> /// <param name="progressBarPrefix">Optional string added to the beginning of the progress bar window header.</param> /// <returns>Size of the directory in bytes.</returns> private static long GetDirectorySize(DirectoryInfo directory, bool includeNested = false, string progressBarPrefix = "") { EditorUtility.DisplayProgressBar(progressBarPrefix + "Calculating size of directories...", "Scanning '" + directory.FullName + "'...", 0f); /// Calculate size of all files in directory. long filesSize = directory.EnumerateFiles().Sum((FileInfo file) => file.Length); /// Calculate size of all nested directories. long directoriesSize = 0; if (includeNested) { IEnumerable <DirectoryInfo> nestedDirectories = directory.EnumerateDirectories(); foreach (DirectoryInfo nestedDir in nestedDirectories) { directoriesSize += ProjectCloner.GetDirectorySize(nestedDir, true, progressBarPrefix); } } return(filesSize + directoriesSize); }
/// <summary> /// Returns the path to the original project. /// If currently open project is the original, returns its own path. /// If the original project folder cannot be found, retuns an empty string. /// </summary> /// <returns></returns> public static string GetOriginalProjectPath() { if (IsClone()) { // If this is a clone... // Original project path can be deduced by removing the suffix from the clone's path. string cloneProjectPath = ProjectCloner.GetCurrentProject().projectPath; string originalProjectPath = cloneProjectPath.Remove(cloneProjectPath.Length - ProjectCloner.CloneNameSuffix.Length); if (Directory.Exists(originalProjectPath)) { return(originalProjectPath); } return(string.Empty); } else { // If this is the original, we return its own path. return(ProjectCloner.GetCurrentProjectPath()); } }
/// <summary> /// Returns the path to the clone project. /// If currently open project is the clone, returns its own path. /// If the clone does not exist yet, retuns an empty string. /// </summary> /// <returns></returns> public static string GetCloneProjectPath() { if (IsClone()) { /// If this is the clone, we return its own path. return(ProjectCloner.GetCurrentProjectPath()); } else { /// If this is the original... /// Clone project path can be deduced by add the suffix to the original's path. string originalProjectPath = ProjectCloner.GetCurrentProject().projectPath; string cloneProjectPath = originalProjectPath + ProjectCloner.CloneNameSuffix; if (Directory.Exists(cloneProjectPath)) { return(cloneProjectPath); } return(string.Empty); } }
private void OnGUI() { GUILayout.Label("Clone settings", EditorStyles.boldLabel); if (isClone) { /// If it is a clone project... string originalProjectPath = ProjectCloner.GetOriginalProjectPath(); if (originalProjectPath == string.Empty) { /// If original project cannot be found, display warning message. string thisProjectName = ProjectCloner.GetCurrentProject().name; string supposedOriginalProjectName = ProjectCloner.GetCurrentProject().name.Replace("_clone", ""); EditorGUILayout.HelpBox( "This project is a clone, but the link to the original seems lost.\nYou have to manually open the original and create a new clone instead of this one.\nThe original project should have a name '" + supposedOriginalProjectName + "', if it was not changed.", MessageType.Warning); } else { /// If original project is present, display some usage info. EditorGUILayout.HelpBox( "This project is a clone of the project '" + Path.GetFileName(originalProjectPath) + "'.\nIf you want to make changes the project files or manage clones, please open the original project through Unity Hub.", MessageType.Info); } } else { /// If it is an original project... if (isCloneCreated) { /// If clone is created, we can either open it or delete it. string cloneProjectPath = ProjectCloner.GetCloneProjectPath(); EditorGUILayout.TextField("Clone project path", cloneProjectPath, EditorStyles.textField); if (GUILayout.Button("Open clone project")) { ProjectCloner.OpenProject(cloneProjectPath); } if (GUILayout.Button("Delete clone")) { bool delete = EditorUtility.DisplayDialog( "Delete the clone?", "Are you sure you want to delete the clone project '" + ProjectCloner.GetCurrentProject().name + "_clone'? If so, you can always create a new clone from ProjectCloner window.", "Delete", "Cancel"); if (delete) { ProjectCloner.DeleteClone(); this.Repaint(); } } } else { /// If no clone created yet, we must create it. EditorGUILayout.HelpBox("No project clones found. Create a new one!", MessageType.Info); if (GUILayout.Button("Create new clone")) { ProjectCloner.CreateCloneFromCurrent(); } } } }
/// <summary> /// Copies directory located at sourcePath to destinationPath. Displays a progress bar. /// Same as the previous method, but uses recursion to copy all nested folders as well. /// </summary> /// <param name="source">Directory to be copied.</param> /// <param name="destination">Destination directory (created automatically if needed).</param> /// <param name="totalBytes">Total bytes to be copied. Calculated automatically, initialize at 0.</param> /// <param name="copiedBytes">To track already copied bytes. Calculated automatically, initialize at 0.</param> /// <param name="progressBarPrefix">Optional string added to the beginning of the progress bar window header.</param> private static void CopyDirectoryWithProgressBarRecursive(DirectoryInfo source, DirectoryInfo destination, ref long totalBytes, ref long copiedBytes, string progressBarPrefix) { // Directory cannot be copied into itself. if (source.FullName.ToLower() == destination.FullName.ToLower()) { Debug.LogError("Cannot copy directory into itself."); return; } // Calculate total bytes, if required. if (totalBytes == 0) { totalBytes = ProjectCloner.GetDirectorySize(source, true, progressBarPrefix); } // Create destination directory, if required. if (!Directory.Exists(destination.FullName)) { Directory.CreateDirectory(destination.FullName); } // Copy all files from the source. foreach (FileInfo file in source.GetFiles()) { var currentFile = file; // Fix which allows to exceed MAX_PATH when working with files in Windows. // Without it, files with full path longer than 259 characters will throw a FileNotFoundException. if (Application.platform == RuntimePlatform.WindowsEditor) { currentFile = new FileInfo($"\\\\?\\{file.FullName}"); } try { currentFile.CopyTo(Path.Combine(destination.ToString(), currentFile.Name), true); } catch (IOException) { // Some files may throw IOException if they are currently open in Unity editor. // Just ignore them in such case. } // Account the copied file size. copiedBytes += currentFile.Length; // Display the progress bar. float progress = (float)copiedBytes / (float)totalBytes; bool cancelCopy = EditorUtility.DisplayCancelableProgressBar( $"{progressBarPrefix}Copying '{source.FullName}' to '{destination.FullName}'...", $"[{(progress * 100f):F2}%] Copying file '{currentFile.Name}'...", progress); if (cancelCopy) { return; } } // Copy all nested directories from the source. foreach (DirectoryInfo sourceNestedDir in source.GetDirectories()) { DirectoryInfo nextDestingationNestedDir = destination.CreateSubdirectory(sourceNestedDir.Name); ProjectCloner.CopyDirectoryWithProgressBarRecursive(sourceNestedDir, nextDestingationNestedDir, ref totalBytes, ref copiedBytes, progressBarPrefix); } }
/// <summary> /// Return a project object that describes all the paths we need to clone it. /// </summary> /// <returns></returns> public static Project GetCurrentProject() { string pathString = ProjectCloner.GetCurrentProjectPath(); return(new Project(pathString)); }
private void OnGUI() { if (isClone) { /// If it is a clone project... string originalProjectPath = ProjectCloner.GetOriginalProjectPath(); if (originalProjectPath == string.Empty) { /// If original project cannot be found, display warning message. string thisProjectName = ProjectCloner.GetCurrentProject().name; string supposedOriginalProjectName = ProjectCloner.GetCurrentProject().name.Replace("_clone", ""); EditorGUILayout.HelpBox( "This project is a clone, but the link to the original seems lost.\nYou have to manually open the original and create a new clone instead of this one.\nThe original project should have a name '" + supposedOriginalProjectName + "', if it was not changed.", MessageType.Warning); } else { /// If original project is present, display some usage info. EditorGUILayout.HelpBox( "This project is a clone of the project '" + Path.GetFileName(originalProjectPath) + "'.\nIf you want to make changes the project files or manage clones, please open the original project through Unity Hub.", MessageType.Info); } } else { /// If it is an original project... if (isCloneCreated) { GUILayout.BeginVertical("HelpBox"); GUILayout.Label("Clones of this Project"); /// If clone(s) is created, we can either open it or delete it. var cloneProjectsPath = ProjectCloner.GetCloneProjectsPath(); for (int i = 0; i < cloneProjectsPath.Count; i++) { GUILayout.BeginVertical("GroupBox"); string cloneProjectPath = cloneProjectsPath[i]; EditorGUILayout.LabelField("Clone " + i); EditorGUILayout.TextField("Clone project path", cloneProjectPath, EditorStyles.textField); if (GUILayout.Button("Open in New Editor")) { ProjectCloner.OpenProject(cloneProjectPath); } GUILayout.BeginHorizontal(); if (GUILayout.Button("Delete")) { bool delete = EditorUtility.DisplayDialog( "Delete the clone?", "Are you sure you want to delete the clone project '" + ProjectCloner.GetCurrentProject().name + "_clone'? If so, you can always create a new clone from ProjectCloner window.", "Delete", "Cancel"); if (delete) { ProjectCloner.DeleteClone(cloneProjectPath); } } //Offer a solution to user in-case they are stuck with deleting project if (GUILayout.Button("?", GUILayout.Width(30))) { var openUrl = EditorUtility.DisplayDialog("Can't delete clone?", "Sometime clone can't be deleted due to it's still being opened by another unity instance running in the background." + "\nYou can read this answer from ServerFault on how to find and kill the process.", "Open Answer"); if (openUrl) { Application.OpenURL("https://serverfault.com/a/537762"); } } GUILayout.EndHorizontal(); GUILayout.EndVertical(); } GUILayout.EndVertical(); //Have difficulty with naming //GUILayout.Label("Other", EditorStyles.boldLabel); if (GUILayout.Button("Add new clone")) { ProjectCloner.CreateCloneFromCurrent(); } } else { /// If no clone created yet, we must create it. EditorGUILayout.HelpBox("No project clones found. Create a new one!", MessageType.Info); if (GUILayout.Button("Create new clone")) { ProjectCloner.CreateCloneFromCurrent(); } } } }