0
0
mirror of https://github.com/sp-tarkov/loot-dump-processor.git synced 2025-02-13 09:50:44 -05:00

Refactored storage layer to improve thread safety and code consistency

This commit is contained in:
bluextx 2025-01-11 11:26:11 +03:00
parent 6cdf3c1a9d
commit 02802ddc10
9 changed files with 54 additions and 98 deletions

View File

@ -22,9 +22,6 @@ public static class Program
await using var serviceProvider = services.BuildServiceProvider(); await using var serviceProvider = services.BuildServiceProvider();
// Setup Data storage
DataStorageFactory.GetInstance().Setup();
// startup the pipeline // startup the pipeline
var pipeline = serviceProvider.GetRequiredService<IPipeline>(); var pipeline = serviceProvider.GetRequiredService<IPipeline>();
await pipeline.Execute(); await pipeline.Execute();

View File

@ -2,13 +2,14 @@ namespace LootDumpProcessor.Storage;
public abstract class AbstractKey(string[] indexes) : IKey public abstract class AbstractKey(string[] indexes) : IKey
{ {
private string[] _indexes = indexes;
public abstract KeyType GetKeyType(); public abstract KeyType GetKeyType();
public string SerializedKey public string SerializedKey
{ {
get => string.Join("|", indexes); get => string.Join("|", _indexes);
set => indexes = value.Split("|"); set => _indexes = value.Split("|");
} }
public string[] GetLookupIndex() => indexes; public string[] GetLookupIndex() => _indexes;
} }

View File

@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using LootDumpProcessor.Storage.Implementations.File; using LootDumpProcessor.Storage.Implementations.File;
using LootDumpProcessor.Storage.Implementations.Memory; using LootDumpProcessor.Storage.Implementations.Memory;
@ -5,9 +6,7 @@ namespace LootDumpProcessor.Storage;
public static class DataStorageFactory public static class DataStorageFactory
{ {
private static readonly Dictionary<DataStorageTypes, IDataStorage> _dataStorage = new(); private static readonly ConcurrentDictionary<DataStorageTypes, IDataStorage> DataStorage = new();
private static object lockObject = new();
/** /**
* Requires LootDumpProcessorContext to be initialized before using * Requires LootDumpProcessorContext to be initialized before using
@ -15,23 +14,18 @@ public static class DataStorageFactory
public static IDataStorage GetInstance() => public static IDataStorage GetInstance() =>
GetInstance(LootDumpProcessorContext.GetConfig().DataStorageConfig.DataStorageType); GetInstance(LootDumpProcessorContext.GetConfig().DataStorageConfig.DataStorageType);
public static IDataStorage GetInstance(DataStorageTypes type) private static IDataStorage GetInstance(DataStorageTypes type)
{ {
IDataStorage dataStorage; if (DataStorage.TryGetValue(type, out var dataStorage)) return dataStorage;
lock (lockObject)
{
if (!_dataStorage.TryGetValue(type, out dataStorage))
{
dataStorage = type switch
{
DataStorageTypes.File => new FileDataStorage(),
DataStorageTypes.Memory => new MemoryDataStorage(),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
_dataStorage.Add(type, dataStorage); dataStorage = type switch
} {
} DataStorageTypes.File => new FileDataStorage(),
DataStorageTypes.Memory => new MemoryDataStorage(),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
DataStorage.TryAdd(type, dataStorage);
return dataStorage; return dataStorage;
} }

View File

@ -2,9 +2,8 @@ namespace LootDumpProcessor.Storage;
public interface IDataStorage public interface IDataStorage
{ {
void Setup(); void Store<TEntity>(TEntity entity) where TEntity : IKeyable;
void Store<T>(T t) where T : IKeyable; TEntity? GetItem<TEntity>(IKey key) where TEntity : IKeyable;
bool Exists(IKey t); bool Exists(IKey key);
T GetItem<T>(IKey key) where T : IKeyable;
void Clear(); void Clear();
} }

View File

@ -2,19 +2,14 @@ namespace LootDumpProcessor.Storage.Implementations.File;
public class FileDataStorage : IDataStorage public class FileDataStorage : IDataStorage
{ {
public void Setup() public void Store<TEntity>(TEntity entity) where TEntity : IKeyable
{ {
StoreHandlerFactory.GetInstance(entity.GetKey().GetKeyType()).Store(entity);
} }
public void Store<T>(T t) where T : IKeyable public bool Exists(IKey key) => StoreHandlerFactory.GetInstance(key.GetKeyType()).Exists(key);
{
StoreHandlerFactory.GetInstance(t.GetKey().GetKeyType()).Store(t);
}
public bool Exists(IKey t) => StoreHandlerFactory.GetInstance(t.GetKeyType()).Exists(t); public T GetItem<T>(IKey key) where T : IKeyable => StoreHandlerFactory.GetInstance(key.GetKeyType()).Retrieve<T>(key);
public T GetItem<T>(IKey key) where T : IKeyable =>
StoreHandlerFactory.GetInstance(key.GetKeyType()).Retrieve<T>(key);
public void Clear() public void Clear()
{ {

View File

@ -5,27 +5,27 @@ namespace LootDumpProcessor.Storage.Implementations.File.Handlers;
public abstract class AbstractStoreHandler : IStoreHandler public abstract class AbstractStoreHandler : IStoreHandler
{ {
public void Store<T>(T obj, bool failIfDuplicate = true) where T : IKeyable public void Store<TEntity>(TEntity entity, bool failIfDuplicate = true) where TEntity : IKeyable
{ {
var locationWithFile = GetLocation(obj.GetKey()); var locationWithFile = GetLocation(entity.GetKey());
if (System.IO.File.Exists(locationWithFile) && failIfDuplicate) if (System.IO.File.Exists(locationWithFile) && failIfDuplicate)
throw new Exception($"Attempted to save duplicated object into data storage: {locationWithFile}"); throw new Exception($"Attempted to save duplicated object into data storage: {locationWithFile}");
System.IO.File.WriteAllText(locationWithFile, JsonSerializer.Serialize(obj, JsonSerializerSettings.Default)); System.IO.File.WriteAllText(locationWithFile, JsonSerializer.Serialize(entity, JsonSerializerSettings.Default));
} }
public T? Retrieve<T>(IKey obj) where T : IKeyable public TEntity? Retrieve<TEntity>(IKey key) where TEntity : IKeyable
{ {
var locationWithFile = GetLocation(obj); var locationWithFile = GetLocation(key);
if (!System.IO.File.Exists(locationWithFile)) return default; if (!System.IO.File.Exists(locationWithFile)) return default;
return JsonSerializer.Deserialize<T>(System.IO.File.ReadAllText(locationWithFile), return JsonSerializer.Deserialize<TEntity>(System.IO.File.ReadAllText(locationWithFile),
JsonSerializerSettings.Default); JsonSerializerSettings.Default);
} }
public bool Exists(IKey obj) public bool Exists(IKey key)
{ {
var locationWithFile = GetLocation(obj); var locationWithFile = GetLocation(key);
return System.IO.File.Exists(locationWithFile); return System.IO.File.Exists(locationWithFile);
} }

View File

@ -2,7 +2,7 @@ namespace LootDumpProcessor.Storage.Implementations.File;
public interface IStoreHandler public interface IStoreHandler
{ {
void Store<T>(T obj, bool failIfDuplicate = true) where T : IKeyable; void Store<TEntity>(TEntity entity, bool failIfDuplicate = true) where TEntity : IKeyable;
T? Retrieve<T>(IKey obj) where T : IKeyable; TEntity? Retrieve<TEntity>(IKey key) where TEntity : IKeyable;
bool Exists(IKey obj); bool Exists(IKey key);
} }

View File

@ -1,28 +1,23 @@
using System.Collections.Concurrent;
using LootDumpProcessor.Storage.Implementations.File.Handlers; using LootDumpProcessor.Storage.Implementations.File.Handlers;
namespace LootDumpProcessor.Storage.Implementations.File; namespace LootDumpProcessor.Storage.Implementations.File;
public class StoreHandlerFactory public class StoreHandlerFactory
{ {
private static Dictionary<KeyType, IStoreHandler> _handlers = new(); private static readonly ConcurrentDictionary<KeyType, IStoreHandler> Handlers = new();
private static object lockObject = new();
public static IStoreHandler GetInstance(KeyType type) public static IStoreHandler GetInstance(KeyType type)
{ {
IStoreHandler handler; if (Handlers.TryGetValue(type, out var handler)) return handler;
lock (lockObject)
handler = type switch
{ {
if (!_handlers.TryGetValue(type, out handler)) KeyType.Unique => new FlatStoreHandler(),
{ KeyType.Subdivisioned => new SubdivisionedStoreHandler(),
handler = type switch _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
{ };
KeyType.Unique => new FlatStoreHandler(), Handlers.TryAdd(type, handler);
KeyType.Subdivisioned => new SubdivisionedStoreHandler(),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};
_handlers.Add(type, handler);
}
}
return handler; return handler;
} }

View File

@ -1,47 +1,22 @@
using System.Collections.Concurrent;
namespace LootDumpProcessor.Storage.Implementations.Memory; namespace LootDumpProcessor.Storage.Implementations.Memory;
public class MemoryDataStorage : IDataStorage public class MemoryDataStorage : IDataStorage
{ {
private static readonly Dictionary<string, object> CachedObjects = new(); private readonly ConcurrentDictionary<string, object> _storage = new();
private static readonly object _cacheObjectLock = new();
public void Setup() public void Store<TEntity>(TEntity entity) where TEntity : IKeyable =>
_storage.TryAdd(GetLookupKey(entity.GetKey()), entity);
public TEntity? GetItem<TEntity>(IKey key) where TEntity : IKeyable
{ {
} if (_storage.TryGetValue(GetLookupKey(key), out var value)) return (TEntity)value;
public void Store<T>(T t) where T : IKeyable
{
lock (_cacheObjectLock)
{
CachedObjects.Add(GetLookupKey(t.GetKey()), t);
}
}
public bool Exists(IKey t)
{
lock (_cacheObjectLock)
{
return CachedObjects.ContainsKey(GetLookupKey(t));
}
}
public T? GetItem<T>(IKey key) where T : IKeyable
{
lock (_cacheObjectLock)
{
if (CachedObjects.TryGetValue(GetLookupKey(key), out var value)) return (T)value;
}
return default; return default;
} }
private string GetLookupKey(IKey key) => string.Join("-", key.GetLookupIndex()); public bool Exists(IKey key) => _storage.ContainsKey(GetLookupKey(key));
public void Clear() private string GetLookupKey(IKey key) => string.Join("-", key.GetLookupIndex());
{ public void Clear() => _storage.Clear();
lock (_cacheObjectLock)
{
CachedObjects.Clear();
}
}
} }