ln.types/odb/ODBCollection.cs

420 lines
13 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using ln.types.odb.values;
using System.IO;
using System.Net.Mime;
using System.Security.AccessControl;
using System.Linq;
using System.ComponentModel.Design.Serialization;
using System.ComponentModel;
using ln.logging;
namespace ln.types.odb
{
public class ODBCollection : IEnumerable<ODBDocument>, IDisposable
{
public ODB ODB { get; }
public String CollectionName { get; }
public DocumentIndex Index => documentIndex;
FileStream fileStream;
DocumentIndex documentIndex;
internal ODBCollection(ODB odb,string collectionName)
{
ODB = odb;
CollectionName = collectionName;
Initialize();
}
private void Initialize()
{
fileStream = new FileStream(Path.Combine(ODB.BasePath, String.Format("{0}.col",CollectionName)),FileMode.OpenOrCreate,FileAccess.ReadWrite);
documentIndex = new DocumentIndex(fileStream);
}
public void Close()
{
Dispose();
}
public bool Insert(ODBDocument document)
{
DocumentIndex.DocumentIndexEntry die = documentIndex.Lookup(document.ID);
if (die == null)
{
byte[] docBytes = document.ToStorage();
die = documentIndex.FindUnused(docBytes.Length);
die.Update(document.ID,docBytes);
return true;
}
return false;
}
public bool Update(ODBDocument document)
{
DocumentIndex.DocumentIndexEntry die = documentIndex.Lookup(document.ID);
if (die != null)
{
byte[] docBytes = document.ToStorage();
if (die.BufferLength < docBytes.Length)
{
DocumentIndex.DocumentIndexEntry ndie = documentIndex.FindUnused(docBytes.Length);
die.DocumentID = ODBNull.Instance;
ndie.Update(document.ID, docBytes);
die.Release();
}
else
{
die.Update(document.ID, docBytes);
}
return true;
}
return false;
}
public bool Upsert(ODBDocument document)
{
byte[] docBytes = document.ToStorage();
DocumentIndex.DocumentIndexEntry die = documentIndex.Lookup(document.ID);
DocumentIndex.DocumentIndexEntry rdie = null;
if ((die == null) || (die.BufferLength < docBytes.Length))
{
rdie = die;
die = documentIndex.FindUnused(docBytes.Length);
}
if (rdie != null)
rdie.DocumentID = ODBNull.Instance;
die.Update(document.ID, docBytes);
if (rdie != null)
rdie.Release();
return true;
}
public ODBDocument GetDocumentByID(ODBValue id)
{
Logging.Log(LogLevel.DEBUG, "ODBCollection.GetDocumentByID(): {0}",id);
DocumentIndex.DocumentIndexEntry die = documentIndex.Lookup(id);
if (die != null)
{
byte[] storageBytes = die.ReadStorageBytes();
ODBDocument document = new ODBDocument(storageBytes, 0, storageBytes.Length);
return document;
}
return null;
}
public IEnumerable<ODBDocument> Find(string propertyName, ODBValue value)
{
foreach (ODBDocument document in this)
{
if (value.Equals(document[propertyName]))
yield return document;
}
}
public ODBDocument FindOne(string propertyName, ODBValue value)
{
foreach (ODBDocument document in this)
{
if (value.Equals(document[propertyName]))
return document;
}
return null;
}
public IEnumerator<ODBDocument> GetEnumerator()
{
foreach (ODBValue id in documentIndex.ToArray())
{
yield return GetDocumentByID(id);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
ODB.DisposeCollection(this);
if (fileStream != null)
{
fileStream.Dispose();
fileStream = null;
}
documentIndex = null;
}
public class DocumentIndex : IEnumerable<ODBValue>
{
public Stream StorageStream { get; }
public DocumentIndexEntry Head { get; private set; }
private Dictionary<ODBValue, DocumentIndexEntry> idLookup = new Dictionary<ODBValue, DocumentIndexEntry>();
public DocumentIndex(Stream stream)
{
StorageStream = stream;
Head = new DocumentIndexEntry(this);
}
public IEnumerable<DocumentIndexEntry> IndexEntries
{
get
{
DocumentIndexEntry entry = Head;
while (entry != null)
{
yield return entry;
entry = entry.Next;
}
}
}
public DocumentIndexEntry Lookup(ODBValue ID)
{
foreach (DocumentIndexEntry die in IndexEntries)
{
if (ID.Equals(die.DocumentID))
return die;
}
return null;
}
public DocumentIndexEntry FindUnused(int minLength)
{
foreach (DocumentIndexEntry die in IndexEntries)
{
if (die.IsUnused && (die.BufferLength > minLength))
return die;
if (die.Next == null)
return die.Extend(minLength);
}
return null;
}
public class DocumentIndexEntry
{
public DocumentIndex Index { get; }
public DocumentIndexEntry Next { get; private set; }
public DocumentIndexEntry Last { get; private set; }
public long Offset { get; private set; }
public int BufferLength { get; private set; }
public long NextOffset => Offset + BufferLength + 4;
private ODBValue documentID;
public ODBValue DocumentID
{
get => documentID;
set
{
lock (Index)
{
if (!ODBNull.Instance.Equals(documentID))
{
Index.idLookup.Remove(documentID);
}
documentID = value;
if (!ODBNull.Instance.Equals(documentID))
{
Index.idLookup.Add(documentID, this);
}
}
}
}
public bool IsUnused => (ODBNull.Instance.Equals(documentID));
public DocumentIndexEntry(DocumentIndex index)
: this(index, 0)
{ }
private DocumentIndexEntry(DocumentIndex index, long offset)
{
Index = index;
Offset = offset;
Read();
}
private DocumentIndexEntry(DocumentIndexEntry lastEntry,int bufferLength)
{
Last = lastEntry;
Last.Next = this;
Index = lastEntry.Index;
Offset = lastEntry.NextOffset;
BufferLength = bufferLength;
WriteHeader();
Release();
}
private DocumentIndexEntry(DocumentIndexEntry lastEntry)
{
Last = lastEntry;
Index = lastEntry.Index;
Offset = lastEntry.NextOffset;
if (Last.Next != null)
{
Next = Last.Next;
Last.Next = this;
Next.Last = this;
BufferLength = (int)(Next.Offset - Offset - 4);
WriteHeader();
Release();
}
else
{
Last.Next = this;
Read();
}
}
private void Read()
{
lock (Index)
{
if (Offset >= Index.StorageStream.Length)
{
BufferLength = 0;
DocumentID = ODBNull.Instance;
}
else
{
Index.StorageStream.Position = Offset;
BufferLength = Index.StorageStream.ReadInteger();
Index.StorageStream.Position = Offset + 4;
DocumentID = ODBValue.Read(Index.StorageStream);
if (NextOffset < Index.StorageStream.Length)
{
Next = new DocumentIndexEntry(this);
}
}
}
}
public void Update(ODBValue id,byte[] storageBytes)
{
lock (Index)
{
if (BufferLength < storageBytes.Length)
throw new ArgumentOutOfRangeException();
if (BufferLength > (storageBytes.Length + 32))
{
Split(storageBytes.Length);
}
Index.StorageStream.Position = Offset + 4;
Index.StorageStream.Write(storageBytes, 0, storageBytes.Length);
DocumentID = id;
}
}
public void Release()
{
lock (Index)
{
DocumentID = ODBNull.Instance;
Index.StorageStream.Position = Offset + 4;
Index.StorageStream.Write(new byte[BufferLength], 0, BufferLength);
if ((Next != null) && Next.IsUnused)
{
Combine();
}
if ((Last != null) && Last.IsUnused)
{
Last.Combine();
}
}
}
public DocumentIndexEntry Extend(int length)
{
if (Next == null)
{
if ((Last == null) && (IsUnused))
{
BufferLength = length;
WriteHeader();
Release();
return this;
}
return new DocumentIndexEntry(this, length);
}
throw new NotSupportedException();
}
private void Split(int length)
{
BufferLength = length;
DocumentIndexEntry dieInsert = new DocumentIndexEntry(this);
WriteHeader();
}
private void Combine()
{
BufferLength += Next.BufferLength + 4;
Next = Next.Next;
if (Next != null)
Next.Last = this;
WriteHeader();
}
private void WriteHeader()
{
lock (Index)
{
Index.StorageStream.Position = Offset;
Index.StorageStream.WriteInteger(BufferLength);
}
}
public byte[] ReadStorageBytes()
{
byte[] buffer = new byte[BufferLength];
lock (Index)
{
Index.StorageStream.Position = Offset + 4;
Index.StorageStream.Read(buffer, 0, BufferLength);
}
return buffer;
}
}
public IEnumerator<ODBValue> GetEnumerator()
{
return idLookup.Keys.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}