Skip to content

Instantly share code, notes, and snippets.

@fabio-stein
Created January 28, 2024 01:16
Show Gist options
  • Save fabio-stein/7b26e00a5c3ebbfc5c38152bd93318c2 to your computer and use it in GitHub Desktop.
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
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; }
}
@fabio-stein
Copy link
Author

Commands:
image

Data file preview:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment