420 lines
13 KiB
C#
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();
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|