2020-09-01 11:54:16 +01:00
|
|
|
|
using System;
|
2020-09-03 00:58:07 +01:00
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
|
using System.IO;
|
2020-09-03 00:14:11 +01:00
|
|
|
|
using System.Linq;
|
2020-09-03 00:39:05 +01:00
|
|
|
|
using System.Runtime.InteropServices;
|
2020-09-01 11:54:16 +01:00
|
|
|
|
using System.Threading.Tasks;
|
2020-09-03 00:14:11 +01:00
|
|
|
|
using RomRepoMgr.Database;
|
2020-09-03 00:58:07 +01:00
|
|
|
|
using RomRepoMgr.Database.Models;
|
2020-09-01 11:54:16 +01:00
|
|
|
|
|
2020-08-30 14:22:41 +01:00
|
|
|
|
namespace RomRepoMgr.Core.Filesystem
|
|
|
|
|
|
{
|
2020-09-01 11:54:16 +01:00
|
|
|
|
public class Vfs : IDisposable
|
2020-08-30 14:22:41 +01:00
|
|
|
|
{
|
2020-09-03 01:25:06 +01:00
|
|
|
|
readonly ConcurrentDictionary<long, ConcurrentDictionary<string, CachedMachine>> _machinesStatCache;
|
|
|
|
|
|
readonly ConcurrentDictionary<long, RomSet> _romSetsCache;
|
|
|
|
|
|
Fuse _fuse;
|
|
|
|
|
|
ConcurrentDictionary<string, long> _rootDirectoryCache;
|
|
|
|
|
|
Winfsp _winfsp;
|
2020-09-03 00:58:07 +01:00
|
|
|
|
|
|
|
|
|
|
public Vfs()
|
|
|
|
|
|
{
|
|
|
|
|
|
_rootDirectoryCache = new ConcurrentDictionary<string, long>();
|
|
|
|
|
|
_romSetsCache = new ConcurrentDictionary<long, RomSet>();
|
2020-09-03 01:25:06 +01:00
|
|
|
|
_machinesStatCache = new ConcurrentDictionary<long, ConcurrentDictionary<string, CachedMachine>>();
|
2020-09-03 00:58:07 +01:00
|
|
|
|
}
|
2020-09-01 11:54:16 +01:00
|
|
|
|
|
2020-08-30 14:22:41 +01:00
|
|
|
|
public static bool IsAvailable => Winfsp.IsAvailable || Fuse.IsAvailable;
|
2020-09-01 11:54:16 +01:00
|
|
|
|
|
|
|
|
|
|
public void Dispose() => _fuse?.Dispose();
|
|
|
|
|
|
|
|
|
|
|
|
public event EventHandler<System.EventArgs> Umounted;
|
|
|
|
|
|
|
2020-09-02 16:53:21 +01:00
|
|
|
|
public void MountTo(string mountPoint)
|
2020-09-01 11:54:16 +01:00
|
|
|
|
{
|
|
|
|
|
|
if(Fuse.IsAvailable)
|
|
|
|
|
|
{
|
2020-09-03 00:14:11 +01:00
|
|
|
|
_fuse = new Fuse(this)
|
2020-09-01 11:54:16 +01:00
|
|
|
|
{
|
2020-09-02 16:53:21 +01:00
|
|
|
|
MountPoint = mountPoint
|
2020-09-01 11:54:16 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
Task.Run(() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
_fuse.Start();
|
|
|
|
|
|
|
|
|
|
|
|
Umounted?.Invoke(this, System.EventArgs.Empty);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2020-09-02 16:53:21 +01:00
|
|
|
|
else if(Winfsp.IsAvailable)
|
|
|
|
|
|
{
|
2020-09-03 00:14:11 +01:00
|
|
|
|
_winfsp = new Winfsp(this);
|
2020-09-02 16:53:21 +01:00
|
|
|
|
bool ret = _winfsp.Mount(mountPoint);
|
|
|
|
|
|
|
|
|
|
|
|
if(ret)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
_winfsp = null;
|
|
|
|
|
|
Umounted?.Invoke(this, System.EventArgs.Empty);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
Umounted?.Invoke(this, System.EventArgs.Empty);
|
2020-09-01 11:54:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Umount()
|
|
|
|
|
|
{
|
|
|
|
|
|
_fuse?.Umount();
|
|
|
|
|
|
_fuse = null;
|
2020-09-02 16:53:21 +01:00
|
|
|
|
_winfsp?.Umount();
|
|
|
|
|
|
_winfsp = null;
|
|
|
|
|
|
|
|
|
|
|
|
Umounted?.Invoke(this, System.EventArgs.Empty);
|
2020-09-01 11:54:16 +01:00
|
|
|
|
}
|
2020-09-03 00:14:11 +01:00
|
|
|
|
|
|
|
|
|
|
internal void GetInfo(out ulong files, out ulong totalSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
|
|
|
|
|
|
|
|
|
|
|
|
totalSize = (ulong)ctx.Files.Where(f => f.IsInRepo).Sum(f => (double)f.Size);
|
|
|
|
|
|
files = (ulong)ctx.Files.Count(f => f.IsInRepo);
|
|
|
|
|
|
}
|
2020-09-03 00:39:05 +01:00
|
|
|
|
|
|
|
|
|
|
internal string[] SplitPath(string path) =>
|
|
|
|
|
|
path.Split(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "\\" : "/",
|
|
|
|
|
|
StringSplitOptions.RemoveEmptyEntries);
|
2020-09-03 00:58:07 +01:00
|
|
|
|
|
|
|
|
|
|
void FillRootDirectoryCache()
|
|
|
|
|
|
{
|
|
|
|
|
|
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
|
|
|
|
|
|
|
|
|
|
|
|
ConcurrentDictionary<string, long> rootCache = new ConcurrentDictionary<string, long>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach(RomSet set in ctx.RomSets)
|
|
|
|
|
|
{
|
|
|
|
|
|
string name;
|
|
|
|
|
|
|
|
|
|
|
|
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
|
|
|
|
{
|
|
|
|
|
|
name = set.Name.Replace('/', '∕').Replace('<', '\uFF1C').Replace('>', '\uFF1E').
|
|
|
|
|
|
Replace(':', '\uFF1A').Replace('"', '\u2033').Replace('\\', '\').Replace('|', '|').
|
|
|
|
|
|
Replace('?', '?').Replace('*', '*');
|
|
|
|
|
|
|
|
|
|
|
|
if(rootCache.ContainsKey(name))
|
|
|
|
|
|
name = Path.GetFileNameWithoutExtension(set.Filename)?.Replace('/', '∕').Replace('<', '\uFF1C').
|
|
|
|
|
|
Replace('>', '\uFF1E').Replace(':', '\uFF1A').Replace('"', '\u2033').
|
|
|
|
|
|
Replace('\\', '\').Replace('|', '|').Replace('?', '?').Replace('*', '*');
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
name = set.Name.Replace('/', '∕');
|
|
|
|
|
|
|
|
|
|
|
|
if(rootCache.ContainsKey(name))
|
|
|
|
|
|
name = Path.GetFileNameWithoutExtension(set.Filename)?.Replace('/', '∕');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(name == null ||
|
|
|
|
|
|
rootCache.ContainsKey(name))
|
|
|
|
|
|
name = Path.GetFileNameWithoutExtension(set.Sha384);
|
|
|
|
|
|
|
|
|
|
|
|
if(name == null)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
rootCache[name] = set.Id;
|
|
|
|
|
|
_romSetsCache[set.Id] = set;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_rootDirectoryCache = rootCache;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal long GetRomSetId(string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(_rootDirectoryCache.Count == 0)
|
|
|
|
|
|
FillRootDirectoryCache();
|
|
|
|
|
|
|
|
|
|
|
|
if(!_rootDirectoryCache.TryGetValue(name, out long romSetId))
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
return romSetId;
|
|
|
|
|
|
}
|
2020-09-03 01:25:06 +01:00
|
|
|
|
|
|
|
|
|
|
internal RomSet GetRomSet(long id)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(_romSetsCache.TryGetValue(id, out RomSet romSet))
|
|
|
|
|
|
return romSet;
|
|
|
|
|
|
|
|
|
|
|
|
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
|
|
|
|
|
|
|
|
|
|
|
|
romSet = ctx.RomSets.Find(id);
|
|
|
|
|
|
|
|
|
|
|
|
if(romSet == null)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
_romSetsCache[id] = romSet;
|
|
|
|
|
|
|
|
|
|
|
|
return romSet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal ConcurrentDictionary<string, CachedMachine> GetMachinesFromRomSet(long id)
|
|
|
|
|
|
{
|
|
|
|
|
|
_machinesStatCache.TryGetValue(id, out ConcurrentDictionary<string, CachedMachine> cachedMachines);
|
|
|
|
|
|
|
|
|
|
|
|
if(cachedMachines != null)
|
|
|
|
|
|
return cachedMachines;
|
|
|
|
|
|
|
|
|
|
|
|
cachedMachines = new ConcurrentDictionary<string, CachedMachine>();
|
|
|
|
|
|
|
|
|
|
|
|
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
|
|
|
|
|
|
|
|
|
|
|
|
foreach(Machine mach in ctx.Machines.Where(m => m.RomSet.Id == id))
|
|
|
|
|
|
{
|
|
|
|
|
|
cachedMachines[mach.Name] = new CachedMachine
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = mach.Id,
|
|
|
|
|
|
CreationDate = mach.CreatedOn,
|
|
|
|
|
|
ModificationDate = mach.UpdatedOn
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_machinesStatCache[id] = cachedMachines;
|
|
|
|
|
|
|
|
|
|
|
|
return cachedMachines;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal CachedMachine GetMachine(long romSetId, string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
ConcurrentDictionary<string, CachedMachine> cachedMachines = GetMachinesFromRomSet(romSetId);
|
|
|
|
|
|
|
|
|
|
|
|
if(cachedMachines == null ||
|
|
|
|
|
|
!cachedMachines.TryGetValue(name, out CachedMachine machine))
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
return machine;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal sealed class CachedMachine
|
|
|
|
|
|
{
|
|
|
|
|
|
public ulong Id { get; set; }
|
|
|
|
|
|
public DateTime CreationDate { get; set; }
|
|
|
|
|
|
public DateTime ModificationDate { get; set; }
|
2020-08-30 14:22:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|