Created
January 28, 2024 01:16
-
-
Save fabio-stein/7b26e00a5c3ebbfc5c38152bd93318c2 to your computer and use it in GitHub Desktop.
Create a simple file system example with simple commands to manage the content. It simulates FS blocks with header metadata
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Text.Json; | |
//File.Delete("data.txt"); | |
var stream = File.Open("data.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite); | |
var fs = new FileSystem(stream); | |
var fc = new FileContext(fs); | |
Console.WriteLine(@"Command list: | |
ls | |
cat @fileName | |
addfile @fileName @fileContent | |
mkdir @dirName | |
cd @dirName"); | |
while (true) | |
{ | |
var input = Console.ReadLine().Split(" "); | |
var command = input[0]; | |
switch (command) | |
{ | |
case "ls": | |
fc.Current.Files.ForEach(f => Console.WriteLine(f.Name)); | |
break; | |
case "cat": | |
var metadata = fc.Current.Files.First(f => f.Name == input[1]); | |
var file = fs.ReadFile(metadata.Block); | |
var text = new string(file.Content.Select(c => (char)c).ToArray()); | |
Console.WriteLine(text); | |
break; | |
case "addfile": | |
var fileN = new TFile() | |
{ | |
Content = input[2].ToByteArray(), | |
Name = input[1] | |
}; | |
fc.AddFile(fileN); | |
break; | |
case "mkdir": | |
fc.CreateFolder(input[1]); | |
break; | |
case "cd": | |
var folder = fc.Current.Files.First(f => f.IsFolder && f.Name == input[1]); | |
fc.GoToBlock(folder.Block); | |
break; | |
} | |
} | |
public class FileContext | |
{ | |
private readonly FileSystem fs; | |
public Path Current { get; set; } | |
public FileContext(FileSystem fs) | |
{ | |
this.fs = fs; | |
if (!GoToBlock(0)) | |
{ | |
CreateRoot(); | |
GoToBlock(0); | |
} | |
} | |
public void AddFile(TFile file) | |
{ | |
var fileBlock = fs.AddFile(file); | |
Current.Files.Add(new MetaFile() | |
{ | |
Block = fileBlock, | |
Name = file.Name, | |
Size = file.Content.Length | |
}); | |
SaveContext(); | |
} | |
public void DeleteFile(string name) | |
{ | |
var file = Current.Files.First(f => f.Name == name); | |
Current.Files.Remove(file); | |
fs.DeleteFile(file.Block); | |
SaveContext(); | |
} | |
public void CreateFolder(string name) | |
{ | |
var block = fs.ReserveNextFreeBlock(); | |
var folderBlock = fs.AddFile(new Path() | |
{ | |
Files = new List<MetaFile>(), | |
ParentBlock = Current.Block, | |
Block = block | |
}.ToFile(), block); | |
Current.Files.Add(new MetaFile() | |
{ | |
Block = folderBlock, | |
IsFolder = true, | |
Name = name | |
}); | |
SaveContext(); | |
} | |
private void SaveContext() | |
{ | |
fs.AddFile(Current.ToFile(), Current.Block); | |
} | |
public bool GoToBlock(int block) | |
{ | |
var file = fs.ReadFile(block); | |
if (file.Content.Length == 0) | |
return false; | |
var str = new string(file.Content.Select(c => (char)c).ToArray()); | |
Current = JsonSerializer.Deserialize<Path>(str); | |
return true; | |
} | |
private void CreateRoot() | |
{ | |
fs.AddFile(new Path() | |
{ | |
Files = new List<MetaFile>(), | |
}.ToFile(), 0); | |
} | |
} | |
public class Path | |
{ | |
public int Block { get; set; } | |
public int ParentBlock { get; set; } | |
public List<MetaFile> Files { get; set; } | |
} | |
public class MetaFile | |
{ | |
public string Name { get; set; } | |
public int Size { get; set; } | |
public int Block { get; set; } | |
public bool IsFolder { get; set; } | |
} | |
///////////////FileSystem.cs | |
/// | |
public class FileSystem | |
{ | |
const int BLOCK_SIZE = 128; | |
const int BLOCKS = 10; | |
const int DISK_SIZE = BLOCK_SIZE * BLOCKS; | |
const int HEADER_1_LOCK_LENGTH = 1; | |
const int HEADER_2_NEXT_LENGTH = 1; | |
const int HEADER_3_FILE_SIZE_LENGTH = 1; | |
const int HEADER_SIZE = HEADER_1_LOCK_LENGTH + HEADER_2_NEXT_LENGTH + HEADER_3_FILE_SIZE_LENGTH; | |
const int DATA_SIZE = BLOCK_SIZE - HEADER_SIZE; | |
int lastBlockPointer = -1; | |
private FileStream stream; | |
public FileSystem(FileStream stream) | |
{ | |
this.stream = stream; | |
if (stream.Length < DISK_SIZE) | |
Initialize(); | |
else if (stream.Length > DISK_SIZE) | |
throw new Exception("Invalid disk"); | |
} | |
public void Initialize() | |
{ | |
ReserveBlock(0); | |
while (stream.Position < DISK_SIZE) | |
stream.WriteByte(0); | |
stream.Flush(); | |
} | |
public TFile ReadFile(int block) | |
{ | |
TFile file = null; | |
BlockHeader header; | |
int bytesRead = 0; | |
while (true) | |
{ | |
header = ReadHeader(block); | |
if (file == null) | |
file = new TFile() | |
{ | |
Content = new byte[header.Size], | |
}; | |
int bytesToRead = file.Content.Length - bytesRead; | |
if (bytesToRead > DATA_SIZE) | |
bytesToRead = DATA_SIZE; | |
stream.Read(file.Content, bytesRead, bytesToRead); | |
if (bytesToRead <= 0) | |
break; | |
block = header.Next; | |
bytesRead += bytesToRead; | |
} | |
return file; | |
} | |
BlockHeader ReadHeader(int block) | |
{ | |
stream.Position = block * BLOCK_SIZE; | |
var header = new BlockHeader() | |
{ | |
Lock = stream.ReadByte(), | |
Next = stream.ReadByte(), | |
Size = stream.ReadByte() | |
}; | |
return header; | |
} | |
byte[] GenerateHeader(BlockHeader header) | |
{ | |
return new byte[HEADER_SIZE] | |
{ | |
(byte)header.Lock, | |
(byte)header.Next, | |
(byte)header.Size | |
}; | |
} | |
public int ReserveNextFreeBlock() | |
{ | |
var blocks = DISK_SIZE / BLOCK_SIZE; | |
for (int i = 0; i < blocks; i++) | |
{ | |
lastBlockPointer++; | |
if (lastBlockPointer >= blocks) | |
lastBlockPointer = 0; | |
if (ReserveBlock(lastBlockPointer)) | |
return lastBlockPointer; | |
} | |
throw new Exception("Disk FULL"); | |
} | |
bool ReserveBlock(int block) | |
{ | |
var pointer = block * BLOCK_SIZE; | |
stream.Position = pointer; | |
if (stream.ReadByte() != 0) | |
return false; | |
stream.Position = pointer; | |
stream.WriteByte(1); | |
return true; | |
} | |
public void DeleteFile(int block) | |
{ | |
BlockHeader header; | |
while (true) | |
{ | |
header = ReadHeader(block); | |
var pointer = block * BLOCK_SIZE; | |
stream.Position = pointer; | |
stream.WriteByte(0); | |
if (header.Next <= 0) | |
break; | |
block = header.Next; | |
} | |
} | |
public int AddFile(TFile file, int? overWriteFirstBlock = null) | |
{ | |
var blockQueue = new Queue<int>(); | |
var totalBlocks = Math.DivRem(file.Content.Length, DATA_SIZE, out int remainder); | |
if (remainder > 0) | |
totalBlocks++; | |
if (overWriteFirstBlock != null) | |
{ | |
DeleteFile((int)overWriteFirstBlock); | |
ReserveBlock((int)overWriteFirstBlock); | |
blockQueue.Enqueue((int)overWriteFirstBlock); | |
} | |
while (blockQueue.Count < totalBlocks) | |
{ | |
blockQueue.Enqueue(ReserveNextFreeBlock()); | |
} | |
var headBlock = blockQueue.Peek(); | |
for (int i = 0; i < totalBlocks; i++) | |
{ | |
var block = blockQueue.Dequeue(); | |
var nextBlock = 0; | |
blockQueue.TryPeek(out nextBlock); | |
var header = new BlockHeader() | |
{ | |
Lock = 1, | |
Next = nextBlock, | |
Size = file.Content.Length | |
}; | |
int diskPointer = block * BLOCK_SIZE; | |
stream.Position = diskPointer; | |
var headerArr = GenerateHeader(header); | |
stream.Write(headerArr); | |
int filePointer = i * DATA_SIZE; | |
int copiedBytes = i * DATA_SIZE; | |
int bytesToCopy = file.Content.Length - copiedBytes; | |
if (bytesToCopy > DATA_SIZE) | |
bytesToCopy = DATA_SIZE; | |
stream.Write(file.Content, filePointer, bytesToCopy); | |
} | |
stream.Flush(); | |
return headBlock; | |
} | |
} | |
public static class FileExtensions | |
{ | |
public static char ReadChar(this Stream stream) | |
{ | |
return (char)stream.ReadByte(); | |
} | |
public static char[] ReadChar(this Stream stream, int length) | |
{ | |
var arr = new char[length]; | |
for (int i = 0; i < length; i++) | |
{ | |
arr[i] = stream.ReadChar(); | |
} | |
return arr; | |
} | |
public static TFile ToFile(this Path path) | |
{ | |
var fileStr = JsonSerializer.Serialize(path); | |
var file = new TFile() | |
{ | |
Name = "empty", | |
Content = fileStr.ToByteArray() | |
}; | |
return file; | |
} | |
public static byte[] ToByteArray(this string input) | |
{ | |
return input.ToArray().Select(c => (byte)c).ToArray(); | |
} | |
} | |
public class BlockHeader | |
{ | |
public int Lock { get; set; } | |
public int Next { get; set; } | |
public int Size { get; set; } | |
} | |
public class Cursor | |
{ | |
private byte[] memory; | |
public int Position = 0; | |
public Cursor(byte[] memory) | |
{ | |
this.memory = memory; | |
} | |
public char[] ReadChars(int size) | |
{ | |
var res = new char[size]; | |
for (int i = 0; i < size; i++) | |
{ | |
res[i] = (char)ReadNext(); | |
} | |
return res; | |
} | |
public byte[] ReadBytes(int size) | |
{ | |
var res = new byte[size]; | |
for (int i = 0; i < size; i++) | |
{ | |
res[i] = ReadNext(); | |
} | |
return res; | |
} | |
public byte ReadNext() | |
{ | |
return memory[Position++]; | |
} | |
} | |
public class TFile | |
{ | |
public string Name { get; set; } | |
public byte[] Content { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Commands:

Data file preview:
