/*
* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials
* File: FilePathCache.cs
*
* FilePathCache.cs is part of VNLib.Plugins.Essentials which is part
* of the larger VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Essentials 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.Plugins.Essentials 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.Diagnostics;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
namespace VNLib.Plugins.Essentials
{
///
/// Represents a cache store for translated file paths to avoid
/// path probing and file system syscalls
///
internal abstract class FilePathCache
{
public abstract bool TryGetMappedPath(string filePath, [NotNullWhen(true)] out string? cachedPath);
///
/// Attempts to store a path mapping in the cache store
///
/// The requested input path
/// The filesystem path this requested path maps to
public abstract void StorePathMapping(string requestPath, string filePath);
///
/// Creates a new cache store with the specified max age. If max age is zero, the
/// cache store will be disabled.
///
/// The max time to store the cahced path reecord
/// The cache store
public static FilePathCache GetCacheStore(TimeSpan maxAge)
{
return maxAge == TimeSpan.Zero
? new DisabledCacheStore()
: new DictBackedFilePathCache(maxAge);
}
/*
* A very basic dictionary cache that stores translated paths
* from a request input path to a filesystem path.
*
* This must be thread safe as it's called in a multithreaded context.
*/
private sealed class DictBackedFilePathCache(TimeSpan maxAge) : FilePathCache
{
private readonly ConcurrentDictionary _pathCache = new(StringComparer.OrdinalIgnoreCase);
///
public override bool TryGetMappedPath(string filePath, [NotNullWhen(true)] out string? cachedPath)
{
if (_pathCache.TryGetValue(filePath, out CachedPath cp))
{
//TODO: Implement a cache eviction policy
cachedPath = cp.Path;
return true;
}
cachedPath = null;
return false;
}
///
public override void StorePathMapping(string requestPath, string filePath)
{
ArgumentException.ThrowIfNullOrWhiteSpace(requestPath);
//Cache path is an internal assignment. Should never be null
Debug.Assert(filePath is not null);
//TODO: Implement a cache eviction policy
_pathCache[requestPath] = new CachedPath { Path = filePath, LastStored = DateTime.MinValue.Ticks };
}
private struct CachedPath
{
public required string Path;
public required long LastStored;
}
}
/*
* A cache store that does nothing, it always misses and will
* cause a normal file fetch
*/
private sealed class DisabledCacheStore : FilePathCache
{
///
public override void StorePathMapping(string requestPath, string filePath)
{ }
///
public override bool TryGetMappedPath(string filePath, [NotNullWhen(true)] out string? cachedPath)
{
cachedPath = null;
return false;
}
}
}
}