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;
}
}