2020-09-01 11:54:16 +01:00
|
|
|
|
using System;
|
2020-09-03 00:58:07 +01:00
|
|
|
|
using System.Collections.Concurrent;
|
2020-09-03 01:54:56 +01:00
|
|
|
|
using System.Collections.Generic;
|
2020-09-03 00:58:07 +01:00
|
|
|
|
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-03 01:54:56 +01:00
|
|
|
|
using SharpCompress.Compressors;
|
|
|
|
|
|
using SharpCompress.Compressors.LZMA;
|
2020-09-01 11:54:16 +01:00
|
|
|
|
|
2020-08-30 14:22:41 +01:00
|
|
|
|
namespace RomRepoMgr.Core.Filesystem
|
|
|
|
|
|
{
|
2020-09-03 01:54:56 +01:00
|
|
|
|
// TODO: Last handle goes negative
|
|
|
|
|
|
// TODO: Invalidate caches
|
|
|
|
|
|
// TODO: Mount options
|
|
|
|
|
|
// TODO: Do not show machines or romsets with no ROMs in repo
|
2020-09-01 11:54:16 +01:00
|
|
|
|
public class Vfs : IDisposable
|
2020-08-30 14:22:41 +01:00
|
|
|
|
{
|
2020-09-04 20:38:43 +01:00
|
|
|
|
readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, CachedDisk>> _machineDisksCache;
|
2020-09-03 01:42:18 +01:00
|
|
|
|
readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, CachedFile>> _machineFilesCache;
|
2020-09-04 22:19:40 +01:00
|
|
|
|
readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, CachedMedia>> _machineMediasCache;
|
2020-09-03 01:25:06 +01:00
|
|
|
|
readonly ConcurrentDictionary<long, ConcurrentDictionary<string, CachedMachine>> _machinesStatCache;
|
|
|
|
|
|
readonly ConcurrentDictionary<long, RomSet> _romSetsCache;
|
2020-09-03 01:54:56 +01:00
|
|
|
|
readonly ConcurrentDictionary<long, Stream> _streamsCache;
|
2020-09-03 01:25:06 +01:00
|
|
|
|
Fuse _fuse;
|
2020-09-03 01:54:56 +01:00
|
|
|
|
long _lastHandle;
|
2020-09-03 01:25:06 +01:00
|
|
|
|
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 01:42:18 +01:00
|
|
|
|
_machineFilesCache = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, CachedFile>>();
|
2020-09-04 20:38:43 +01:00
|
|
|
|
_machineDisksCache = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, CachedDisk>>();
|
2020-09-04 22:19:40 +01:00
|
|
|
|
_machineMediasCache = new ConcurrentDictionary<ulong, ConcurrentDictionary<string, CachedMedia>>();
|
2020-09-03 01:54:56 +01:00
|
|
|
|
_streamsCache = new ConcurrentDictionary<long, Stream>();
|
|
|
|
|
|
_lastHandle = 0;
|
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
|
|
|
|
|
2020-09-03 01:54:56 +01:00
|
|
|
|
public void Dispose() => Umount();
|
2020-09-01 11:54:16 +01:00
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
2020-09-03 01:54:56 +01:00
|
|
|
|
CleanUp();
|
2020-09-01 11:54:16 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
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;
|
2020-09-03 01:54:56 +01:00
|
|
|
|
CleanUp();
|
2020-09-02 16:53:21 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
2020-09-03 01:54:56 +01:00
|
|
|
|
CleanUp();
|
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;
|
|
|
|
|
|
|
2020-09-03 01:54:56 +01:00
|
|
|
|
CleanUp();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void CleanUp()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach(KeyValuePair<long, Stream> handle in _streamsCache)
|
|
|
|
|
|
handle.Value.Close();
|
|
|
|
|
|
|
|
|
|
|
|
_streamsCache.Clear();
|
|
|
|
|
|
_lastHandle = 0;
|
|
|
|
|
|
|
2020-09-02 16:53:21 +01:00
|
|
|
|
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);
|
|
|
|
|
|
|
2020-09-04 22:19:40 +01:00
|
|
|
|
totalSize = (ulong)(ctx.Files.Where(f => f.IsInRepo).Sum(f => (double)f.Size) +
|
|
|
|
|
|
ctx.Disks.Where(f => f.IsInRepo).Sum(f => (double)f.Size) +
|
|
|
|
|
|
ctx.Medias.Where(f => f.IsInRepo).Sum(f => (double)f.Size));
|
|
|
|
|
|
|
|
|
|
|
|
files = (ulong)(ctx.Files.Count(f => f.IsInRepo) + ctx.Disks.Count(f => f.IsInRepo) +
|
|
|
|
|
|
ctx.Medias.Count(f => f.IsInRepo));
|
2020-09-03 00:14:11 +01:00
|
|
|
|
}
|
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;
|
|
|
|
|
|
}
|
2020-09-03 01:42:18 +01:00
|
|
|
|
|
|
|
|
|
|
internal ConcurrentDictionary<string, CachedFile> GetFilesFromMachine(ulong id)
|
|
|
|
|
|
{
|
|
|
|
|
|
_machineFilesCache.TryGetValue(id, out ConcurrentDictionary<string, CachedFile> cachedMachineFiles);
|
|
|
|
|
|
|
|
|
|
|
|
if(cachedMachineFiles != null)
|
|
|
|
|
|
return cachedMachineFiles;
|
|
|
|
|
|
|
|
|
|
|
|
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
|
|
|
|
|
|
|
|
|
|
|
|
cachedMachineFiles = new ConcurrentDictionary<string, CachedFile>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach(FileByMachine machineFile in ctx.FilesByMachines.Where(fbm => fbm.Machine.Id == id &&
|
|
|
|
|
|
fbm.File.IsInRepo))
|
|
|
|
|
|
{
|
|
|
|
|
|
var cachedFile = new CachedFile
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = machineFile.File.Id,
|
|
|
|
|
|
Crc32 = machineFile.File.Crc32,
|
|
|
|
|
|
Md5 = machineFile.File.Md5,
|
|
|
|
|
|
Sha1 = machineFile.File.Sha1,
|
|
|
|
|
|
Sha256 = machineFile.File.Sha256,
|
|
|
|
|
|
Sha384 = machineFile.File.Sha384,
|
|
|
|
|
|
Sha512 = machineFile.File.Sha512,
|
|
|
|
|
|
Size = machineFile.File.Size,
|
|
|
|
|
|
CreatedOn = machineFile.File.CreatedOn,
|
2020-09-06 19:09:35 +01:00
|
|
|
|
UpdatedOn = machineFile.File.UpdatedOn,
|
|
|
|
|
|
FileLastModification = machineFile.FileLastModification
|
2020-09-03 01:42:18 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
cachedMachineFiles[machineFile.Name] = cachedFile;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_machineFilesCache[id] = cachedMachineFiles;
|
|
|
|
|
|
|
|
|
|
|
|
return cachedMachineFiles;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-04 20:38:43 +01:00
|
|
|
|
internal ConcurrentDictionary<string, CachedDisk> GetDisksFromMachine(ulong id)
|
|
|
|
|
|
{
|
|
|
|
|
|
_machineDisksCache.TryGetValue(id, out ConcurrentDictionary<string, CachedDisk> cachedMachineDisks);
|
|
|
|
|
|
|
|
|
|
|
|
if(cachedMachineDisks != null)
|
|
|
|
|
|
return cachedMachineDisks;
|
|
|
|
|
|
|
|
|
|
|
|
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
|
|
|
|
|
|
|
|
|
|
|
|
cachedMachineDisks = new ConcurrentDictionary<string, CachedDisk>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach(DiskByMachine machineDisk in ctx.DisksByMachines.Where(dbm => dbm.Machine.Id == id &&
|
|
|
|
|
|
dbm.Disk.IsInRepo &&
|
|
|
|
|
|
dbm.Disk.Size != null))
|
|
|
|
|
|
{
|
|
|
|
|
|
var cachedDisk = new CachedDisk
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = machineDisk.Disk.Id,
|
|
|
|
|
|
Md5 = machineDisk.Disk.Md5,
|
|
|
|
|
|
Sha1 = machineDisk.Disk.Sha1,
|
|
|
|
|
|
Size = machineDisk.Disk.Size ?? 0,
|
|
|
|
|
|
CreatedOn = machineDisk.Disk.CreatedOn,
|
|
|
|
|
|
UpdatedOn = machineDisk.Disk.UpdatedOn
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
cachedMachineDisks[machineDisk.Name] = cachedDisk;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_machineDisksCache[id] = cachedMachineDisks;
|
|
|
|
|
|
|
|
|
|
|
|
return cachedMachineDisks;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-04 22:19:40 +01:00
|
|
|
|
internal ConcurrentDictionary<string, CachedMedia> GetMediasFromMachine(ulong id)
|
|
|
|
|
|
{
|
|
|
|
|
|
_machineMediasCache.TryGetValue(id, out ConcurrentDictionary<string, CachedMedia> cachedMachineMedias);
|
|
|
|
|
|
|
|
|
|
|
|
if(cachedMachineMedias != null)
|
|
|
|
|
|
return cachedMachineMedias;
|
|
|
|
|
|
|
|
|
|
|
|
using var ctx = Context.Create(Settings.Settings.Current.DatabasePath);
|
|
|
|
|
|
|
|
|
|
|
|
cachedMachineMedias = new ConcurrentDictionary<string, CachedMedia>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach(MediaByMachine machineMedia in ctx.MediasByMachines.Where(mbm => mbm.Machine.Id == id &&
|
|
|
|
|
|
mbm.Media.IsInRepo &&
|
|
|
|
|
|
mbm.Media.Size != null))
|
|
|
|
|
|
{
|
|
|
|
|
|
var cachedDisk = new CachedMedia
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = machineMedia.Media.Id,
|
|
|
|
|
|
Md5 = machineMedia.Media.Md5,
|
|
|
|
|
|
Sha1 = machineMedia.Media.Sha1,
|
|
|
|
|
|
Sha256 = machineMedia.Media.Sha256,
|
|
|
|
|
|
SpamSum = machineMedia.Media.SpamSum,
|
|
|
|
|
|
Size = machineMedia.Media.Size ?? 0,
|
|
|
|
|
|
CreatedOn = machineMedia.Media.CreatedOn,
|
|
|
|
|
|
UpdatedOn = machineMedia.Media.UpdatedOn
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
cachedMachineMedias[machineMedia.Name] = cachedDisk;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_machineMediasCache[id] = cachedMachineMedias;
|
|
|
|
|
|
|
|
|
|
|
|
return cachedMachineMedias;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-03 01:42:18 +01:00
|
|
|
|
internal CachedFile GetFile(ulong machineId, string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
ConcurrentDictionary<string, CachedFile> cachedFiles = GetFilesFromMachine(machineId);
|
|
|
|
|
|
|
|
|
|
|
|
if(cachedFiles == null ||
|
|
|
|
|
|
!cachedFiles.TryGetValue(name, out CachedFile file))
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
return file;
|
|
|
|
|
|
}
|
2020-09-03 01:54:56 +01:00
|
|
|
|
|
2020-09-04 20:38:43 +01:00
|
|
|
|
internal CachedDisk GetDisk(ulong machineId, string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(name.EndsWith(".chd", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
name = name.Substring(0, name.Length - 4);
|
|
|
|
|
|
|
|
|
|
|
|
ConcurrentDictionary<string, CachedDisk> cachedDisks = GetDisksFromMachine(machineId);
|
|
|
|
|
|
|
|
|
|
|
|
if(cachedDisks == null ||
|
|
|
|
|
|
!cachedDisks.TryGetValue(name, out CachedDisk disk))
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
return disk;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-04 22:19:40 +01:00
|
|
|
|
internal CachedMedia GetMedia(ulong machineId, string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(name.EndsWith(".aif", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
name = name.Substring(0, name.Length - 4);
|
|
|
|
|
|
|
|
|
|
|
|
ConcurrentDictionary<string, CachedMedia> cachedMedias = GetMediasFromMachine(machineId);
|
|
|
|
|
|
|
|
|
|
|
|
if(cachedMedias == null ||
|
|
|
|
|
|
!cachedMedias.TryGetValue(name, out CachedMedia media))
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
return media;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-09-03 01:54:56 +01:00
|
|
|
|
internal long Open(string sha384, long fileSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] sha384Bytes = new byte[48];
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 48; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(sha384[i * 2] >= 0x30 &&
|
|
|
|
|
|
sha384[i * 2] <= 0x39)
|
|
|
|
|
|
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x30) * 0x10);
|
|
|
|
|
|
else if(sha384[i * 2] >= 0x41 &&
|
|
|
|
|
|
sha384[i * 2] <= 0x46)
|
|
|
|
|
|
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x37) * 0x10);
|
|
|
|
|
|
else if(sha384[i * 2] >= 0x61 &&
|
|
|
|
|
|
sha384[i * 2] <= 0x66)
|
|
|
|
|
|
sha384Bytes[i] = (byte)((sha384[i * 2] - 0x57) * 0x10);
|
|
|
|
|
|
|
|
|
|
|
|
if(sha384[(i * 2) + 1] >= 0x30 &&
|
|
|
|
|
|
sha384[(i * 2) + 1] <= 0x39)
|
|
|
|
|
|
sha384Bytes[i] += (byte)(sha384[(i * 2) + 1] - 0x30);
|
|
|
|
|
|
else if(sha384[(i * 2) + 1] >= 0x41 &&
|
|
|
|
|
|
sha384[(i * 2) + 1] <= 0x46)
|
|
|
|
|
|
sha384Bytes[i] += (byte)(sha384[(i * 2) + 1] - 0x37);
|
|
|
|
|
|
else if(sha384[(i * 2) + 1] >= 0x61 &&
|
|
|
|
|
|
sha384[(i * 2) + 1] <= 0x66)
|
|
|
|
|
|
sha384Bytes[i] += (byte)(sha384[(i * 2) + 1] - 0x57);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string sha384B32 = Base32.ToBase32String(sha384Bytes);
|
|
|
|
|
|
|
|
|
|
|
|
string repoPath = Path.Combine(Settings.Settings.Current.RepositoryPath, "files", sha384B32[0].ToString(),
|
|
|
|
|
|
sha384B32[1].ToString(), sha384B32[2].ToString(), sha384B32[3].ToString(),
|
|
|
|
|
|
sha384B32[4].ToString(), sha384B32 + ".lz");
|
|
|
|
|
|
|
|
|
|
|
|
if(!File.Exists(repoPath))
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
_lastHandle++;
|
|
|
|
|
|
long handle = _lastHandle;
|
|
|
|
|
|
|
|
|
|
|
|
_streamsCache[handle] =
|
|
|
|
|
|
Stream.Synchronized(new ForcedSeekStream<LZipStream>(fileSize,
|
|
|
|
|
|
new FileStream(repoPath, FileMode.Open,
|
|
|
|
|
|
FileAccess.Read),
|
|
|
|
|
|
CompressionMode.Decompress));
|
|
|
|
|
|
|
|
|
|
|
|
return handle;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal int Read(long handle, byte[] buf, long offset)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!_streamsCache.TryGetValue(handle, out Stream stream))
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
2020-09-04 21:03:24 +01:00
|
|
|
|
lock(stream)
|
|
|
|
|
|
{
|
|
|
|
|
|
stream.Position = offset;
|
2020-09-03 01:54:56 +01:00
|
|
|
|
|
2020-09-04 21:03:24 +01:00
|
|
|
|
return stream.Read(buf, 0, buf.Length);
|
|
|
|
|
|
}
|
2020-09-03 01:54:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
internal bool Close(long handle)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(!_streamsCache.TryGetValue(handle, out Stream stream))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
stream.Close();
|
|
|
|
|
|
_streamsCache.TryRemove(handle, out _);
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2020-09-03 01:58:08 +01:00
|
|
|
|
|
|
|
|
|
|
internal IEnumerable<string> GetRootEntries()
|
|
|
|
|
|
{
|
|
|
|
|
|
if(_rootDirectoryCache.Count == 0)
|
|
|
|
|
|
FillRootDirectoryCache();
|
|
|
|
|
|
|
|
|
|
|
|
return _rootDirectoryCache.Keys.ToArray();
|
|
|
|
|
|
}
|
2020-09-04 20:38:43 +01:00
|
|
|
|
|
|
|
|
|
|
public long OpenDisk(string sha1, string md5)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(sha1 == null &&
|
|
|
|
|
|
md5 == null)
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
string repoPath = null;
|
|
|
|
|
|
string md5Path = null;
|
|
|
|
|
|
string sha1Path = null;
|
|
|
|
|
|
|
|
|
|
|
|
if(sha1 != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] sha1Bytes = new byte[20];
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 20; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(sha1[i * 2] >= 0x30 &&
|
|
|
|
|
|
sha1[i * 2] <= 0x39)
|
|
|
|
|
|
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x30) * 0x10);
|
|
|
|
|
|
else if(sha1[i * 2] >= 0x41 &&
|
|
|
|
|
|
sha1[i * 2] <= 0x46)
|
|
|
|
|
|
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x37) * 0x10);
|
|
|
|
|
|
else if(sha1[i * 2] >= 0x61 &&
|
|
|
|
|
|
sha1[i * 2] <= 0x66)
|
|
|
|
|
|
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x57) * 0x10);
|
|
|
|
|
|
|
|
|
|
|
|
if(sha1[(i * 2) + 1] >= 0x30 &&
|
|
|
|
|
|
sha1[(i * 2) + 1] <= 0x39)
|
|
|
|
|
|
sha1Bytes[i] += (byte)(sha1[(i * 2) + 1] - 0x30);
|
|
|
|
|
|
else if(sha1[(i * 2) + 1] >= 0x41 &&
|
|
|
|
|
|
sha1[(i * 2) + 1] <= 0x46)
|
|
|
|
|
|
sha1Bytes[i] += (byte)(sha1[(i * 2) + 1] - 0x37);
|
|
|
|
|
|
else if(sha1[(i * 2) + 1] >= 0x61 &&
|
|
|
|
|
|
sha1[(i * 2) + 1] <= 0x66)
|
|
|
|
|
|
sha1Bytes[i] += (byte)(sha1[(i * 2) + 1] - 0x57);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string sha1B32 = Base32.ToBase32String(sha1Bytes);
|
|
|
|
|
|
|
|
|
|
|
|
sha1Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "chd", "sha1", sha1B32[0].ToString(),
|
|
|
|
|
|
sha1B32[1].ToString(), sha1B32[2].ToString(), sha1B32[3].ToString(),
|
|
|
|
|
|
sha1B32[4].ToString(), sha1B32 + ".chd");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(md5 != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] md5Bytes = new byte[16];
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 16; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(md5[i * 2] >= 0x30 &&
|
|
|
|
|
|
md5[i * 2] <= 0x39)
|
|
|
|
|
|
md5Bytes[i] = (byte)((md5[i * 2] - 0x30) * 0x10);
|
|
|
|
|
|
else if(md5[i * 2] >= 0x41 &&
|
|
|
|
|
|
md5[i * 2] <= 0x46)
|
|
|
|
|
|
md5Bytes[i] = (byte)((md5[i * 2] - 0x37) * 0x10);
|
|
|
|
|
|
else if(md5[i * 2] >= 0x61 &&
|
|
|
|
|
|
md5[i * 2] <= 0x66)
|
|
|
|
|
|
md5Bytes[i] = (byte)((md5[i * 2] - 0x57) * 0x10);
|
|
|
|
|
|
|
|
|
|
|
|
if(md5[(i * 2) + 1] >= 0x30 &&
|
|
|
|
|
|
md5[(i * 2) + 1] <= 0x39)
|
|
|
|
|
|
md5Bytes[i] += (byte)(md5[(i * 2) + 1] - 0x30);
|
|
|
|
|
|
else if(md5[(i * 2) + 1] >= 0x41 &&
|
|
|
|
|
|
md5[(i * 2) + 1] <= 0x46)
|
|
|
|
|
|
md5Bytes[i] += (byte)(md5[(i * 2) + 1] - 0x37);
|
|
|
|
|
|
else if(md5[(i * 2) + 1] >= 0x61 &&
|
|
|
|
|
|
md5[(i * 2) + 1] <= 0x66)
|
|
|
|
|
|
md5Bytes[i] += (byte)(md5[(i * 2) + 1] - 0x57);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string md5B32 = Base32.ToBase32String(md5Bytes);
|
|
|
|
|
|
|
|
|
|
|
|
md5Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "chd", "md5", md5B32[0].ToString(),
|
|
|
|
|
|
md5B32[1].ToString(), md5B32[2].ToString(), md5B32[3].ToString(),
|
|
|
|
|
|
md5B32[4].ToString(), md5B32 + ".chd");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(File.Exists(sha1Path))
|
|
|
|
|
|
repoPath = sha1Path;
|
|
|
|
|
|
else if(File.Exists(md5Path))
|
|
|
|
|
|
repoPath = md5Path;
|
|
|
|
|
|
|
|
|
|
|
|
if(repoPath == null)
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
_lastHandle++;
|
|
|
|
|
|
long handle = _lastHandle;
|
|
|
|
|
|
|
|
|
|
|
|
_streamsCache[handle] = Stream.Synchronized(new FileStream(repoPath, FileMode.Open, FileAccess.Read));
|
|
|
|
|
|
|
|
|
|
|
|
return handle;
|
|
|
|
|
|
}
|
2020-09-04 22:19:40 +01:00
|
|
|
|
|
|
|
|
|
|
public long OpenMedia(string sha256, string sha1, string md5)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(sha256 == null &&
|
|
|
|
|
|
sha1 == null &&
|
|
|
|
|
|
md5 == null)
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
string repoPath = null;
|
|
|
|
|
|
string md5Path = null;
|
|
|
|
|
|
string sha1Path = null;
|
|
|
|
|
|
string sha256Path = null;
|
|
|
|
|
|
|
|
|
|
|
|
if(sha256 != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] sha256Bytes = new byte[32];
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 32; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(sha256[i * 2] >= 0x30 &&
|
|
|
|
|
|
sha256[i * 2] <= 0x39)
|
|
|
|
|
|
sha256Bytes[i] = (byte)((sha256[i * 2] - 0x30) * 0x10);
|
|
|
|
|
|
else if(sha256[i * 2] >= 0x41 &&
|
|
|
|
|
|
sha256[i * 2] <= 0x46)
|
|
|
|
|
|
sha256Bytes[i] = (byte)((sha256[i * 2] - 0x37) * 0x10);
|
|
|
|
|
|
else if(sha256[i * 2] >= 0x61 &&
|
|
|
|
|
|
sha256[i * 2] <= 0x66)
|
|
|
|
|
|
sha256Bytes[i] = (byte)((sha256[i * 2] - 0x57) * 0x10);
|
|
|
|
|
|
|
|
|
|
|
|
if(sha256[(i * 2) + 1] >= 0x30 &&
|
|
|
|
|
|
sha256[(i * 2) + 1] <= 0x39)
|
|
|
|
|
|
sha256Bytes[i] += (byte)(sha256[(i * 2) + 1] - 0x30);
|
|
|
|
|
|
else if(sha256[(i * 2) + 1] >= 0x41 &&
|
|
|
|
|
|
sha256[(i * 2) + 1] <= 0x46)
|
|
|
|
|
|
sha256Bytes[i] += (byte)(sha256[(i * 2) + 1] - 0x37);
|
|
|
|
|
|
else if(sha256[(i * 2) + 1] >= 0x61 &&
|
|
|
|
|
|
sha256[(i * 2) + 1] <= 0x66)
|
|
|
|
|
|
sha256Bytes[i] += (byte)(sha256[(i * 2) + 1] - 0x57);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string sha256B32 = Base32.ToBase32String(sha256Bytes);
|
|
|
|
|
|
|
|
|
|
|
|
sha256Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "aaru", "sha256",
|
|
|
|
|
|
sha256B32[0].ToString(), sha256B32[1].ToString(), sha256B32[2].ToString(),
|
|
|
|
|
|
sha256B32[3].ToString(), sha256B32[4].ToString(), sha256B32 + ".aif");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(sha1 != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] sha1Bytes = new byte[20];
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 20; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(sha1[i * 2] >= 0x30 &&
|
|
|
|
|
|
sha1[i * 2] <= 0x39)
|
|
|
|
|
|
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x30) * 0x10);
|
|
|
|
|
|
else if(sha1[i * 2] >= 0x41 &&
|
|
|
|
|
|
sha1[i * 2] <= 0x46)
|
|
|
|
|
|
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x37) * 0x10);
|
|
|
|
|
|
else if(sha1[i * 2] >= 0x61 &&
|
|
|
|
|
|
sha1[i * 2] <= 0x66)
|
|
|
|
|
|
sha1Bytes[i] = (byte)((sha1[i * 2] - 0x57) * 0x10);
|
|
|
|
|
|
|
|
|
|
|
|
if(sha1[(i * 2) + 1] >= 0x30 &&
|
|
|
|
|
|
sha1[(i * 2) + 1] <= 0x39)
|
|
|
|
|
|
sha1Bytes[i] += (byte)(sha1[(i * 2) + 1] - 0x30);
|
|
|
|
|
|
else if(sha1[(i * 2) + 1] >= 0x41 &&
|
|
|
|
|
|
sha1[(i * 2) + 1] <= 0x46)
|
|
|
|
|
|
sha1Bytes[i] += (byte)(sha1[(i * 2) + 1] - 0x37);
|
|
|
|
|
|
else if(sha1[(i * 2) + 1] >= 0x61 &&
|
|
|
|
|
|
sha1[(i * 2) + 1] <= 0x66)
|
|
|
|
|
|
sha1Bytes[i] += (byte)(sha1[(i * 2) + 1] - 0x57);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string sha1B32 = Base32.ToBase32String(sha1Bytes);
|
|
|
|
|
|
|
|
|
|
|
|
sha1Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "aaru", "sha1", sha1B32[0].ToString(),
|
|
|
|
|
|
sha1B32[1].ToString(), sha1B32[2].ToString(), sha1B32[3].ToString(),
|
|
|
|
|
|
sha1B32[4].ToString(), sha1B32 + ".aif");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(md5 != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] md5Bytes = new byte[16];
|
|
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < 16; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if(md5[i * 2] >= 0x30 &&
|
|
|
|
|
|
md5[i * 2] <= 0x39)
|
|
|
|
|
|
md5Bytes[i] = (byte)((md5[i * 2] - 0x30) * 0x10);
|
|
|
|
|
|
else if(md5[i * 2] >= 0x41 &&
|
|
|
|
|
|
md5[i * 2] <= 0x46)
|
|
|
|
|
|
md5Bytes[i] = (byte)((md5[i * 2] - 0x37) * 0x10);
|
|
|
|
|
|
else if(md5[i * 2] >= 0x61 &&
|
|
|
|
|
|
md5[i * 2] <= 0x66)
|
|
|
|
|
|
md5Bytes[i] = (byte)((md5[i * 2] - 0x57) * 0x10);
|
|
|
|
|
|
|
|
|
|
|
|
if(md5[(i * 2) + 1] >= 0x30 &&
|
|
|
|
|
|
md5[(i * 2) + 1] <= 0x39)
|
|
|
|
|
|
md5Bytes[i] += (byte)(md5[(i * 2) + 1] - 0x30);
|
|
|
|
|
|
else if(md5[(i * 2) + 1] >= 0x41 &&
|
|
|
|
|
|
md5[(i * 2) + 1] <= 0x46)
|
|
|
|
|
|
md5Bytes[i] += (byte)(md5[(i * 2) + 1] - 0x37);
|
|
|
|
|
|
else if(md5[(i * 2) + 1] >= 0x61 &&
|
|
|
|
|
|
md5[(i * 2) + 1] <= 0x66)
|
|
|
|
|
|
md5Bytes[i] += (byte)(md5[(i * 2) + 1] - 0x57);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
string md5B32 = Base32.ToBase32String(md5Bytes);
|
|
|
|
|
|
|
|
|
|
|
|
md5Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "aaru", "md5", md5B32[0].ToString(),
|
|
|
|
|
|
md5B32[1].ToString(), md5B32[2].ToString(), md5B32[3].ToString(),
|
|
|
|
|
|
md5B32[4].ToString(), md5B32 + ".aif");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(File.Exists(sha256Path))
|
|
|
|
|
|
repoPath = sha256Path;
|
|
|
|
|
|
else if(File.Exists(sha1Path))
|
|
|
|
|
|
repoPath = sha1Path;
|
|
|
|
|
|
else if(File.Exists(md5Path))
|
|
|
|
|
|
repoPath = md5Path;
|
|
|
|
|
|
|
|
|
|
|
|
if(repoPath == null)
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
_lastHandle++;
|
|
|
|
|
|
long handle = _lastHandle;
|
|
|
|
|
|
|
|
|
|
|
|
_streamsCache[handle] = Stream.Synchronized(new FileStream(repoPath, FileMode.Open, FileAccess.Read));
|
|
|
|
|
|
|
|
|
|
|
|
return handle;
|
|
|
|
|
|
}
|
2020-09-03 01:25:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2020-09-03 01:42:18 +01:00
|
|
|
|
|
|
|
|
|
|
internal sealed class CachedFile
|
|
|
|
|
|
{
|
2020-09-06 19:09:35 +01:00
|
|
|
|
public ulong Id { get; set; }
|
|
|
|
|
|
public ulong Size { get; set; }
|
|
|
|
|
|
public string Crc32 { get; set; }
|
|
|
|
|
|
public string Md5 { get; set; }
|
|
|
|
|
|
public string Sha1 { get; set; }
|
|
|
|
|
|
public string Sha256 { get; set; }
|
|
|
|
|
|
public string Sha384 { get; set; }
|
|
|
|
|
|
public string Sha512 { get; set; }
|
|
|
|
|
|
public DateTime CreatedOn { get; set; }
|
|
|
|
|
|
public DateTime UpdatedOn { get; set; }
|
|
|
|
|
|
public DateTime? FileLastModification { get; set; }
|
2020-09-03 01:42:18 +01:00
|
|
|
|
}
|
2020-09-04 20:38:43 +01:00
|
|
|
|
|
|
|
|
|
|
internal sealed class CachedDisk
|
|
|
|
|
|
{
|
|
|
|
|
|
public ulong Id { get; set; }
|
|
|
|
|
|
public ulong Size { get; set; }
|
|
|
|
|
|
public string Md5 { get; set; }
|
|
|
|
|
|
public string Sha1 { get; set; }
|
|
|
|
|
|
public DateTime CreatedOn { get; set; }
|
|
|
|
|
|
public DateTime UpdatedOn { get; set; }
|
|
|
|
|
|
}
|
2020-09-04 22:19:40 +01:00
|
|
|
|
|
|
|
|
|
|
internal sealed class CachedMedia
|
|
|
|
|
|
{
|
|
|
|
|
|
public ulong Id { get; set; }
|
|
|
|
|
|
public ulong Size { get; set; }
|
|
|
|
|
|
public string Md5 { get; set; }
|
|
|
|
|
|
public string Sha1 { get; set; }
|
|
|
|
|
|
public string Sha256 { get; set; }
|
|
|
|
|
|
public string SpamSum { get; set; }
|
|
|
|
|
|
public DateTime CreatedOn { get; set; }
|
|
|
|
|
|
public DateTime UpdatedOn { get; set; }
|
|
|
|
|
|
}
|
2020-08-30 14:22:41 +01:00
|
|
|
|
}
|