// // FileSystemObjectModel.cs - provides an ObjectModel for manipulating files or block devices which contain other files, such as archives, file systems, and disk images // // Author: // Michael Becker // // Copyright (c) 2011-2020 Mike Becker's Software // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . using System; using System.Collections.Generic; using UniversalEditor.Accessors; namespace UniversalEditor.ObjectModels.FileSystem { /// /// Provides an for manipulating files which contain other files, such as archives, file systems, and disk images. /// public class FileSystemObjectModel : ObjectModel, IFileSystemContainer { private ObjectModelReference _omr = null; protected override ObjectModelReference MakeReferenceInternal() { if (_omr == null) { _omr = new ObjectModelReference(GetType(), new Guid("{A23026E9-DFE1-4090-AF35-8B916D3F1FCD}")); _omr.Title = "File system/archive"; _omr.Path = new string[] { "General", "File system/archive" }; } return _omr; } public override void Clear() { Files.Clear(); Folders.Clear(); ID = Guid.Empty; Title = String.Empty; mvarPathSeparators = new string[] { System.IO.Path.DirectorySeparatorChar.ToString(), System.IO.Path.AltDirectorySeparatorChar.ToString() }; } public override void CopyTo(ObjectModel where) { FileSystemObjectModel clone = (where as FileSystemObjectModel); clone.ID = ID; for (int i = 0; i < Files.Count; i++) { File file = Files[i]; clone.Files.Add(file.Clone() as File); } for (int i = 0; i < Folders.Count; i++) { Folder folder = Folders[i]; clone.Folders.Add(folder.Clone() as Folder); } } public FileSystemObjectModel FileSystem { get { return this; } } public static FileSystemObjectModel FromFiles(string[] fileNames) { // TODO: This doesn't work because GetAvailableObjectModel returns an // ObjectModel but automatically closes the file after reading... deferred // FileSystemObjectModels associated files need to remain open in order to // read the file data. Consider using a Document-based approach which provides // more control over closing files as needed? FileSystemObjectModel fsom = new FileSystemObjectModel(); foreach (string fileName in fileNames) { FileAccessor accessor = new FileAccessor(fileName); FileSystemObjectModel fsom1 = UniversalEditor.Common.Reflection.GetAvailableObjectModel(accessor); if (fsom1 == null) continue; fsom1.CopyTo(fsom); } return fsom; } public static FileSystemObjectModel FromDirectory(string path, string searchPattern = "*.*", System.IO.SearchOption searchOption = System.IO.SearchOption.TopDirectoryOnly) { string[] folders = System.IO.Directory.GetDirectories(path); string[] files = System.IO.Directory.GetFiles(path); FileSystemObjectModel fsom = new FileSystemObjectModel(); foreach (string folder in folders) { string title = System.IO.Path.GetFileName(folder); fsom.Folders.Add(title); } foreach (string fileName in files) { string title = System.IO.Path.GetFileName(fileName); fsom.Files.Add(title, fileName); } return fsom; // string[] files = System.IO.Directory.GetFiles(path, searchPattern, searchOption); // return FromFiles(files); } public FileSystemObjectModel() { Files = new File.FileCollection(this); Folders = new Folder.FolderCollection(this); } public File.FileCollection Files { get; private set; } = null; public Folder.FolderCollection Folders { get; private set; } = null; /// /// The unique ID associated with this file system. Not supported by all s. /// public Guid ID { get; set; } = Guid.Empty; /// /// The title associated with this file system. Not supported by all s. /// public string Title { get; set; } = String.Empty; private string[] mvarPathSeparators = new string[] { "/", "\\" }; // System.IO.Path.DirectorySeparatorChar.ToString(), System.IO.Path.AltDirectorySeparatorChar.ToString() }; public string[] PathSeparators { get { return mvarPathSeparators; } set { mvarPathSeparators = value; } } public bool ContainsFile(string path) { return (FindFile(path) != null); } public File FindFile(string path, MBS.Framework.IO.CaseSensitiveHandling caseSensitiveHandling = MBS.Framework.IO.CaseSensitiveHandling.System) { string[] pathParts = path.Split(PathSeparators); if (pathParts.Length == 1) { File file = Files[pathParts[0], caseSensitiveHandling]; if (file != null) return file; } else { Folder parentFolder = Folders[pathParts[0], caseSensitiveHandling]; if (parentFolder == null) return null; for (int i = 1; i < pathParts.Length; i++) { if (i < pathParts.Length - 1) { parentFolder = parentFolder.Folders[pathParts[i], caseSensitiveHandling]; if (parentFolder == null) return null; } else { return parentFolder.Files[pathParts[i], caseSensitiveHandling]; } } } return null; } public Folder FindFolder(string name) { string[] path = name.Split(new char[] { '/' }); Folder parent = null; for (int i = 0; i < path.Length - 1; i++) { if (parent == null) { parent = Folders[path[i]]; } else { parent = parent.Folders[path[i]]; } } if (parent == null) { return Folders[path[path.Length - 1]]; } else { return parent.Folders[path[path.Length - 1]]; } } public object FindObject(string name) { string[] path = name.Split(new char[] { '/' }); Folder parent = null; for (int i = 0; i < path.Length - 1; i++) { if (parent == null) { parent = Folders[path[i]]; } else { parent = parent.Folders[path[i]]; } } if (parent == null) { File file = Files[path[path.Length - 1]]; Folder folder = Folders[path[path.Length - 1]]; if (folder == null) return file; return folder; } else { File file = parent.Files[path[path.Length - 1]]; Folder folder = parent.Folders[path[path.Length - 1]]; if (folder == null) return file; return folder; } } public Folder AddFolder(string name) { string[] path = name.Split(mvarPathSeparators, StringSplitOptions.None); Folder parent = null; for (int i = 0; i < path.Length - 1; i++) { if (parent == null) { parent = Folders[path[i]]; } else { parent = parent.Folders[path[i]]; } if (parent == null) throw new System.IO.DirectoryNotFoundException(); } if (parent == null) { return Folders.Add(path[path.Length - 1]); } return parent.Folders.Add(path[path.Length - 1]); } /// /// Adds a to this , building the parent directory hierarchy as appropriate. /// /// The full path of the to create, including any parent directories. /// /// public File AddFile(string name, byte[] fileData = null) { if (name == null) name = String.Empty; string[] path = name.Split(mvarPathSeparators, StringSplitOptions.None); Folder parent = null; for (int i = 0; i < path.Length - 1; i++) { if (parent == null) { if (Folders.Contains(path[i])) { parent = Folders[path[i]]; } else { parent = Folders.Add(path[i]); } } else { if (parent.Folders.Contains(path[i])) { parent = parent.Folders[path[i]]; } else { parent = parent.Folders.Add(path[i]); } } if (parent == null) { throw new System.IO.DirectoryNotFoundException(); } } File file = new File(); file.Name = path[path.Length - 1]; if (fileData != null) { file.SetData(fileData); } if (parent == null) { Files.Add(file); } else { parent.Files.Add(file); } return file; } /// /// Gets all files in all folders of the with file names that /// match the , and assigns the file names separated by the /// . /// /// The string by which to filter the retrieved file names. /// The string by which to separate directory and file names. /// public File[] GetFiles(string searchPattern = null, string pathSeparator = null) { if (pathSeparator == null) pathSeparator = "/"; List files = new List(); for (int i = 0; i < Files.Count; i++) { File file = Files[i]; if (searchPattern != null && !file.Name.Match(searchPattern)) continue; files.Add(file); } for (int i = 0; i < Folders.Count; i++) { Folder folder = Folders[i]; GetAllFilesRecursively(folder, ref files, folder.Name, pathSeparator, searchPattern); } return files.ToArray(); } /// /// Gets all files in all folders of the , and assigns the file names /// separated by the default path separator. /// /// public File[] GetAllFiles(string pathSeparator = null) { if (pathSeparator == null) pathSeparator = "/"; List files = new List(); for (int i = 0; i < Files.Count; i++) { File file = Files[i]; files.Add(file); } for (int i = 0; i < Folders.Count; i++) { Folder folder = Folders[i]; GetAllFilesRecursively(folder, ref files, folder.Name, pathSeparator); } return files.ToArray(); } private void GetAllFilesRecursively(Folder folder, ref List files, string parentPath, string pathSeparator, string searchPattern = null) { for (int i = 0; i < folder.Files.Count; i++) { File file = folder.Files[i]; if (searchPattern != null && !file.Name.Match(searchPattern)) continue; File file2 = (file.Clone() as File); file2.Name = parentPath + pathSeparator + file.Name; files.Add(file2); } for (int i = 0; i < folder.Folders.Count; i++) { Folder folder1 = folder.Folders[i]; GetAllFilesRecursively(folder1, ref files, parentPath + pathSeparator + folder1.Name, pathSeparator, searchPattern); } } public IFileSystemObject[] GetAllObjects(string pathSeparator = null, System.IO.SearchOption option = System.IO.SearchOption.AllDirectories, IFileSystemObjectType objectTypes = IFileSystemObjectType.All) { if (pathSeparator == null) pathSeparator = "/"; List files = new List(); if ((objectTypes & IFileSystemObjectType.File) == IFileSystemObjectType.File) { for (int i = 0; i < Files.Count; i++) { File file = Files[i]; files.Add(file); } } for (int i = 0; i < Folders.Count; i++) { Folder folder = Folders[i]; if ((objectTypes & IFileSystemObjectType.Folder) == IFileSystemObjectType.Folder) { files.Add(folder); } if (option == System.IO.SearchOption.AllDirectories) GetAllObjectsRecursively(folder, ref files, folder.Name, pathSeparator, null, objectTypes); } return files.ToArray(); } private void GetAllObjectsRecursively(Folder folder, ref List files, string parentPath, string pathSeparator, string searchPattern = null, IFileSystemObjectType objectTypes = IFileSystemObjectType.All) { if ((objectTypes & IFileSystemObjectType.File) == IFileSystemObjectType.File) { for (int i = 0; i < folder.Files.Count; i++) { File file = folder.Files[i]; if (searchPattern != null && !file.Name.Match(searchPattern)) continue; File file2 = (file.Clone() as File); file2.Name = parentPath + pathSeparator + file.Name; files.Add(file2); } } for (int i = 0; i < folder.Folders.Count; i++) { Folder folder1 = folder.Folders[i]; if ((objectTypes & IFileSystemObjectType.Folder) == IFileSystemObjectType.Folder) { files.Add(folder1); } GetAllObjectsRecursively(folder1, ref files, parentPath + pathSeparator + folder1.Name, pathSeparator, searchPattern); } } /// /// Gets the next available "New Folder" name for the given . /// /// A string "New Folder" if there are no other "New Folder"s in the given ; otherwise, a string "New Folder (n)" where N is the number of "New Folder"s in the given plus one. public static string GetNewFolderName(IFileSystemContainer container) { int count = 0; foreach (Folder f in container.Folders) { if (f.Name.StartsWith("New Folder (") && f.Name.EndsWith(")")) { string strIntPart = f.Name.Substring("New Folder (".Length, f.Name.Length - "New Folder (".Length - 1); int intPart = 0; if (Int32.TryParse(strIntPart, out intPart)) { if (intPart > count) count = intPart; } } else if (f.Name == "New Folder") { count++; } } if (count == 0) return "New Folder"; return "New Folder (" + (count + 1).ToString() + ")"; } /// /// Gets the next available "New Folder" name for this . /// /// A string "New Folder" if there are no other "New Folder"s in this ; otherwise, a string "New Folder (n)" where N is the number of "New Folder"s in this plus one. public string GetNewFolderName() { return FileSystemObjectModel.GetNewFolderName(this); } public FileAdditionalDetail.FileAdditionalDetailCollection AdditionalDetails { get; } = new FileAdditionalDetail.FileAdditionalDetailCollection(); } }