using System.Collections.Concurrent; namespace DevDrive; /// /// Manages the in-memory file system storage for the virtual drive. /// public class FloppyStorage { public const long DefaultCapacity = 1_474_560; public const int SectorSize = 512; public const int AllocationUnitSize = 512; private readonly ConcurrentDictionary _files = new(StringComparer.OrdinalIgnoreCase); private readonly object _lock = new(); public long Capacity { get; } public long FreeSpace { get; } public FloppyStorage() : this(DefaultCapacity, DefaultCapacity) { } public FloppyStorage(long totalBytes, long freeBytes) { Capacity = totalBytes; FreeSpace = freeBytes; var root = new FloppyFileNode("\\", true); _files["\\"] = root; } public FloppyFileNode? CreateSymlink(string path, string targetPath, bool isDirectory) { path = NormalizePath(path); if (_files.ContainsKey(path)) { return null; } var parentPath = GetParentPath(path); if (!_files.ContainsKey(parentPath)) { return null; } var node = new FloppyFileNode(path, isDirectory) { SymlinkTarget = targetPath, Attributes = (isDirectory ? FileAttributes.Directory : FileAttributes.Normal) | FileAttributes.ReparsePoint }; _files[path] = node; return node; } public static string NormalizePath(string path) { if (string.IsNullOrEmpty(path)) { return "\\"; } path = path.Replace('/', '\\'); if (!path.StartsWith('\\')) { path = "\\" + path; } while (path.Contains("\\\\")) { path = path.Replace("\\\\", "\\"); } if (path.Length > 1 && path.EndsWith('\\')) { path = path.TrimEnd('\\'); } return path; } public static string GetParentPath(string path) { path = NormalizePath(path); if (path == "\\") { return "\\"; } var lastSlash = path.LastIndexOf('\\'); return lastSlash <= 0 ? "\\" : path[..lastSlash]; } public bool Exists(string path) { path = NormalizePath(path); return _files.ContainsKey(path); } public FloppyFileNode? GetNode(string path) { path = NormalizePath(path); _files.TryGetValue(path, out var node); return node; } public FloppyFileNode? CreateNode(string path, bool isDirectory) { path = NormalizePath(path); if (_files.ContainsKey(path)) { return _files[path]; } var parentPath = GetParentPath(path); if (!_files.ContainsKey(parentPath)) { return null; } var node = new FloppyFileNode(path, isDirectory); _files[path] = node; return node; } public bool DeleteNode(string path) { path = NormalizePath(path); if (path == "\\") { return false; } var node = GetNode(path); if (node == null) { return false; } if (node.IsDirectory) { var children = GetChildren(path); if (children.Any()) { return false; } } return _files.TryRemove(path, out _); } public IEnumerable GetChildren(string path) { path = NormalizePath(path); foreach (var kvp in _files) { var filePath = kvp.Key; if (filePath == path) { continue; } var parentPath = GetParentPath(filePath); if (string.Equals(parentPath, path, StringComparison.OrdinalIgnoreCase)) { yield return kvp.Value; } } } public bool MoveNode(string oldPath, string newPath, bool replace) { oldPath = NormalizePath(oldPath); newPath = NormalizePath(newPath); if (!_files.TryGetValue(oldPath, out var node)) { return false; } if (_files.ContainsKey(newPath)) { if (!replace) { return false; } _files.TryRemove(newPath, out _); } var newParentPath = GetParentPath(newPath); if (!_files.ContainsKey(newParentPath)) { return false; } _files.TryRemove(oldPath, out _); node.FullPath = newPath; node.Name = Path.GetFileName(newPath); node.LastWriteTime = DateTime.Now; _files[newPath] = node; return true; } public bool HasEnoughSpace(long additionalBytes) { return FreeSpace >= additionalBytes; } }