2020-12-22 03:13:43 +00:00
|
|
|
|
/* 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;
|
2020-03-27 16:46:46 +00:00
|
|
|
|
using System.Collections;
|
2020-03-22 19:05:26 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
2021-03-16 11:06:54 +00:00
|
|
|
|
using System.Reflection;
|
2020-03-22 19:05:26 +00:00
|
|
|
|
using MessagePack;
|
|
|
|
|
|
|
|
|
|
|
|
public class DatabaseCache
|
|
|
|
|
|
{
|
2020-03-24 14:36:22 +00:00
|
|
|
|
public event Action<DatabaseEntry> OnDataInsertLocal;
|
2020-03-22 19:05:26 +00:00
|
|
|
|
public event Action<DatabaseEntry> OnDataUpdateLocal;
|
2020-03-24 00:02:23 +00:00
|
|
|
|
public event Action<DatabaseEntry> OnDataDeleteLocal;
|
2020-05-22 22:34:08 +01:00
|
|
|
|
public event Action<DatabaseEntry> OnDataInsertRemote;
|
2020-03-24 14:36:22 +00:00
|
|
|
|
public event Action<DatabaseEntry> OnDataUpdateRemote;
|
2020-05-22 22:34:08 +01:00
|
|
|
|
public event Action<DatabaseEntry> OnDataDeleteRemote;
|
2020-03-22 19:05:26 +00:00
|
|
|
|
|
2021-03-04 03:58:46 +00:00
|
|
|
|
private readonly object addLock = new object();
|
|
|
|
|
|
|
2021-03-16 11:06:54 +00:00
|
|
|
|
//public Action<string> Logger = Console.WriteLine;
|
|
|
|
|
|
private string _filePath;
|
2020-03-22 19:05:26 +00:00
|
|
|
|
|
|
|
|
|
|
private readonly Dictionary<Guid, DatabaseEntry> _entries = new Dictionary<Guid, DatabaseEntry>();
|
|
|
|
|
|
|
2021-03-16 11:06:54 +00:00
|
|
|
|
private readonly Dictionary<Type, HashSet<DatabaseEntry>> _types = new Dictionary<Type, HashSet<DatabaseEntry>>();
|
|
|
|
|
|
|
|
|
|
|
|
private readonly Dictionary<Type, (DirectoryInfo directory, List<Guid> entries)> _externalTypes = new Dictionary<Type, (DirectoryInfo directory, List<Guid> entries)>();
|
|
|
|
|
|
|
|
|
|
|
|
private class ExternalEntry
|
|
|
|
|
|
{
|
|
|
|
|
|
public string FilePath;
|
|
|
|
|
|
public DatabaseEntry Entry;
|
|
|
|
|
|
|
|
|
|
|
|
public ExternalEntry(string filePath, DatabaseEntry entry)
|
|
|
|
|
|
{
|
|
|
|
|
|
FilePath = filePath;
|
|
|
|
|
|
Entry = entry;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
private readonly Dictionary<Guid, ExternalEntry> _externalEntries = new Dictionary<Guid, ExternalEntry>();
|
2020-03-22 19:05:26 +00:00
|
|
|
|
|
|
|
|
|
|
public IEnumerable<DatabaseEntry> AllEntries => _entries.Values;
|
|
|
|
|
|
|
2021-03-16 11:06:54 +00:00
|
|
|
|
public DatabaseCache(string filePath)
|
|
|
|
|
|
{
|
|
|
|
|
|
RegisterResolver.Register();
|
|
|
|
|
|
_filePath = filePath;
|
|
|
|
|
|
var fileInfo = new FileInfo(filePath);
|
|
|
|
|
|
var dataDirectory = fileInfo.Directory;
|
|
|
|
|
|
|
|
|
|
|
|
// Entry types can be marked external
|
|
|
|
|
|
// External entries are stored individually and loaded on demand
|
|
|
|
|
|
// This is mainly to accomodate large files like datasets
|
|
|
|
|
|
foreach (var type in typeof(DatabaseEntry).GetAllChildClasses())
|
|
|
|
|
|
{
|
|
|
|
|
|
if (type.GetCustomAttribute<ExternalEntryAttribute>() != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var typeDirectory = dataDirectory.CreateSubdirectory(type.Name);
|
|
|
|
|
|
_externalTypes[type] = (typeDirectory, new List<Guid>());
|
|
|
|
|
|
|
|
|
|
|
|
// Cache the GUIDs for all available external entries for querying
|
|
|
|
|
|
// Initially set values to null to indicate they haven't been loaded yet
|
|
|
|
|
|
foreach (var file in typeDirectory.EnumerateFiles("*.msgpack"))
|
|
|
|
|
|
{
|
|
|
|
|
|
Guid id;
|
|
|
|
|
|
if (Guid.TryParse(file.Name.Substring(0, file.Name.IndexOf('.')), out id))
|
|
|
|
|
|
{
|
|
|
|
|
|
_externalEntries.Add(id, new ExternalEntry(file.FullName, null));
|
|
|
|
|
|
_externalTypes[type].entries.Add(id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else _types[type] = new HashSet<DatabaseEntry>();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-22 19:05:26 +00:00
|
|
|
|
public void Add(DatabaseEntry entry, bool remote = false)
|
|
|
|
|
|
{
|
2021-03-04 03:58:46 +00:00
|
|
|
|
lock(addLock)
|
2020-03-22 19:05:26 +00:00
|
|
|
|
{
|
2021-03-04 03:58:46 +00:00
|
|
|
|
if (entry != null)
|
2020-03-22 19:05:26 +00:00
|
|
|
|
{
|
2021-03-16 11:06:54 +00:00
|
|
|
|
entry.Database = this;
|
2021-03-04 03:58:46 +00:00
|
|
|
|
var exists = _entries.ContainsKey(entry.ID);
|
2021-03-16 11:06:54 +00:00
|
|
|
|
|
|
|
|
|
|
// If an external entry is added, store it separately
|
2021-03-04 03:58:46 +00:00
|
|
|
|
var type = entry.GetType();
|
2021-03-16 11:06:54 +00:00
|
|
|
|
if (_externalTypes.ContainsKey(type))
|
2021-03-04 03:58:46 +00:00
|
|
|
|
{
|
2021-03-16 11:06:54 +00:00
|
|
|
|
if (_externalEntries.ContainsKey(entry.ID))
|
|
|
|
|
|
{
|
|
|
|
|
|
_externalEntries[entry.ID].Entry = entry;
|
|
|
|
|
|
}
|
2021-03-04 03:58:46 +00:00
|
|
|
|
else
|
2021-03-16 11:06:54 +00:00
|
|
|
|
{
|
|
|
|
|
|
_externalTypes[type].entries.Add(entry.ID);
|
|
|
|
|
|
_externalEntries[entry.ID] = new ExternalEntry(
|
|
|
|
|
|
Path.Combine(_externalTypes[type].directory.FullName, $"{entry.ID.ToString()}.msgpack"),
|
|
|
|
|
|
entry);
|
|
|
|
|
|
}
|
2021-03-04 03:58:46 +00:00
|
|
|
|
}
|
2020-03-24 14:36:22 +00:00
|
|
|
|
else
|
2021-03-04 03:58:46 +00:00
|
|
|
|
{
|
2021-03-16 11:06:54 +00:00
|
|
|
|
_entries[entry.ID] = entry;
|
|
|
|
|
|
_types[type].Add(entry);
|
|
|
|
|
|
|
|
|
|
|
|
if (remote)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (exists)
|
|
|
|
|
|
OnDataUpdateRemote?.Invoke(entry);
|
|
|
|
|
|
else
|
|
|
|
|
|
OnDataInsertRemote?.Invoke(entry);
|
|
|
|
|
|
}
|
2021-03-04 03:58:46 +00:00
|
|
|
|
else
|
2021-03-16 11:06:54 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (exists)
|
|
|
|
|
|
OnDataUpdateLocal?.Invoke(entry);
|
|
|
|
|
|
else
|
|
|
|
|
|
OnDataInsertLocal?.Invoke(entry);
|
|
|
|
|
|
}
|
2021-03-04 03:58:46 +00:00
|
|
|
|
}
|
2020-03-24 14:36:22 +00:00
|
|
|
|
}
|
2020-03-22 19:05:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddAll(IEnumerable<DatabaseEntry> entries, bool remote = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var entry in entries)
|
|
|
|
|
|
{
|
|
|
|
|
|
Add(entry, remote);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public DatabaseEntry Get(Guid guid)
|
|
|
|
|
|
{
|
2021-03-16 11:06:54 +00:00
|
|
|
|
if (_externalEntries.ContainsKey(guid))
|
|
|
|
|
|
{
|
|
|
|
|
|
if(_externalEntries[guid].Entry == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var bytes = File.ReadAllBytes(_externalEntries[guid].FilePath);
|
|
|
|
|
|
var e = MessagePackSerializer.Deserialize<DatabaseEntry>(bytes);
|
|
|
|
|
|
e.Database = this;
|
|
|
|
|
|
_externalEntries[guid].Entry = e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return _externalEntries[guid].Entry;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-03-22 19:05:26 +00:00
|
|
|
|
DatabaseEntry entry;
|
|
|
|
|
|
_entries.TryGetValue(guid, out entry);
|
|
|
|
|
|
return entry;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public T Get<T>(Guid guid) where T : DatabaseEntry
|
|
|
|
|
|
{
|
2021-03-16 11:06:54 +00:00
|
|
|
|
return Get(guid) as T;
|
2020-03-22 19:05:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-16 11:06:54 +00:00
|
|
|
|
public IEnumerable<DatabaseEntry> GetAll(Type type)
|
2020-03-22 19:05:26 +00:00
|
|
|
|
{
|
2021-03-16 11:06:54 +00:00
|
|
|
|
if (_externalTypes.ContainsKey(type))
|
|
|
|
|
|
{
|
|
|
|
|
|
return _externalTypes[type].entries.Select(Get);
|
|
|
|
|
|
}
|
|
|
|
|
|
return !_types.ContainsKey(type) ? Enumerable.Empty<DatabaseEntry>() : _types[type];
|
2020-03-22 19:05:26 +00:00
|
|
|
|
}
|
2020-03-24 00:02:23 +00:00
|
|
|
|
|
2021-03-16 11:06:54 +00:00
|
|
|
|
public IEnumerable<T> GetAll<T>() where T : DatabaseEntry
|
2020-03-24 00:02:23 +00:00
|
|
|
|
{
|
2021-03-16 11:06:54 +00:00
|
|
|
|
var type = typeof(T);
|
|
|
|
|
|
if (_externalTypes.ContainsKey(type))
|
2020-03-24 00:02:23 +00:00
|
|
|
|
{
|
2021-03-16 11:06:54 +00:00
|
|
|
|
return _externalTypes[type].entries.Select(Get<T>);
|
2020-03-24 00:02:23 +00:00
|
|
|
|
}
|
2021-03-16 11:06:54 +00:00
|
|
|
|
return !_types.ContainsKey(type) ? Enumerable.Empty<T>() : _types[type].Cast<T>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Delete(DatabaseEntry entry, bool remote = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
_entries.Remove(entry.ID);
|
2020-03-24 00:02:23 +00:00
|
|
|
|
|
2020-05-22 22:34:08 +01:00
|
|
|
|
if (remote)
|
|
|
|
|
|
OnDataDeleteRemote?.Invoke(entry);
|
|
|
|
|
|
else
|
2020-03-27 01:53:03 +00:00
|
|
|
|
OnDataDeleteLocal?.Invoke(entry);
|
2020-03-24 00:02:23 +00:00
|
|
|
|
}
|
2020-04-23 03:31:17 +01:00
|
|
|
|
|
2021-03-16 11:06:54 +00:00
|
|
|
|
public void Load()
|
2020-04-23 03:31:17 +01:00
|
|
|
|
{
|
2021-03-16 11:06:54 +00:00
|
|
|
|
var bytes = File.ReadAllBytes(_filePath);
|
2020-04-23 03:31:17 +01:00
|
|
|
|
var entries = MessagePackSerializer.Deserialize<DatabaseEntry[]>(bytes);
|
2021-03-16 11:06:54 +00:00
|
|
|
|
foreach (var entry in entries) entry.Database = this;
|
2020-04-23 03:31:17 +01:00
|
|
|
|
AddAll(entries);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-16 11:06:54 +00:00
|
|
|
|
public void Save()
|
2020-04-23 03:31:17 +01:00
|
|
|
|
{
|
|
|
|
|
|
var entries = AllEntries.ToArray();
|
2021-03-16 11:06:54 +00:00
|
|
|
|
File.WriteAllBytes(_filePath, MessagePackSerializer.Serialize(entries));
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var external in _externalEntries.Values)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (external.Entry != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
File.WriteAllBytes(external.FilePath, MessagePackSerializer.Serialize(external.Entry));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-04-23 03:31:17 +01:00
|
|
|
|
}
|
2020-03-22 19:05:26 +00:00
|
|
|
|
}
|