/*
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Data.Caching.ObjectCache
* File: BlobCache.cs
*
* BlobCache.cs is part of VNLib.Data.Caching.ObjectCache which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Data.Caching.ObjectCache is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* VNLib.Data.Caching.ObjectCache is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using VNLib.Utils.Memory;
using VNLib.Utils.Memory.Caching;
namespace VNLib.Data.Caching.ObjectCache
{
///
/// A general purpose binary data storage
///
public sealed class BlobCache : LRUCache, IBlobCache
{
private bool disposedValue;
///
public override bool IsReadOnly { get; }
///
protected override int MaxCapacity { get; }
///
public IUnmangedHeap CacheHeap { get; }
///
/// Initializes a new store
///
/// The maximum number of items to keep in memory
/// The unmanaged heap used to allocate cache entry buffers from
///
public BlobCache(int maxCapacity, IUnmangedHeap heap)
:base(StringComparer.Ordinal)
{
if(maxCapacity < 1)
{
throw new ArgumentException("The maxium capacity of the store must be a positive integer larger than 0", nameof(maxCapacity));
}
CacheHeap = heap;
MaxCapacity = maxCapacity;
//Update the lookup table size
LookupTable.EnsureCapacity(maxCapacity);
}
///
protected override bool CacheMiss(string key, out CacheEntry value)
{
value = default;
return false;
}
///
protected override void Evicted(ref KeyValuePair evicted)
{
//Dispose the cache item
evicted.Value.Dispose();
}
///
public bool TryChangeKey(string objectId, string newId, out CacheEntry blob)
{
//Try to get the node at the current key
if (LookupTable.Remove(objectId, out LinkedListNode> ? node))
{
//Remove the node from the ll
List.Remove(node);
//Get the stored blob
blob = node.ValueRef.Value;
//Update the
node.Value = new KeyValuePair(newId, blob);
//Add to end of list
List.AddLast(node);
//Re-add to lookup table with new key
LookupTable.Add(newId, node);
return true;
}
blob = default;
return false;
}
///
public override bool Remove(string key)
{
//Remove the item from the lookup table and if it exists, remove the node from the list
if (!LookupTable.Remove(key, out LinkedListNode>? node))
{
return false;
}
//always dispose blob
using (node.ValueRef.Value)
{
//Remove the node from the list
List.Remove(node);
}
return true;
}
///
/// Removes all cache entires and disposes their held resources
///
public override void Clear()
{
//Start from first node
LinkedListNode>? node = List.First;
//Classic ll node itteration
while(node != null)
{
//Dispose the cache entry
node.ValueRef.Value.Dispose();
//Move to next node
node = node.Next;
}
//empty all cache entires in the store
base.Clear();
}
///
public bool Remove(string objectId, out CacheEntry entry)
{
//Try to get the stored object
if(TryGetValue(objectId, out entry))
{
//remove the entry and bypass the disposal
bool result = base.Remove(objectId);
#if DEBUG
Debug.Assert(result == true);
#endif
return true;
}
entry = default;
return false;
}
///
void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
Clear();
}
disposedValue = true;
}
}
///
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}