/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using MessagePack; using UniRx; public interface ILegacyCatalogReader { DatabaseEntry Get(Guid guid); T Get(Guid guid) where T : DatabaseEntry; IEnumerable GetAll() where T : DatabaseEntry; } public class LegacyCatalogCache : ILegacyCatalogReader { private readonly object addLock = new object(); private List _backingStores = new List(); private readonly Dictionary _typeStores = new Dictionary(); private readonly Dictionary _storeTypes = new Dictionary(); private readonly Dictionary _entries = new Dictionary(); private readonly Dictionary _globals = new Dictionary(); private readonly Dictionary> _types = new Dictionary>(); public IEnumerable AllEntries => _entries.Values; public LegacyCatalogCache() { foreach (var type in typeof(DatabaseEntry).GetAllChildClasses()) { _types[type] = new HashSet(); if (type.GetCustomAttribute() != null) { _globals[type] = null; AddInternal(Activator.CreateInstance(type) as DatabaseEntry); } } } public void AddBackingStore(CacheBackingStore store, params Type[] domain) { if (domain.Length > 0) { _storeTypes[store] = domain; foreach (var t in domain) _typeStores[t] = store; } else { _backingStores.Add(store); } store.EntryAdded.Subscribe(entry => { AddInternal(entry, store); }); } public void PullAllBackingStores() { foreach(var store in _backingStores) store.PullAll(); foreach(var store in _storeTypes.Keys) store.PullAll(); } private void AddInternal(DatabaseEntry entry, CacheBackingStore source = null) { lock(addLock) { if (entry != null) { var exists = _entries.ContainsKey(entry.ID); var type = entry.GetType(); if (_globals.ContainsKey(type)) { if(_globals[type]!=null) { RemoveInternal(_globals[type]); exists = true; } _globals[type] = entry; } _entries[entry.ID] = entry; _types[type].Add(entry); foreach (var parentType in type.GetParentTypes()) { if(_types.ContainsKey(parentType)) _types[parentType].Add(entry); } } } } public bool IsGlobal(DatabaseEntry entry) => _globals.ContainsKey(entry.GetType()); public DatabaseEntry Get(Guid guid) { DatabaseEntry entry; _entries.TryGetValue(guid, out entry); return entry; } public T Get(Guid guid) where T : DatabaseEntry { return Get(guid) as T; } public DatabaseEntry GetGlobal(Type type) { if (_globals.ContainsKey(type)) return _globals[type]; return null; } public T GetGlobal() where T : DatabaseEntry { return GetGlobal(typeof(T)) as T; } public IEnumerable GetAll(Type type) { return !_types.ContainsKey(type) ? Enumerable.Empty() : _types[type]; } public IEnumerable GetAll() where T : DatabaseEntry { var type = typeof(T); return !_types.ContainsKey(type) ? Enumerable.Empty() : _types[type].Cast(); } private void RemoveInternal(DatabaseEntry entry) { _entries.Remove(entry.ID); var type = entry.GetType(); foreach (var parentType in type.GetParentTypes()) { if(_types.ContainsKey(parentType)) _types[parentType].Remove(entry); } } } public abstract class CacheBackingStore { protected CacheBackingStore() { EntryAdded = new Subject(); } public abstract void PullAll(); public Subject EntryAdded { get; } protected Dictionary Entries = new Dictionary(); } public abstract class MultiFileBackingStore : CacheBackingStore { public DirectoryInfo DirectoryInfo { get; } protected Dictionary _entryTypeDirectories = new Dictionary(); public abstract DatabaseEntry Deserialize(byte[] data); public abstract string Extension { get; } public MultiFileBackingStore(string path) { DirectoryInfo = new DirectoryInfo(path); foreach (var type in typeof(DatabaseEntry).GetAllChildClasses()) { _entryTypeDirectories[type] = new DirectoryInfo(Path.Combine(DirectoryInfo.FullName, type.Name)); } } public override void PullAll() { if (!DirectoryInfo.Exists) return; foreach (var directory in _entryTypeDirectories.Values) { foreach (var file in directory.EnumerateFiles($"*.{Extension}")) { var entry = Deserialize(File.ReadAllBytes(file.FullName)); Entries[entry.ID] = entry; EntryAdded.OnNext(entry); } } } } public class MultiFileMessagePackBackingStore : MultiFileBackingStore { public MultiFileMessagePackBackingStore(string path) : base(path) { RegisterResolver.Register(); } public override DatabaseEntry Deserialize(byte[] data) { return MessagePackSerializer.Deserialize(data); } public override string Extension => "msgpack"; } public abstract class SingleFileBackingStore : CacheBackingStore { public FileInfo FileInfo { get; } public abstract DatabaseEntry[] Deserialize(byte[] data); public SingleFileBackingStore(string filePath) { FileInfo = new FileInfo(filePath); } public override void PullAll() { if (!FileInfo.Exists) return; foreach (var entry in Deserialize(File.ReadAllBytes(FileInfo.FullName))) { Entries[entry.ID] = entry; EntryAdded.OnNext(entry); } } } public class SingleFileMessagePackBackingStore : SingleFileBackingStore { public SingleFileMessagePackBackingStore(string filePath) : base(filePath) { RegisterResolver.Register(); } public override DatabaseEntry[] Deserialize(byte[] data) { return MessagePackSerializer.Deserialize(data); } }