ln.types/odb/ng/storage/FSStorage.cs

293 lines
8.9 KiB
C#

// /**
// * File: FSSTorage.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization.Advanced;
using ln.logging;
namespace ln.types.odb.ng.storage
{
/**
* FSStorage
*
* Directory Layout:
*
* <BasePath>/<StorageName>
* /data.odb Serialized Document Data
* /data.idx Serialized Lookup Index for Documents and Free Areas
*
* data.odb
* ----------
* 0000 4 MAGIC Bytes
* 0004 4 Version
* 0008 8 LastCloseTimestamp
* 0010 4 FirstOffset
* 0014 4 GranularWidth
* 0018 8 Reserved 0
*
**/
public class FSStorage : IStorage
{
public byte[] MagicBytes { get; } = new byte[] { 0x0F, 0x0E, 0x0D, 0x0A };
public String StoragePath { get; }
public String StorageName { get; }
public int FileVersion { get; private set; }
public long LastCloseTimestamp { get; private set; }
public int FirstOffset { get; private set; }
public int GranularWidth { get; private set; } = 12;
public int GranularityMask => (1 << GranularWidth) - 1;
public int AppendOffset { get; private set; }
StorageAreaContainer storageAreas = new StorageAreaContainer();
Dictionary<Guid, StorageArea> documentAreas = new Dictionary<Guid, StorageArea>();
FileStream fileStream;
public FSStorage(string storagePath)
{
StoragePath = storagePath;
StorageName = Path.GetFileName(storagePath);
}
public FSStorage(string storagePath,int granularWidth)
:this(storagePath)
{
GranularWidth = granularWidth;
}
private void AssertOpen()
{
if (fileStream == null)
throw new IOException("FSStorage not opened");
}
public bool IsOpen => (fileStream != null);
public bool Open()
{
if (!IsOpen)
{
try
{
if (!Directory.Exists(StoragePath))
Directory.CreateDirectory(StoragePath);
fileStream = new FileStream(Path.Combine(StoragePath, "data.odb"), FileMode.OpenOrCreate);
if (fileStream.Length == 0)
{
FileVersion = 0;
LastCloseTimestamp = 0;
FirstOffset = (1 << GranularWidth);
if (FirstOffset < 0x20)
throw new NotSupportedException("Granularity too small");
AppendOffset = FirstOffset;
Close();
return Open();
}
else
{
if (!fileStream.ReadBytes(4).SequenceEqual(MagicBytes))
throw new IOException("Magic bytes do not match");
FileVersion = fileStream.ReadInteger();
LastCloseTimestamp = fileStream.ReadLong();
FirstOffset = fileStream.ReadInteger();
GranularWidth = fileStream.ReadInteger();
Scan();
}
}
catch (Exception e)
{
Logging.Log(e);
if (fileStream != null)
{
fileStream.Close();
fileStream.Dispose();
fileStream = null;
}
return false;
}
return true;
}
return false;
}
private void Scan()
{
int offset = FirstOffset;
while (offset < fileStream.Length)
{
fileStream.Position = offset;
StorageArea storageArea = new StorageArea(offset, fileStream.ReadInteger());
Guid documentID = new Guid(fileStream.ReadBytes(16));
if (Guid.Empty.Equals(documentID))
storageAreas.Push(storageArea);
else
{
if (documentAreas.ContainsKey(documentID))
{
Document previousDoc = LoadDocument(documentAreas[documentID]);
Document currentDoc = LoadDocument(storageArea);
if (previousDoc.StorageTimeStamp < currentDoc.StorageTimeStamp)
{
WriteStorageArea(documentAreas[documentID]);
storageAreas.Push(documentAreas[documentID]);
documentAreas[documentID] = storageArea;
}
else
{
WriteStorageArea(storageArea);
storageAreas.Push(storageArea);
}
} else
{
documentAreas.Add(documentID, storageArea);
}
}
offset = storageArea.NextOffset;
}
AppendOffset = offset;
}
public void Close()
{
lock (this){
AssertOpen();
fileStream.Position = 0;
fileStream.WriteBytes(MagicBytes);
fileStream.WriteInteger(FileVersion);
LastCloseTimestamp = (long)DateTime.Now.ToUnixTimeMilliseconds();
fileStream.WriteLong(LastCloseTimestamp);
fileStream.WriteInteger(FirstOffset);
fileStream.WriteInteger(GranularWidth);
fileStream.Close();
fileStream.Dispose();
fileStream = null;
}
}
public IEnumerable<Guid> GetDocumentIDs()
{
lock (this)
{
return documentAreas.Keys.ToArray();
}
}
public Document Load(Guid documentID)
{
lock (this)
{
if (!documentAreas.ContainsKey(documentID))
throw new KeyNotFoundException();
StorageArea storageArea = documentAreas[documentID];
return LoadDocument(storageArea);
}
}
private Document LoadDocument(StorageArea storageArea)
{
fileStream.Position = storageArea.Offset + 4;
Guid documentID = new Guid(fileStream.ReadBytes(16));
byte[] storageBytes = fileStream.ReadBytes(storageArea.Size - 20);
return new Document(documentID, storageBytes);
}
public void Save(Document document)
{
lock (this)
{
document.StorageTimeStamp = DateTime.Now;
byte[] storageBytes = document.ToStorage();
StorageArea storageArea = storageAreas.Pop(storageBytes.Length + 20);
if (storageArea == null)
storageArea = AppendStorageArea(storageBytes.Length + 20);
int neededSize = storageBytes.Length + 20;
CheckGranularity(ref neededSize);
if (storageArea.Size > neededSize)
{
StorageArea splitArea = storageArea.Split(storageArea.Size - neededSize);
WriteStorageArea(splitArea);
}
WriteStorageArea(storageArea, storageBytes);
if (documentAreas.ContainsKey(document.ID))
{
StorageArea oldStorageArea = documentAreas[document.ID];
WriteStorageArea(oldStorageArea);
storageAreas.Push(oldStorageArea);
}
documentAreas[document.ID] = storageArea;
}
}
public void Delete(Guid documentID)
{
lock (this)
{
if (documentAreas.ContainsKey(documentID))
{
StorageArea storageArea = documentAreas[documentID];
documentAreas.Remove(documentID);
storageArea = storageAreas.Push(storageArea);
WriteStorageArea(storageArea);
}
}
}
private StorageArea AppendStorageArea(int size)
{
CheckGranularity(ref size);
return new StorageArea(AppendOffset, size);
}
private void WriteStorageArea(StorageArea storageArea) => WriteStorageArea(storageArea, new byte[0]);
private void WriteStorageArea(StorageArea storageArea,byte[] data)
{
AssertOpen();
if (data.Length > (storageArea.Size - 4))
throw new ArgumentOutOfRangeException(nameof(data));
fileStream.Position = storageArea.Offset;
fileStream.WriteInteger(storageArea.Size);
fileStream.WriteBytes(data);
fileStream.WriteBytes(new byte[ storageArea.Size - 4 - data.Length]);
}
private void CheckGranularity(ref int i)
{
i = (i + GranularityMask) & ~GranularityMask;
}
}
}