DokanはWindowsでデバイスドライバを作らなくても独自のファイルシステムを提供することができるソフト。そのDokanのユーザモードの部分を.NETで実装しやすくするのがDokan.NET Binding。Dokanの開発はずっと前に終わったと思っていた。その継続としてフォークされたDokanXも更新が止まっていた。
どうもDokanyがまだ健在である。そして,Dokan.NETもこのDokanyを使うように改修が続いている。
だいぶん前の「Dokan .netでファイルシステムを作って遊ぶ - Simple HashtableFS -(しょんぼり技術メモまいにちがしょんぼり)」というページの内容は古いがかなりその構成を維持したまま移行できるみたい(まったく同じとはいかない)。
// LinkedFS.cs using System; using System.Collections.Generic; using System.ComponentModel.Design.Serialization; using System.IO; using System.Linq; using System.Security.AccessControl; using System.Text; using System.Threading.Tasks; using DokanNet; using Linked; namespace Linked { public class LinkedFS : IDokanOperations { LinkedRecord root; #region Constructor /// <summary> /// コンストラクタ /// </summary> /// <param name="root">対象</param> public LinkedFS(ref LinkedRecord root) { this.root = root; } #endregion #region IDokanOperations public void Cleanup(string fileName, IDokanFileInfo info) { } public void CloseFile(string fileName, IDokanFileInfo info) { } public NtStatus CreateFile(string fileName, DokanNet.FileAccess access, FileShare share, FileMode mode, FileOptions options, FileAttributes attributes, IDokanFileInfo info) { if (fileName == "\\") // root { info.IsDirectory = true; return DokanResult.Success; } else { switch (mode) { case FileMode.Open: { string fileName2 = fileName.TrimStart('\\'); return RecursiveSearch(root, fileName2, ref info); } case FileMode.CreateNew: { string fileName2 = fileName.TrimStart('\\'); var fileInfo = new FileInformation(); // dummy fileInfo LinkedRecord result = RecursiveSearchItem(root, fileName2, ref fileInfo, ref info); if (result == null) { string[] data = fileName2.Split('\\'); string fileName3 = ""; for (int i = 0; i < data.Length - 1; i++) { fileName3 += data[i]; if (i < data.Length - 2) { fileName3 += "\\"; } } string fileName4 = data[data.Length - 1]; result = RecursiveSearchItem(root, fileName3, ref fileInfo, ref info); if (fileName3 == string.Empty) result = root; if (result == null) { return DokanResult.Error; } else { var item = new LinkedRecord(); item.IsDirectory = info.IsDirectory ? true : false; if (!item.IsDirectory) item.Contents = new List<byte>(); item.Name = fileName4; result.LinkedItems.Add(item); } return DokanResult.Success; } else { return DokanResult.AlreadyExists; } } default: { string fileName2 = fileName.TrimStart('\\'); return RecursiveSearch(root, fileName2, ref info); } } } // return DokanResult.FileNotFound; } public NtStatus DeleteDirectory(string fileName, IDokanFileInfo info) { return DokanResult.NotImplemented; } public NtStatus DeleteFile(string fileName, IDokanFileInfo info) { return DokanResult.NotImplemented; } public NtStatus FindFiles(string fileName, out IList<FileInformation> files, IDokanFileInfo info) { files = new List<FileInformation>(); if (fileName == "\\") { foreach (var item in root.LinkedItems) { var finfo = new FileInformation { FileName = item.Name, Attributes = item.IsDirectory ? FileAttributes.Directory : FileAttributes.Normal, LastAccessTime = DateTime.Now, LastWriteTime = null, CreationTime = null, Length = item.IsDirectory ? 0 : item.Contents.Count, }; files.Add(finfo); } return DokanResult.Success; } else { string fileName2 = fileName.TrimStart('\\'); var fileInfo = new FileInformation(); // dummy fileInfo LinkedRecord itemRoot = RecursiveSearchItem(root, fileName2, ref fileInfo, ref info); if (itemRoot == null) return DokanResult.Error; foreach (var item in itemRoot.LinkedItems) { var finfo = new FileInformation { FileName = item.Name, Attributes = item.IsDirectory ? FileAttributes.Directory : FileAttributes.Normal, LastAccessTime = DateTime.Now, LastWriteTime = DateTime.Now, CreationTime = null, Length = item.IsDirectory ? 0 : item.Contents.Count, }; files.Add(finfo); } return DokanResult.Success; } } public NtStatus GetFileInformation( string fileName, out FileInformation fileInfo, IDokanFileInfo info) { fileInfo = new FileInformation { FileName = fileName }; if (fileName == "\\") { fileInfo.Attributes = FileAttributes.Directory; fileInfo.LastAccessTime = DateTime.Now; fileInfo.LastWriteTime = null; fileInfo.CreationTime = null; return DokanResult.Success; } string fileName2 = fileName.TrimStart('\\'); LinkedRecord item = RecursiveSearchItem(root, fileName2, ref fileInfo, ref info); if (item == null) return DokanResult.Error; fileInfo.Attributes = item.IsDirectory ? FileAttributes.Directory : FileAttributes.Normal; fileInfo.LastAccessTime = DateTime.Now; fileInfo.LastWriteTime = DateTime.Now; fileInfo.CreationTime = DateTime.Now; fileInfo.FileName = item.Name; if (item.Contents != null) fileInfo.Length = item.Contents.Count; return DokanResult.Success; } public NtStatus ReadFile( string fileName, byte[] buffer, out int readBytes, long offset, IDokanFileInfo info) { readBytes = 0; string fileName2 = fileName.TrimStart('\\'); var fileInfo = new FileInformation(); // dummy fileInfo LinkedRecord item = RecursiveSearchItem(root, fileName2, ref fileInfo, ref info); if (item == null) return DokanResult.FileNotFound; if (item.IsDirectory) { return DokanResult.NotADirectory; } { try { // バイナリ列 byte[] raw = item.Contents.ToArray(); if (raw == null) { return DokanResult.Success; } // 超過チェック if (offset >= raw.Length) { return DokanResult.Error; } // offsetから読み込むバイト数 long read_try_byte = (raw.Length - offset < buffer.Length) ? raw.Length - offset : buffer.Length; // コピー int i = 0; for (i = 0; i < read_try_byte; i++) { buffer[i] = raw[offset + i]; } readBytes = (int)i; return DokanResult.Success; } catch (Exception) { return DokanResult.Error; } } } public NtStatus SetEndOfFile(string fileName, long length, IDokanFileInfo info) { return DokanResult.Error; } public NtStatus SetAllocationSize(string fileName, long length, IDokanFileInfo info) { return DokanResult.Success; } public NtStatus SetFileAttributes( string filename, FileAttributes attr, IDokanFileInfo info) { return DokanResult.Error; } public NtStatus SetFileTime( string filename, DateTime? ctime, DateTime? atime, DateTime? mtime, IDokanFileInfo info) { return DokanResult.Error; } public NtStatus UnlockFile(string filename, long offset, long length, IDokanFileInfo info) { return DokanResult.Success; } public NtStatus Mounted(IDokanFileInfo info) { return DokanResult.Success; } public NtStatus LockFile( string filename, long offset, long length, IDokanFileInfo info) { return DokanResult.Success; } public NtStatus MoveFile( string fileName, string newName, bool replace, IDokanFileInfo info) { var lid_s = fileName.LastIndexOf('\\'); var lid_d = newName.LastIndexOf('\\'); if (fileName.Substring(0, lid_s) == newName.Substring(0, lid_d)) { string fileName2 = fileName.TrimStart('\\'); var fileInfo = new FileInformation(); // dummy fileInfo LinkedRecord item = RecursiveSearchItem(root, fileName2, ref fileInfo, ref info); if (item == null) return DokanResult.FileNotFound; string newFileName2 = newName.TrimStart('\\'); var newFileInfo = new FileInformation(); // dummy fileInfo LinkedRecord itemNew = RecursiveSearchItem(root, newFileName2, ref newFileInfo, ref info); if (itemNew != null) return DokanResult.AlreadyExists; string[] data = newName.Split('\\'); item.Name = data[data.Length - 1]; return DokanResult.Success; } else { // 移動元 string fileName2 = fileName.TrimStart('\\'); var fileInfo = new FileInformation(); // dummy fileInfo LinkedRecord item = RecursiveSearchItem(root, fileName2, ref fileInfo, ref info); if (item == null) return DokanResult.FileNotFound; // 移動元の親 string fileName2Parent = fileName.Substring(0, lid_s).TrimStart('\\'); var fileInfoParent = new FileInformation(); // dummy fileInfo LinkedRecord itemP = RecursiveSearchItem(root, fileName2Parent, ref fileInfoParent, ref info); if (itemP == null) { if (fileName2Parent == string.Empty) { itemP = root; } else return DokanResult.FileNotFound; } // 移動先 string newFileName2 = newName.TrimStart('\\'); var newFileInfo = new FileInformation(); // dummy fileInfo LinkedRecord itemNew = RecursiveSearchItem(root, newFileName2, ref newFileInfo, ref info); if (itemNew != null) return DokanResult.AlreadyExists; // 移動先の親 string newFileName2Parent = newName.Substring(0, lid_d).TrimStart('\\'); var newFileInfoParent = new FileInformation(); // dummy fileInfo LinkedRecord itemNewP = RecursiveSearchItem(root, newFileName2Parent, ref newFileInfoParent, ref info); if (itemNewP == null) { if (newFileName2Parent == string.Empty) { itemNewP = root; } else return DokanResult.FileNotFound; } string[] data = newName.Split('\\'); item.Name = data[data.Length - 1]; itemNewP.LinkedItems.Add(item); itemP.LinkedItems.Remove(item); return DokanResult.Success; } } public NtStatus Unmounted(IDokanFileInfo info) { return DokanResult.Success; } public NtStatus GetDiskFreeSpace( out long freeBytesAvailable, out long totalBytes, out long totalFreeBytes, IDokanFileInfo info) { long currentSet = GC.GetTotalMemory(true); freeBytesAvailable = 1024 * 100 - currentSet;// 1024 * 10;// 512; totalBytes = 1024 * 100; totalFreeBytes = 1024 * 100 - currentSet; return DokanResult.Success; } public NtStatus WriteFile( string fileName, byte[] buffer, out int writtenBytes, long offset, IDokanFileInfo info) { writtenBytes = 0; var append = offset == -1; string fileName2 = fileName.TrimStart('\\'); var fileInfo = new FileInformation(); // dummy fileInfo LinkedRecord item = RecursiveSearchItem(root, fileName2, ref fileInfo, ref info); if (item == null) return DokanResult.FileNotFound; if (info.Context == null) { if (!append) { item.Contents.Clear(); item.Contents.AddRange(buffer); writtenBytes = buffer.Length; } } else { return DokanResult.Error; } return DokanResult.Success; } public NtStatus GetVolumeInformation(out string volumeLabel, out FileSystemFeatures features, out string fileSystemName, out uint maximumComponentLength, IDokanFileInfo info) { volumeLabel = "LinkedFS"; features = FileSystemFeatures.None; fileSystemName = "MASTER_DB"; maximumComponentLength = 256; return DokanResult.Success; } public NtStatus GetFileSecurity(string fileName, out FileSystemSecurity security, AccessControlSections sections, IDokanFileInfo info) { security = null; return DokanResult.Error; } public NtStatus SetFileSecurity(string fileName, FileSystemSecurity security, AccessControlSections sections, IDokanFileInfo info) { return DokanResult.Error; } public NtStatus EnumerateNamedStreams(string fileName, IntPtr enumContext, out string streamName, out long streamSize, IDokanFileInfo info) { streamName = string.Empty; streamSize = 0; return DokanResult.NotImplemented; } public NtStatus FlushFileBuffers( string filename, IDokanFileInfo info) { return DokanResult.Success; //return DokanResult.Error; } public NtStatus FindStreams(string fileName, out IList<FileInformation> streams, IDokanFileInfo info) { streams = new FileInformation[0]; return DokanResult.NotImplemented; } public NtStatus FindFilesWithPattern(string fileName, string searchPattern, out IList<FileInformation> files, IDokanFileInfo info) { files = new FileInformation[0]; return DokanResult.NotImplemented; } #endregion private NtStatus RecursiveSearch(LinkedRecord root, string fileName, ref IDokanFileInfo info) { string[] data = fileName.Split('\\'); for (Int32 i = 0; i < root.LinkedItems.Count; i++) { if (root.LinkedItems[i].Name == data[0]) { if (root.LinkedItems[i].IsDirectory == true) { var id = fileName.IndexOf('\\'); if (id < 0) { return DokanResult.Success; } else { string fileName_ = fileName.Substring(id).TrimStart('\\'); if (fileName_ == String.Empty) { return DokanResult.Success; } else { return RecursiveSearch(root.LinkedItems[i], fileName_, ref info); } } } else // ファイルである { if (data.Length > 1) // 要求がディレクトリである { return DokanResult.NotADirectory; } else { return DokanResult.Success; } } } } return DokanResult.FileNotFound; } private LinkedRecord RecursiveSearchItem(LinkedRecord root, string fileName, ref FileInformation fileInfo, ref IDokanFileInfo info) { string[] data = fileName.Split('\\'); for (Int32 i = 0; i < root.LinkedItems.Count; i++) { if (root.LinkedItems[i].Name == data[0]) { if (root.LinkedItems[i].IsDirectory == true) { var id = fileName.IndexOf('\\'); if (id < 0) { return root.LinkedItems[i]; } else { string fileName_ = fileName.Substring(id).TrimStart('\\'); if (fileName_ == String.Empty) { return root.LinkedItems[i]; } else { return RecursiveSearchItem(root.LinkedItems[i], fileName_, ref fileInfo, ref info); } } } else // ファイルである { if (data.Length > 1) // 要求がディレクトリである { return null; } else { fileInfo.Attributes = FileAttributes.Normal; fileInfo.CreationTime = System.DateTime.Now; fileInfo.LastAccessTime = System.DateTime.Now; fileInfo.LastWriteTime = System.DateTime.Now; fileInfo.FileName = root.LinkedItems[i].Name; // 長さ //byte[] raw = root.LinkedItems[i].Contents.ToArray(); fileInfo.Length = root.LinkedItems[i].Contents.Count;// raw.Length; return root.LinkedItems[i]; } } } } return null; } } }
// LinkedRecord.cs using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; namespace Linked { public class LinkedRecord { private String name = "\\"; public String Name { get { return name; } set { name = value; } } public Boolean IsDirectory { get; set; } = true; public List<LinkedRecord> LinkedItems { get; set; } = new List<LinkedRecord>(); public List<Byte> Contents { get; set; } = null;//new List<byte>(); } }
//Program.cs using DokanNet; using DokanNet.Logging; using Linked; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LinkedB { class Program { static void Main(string[] args) { LinkedRecord root = new LinkedRecord(); LinkedFS linkedFS = new LinkedFS(ref root); var dir = new LinkedRecord(); dir.Name = "明治"; dir.IsDirectory = true; root.LinkedItems.Add(dir); var file = new LinkedRecord(); file.Name = "大正.txt"; file.IsDirectory = false; file.Contents = Encoding.UTF8.GetBytes("昭和平成").ToList<Byte>(); dir.LinkedItems.Add(file); linkedFS.Mount("X" + ":\\", /*DokanOptions.DebugMode | */DokanOptions.RemovableDrive, new NullLogger()); } } }