using System; using System.IO; using ln.types.btree; using System.Collections.Generic; using System.Linq; using ln.objects.catalog; namespace ln.types.odb.ng.storage { public class OrganizedFile : IDisposable { public String FileName { get; private set; } public OrganizedFileHeader FileHeader { get; private set; } = new OrganizedFileHeader(); public int CurrentStoreLength { get; private set; } FileStream fileStream; FileStream lckFileStream; MappingBTree freeAreas = new MappingBTree((a)=>a.Offset); MappingBTree usedAreas = new MappingBTree((a) => a.Offset); public OrganizedFile(string filename) { FileName = filename; lckFileStream = new FileStream(string.Format("{0}.lck", FileName), FileMode.CreateNew); fileStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.ReadWrite); if (fileStream.Length > 0) { FileHeader.Read(fileStream); int nextOffset = FileHeader.FirstOffset; while (nextOffset < fileStream.Length) { OrganizedFileArea fileArea = new OrganizedFileArea(fileStream, nextOffset); if (fileArea.ReadTypeCode(fileStream) == 0) freeAreas.Add(fileArea); else usedAreas.Add(fileArea); nextOffset = fileArea.NextOffset; } CurrentStoreLength = nextOffset; } } public void Close() { if (fileStream != null) { lock (fileStream) { FileHeader.Write(fileStream); fileStream.Close(); fileStream.Dispose(); lckFileStream.Close(); lckFileStream.Dispose(); File.Delete(string.Format("{0}.lck", FileName)); freeAreas.Clear(); usedAreas.Clear(); } fileStream = null; lckFileStream = null; } } public IEnumerable UsedAreas => usedAreas; public void Clear(OrganizedFileArea fileArea) { lock (fileStream) { usedAreas.TryRemove(fileArea); fileArea.Clear(fileStream); freeAreas.Add(fileArea); } } public OrganizedFileArea Store(ODBEntity value) { byte[] storageBytes = value.GetStorageBytes(); return Store(storageBytes); } public OrganizedFileArea Store(byte[] bytes) { lock (fileStream) { OrganizedFileArea fileArea = freeAreas.Where((a) => a.MaxBytes >= bytes.Length).FirstOrDefault(); if (fileArea == null) { fileArea = new OrganizedFileArea(CurrentStoreLength, bytes.Length + 4); CurrentStoreLength = fileArea.NextOffset; } else { freeAreas.Remove(fileArea); } fileArea.Store(fileStream, bytes); usedAreas.Add(fileArea); return fileArea; } } public void Dispose() => Close(); public class OrganizedFileHeader { public readonly UInt32 CurrentFileMagic = 0x00EEFFC0; public UInt32 Magic; public OrganizedFileType FileType; public Int32 FirstOffset; public UInt32 Reserve0; public UInt64 OpenCounter; public OrganizedFileHeader() { Magic = CurrentFileMagic; FileType = OrganizedFileType.DATA; FirstOffset = 1024; Reserve0 = 0; OpenCounter = 0; } public void Read(FileStream stream) { stream.Position = 0; Magic = stream.ReadUInteger(); if (Magic != CurrentFileMagic) throw new FormatException("File Magic does not match! Possibly corrupted file!"); FileType = (OrganizedFileType)stream.ReadInteger(); FirstOffset = stream.ReadInteger(); Reserve0 = stream.ReadUInteger(); OpenCounter = stream.ReadULong(); OpenCounter++; } public void Write(FileStream stream) { stream.Position = 0; stream.WriteUInteger(Magic); stream.WriteInteger((int)FileType); stream.WriteInteger(FirstOffset); stream.WriteUInteger(Reserve0); stream.WriteULong(OpenCounter); } } public class OrganizedFileArea : IComparable,IComparable { public Int32 Offset; public Int32 Size; public Int32 MaxBytes => Size - 4; public Int32 NextOffset => Offset + Size; public OrganizedFileArea(int offset,int size) { Offset = offset; Size = size; } public OrganizedFileArea(FileStream fileStream,int offset) { Offset = offset; fileStream.Position = Offset; Size = fileStream.ReadInteger(); } public int ReadTypeCode(FileStream fileStream) { fileStream.Position = Offset + 4; return fileStream.ReadInteger(); } public void Store(FileStream fileStream,byte[] buffer) { if (MaxBytes < buffer.Length) throw new ArgumentException("Area too small for buffer to fit", nameof(buffer)); fileStream.Position = Offset; fileStream.WriteInteger(Size); fileStream.Write(buffer, 0, buffer.Length); if (buffer.Length < MaxBytes) { byte[] padding = new byte[MaxBytes - buffer.Length]; fileStream.Write(padding, 0, padding.Length); } } public byte[] Load(FileStream fileStream) { byte[] buffer = new byte[MaxBytes]; fileStream.Position = Offset + 4; fileStream.Read(buffer, 0, buffer.Length); return buffer; } public void Clear(FileStream fileStream) { byte[] zero = new byte[MaxBytes]; fileStream.WriteInteger(Size); fileStream.Write(zero,0,zero.Length); } public int CompareTo(OrganizedFileArea other) { return Offset - other.Offset; } public int CompareTo(object obj) { if (obj is OrganizedFileArea) { return Offset - (obj as OrganizedFileArea).Offset; } throw new NotSupportedException(); } } } }