/// <summary> /// Adds files to a project, potentially asking the user whether to move, copy or link the files. /// </summary> public IList<ProjectFile> AddFilesToProject (Project project, FilePath[] files, FilePath[] targetPaths, string buildAction) { Debug.Assert (project != null); Debug.Assert (files != null); Debug.Assert (targetPaths != null); Debug.Assert (files.Length == targetPaths.Length); AddAction action = AddAction.Copy; bool applyToAll = true; bool dialogShown = false; IProgressMonitor monitor = null; if (files.Length > 10) { monitor = new MessageDialogProgressMonitor (true); monitor.BeginTask (GettextCatalog.GetString("Adding files..."), files.Length); } var newFileList = new List<ProjectFile> (); //project.AddFile (string) does linear search for duplicate file, so instead we use this HashSet and //and add the ProjectFiles directly. With large project and many files, this should really help perf. //Also, this is a better check because we handle vpaths and links. //FIXME: it would be really nice if project.Files maintained these hashmaps var vpathsInProject = new HashSet<FilePath> (project.Files.Select (pf => pf.ProjectVirtualPath)); var filesInProject = new Dictionary<FilePath,ProjectFile> (); foreach (var pf in project.Files) filesInProject [pf.FilePath] = pf; using (monitor) { for (int i = 0; i < files.Length; i++) { FilePath file = files[i]; if (monitor != null) { monitor.Log.WriteLine (file); monitor.Step (1); } if (FileService.IsDirectory (file)) { //FIXME: warning about skipping? newFileList.Add (null); continue; } FilePath targetPath = targetPaths[i].CanonicalPath; Debug.Assert (targetPath.IsChildPathOf (project.BaseDirectory)); var vpath = targetPath.ToRelative (project.BaseDirectory); if (vpathsInProject.Contains (vpath)) { if (project.Files.GetFileWithVirtualPath (vpath).FilePath != file) MessageService.ShowWarning (GettextCatalog.GetString ( "There is a already a file or link in the project with the name '{0}'", vpath)); continue; } string fileBuildAction = buildAction; if (string.IsNullOrEmpty (buildAction)) fileBuildAction = project.GetDefaultBuildAction (targetPath); //files in the target directory get added directly in their current location without moving/copying if (file.CanonicalPath == targetPath) { AddFileToFolder (newFileList, vpathsInProject, filesInProject, file, fileBuildAction); continue; } //for files outside the project directory, we ask the user whether to move, copy or link AddExternalFileDialog addExternalDialog = null; if (!dialogShown || !applyToAll) { addExternalDialog = new AddExternalFileDialog (file); if (files.Length > 1) { addExternalDialog.ApplyToAll = applyToAll; addExternalDialog.ShowApplyAll = true; } if (file.IsChildPathOf (targetPath.ParentDirectory)) addExternalDialog.ShowKeepOption (file.ParentDirectory.ToRelative (targetPath.ParentDirectory)); else { if (action == AddAction.Keep) action = AddAction.Copy; addExternalDialog.SelectedAction = action; } } try { if (!dialogShown || !applyToAll) { if (MessageService.RunCustomDialog (addExternalDialog) == (int) Gtk.ResponseType.Cancel) { project.Files.AddRange (newFileList.Where (f => f != null)); return newFileList; } action = addExternalDialog.SelectedAction; applyToAll = addExternalDialog.ApplyToAll; dialogShown = true; } if (action == AddAction.Keep) { AddFileToFolder (newFileList, vpathsInProject, filesInProject, file, fileBuildAction); continue; } if (action == AddAction.Link) { ProjectFile pf = new ProjectFile (file, fileBuildAction) { Link = vpath }; vpathsInProject.Add (pf.ProjectVirtualPath); filesInProject [pf.FilePath] = pf; newFileList.Add (pf); continue; } try { if (!Directory.Exists (targetPath.ParentDirectory)) FileService.CreateDirectory (targetPath.ParentDirectory); if (MoveCopyFile (file, targetPath, action == AddAction.Move)) { var pf = new ProjectFile (targetPath, fileBuildAction); vpathsInProject.Add (pf.ProjectVirtualPath); filesInProject [pf.FilePath] = pf; newFileList.Add (pf); } else { newFileList.Add (null); } } catch (Exception ex) { MessageService.ShowException (ex, GettextCatalog.GetString ( "An error occurred while attempt to move/copy that file. Please check your permissions.")); newFileList.Add (null); } } finally { if (addExternalDialog != null) addExternalDialog.Destroy (); } } } project.Files.AddRange (newFileList.Where (f => f != null)); return newFileList; }
/// <summary> /// Adds files to a project, potentially asking the user whether to move, copy or link the files. /// </summary> public IList<ProjectFile> AddFilesToProject (Project project, FilePath[] files, FilePath[] targetPaths, string buildAction) { Debug.Assert (project != null); Debug.Assert (files != null); Debug.Assert (targetPaths != null); Debug.Assert (files.Length == targetPaths.Length); AddAction action = AddAction.Copy; bool applyToAll = true; bool dialogShown = false; bool supportsLinking = !(project is MonoDevelop.Projects.SharedAssetsProjects.SharedAssetsProject); var confirmReplaceFileMessage = new QuestionMessage (); if (files.Length > 1) { confirmReplaceFileMessage.AllowApplyToAll = true; confirmReplaceFileMessage.Buttons.Add (new AlertButton (GettextCatalog.GetString ("Skip"))); } confirmReplaceFileMessage.Buttons.Add (AlertButton.Cancel); confirmReplaceFileMessage.Buttons.Add (AlertButton.OverwriteFile); confirmReplaceFileMessage.DefaultButton = confirmReplaceFileMessage.Buttons.Count - 1; ProgressMonitor monitor = null; if (files.Length > 10) { monitor = new MessageDialogProgressMonitor (true); monitor.BeginTask (GettextCatalog.GetString("Adding files..."), files.Length); } var newFileList = new List<ProjectFile> (); //project.AddFile (string) does linear search for duplicate file, so instead we use this HashSet and //and add the ProjectFiles directly. With large project and many files, this should really help perf. //Also, this is a better check because we handle vpaths and links. //FIXME: it would be really nice if project.Files maintained these hashmaps var vpathsInProject = new Dictionary<FilePath, ProjectFile> (); var filesInProject = new Dictionary<FilePath,ProjectFile> (); foreach (var pf in project.Files) { filesInProject [pf.FilePath] = pf; vpathsInProject [pf.ProjectVirtualPath] = pf; } using (monitor) { for (int i = 0; i < files.Length; i++) { FilePath file = files[i]; if (monitor != null) { monitor.Log.WriteLine (file); monitor.Step (1); } if (FileService.IsDirectory (file)) { //FIXME: warning about skipping? newFileList.Add (null); continue; } FilePath targetPath = targetPaths[i].CanonicalPath; Debug.Assert (targetPath.IsChildPathOf (project.BaseDirectory)); ProjectFile vfile; var vpath = targetPath.ToRelative (project.BaseDirectory); if (vpathsInProject.TryGetValue (vpath, out vfile)) { if (vfile.IsLink) { MessageService.ShowWarning (GettextCatalog.GetString ( "There is already a link in the project with the name '{0}'", vpath)); continue; } else if (vfile.FilePath == file) { // File already exists in project. continue; } } string fileBuildAction = buildAction; if (string.IsNullOrEmpty (buildAction)) fileBuildAction = project.GetDefaultBuildAction (targetPath); //files in the target directory get added directly in their current location without moving/copying if (file.CanonicalPath == targetPath) { if (vfile != null) ShowFileExistsInProjectMessage (vpath); else AddFileToFolder (newFileList, vpathsInProject, filesInProject, file, fileBuildAction); continue; } //for files outside the project directory, we ask the user whether to move, copy or link AddExternalFileDialog addExternalDialog = null; if (!dialogShown || !applyToAll) { addExternalDialog = new AddExternalFileDialog (file); if (!supportsLinking) addExternalDialog.DisableLinkOption (); if (files.Length > 1) { addExternalDialog.ApplyToAll = applyToAll; addExternalDialog.ShowApplyAll = true; } if (file.IsChildPathOf (targetPath.ParentDirectory)) addExternalDialog.ShowKeepOption (file.ParentDirectory.ToRelative (targetPath.ParentDirectory)); else { if (action == AddAction.Keep) action = AddAction.Copy; addExternalDialog.SelectedAction = action; } } try { if (!dialogShown || !applyToAll) { int response = MessageService.RunCustomDialog (addExternalDialog); // A dialog emits DeleteEvent rather than Cancel in response to Escape being pressed if (response == (int) Gtk.ResponseType.Cancel || response == (int) Gtk.ResponseType.DeleteEvent) { project.Files.AddRange (newFileList.Where (f => f != null)); return newFileList; } action = addExternalDialog.SelectedAction; applyToAll = addExternalDialog.ApplyToAll; dialogShown = true; } if (action == AddAction.Keep) { if (vfile != null) ShowFileExistsInProjectMessage (vpath); else AddFileToFolder (newFileList, vpathsInProject, filesInProject, file, fileBuildAction); continue; } if (action == AddAction.Link) { if (vfile != null) { ShowFileExistsInProjectMessage (vpath); continue; } ProjectFile pf = new ProjectFile (file, fileBuildAction) { Link = vpath }; vpathsInProject [pf.ProjectVirtualPath] = pf; filesInProject [pf.FilePath] = pf; newFileList.Add (pf); continue; } try { if (!Directory.Exists (targetPath.ParentDirectory)) FileService.CreateDirectory (targetPath.ParentDirectory); bool? result = MoveCopyFile (file, targetPath, action == AddAction.Move, confirmReplaceFileMessage); if (result == true) { if (vfile == null) { var pf = new ProjectFile (targetPath, fileBuildAction); vpathsInProject [pf.ProjectVirtualPath] = pf; filesInProject [pf.FilePath] = pf; newFileList.Add (pf); } } else if (result == null) { project.Files.AddRange (newFileList.Where (f => f != null)); return newFileList; } else { newFileList.Add (null); } } catch (Exception ex) { MessageService.ShowError (GettextCatalog.GetString ( "An error occurred while attempt to move/copy that file. Please check your permissions."), ex); newFileList.Add (null); } } finally { if (addExternalDialog != null) { addExternalDialog.Destroy (); addExternalDialog.Dispose (); } } } } project.Files.AddRange (newFileList.Where (f => f != null)); return newFileList; }