//******************************************************************************************************// FileBackedLookupTableBase.cs - Gbtc//// Copyright © 2014, Grid Protection Alliance. All Rights Reserved.//// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See// the NOTICE file distributed with this work for additional information regarding copyright ownership.// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may// not use this file except in compliance with the License. You may obtain a copy of the License at://// http://www.opensource.org/licenses/MIT//// Unless agreed to in writing, the subject software distributed under the License is distributed on an// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the// License for the specific language governing permissions and limitations.//// Code Modification History:// ----------------------------------------------------------------------------------------------------// 12/02/2014 - Stephen C. Wills// Generated original version of source code.////****************************************************************************************************** using System;using System.Collections;using System.Collections.Generic;using System.IO;using System.Linq;using System.Linq.Expressions;using System.Reflection;using System.Runtime.Serialization.Formatters.Binary;using GSF.IO;using GSF.IO.Checksums; namespace GSF.Collections{ internal enum LookupTableType { /// <summary> /// Dictionary with keys and values in the item nodes. /// </summary> Dictionary, /// <summary> /// HashSet with lookup node markers for set operations. /// </summary> HashSet } internal class FileBackedLookupTable<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IDisposable { #region [ Members ] // Nested Types protected class HeaderNode { public const int SignatureSize = 16; public const int FixedSize = SignatureSize + 4 * sizeof(long); public HeaderNode(LookupTableType type) { if (type == LookupTableType.Dictionary) Signature = Guid.Parse(DictionarySignature).ToRfcBytes(); else Signature = Guid.Parse(HashSetSignature).ToRfcBytes(); } public byte[] Signature; public long Count; public long Capacity; public long ItemSectionPointer; public long EndOfFilePointer; } protected class JournalNode { public const int FixedSize = sizeof(int) + 3 * sizeof(long) + sizeof(int); public const int None = 0; public const int Set = 1; public const int Delete = 2; public const int GrowLookupSection = 3; public const int RebuildLookupTable = 4; public const int WriteItemNodePointers = 5; public const int Truncate = 6; public const int Clear = 7; public int Operation; public long LookupPointer; public long ItemPointer; public long Sync; public int Checksum; } protected class LookupNode { public long ItemPointer; public int Marker; } protected class ItemNode { // Size of only the fixed size portion of // the item node (not including key and value) public const int FixedSize = 2 * sizeof(long) + sizeof(int); public long LookupPointer; public long NextItemPointer; public int HashCode; public TKey Key; public TValue Value; } private class KeysEnumerable : IEnumerable<TKey> { private Func<IEnumerator<TKey>> m_getEnumeratorFunc; public KeysEnumerable(Func<IEnumerator<TKey>> getEnumeratorFunc) { m_getEnumeratorFunc = getEnumeratorFunc; } public IEnumerator<TKey> GetEnumerator() { return m_getEnumeratorFunc(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } // Constants public const string DictionarySignature = "3165E4F9-203B-4741-A186-EA34659A94B7"; public const string HashSetSignature = "6527713F-78AE-43DA-8E37-718AFED99927"; private const double MaximumLoadFactor = 0.7D; private const int MaximumChainedEmptyNodes = 3; // Fields private HeaderNode m_headerNode; private JournalNode m_journalNode; private CachedFileStream m_fileStream; private BinaryWriter m_fileWriter; private BinaryReader m_fileReader; private string m_filePath; private IEqualityComparer<TKey> m_keyComparer; private int m_fragmentationCount; private LookupTableType m_lookupTableType; #endregion #region [ Constructors ] /// <summary> /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class. /// </summary> /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param> /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception> public FileBackedLookupTable(LookupTableType lookupTableType) : this(lookupTableType, EqualityComparer<TKey>.Default) { } /// <summary> /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class. /// </summary> /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param> /// <param name="filePath">The path to the file used to store the lookup table.</param> /// <exception cref="ArgumentException"><paramref name="filePath"/> is a zero-length string, contains only white space, or contains one or more invalid characters as defined by <see cref="Path.GetInvalidPathChars"/>.</exception> /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is null.</exception> /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception> public FileBackedLookupTable(LookupTableType lookupTableType, string filePath) : this(lookupTableType, filePath, EqualityComparer<TKey>.Default) { } /// <summary> /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class. /// </summary> /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param> /// <param name="keyComparer">The equality comparer used to compare keys in the lookup table.</param> /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception> public FileBackedLookupTable(LookupTableType lookupTableType, IEqualityComparer<TKey> keyComparer) : this(lookupTableType, Path.GetTempFileName(), keyComparer) { } /// <summary> /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class. /// </summary> /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param> /// <param name="filePath">The path to the file used to store the lookup table.</param> /// <param name="keyComparer">The equality comparer used to compare keys in the lookup table.</param> /// <exception cref="ArgumentException"><paramref name="filePath"/> is a zero-length string, contains only white space, or contains one or more invalid characters as defined by <see cref="Path.GetInvalidPathChars"/>.</exception> /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is null.</exception> /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception> public FileBackedLookupTable(LookupTableType lookupTableType, string filePath, IEqualityComparer<TKey> keyComparer) { if ((object)WriteKeyAction == null || (object)ReadKeyFunc == null) throw new InvalidOperationException("Unable to create FileBackedLookupTable with keys that are not serializable"); if (lookupTableType == LookupTableType.Dictionary && ((object)WriteValueAction == null || (object)ReadValueFunc == null)) throw new InvalidOperationException("Unable to create FileBackedLookupTable with values that are not serializable"); if ((object)filePath == null) throw new ArgumentNullException("filePath"); if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("Path is zero-length or contains only whitespace", "filePath"); m_lookupTableType = lookupTableType; FilePath = filePath; if ((object)keyComparer != null) m_keyComparer = keyComparer; else m_keyComparer = EqualityComparer<TKey>.Default; } #endregion #region [ Properties ] /// <summary> /// Gets or sets the path to the file backing this lookup table. /// </summary> /// <exception cref="ArgumentException">FilePath contains one or more invalid characters as defined by <see cref="Path.GetInvalidPathChars"/>.</exception> /// <remarks> /// Changes to this property will cause the file to close if the file is already opened. /// Data will not be automatically written from the old file to the new file. /// </remarks> public string FilePath { get { return m_filePath; } set { char[] invalidPathChars; if (m_filePath != value) { if (!string.IsNullOrWhiteSpace(value)) { invalidPathChars = Path.GetInvalidPathChars(); if (value.Any(invalidPathChars.Contains)) throw new ArgumentException("Path contains one or more invalid characters", "value"); } Close(); m_filePath = value; } } } /// <summary> /// Gets the number of elements contained in the <see cref="FileBackedLookupTable{TKey, TValue}"/>. /// </summary> /// <returns> /// The number of elements contained in the <see cref="FileBackedLookupTable{TKey, TValue}"/>. /// </returns> public int Count { get { if ((object)m_fileStream == null) Open(); return (int)m_headerNode.Count; } } /// <summary> /// Gets a value indicating whether the <see cref="FileBackedLookupTable{TKey, TValue}"/> is read-only. /// </summary> /// <returns> /// true if the <see cref="FileBackedLookupTable{TKey, TValue}"/> is read-only; otherwise, false. /// </returns> public bool IsReadOnly { get { if ((object)m_fileStream == null) Open(); return (object)m_fileWriter == null; } } /// <summary> /// Gets or sets the signature of the file backing the lookup table. /// </summary> /// <exception cref="ArgumentNullException">Attempt is made to set Signature to a null value.</exception> /// <exception cref="ArgumentException">Attempt is made to set Signature to a value larger than the maximum allowed size.</exception> /// <exception cref="NotSupportedException">Attempt is made to modify Signature of a read-only lookup table.</exception> public byte[] Signature { get { byte[] signature; if ((object)m_fileStream == null) Open(); signature = new byte[HeaderNode.SignatureSize]; Buffer.BlockCopy(m_headerNode.Signature, 0, signature, 0, HeaderNode.SignatureSize); return signature; } set { if ((object)value == null) throw new ArgumentNullException("value"); if (value.Length > HeaderNode.SignatureSize) throw new ArgumentException("Attempt was made to set signature to a value larger than the maximum signature size of " + HeaderNode.SignatureSize); if (IsReadOnly) throw new NotSupportedException("Unable to modify read-only lookup table"); Buffer.BlockCopy(value, 0, m_headerNode.Signature, 0, value.Length); Array.Clear(m_headerNode.Signature, value.Length, HeaderNode.SignatureSize - value.Length); Write(m_headerNode); } } /// <summary> /// Gets or sets the element with the specified key. /// </summary> /// <returns> /// The element with the specified key. /// </returns> /// <param name="key">The key of the element to get or set.</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is null.</exception> /// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="key"/> is not found.</exception> /// <exception cref="NotSupportedException">The property is set and the <see cref="FileBackedLookupTable{TKey, TValue}"/> is read-only.</exception> public TValue this[TKey key] { get { TValue value; if ((object)key == null) throw new ArgumentNullException("key"); if (!TryGetValue(key, out value)) throw new KeyNotFoundException("Item with the given key was not found in the lookup table"); return value; } set { long lookupPointer; long itemPointer; ItemNode itemNode; long count; if ((object)key == null) throw new ArgumentNullException("key"); if (IsReadOnly) throw new NotSupportedException("Unable to modify read-only lookup table"); Find(key, out lookupPointer, out itemPointer); if (itemPointer >= m_headerNode.ItemSectionPointer) count = m_headerNode.Count; else count = m_headerNode.Count + 1; itemNode = new ItemNode(); itemNode.LookupPointer = lookupPointer; itemNode.HashCode = m_keyComparer.GetHashCode(key); itemNode.Key = key; itemNode.Value = value; m_fileStream.Seek(m_headerNode.EndOfFilePointer, SeekOrigin.Begin); Write(itemNode); Set(lookupPointer, m_headerNode.EndOfFilePointer, count); if (itemPointer >= m_headerNode.ItemSectionPointer) m_fragmentationCount++; } } /// <summary> /// Gets or sets the size of the cache used /// to store data from the file in memory. /// </summary> public long CacheSize { get { if ((object)m_fileStream == null) Open(); // ReSharper disable once PossibleNullReferenceException return m_fileStream.CacheSize; } set { if ((object)m_fileStream == null) Open(); // ReSharper disable once PossibleNullReferenceException m_fileStream.CacheSize = value; } } /// <summary> /// Gets the number of operations that fragment the /// lookup table that have occurred since the last /// time the lookup table was compacted. /// </summary> /// <remarks> /// This value is not stored in the file and may therefore /// be inaccurate if the lookup table has not been compacted /// since the last time it was opened. /// </remarks> public int FragmentationCount { get { return m_fragmentationCount; } } private int LookupNodeSize { get { const int FixedSize = sizeof(long); int variableSize = (m_lookupTableType == LookupTableType.HashSet) ? sizeof(int) : 0; return FixedSize + variableSize; } } #endregion #region [ Methods ] /// <summary> /// Opens the file backing this lookup table. /// </summary> /// <exception cref="InvalidOperationException">File is already open.</exception> public void Open() { string directory; if ((object)m_fileStream != null) throw new InvalidOperationException("File is already open"); m_headerNode = new HeaderNode(m_lookupTableType); m_journalNode = new JournalNode(); if (string.IsNullOrWhiteSpace(m_filePath)) m_filePath = Path.GetTempFileName(); try { directory = Path.GetDirectoryName(m_filePath); // Attempt to create the directory if it doesn't already exist if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) Directory.CreateDirectory(directory); // Open the file in read/write mode m_fileStream = new CachedFileStream(m_filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); m_fileWriter = new BinaryWriter(m_fileStream); m_fileReader = new BinaryReader(m_fileStream); if (m_fileStream.Length > HeaderNode.FixedSize + JournalNode.FixedSize) { // Read the header node and the journal // node from the start of the file Read(m_headerNode); Read(m_journalNode); // If the journal node has an indicates that an operation was interrupted // the last time this file was open, replay that operation to return the // file to a consistent state switch (m_journalNode.Operation) { case JournalNode.Set: Set(m_journalNode.LookupPointer, m_journalNode.ItemPointer, m_journalNode.Sync); break; case JournalNode.Delete: Delete(m_journalNode.LookupPointer, m_journalNode.Sync); break; case JournalNode.GrowLookupSection:Lines 1–500 of 1771. Load more
//****************************************************************************************************** // FileBackedLookupTableBase.cs - Gbtc // // Copyright © 2014, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 12/02/2014 - Stephen C. Wills // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; using GSF.IO; using GSF.IO.Checksums; namespace GSF.Collections { internal enum LookupTableType { /// <summary> /// Dictionary with keys and values in the item nodes. /// </summary> Dictionary, /// <summary> /// HashSet with lookup node markers for set operations. /// </summary> HashSet } internal class FileBackedLookupTable<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IDisposable { #region [ Members ] // Nested Types protected class HeaderNode { public const int SignatureSize = 16; public const int FixedSize = SignatureSize + 4 * sizeof(long); public HeaderNode(LookupTableType type) { if (type == LookupTableType.Dictionary) Signature = Guid.Parse(DictionarySignature).ToRfcBytes(); else Signature = Guid.Parse(HashSetSignature).ToRfcBytes(); } public byte[] Signature; public long Count; public long Capacity; public long ItemSectionPointer; public long EndOfFilePointer; } protected class JournalNode { public const int FixedSize = sizeof(int) + 3 * sizeof(long) + sizeof(int); public const int None = 0; public const int Set = 1; public const int Delete = 2; public const int GrowLookupSection = 3; public const int RebuildLookupTable = 4; public const int WriteItemNodePointers = 5; public const int Truncate = 6; public const int Clear = 7; public int Operation; public long LookupPointer; public long ItemPointer; public long Sync; public int Checksum; } protected class LookupNode { public long ItemPointer; public int Marker; } protected class ItemNode { // Size of only the fixed size portion of // the item node (not including key and value) public const int FixedSize = 2 * sizeof(long) + sizeof(int); public long LookupPointer; public long NextItemPointer; public int HashCode; public TKey Key; public TValue Value; } private class KeysEnumerable : IEnumerable<TKey> { private Func<IEnumerator<TKey>> m_getEnumeratorFunc; public KeysEnumerable(Func<IEnumerator<TKey>> getEnumeratorFunc) { m_getEnumeratorFunc = getEnumeratorFunc; } public IEnumerator<TKey> GetEnumerator() { return m_getEnumeratorFunc(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } // Constants public const string DictionarySignature = "3165E4F9-203B-4741-A186-EA34659A94B7"; public const string HashSetSignature = "6527713F-78AE-43DA-8E37-718AFED99927"; private const double MaximumLoadFactor = 0.7D; private const int MaximumChainedEmptyNodes = 3; // Fields private HeaderNode m_headerNode; private JournalNode m_journalNode; private CachedFileStream m_fileStream; private BinaryWriter m_fileWriter; private BinaryReader m_fileReader; private string m_filePath; private IEqualityComparer<TKey> m_keyComparer; private int m_fragmentationCount; private LookupTableType m_lookupTableType; #endregion #region [ Constructors ] /// <summary> /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class. /// </summary> /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param> /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception> public FileBackedLookupTable(LookupTableType lookupTableType) : this(lookupTableType, EqualityComparer<TKey>.Default) { } /// <summary> /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class. /// </summary> /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param> /// <param name="filePath">The path to the file used to store the lookup table.</param> /// <exception cref="ArgumentException"><paramref name="filePath"/> is a zero-length string, contains only white space, or contains one or more invalid characters as defined by <see cref="Path.GetInvalidPathChars"/>.</exception> /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is null.</exception> /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception> public FileBackedLookupTable(LookupTableType lookupTableType, string filePath) : this(lookupTableType, filePath, EqualityComparer<TKey>.Default) { } /// <summary> /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class. /// </summary> /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param> /// <param name="keyComparer">The equality comparer used to compare keys in the lookup table.</param> /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception> public FileBackedLookupTable(LookupTableType lookupTableType, IEqualityComparer<TKey> keyComparer) : this(lookupTableType, Path.GetTempFileName(), keyComparer) { } /// <summary> /// Creates a new instance of the <see cref="FileBackedLookupTable{TKey, TValue}"/> class. /// </summary> /// <param name="lookupTableType">Type of the lookup table used to tweak the file format.</param> /// <param name="filePath">The path to the file used to store the lookup table.</param> /// <param name="keyComparer">The equality comparer used to compare keys in the lookup table.</param> /// <exception cref="ArgumentException"><paramref name="filePath"/> is a zero-length string, contains only white space, or contains one or more invalid characters as defined by <see cref="Path.GetInvalidPathChars"/>.</exception> /// <exception cref="ArgumentNullException"><paramref name="filePath"/> is null.</exception> /// <exception cref="InvalidOperationException">Either <typeparamref name="TKey"/> or <typeparamref name="TValue"/> cannot be serialized.</exception> public FileBackedLookupTable(LookupTableType lookupTableType, string filePath, IEqualityComparer<TKey> keyComparer) { if ((object)WriteKeyAction == null || (object)ReadKeyFunc == null) throw new InvalidOperationException("Unable to create FileBackedLookupTable with keys that are not serializable"); if (lookupTableType == LookupTableType.Dictionary && ((object)WriteValueAction == null || (object)ReadValueFunc == null)) throw new InvalidOperationException("Unable to create FileBackedLookupTable with values that are not serializable"); if ((object)filePath == null) throw new ArgumentNullException("filePath"); if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("Path is zero-length or contains only whitespace", "filePath"); m_lookupTableType = lookupTableType; FilePath = filePath; if ((object)keyComparer != null) m_keyComparer = keyComparer; else m_keyComparer = EqualityComparer<TKey>.Default; } #endregion #region [ Properties ] /// <summary> /// Gets or sets the path to the file backing this lookup table. /// </summary> /// <exception cref="ArgumentException">FilePath contains one or more invalid characters as defined by <see cref="Path.GetInvalidPathChars"/>.</exception> /// <remarks> /// Changes to this property will cause the file to close if the file is already opened. /// Data will not be automatically written from the old file to the new file. /// </remarks> public string FilePath { get { return m_filePath; } set { char[] invalidPathChars; if (m_filePath != value) { if (!string.IsNullOrWhiteSpace(value)) { invalidPathChars = Path.GetInvalidPathChars(); if (value.Any(invalidPathChars.Contains)) throw new ArgumentException("Path contains one or more invalid characters", "value"); } Close(); m_filePath = value; } } } /// <summary> /// Gets the number of elements contained in the <see cref="FileBackedLookupTable{TKey, TValue}"/>. /// </summary> /// <returns> /// The number of elements contained in the <see cref="FileBackedLookupTable{TKey, TValue}"/>. /// </returns> public int Count { get { if ((object)m_fileStream == null) Open(); return (int)m_headerNode.Count; } } /// <summary> /// Gets a value indicating whether the <see cref="FileBackedLookupTable{TKey, TValue}"/> is read-only. /// </summary> /// <returns> /// true if the <see cref="FileBackedLookupTable{TKey, TValue}"/> is read-only; otherwise, false. /// </returns> public bool IsReadOnly { get { if ((object)m_fileStream == null) Open(); return (object)m_fileWriter == null; } } /// <summary> /// Gets or sets the signature of the file backing the lookup table. /// </summary> /// <exception cref="ArgumentNullException">Attempt is made to set Signature to a null value.</exception> /// <exception cref="ArgumentException">Attempt is made to set Signature to a value larger than the maximum allowed size.</exception> /// <exception cref="NotSupportedException">Attempt is made to modify Signature of a read-only lookup table.</exception> public byte[] Signature { get { byte[] signature; if ((object)m_fileStream == null) Open(); signature = new byte[HeaderNode.SignatureSize]; Buffer.BlockCopy(m_headerNode.Signature, 0, signature, 0, HeaderNode.SignatureSize); return signature; } set { if ((object)value == null) throw new ArgumentNullException("value"); if (value.Length > HeaderNode.SignatureSize) throw new ArgumentException("Attempt was made to set signature to a value larger than the maximum signature size of " + HeaderNode.SignatureSize); if (IsReadOnly) throw new NotSupportedException("Unable to modify read-only lookup table"); Buffer.BlockCopy(value, 0, m_headerNode.Signature, 0, value.Length); Array.Clear(m_headerNode.Signature, value.Length, HeaderNode.SignatureSize - value.Length); Write(m_headerNode); } } /// <summary> /// Gets or sets the element with the specified key. /// </summary> /// <returns> /// The element with the specified key. /// </returns> /// <param name="key">The key of the element to get or set.</param> /// <exception cref="ArgumentNullException"><paramref name="key"/> is null.</exception> /// <exception cref="KeyNotFoundException">The property is retrieved and <paramref name="key"/> is not found.</exception> /// <exception cref="NotSupportedException">The property is set and the <see cref="FileBackedLookupTable{TKey, TValue}"/> is read-only.</exception> public TValue this[TKey key] { get { TValue value; if ((object)key == null) throw new ArgumentNullException("key"); if (!TryGetValue(key, out value)) throw new KeyNotFoundException("Item with the given key was not found in the lookup table"); return value; } set { long lookupPointer; long itemPointer; ItemNode itemNode; long count; if ((object)key == null) throw new ArgumentNullException("key"); if (IsReadOnly) throw new NotSupportedException("Unable to modify read-only lookup table"); Find(key, out lookupPointer, out itemPointer); if (itemPointer >= m_headerNode.ItemSectionPointer) count = m_headerNode.Count; else count = m_headerNode.Count + 1; itemNode = new ItemNode(); itemNode.LookupPointer = lookupPointer; itemNode.HashCode = m_keyComparer.GetHashCode(key); itemNode.Key = key; itemNode.Value = value; m_fileStream.Seek(m_headerNode.EndOfFilePointer, SeekOrigin.Begin); Write(itemNode); Set(lookupPointer, m_headerNode.EndOfFilePointer, count); if (itemPointer >= m_headerNode.ItemSectionPointer) m_fragmentationCount++; } } /// <summary> /// Gets or sets the size of the cache used /// to store data from the file in memory. /// </summary> public long CacheSize { get { if ((object)m_fileStream == null) Open(); // ReSharper disable once PossibleNullReferenceException return m_fileStream.CacheSize; } set { if ((object)m_fileStream == null) Open(); // ReSharper disable once PossibleNullReferenceException m_fileStream.CacheSize = value; } } /// <summary> /// Gets the number of operations that fragment the /// lookup table that have occurred since the last /// time the lookup table was compacted. /// </summary> /// <remarks> /// This value is not stored in the file and may therefore /// be inaccurate if the lookup table has not been compacted /// since the last time it was opened. /// </remarks> public int FragmentationCount { get { return m_fragmentationCount; } } private int LookupNodeSize { get { const int FixedSize = sizeof(long); int variableSize = (m_lookupTableType == LookupTableType.HashSet) ? sizeof(int) : 0; return FixedSize + variableSize; } } #endregion #region [ Methods ] /// <summary> /// Opens the file backing this lookup table. /// </summary> /// <exception cref="InvalidOperationException">File is already open.</exception> public void Open() { string directory; if ((object)m_fileStream != null) throw new InvalidOperationException("File is already open"); m_headerNode = new HeaderNode(m_lookupTableType); m_journalNode = new JournalNode(); if (string.IsNullOrWhiteSpace(m_filePath)) m_filePath = Path.GetTempFileName(); try { directory = Path.GetDirectoryName(m_filePath); // Attempt to create the directory if it doesn't already exist if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) Directory.CreateDirectory(directory); // Open the file in read/write mode m_fileStream = new CachedFileStream(m_filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); m_fileWriter = new BinaryWriter(m_fileStream); m_fileReader = new BinaryReader(m_fileStream); if (m_fileStream.Length > HeaderNode.FixedSize + JournalNode.FixedSize) { // Read the header node and the journal // node from the start of the file Read(m_headerNode); Read(m_journalNode); // If the journal node has an indicates that an operation was interrupted // the last time this file was open, replay that operation to return the // file to a consistent state switch (m_journalNode.Operation) { case JournalNode.Set: Set(m_journalNode.LookupPointer, m_journalNode.ItemPointer, m_journalNode.Sync); break; case JournalNode.Delete: Delete(m_journalNode.LookupPointer, m_journalNode.Sync); break; case JournalNode.GrowLookupSection: