diff --git a/RomRepoMgr.Core/Filesystem/Fuse.cs b/RomRepoMgr.Core/Filesystem/Fuse.cs index c1bf86c..f66a70e 100644 --- a/RomRepoMgr.Core/Filesystem/Fuse.cs +++ b/RomRepoMgr.Core/Filesystem/Fuse.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using Mono.Fuse.NETStandard; using Mono.Unix.Native; using RomRepoMgr.Database.Models; @@ -162,7 +163,29 @@ namespace RomRepoMgr.Core.Filesystem CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); - if(disk == null) + if(disk != null) + { + if(pieces.Length != 3) + return Errno.ENOSYS; + + stat = new Stat + { + st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), + st_nlink = 1, + st_ctime = NativeConvert.ToTimeT(disk.CreatedOn.ToUniversalTime()), + st_mtime = NativeConvert.ToTimeT(disk.UpdatedOn.ToUniversalTime()), + st_blksize = 512, + st_blocks = (long)(disk.Size / 512), + st_ino = disk.Id, + st_size = (long)disk.Size + }; + + return 0; + } + + CachedMedia media = _vfs.GetMedia(machine.Id, pieces[2]); + + if(media == null) return Errno.ENOENT; if(pieces.Length != 3) @@ -172,12 +195,12 @@ namespace RomRepoMgr.Core.Filesystem { st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), st_nlink = 1, - st_ctime = NativeConvert.ToTimeT(disk.CreatedOn.ToUniversalTime()), - st_mtime = NativeConvert.ToTimeT(disk.UpdatedOn.ToUniversalTime()), + st_ctime = NativeConvert.ToTimeT(media.CreatedOn.ToUniversalTime()), + st_mtime = NativeConvert.ToTimeT(media.UpdatedOn.ToUniversalTime()), st_blksize = 512, - st_blocks = (long)(disk.Size / 512), - st_ino = disk.Id, - st_size = (long)disk.Size + st_blocks = (long)(media.Size / 512), + st_ino = media.Id, + st_size = (long)media.Size }; return 0; @@ -277,35 +300,70 @@ namespace RomRepoMgr.Core.Filesystem { CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); - if(disk == null) - return Errno.ENOENT; - - if(pieces.Length > 3) - return Errno.ENOSYS; - - if(disk.Sha1 == null && - disk.Md5 == null) - return Errno.ENOENT; - - if(info.OpenAccess.HasFlag(OpenFlags.O_APPEND) || - info.OpenAccess.HasFlag(OpenFlags.O_CREAT) || - info.OpenAccess.HasFlag(OpenFlags.O_EXCL) || - info.OpenAccess.HasFlag(OpenFlags.O_TRUNC)) - return Errno.EROFS; - - handle = _vfs.OpenDisk(disk.Sha1, disk.Md5); - - stat = new Stat + if(disk != null) { - st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), - st_nlink = 1, - st_ctime = NativeConvert.ToTimeT(disk.CreatedOn.ToUniversalTime()), - st_mtime = NativeConvert.ToTimeT(disk.UpdatedOn.ToUniversalTime()), - st_blksize = 512, - st_blocks = (long)(disk.Size / 512), - st_ino = disk.Id, - st_size = (long)disk.Size - }; + if(pieces.Length > 3) + return Errno.ENOSYS; + + if(disk.Sha1 == null && + disk.Md5 == null) + return Errno.ENOENT; + + if(info.OpenAccess.HasFlag(OpenFlags.O_APPEND) || + info.OpenAccess.HasFlag(OpenFlags.O_CREAT) || + info.OpenAccess.HasFlag(OpenFlags.O_EXCL) || + info.OpenAccess.HasFlag(OpenFlags.O_TRUNC)) + return Errno.EROFS; + + handle = _vfs.OpenDisk(disk.Sha1, disk.Md5); + + stat = new Stat + { + st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), + st_nlink = 1, + st_ctime = NativeConvert.ToTimeT(disk.CreatedOn.ToUniversalTime()), + st_mtime = NativeConvert.ToTimeT(disk.UpdatedOn.ToUniversalTime()), + st_blksize = 512, + st_blocks = (long)(disk.Size / 512), + st_ino = disk.Id, + st_size = (long)disk.Size + }; + } + else + { + CachedMedia media = _vfs.GetMedia(machine.Id, pieces[2]); + + if(media == null) + return Errno.ENOENT; + + if(pieces.Length > 3) + return Errno.ENOSYS; + + if(media.Sha256 == null && + media.Sha1 == null && + media.Md5 == null) + return Errno.ENOENT; + + if(info.OpenAccess.HasFlag(OpenFlags.O_APPEND) || + info.OpenAccess.HasFlag(OpenFlags.O_CREAT) || + info.OpenAccess.HasFlag(OpenFlags.O_EXCL) || + info.OpenAccess.HasFlag(OpenFlags.O_TRUNC)) + return Errno.EROFS; + + handle = _vfs.OpenMedia(media.Sha256, media.Sha1, media.Md5); + + stat = new Stat + { + st_mode = FilePermissions.S_IFREG | NativeConvert.FromOctalPermissionString("0444"), + st_nlink = 1, + st_ctime = NativeConvert.ToTimeT(media.CreatedOn.ToUniversalTime()), + st_mtime = NativeConvert.ToTimeT(media.UpdatedOn.ToUniversalTime()), + st_blksize = 512, + st_blocks = (long)(media.Size / 512), + st_ino = media.Id, + st_size = (long)media.Size + }; + } } if(handle <= 0) @@ -467,48 +525,84 @@ namespace RomRepoMgr.Core.Filesystem { CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); - if(disk == null) - return Errno.ENOENT; - - switch(name) + if(disk != null) { - case "user.md5": - hash = disk.Md5; + switch(name) + { + case "user.md5": + hash = disk.Md5; - break; - case "user.sha1": - hash = disk.Sha1; + break; + case "user.sha1": + hash = disk.Sha1; - break; + break; + } + } + else + { + CachedMedia media = _vfs.GetMedia(machine.Id, pieces[2]); + + if(media == null) + return Errno.ENOENT; + + switch(name) + { + case "user.md5": + hash = media.Md5; + + break; + case "user.sha1": + hash = media.Sha1; + + break; + case "user.sha256": + hash = media.Sha256; + + break; + case "user.spamsum": + hash = media.SpamSum; + + break; + } } } if(hash == null) return Errno.ENODATA; - byte[] xattr = new byte[hash.Length / 2]; + byte[] xattr = null; - for(int i = 0; i < xattr.Length; i++) + if(name == "user.spamsum") { - if(hash[i * 2] >= 0x30 && - hash[i * 2] <= 0x39) - xattr[i] = (byte)((hash[i * 2] - 0x30) * 0x10); - else if(hash[i * 2] >= 0x41 && - hash[i * 2] <= 0x46) - xattr[i] = (byte)((hash[i * 2] - 0x37) * 0x10); - else if(hash[i * 2] >= 0x61 && - hash[i * 2] <= 0x66) - xattr[i] = (byte)((hash[i * 2] - 0x57) * 0x10); + xattr = Encoding.ASCII.GetBytes(hash); + } + else + { + xattr = new byte[hash.Length / 2]; - if(hash[(i * 2) + 1] >= 0x30 && - hash[(i * 2) + 1] <= 0x39) - xattr[i] += (byte)(hash[(i * 2) + 1] - 0x30); - else if(hash[(i * 2) + 1] >= 0x41 && - hash[(i * 2) + 1] <= 0x46) - xattr[i] += (byte)(hash[(i * 2) + 1] - 0x37); - else if(hash[(i * 2) + 1] >= 0x61 && - hash[(i * 2) + 1] <= 0x66) - xattr[i] += (byte)(hash[(i * 2) + 1] - 0x57); + for(int i = 0; i < xattr.Length; i++) + { + if(hash[i * 2] >= 0x30 && + hash[i * 2] <= 0x39) + xattr[i] = (byte)((hash[i * 2] - 0x30) * 0x10); + else if(hash[i * 2] >= 0x41 && + hash[i * 2] <= 0x46) + xattr[i] = (byte)((hash[i * 2] - 0x37) * 0x10); + else if(hash[i * 2] >= 0x61 && + hash[i * 2] <= 0x66) + xattr[i] = (byte)((hash[i * 2] - 0x57) * 0x10); + + if(hash[(i * 2) + 1] >= 0x30 && + hash[(i * 2) + 1] <= 0x39) + xattr[i] += (byte)(hash[(i * 2) + 1] - 0x30); + else if(hash[(i * 2) + 1] >= 0x41 && + hash[(i * 2) + 1] <= 0x46) + xattr[i] += (byte)(hash[(i * 2) + 1] - 0x37); + else if(hash[(i * 2) + 1] >= 0x61 && + hash[(i * 2) + 1] <= 0x66) + xattr[i] += (byte)(hash[(i * 2) + 1] - 0x57); + } } if(value == null) @@ -590,18 +684,42 @@ namespace RomRepoMgr.Core.Filesystem CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); - if(disk == null) + if(disk != null) + { + if(pieces.Length > 3) + return Errno.ENOSYS; + + if(disk.Md5 != null) + xattrs.Add("user.md5"); + + if(disk.Sha1 != null) + xattrs.Add("user.sha1"); + + names = xattrs.ToArray(); + + return 0; + } + + CachedMedia media = _vfs.GetMedia(machine.Id, pieces[2]); + + if(media == null) return Errno.ENOENT; if(pieces.Length > 3) return Errno.ENOSYS; - if(disk.Md5 != null) + if(media.Md5 != null) xattrs.Add("user.md5"); - if(disk.Sha1 != null) + if(media.Sha1 != null) xattrs.Add("user.sha1"); + if(media.Sha256 != null) + xattrs.Add("user.sha256"); + + if(media.SpamSum != null) + xattrs.Add("user.spamsum"); + names = xattrs.ToArray(); return 0; @@ -671,8 +789,9 @@ namespace RomRepoMgr.Core.Filesystem if(machine == null) return Errno.ENOENT; - ConcurrentDictionary cachedMachineFiles = _vfs.GetFilesFromMachine(machine.Id); - ConcurrentDictionary cachedMachineDisks = _vfs.GetDisksFromMachine(machine.Id); + ConcurrentDictionary cachedMachineFiles = _vfs.GetFilesFromMachine(machine.Id); + ConcurrentDictionary cachedMachineDisks = _vfs.GetDisksFromMachine(machine.Id); + ConcurrentDictionary cachedMachineMedias = _vfs.GetMediasFromMachine(machine.Id); if(pieces.Length == 2) { @@ -683,7 +802,8 @@ namespace RomRepoMgr.Core.Filesystem }; entries.AddRange(cachedMachineFiles.Select(file => new DirectoryEntry(file.Key))); - entries.AddRange(cachedMachineDisks.Select(disk => new DirectoryEntry(disk.Key + ".chd"))); + entries.AddRange(cachedMachineDisks.Select(disk => new DirectoryEntry(disk.Key + ".chd"))); + entries.AddRange(cachedMachineMedias.Select(media => new DirectoryEntry(media.Key + ".aif"))); _lastHandle++; info.Handle = new IntPtr(_lastHandle); @@ -773,7 +893,17 @@ namespace RomRepoMgr.Core.Filesystem CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); - if(disk == null) + if(disk != null) + { + if(pieces.Length > 3) + return Errno.ENOSYS; + + return mode.HasFlag(AccessModes.W_OK) ? Errno.EROFS : 0; + } + + CachedMedia media = _vfs.GetMedia(machine.Id, pieces[2]); + + if(media == null) return Errno.ENOENT; if(pieces.Length > 3) diff --git a/RomRepoMgr.Core/Filesystem/Vfs.cs b/RomRepoMgr.Core/Filesystem/Vfs.cs index 42e88b1..4442d68 100644 --- a/RomRepoMgr.Core/Filesystem/Vfs.cs +++ b/RomRepoMgr.Core/Filesystem/Vfs.cs @@ -20,6 +20,7 @@ namespace RomRepoMgr.Core.Filesystem { readonly ConcurrentDictionary> _machineDisksCache; readonly ConcurrentDictionary> _machineFilesCache; + readonly ConcurrentDictionary> _machineMediasCache; readonly ConcurrentDictionary> _machinesStatCache; readonly ConcurrentDictionary _romSetsCache; readonly ConcurrentDictionary _streamsCache; @@ -35,6 +36,7 @@ namespace RomRepoMgr.Core.Filesystem _machinesStatCache = new ConcurrentDictionary>(); _machineFilesCache = new ConcurrentDictionary>(); _machineDisksCache = new ConcurrentDictionary>(); + _machineMediasCache = new ConcurrentDictionary>(); _streamsCache = new ConcurrentDictionary(); _lastHandle = 0; } @@ -101,8 +103,12 @@ namespace RomRepoMgr.Core.Filesystem { 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); + 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)); } internal string[] SplitPath(string path) => @@ -286,6 +292,41 @@ namespace RomRepoMgr.Core.Filesystem return cachedMachineDisks; } + internal ConcurrentDictionary GetMediasFromMachine(ulong id) + { + _machineMediasCache.TryGetValue(id, out ConcurrentDictionary cachedMachineMedias); + + if(cachedMachineMedias != null) + return cachedMachineMedias; + + using var ctx = Context.Create(Settings.Settings.Current.DatabasePath); + + cachedMachineMedias = new ConcurrentDictionary(); + + 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; + } + internal CachedFile GetFile(ulong machineId, string name) { ConcurrentDictionary cachedFiles = GetFilesFromMachine(machineId); @@ -311,6 +352,20 @@ namespace RomRepoMgr.Core.Filesystem return disk; } + internal CachedMedia GetMedia(ulong machineId, string name) + { + if(name.EndsWith(".aif", StringComparison.OrdinalIgnoreCase)) + name = name.Substring(0, name.Length - 4); + + ConcurrentDictionary cachedMedias = GetMediasFromMachine(machineId); + + if(cachedMedias == null || + !cachedMedias.TryGetValue(name, out CachedMedia media)) + return null; + + return media; + } + internal long Open(string sha384, long fileSize) { byte[] sha384Bytes = new byte[48]; @@ -484,6 +539,138 @@ namespace RomRepoMgr.Core.Filesystem return handle; } + + 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; + } } internal sealed class CachedMachine @@ -516,4 +703,16 @@ namespace RomRepoMgr.Core.Filesystem public DateTime CreatedOn { get; set; } public DateTime UpdatedOn { get; set; } } + + 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; } + } } \ No newline at end of file diff --git a/RomRepoMgr.Core/Filesystem/Winfsp.cs b/RomRepoMgr.Core/Filesystem/Winfsp.cs index 1c82dc6..02127b7 100644 --- a/RomRepoMgr.Core/Filesystem/Winfsp.cs +++ b/RomRepoMgr.Core/Filesystem/Winfsp.cs @@ -292,17 +292,61 @@ namespace RomRepoMgr.Core.Filesystem CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); - if(disk == null) + if(disk != null) + { + if(pieces.Length > 3) + return STATUS_INVALID_DEVICE_REQUEST; + + if(disk.Sha1 == null && + disk.Md5 == null) + return STATUS_OBJECT_NAME_NOT_FOUND; + + handle = _vfs.OpenDisk(disk.Sha1, disk.Md5); + + if(handle <= 0) + return STATUS_OBJECT_NAME_NOT_FOUND; + + normalizedName = Path.GetFileName(fileName); + + // TODO: Real allocation size + fileInfo = new FileInfo + { + ChangeTime = (ulong)disk.UpdatedOn.ToFileTimeUtc(), + AllocationSize = (disk.Size + 511) / 512, + FileSize = disk.Size, + CreationTime = (ulong)disk.CreatedOn.ToFileTimeUtc(), + FileAttributes = + (uint)(FileAttributes.Normal | FileAttributes.Compressed | FileAttributes.ReadOnly), + IndexNumber = disk.Id, + LastAccessTime = (ulong)DateTime.UtcNow.ToFileTimeUtc(), + LastWriteTime = (ulong)disk.UpdatedOn.ToFileTimeUtc() + }; + + fileNode = new FileNode + { + FileName = normalizedName, + Info = fileInfo, + Path = fileName, + Handle = handle + }; + + return STATUS_SUCCESS; + } + + CachedMedia media = _vfs.GetMedia(machine.Id, pieces[2]); + + if(media == null) return STATUS_OBJECT_NAME_NOT_FOUND; if(pieces.Length > 3) return STATUS_INVALID_DEVICE_REQUEST; - if(disk.Sha1 == null && - disk.Md5 == null) + if(media.Sha256 == null && + media.Sha1 == null && + media.Md5 == null) return STATUS_OBJECT_NAME_NOT_FOUND; - handle = _vfs.OpenDisk(disk.Sha1, disk.Md5); + handle = _vfs.OpenMedia(media.Sha256, media.Sha1, media.Md5); if(handle <= 0) return STATUS_OBJECT_NAME_NOT_FOUND; @@ -312,14 +356,14 @@ namespace RomRepoMgr.Core.Filesystem // TODO: Real allocation size fileInfo = new FileInfo { - ChangeTime = (ulong)disk.UpdatedOn.ToFileTimeUtc(), - AllocationSize = (disk.Size + 511) / 512, - FileSize = disk.Size, - CreationTime = (ulong)disk.CreatedOn.ToFileTimeUtc(), + ChangeTime = (ulong)media.UpdatedOn.ToFileTimeUtc(), + AllocationSize = (media.Size + 511) / 512, + FileSize = media.Size, + CreationTime = (ulong)media.CreatedOn.ToFileTimeUtc(), FileAttributes = (uint)(FileAttributes.Normal | FileAttributes.Compressed | FileAttributes.ReadOnly), - IndexNumber = disk.Id, + IndexNumber = media.Id, LastAccessTime = (ulong)DateTime.UtcNow.ToFileTimeUtc(), - LastWriteTime = (ulong)disk.UpdatedOn.ToFileTimeUtc() + LastWriteTime = (ulong)media.UpdatedOn.ToFileTimeUtc() }; fileNode = new FileNode @@ -399,6 +443,9 @@ namespace RomRepoMgr.Core.Filesystem ConcurrentDictionary cachedMachineDisks = _vfs.GetDisksFromMachine(node.MachineId); + ConcurrentDictionary cachedMachineMedias = + _vfs.GetMediasFromMachine(node.MachineId); + node.Children = new List { new FileEntry @@ -446,6 +493,23 @@ namespace RomRepoMgr.Core.Filesystem LastWriteTime = (ulong)disk.Value.UpdatedOn.ToFileTimeUtc() } })); + + node.Children.AddRange(cachedMachineMedias.Select(media => new FileEntry + { + FileName = media.Key + ".aif", + Info = new FileInfo + { + ChangeTime = (ulong)media.Value.UpdatedOn.ToFileTimeUtc(), + AllocationSize = (media.Value.Size + 511) / 512, + FileSize = media.Value.Size, + CreationTime = (ulong)media.Value.CreatedOn.ToFileTimeUtc(), + FileAttributes = + (uint)(FileAttributes.Normal | FileAttributes.Compressed | FileAttributes.ReadOnly), + IndexNumber = media.Value.Id, + LastAccessTime = (ulong)DateTime.UtcNow.ToFileTimeUtc(), + LastWriteTime = (ulong)media.Value.UpdatedOn.ToFileTimeUtc() + } + })); } else if(node.RomSetId > 0) { @@ -612,17 +676,39 @@ namespace RomRepoMgr.Core.Filesystem CachedDisk disk = _vfs.GetDisk(machine.Id, pieces[2]); - if(disk == null) + if(disk != null) + { + if(pieces.Length > 3) + return STATUS_INVALID_DEVICE_REQUEST; + + if(disk.Sha1 == null && + disk.Md5 == null) + return STATUS_OBJECT_NAME_NOT_FOUND; + + handle = _vfs.OpenDisk(disk.Sha1, disk.Md5); + + if(handle <= 0) + return STATUS_OBJECT_NAME_NOT_FOUND; + + fileAttributes = (uint)(FileAttributes.Normal | FileAttributes.Compressed | FileAttributes.ReadOnly); + + return STATUS_SUCCESS; + } + + CachedMedia media = _vfs.GetMedia(machine.Id, pieces[2]); + + if(media == null) return STATUS_OBJECT_NAME_NOT_FOUND; if(pieces.Length > 3) return STATUS_INVALID_DEVICE_REQUEST; - if(disk.Sha1 == null && - disk.Md5 == null) + if(media.Sha256 == null && + media.Sha1 == null && + media.Md5 == null) return STATUS_OBJECT_NAME_NOT_FOUND; - handle = _vfs.OpenDisk(disk.Sha1, disk.Md5); + handle = _vfs.OpenMedia(media.Sha256, media.Sha1, media.Md5); if(handle <= 0) return STATUS_OBJECT_NAME_NOT_FOUND; diff --git a/RomRepoMgr.Core/Resources/Localization.Designer.cs b/RomRepoMgr.Core/Resources/Localization.Designer.cs index 7dc0184..47ddfb0 100644 --- a/RomRepoMgr.Core/Resources/Localization.Designer.cs +++ b/RomRepoMgr.Core/Resources/Localization.Designer.cs @@ -368,5 +368,23 @@ namespace RomRepoMgr.Core.Resources { return ResourceManager.GetString("CopyingFile", resourceCulture); } } + + internal static string AddingMedias { + get { + return ResourceManager.GetString("AddingMedias", resourceCulture); + } + } + + internal static string FoundMediaWithoutMachine { + get { + return ResourceManager.GetString("FoundMediaWithoutMachine", resourceCulture); + } + } + + internal static string NotAnAaruFormatFile { + get { + return ResourceManager.GetString("NotAnAaruFormatFile", resourceCulture); + } + } } } diff --git a/RomRepoMgr.Core/Resources/Localization.es.resx b/RomRepoMgr.Core/Resources/Localization.es.resx index 5a3b493..83702f1 100644 --- a/RomRepoMgr.Core/Resources/Localization.es.resx +++ b/RomRepoMgr.Core/Resources/Localization.es.resx @@ -15,7 +15,7 @@ Añadiendo DAT a la base de datos... - Añadiendo máquinas (juegos) + Añadiendo máquinas (juegos)... Añadiendo ROMs... @@ -173,4 +173,13 @@ Copiando archivo... + + Se encontró un medio sin máquina, esto no debería pasar. + + + No es un archivo AaruFormat. + + + Añadiendo medios... + \ No newline at end of file diff --git a/RomRepoMgr.Core/Resources/Localization.resx b/RomRepoMgr.Core/Resources/Localization.resx index 19dde30..647d27d 100644 --- a/RomRepoMgr.Core/Resources/Localization.resx +++ b/RomRepoMgr.Core/Resources/Localization.resx @@ -178,4 +178,13 @@ Copying file... + + Adding medias... + + + Found media with an unknown machine, this should not happen. + + + Not an AaruFormat file. + \ No newline at end of file diff --git a/RomRepoMgr.Core/Workers/DatImporter.cs b/RomRepoMgr.Core/Workers/DatImporter.cs index 1f58800..6996249 100644 --- a/RomRepoMgr.Core/Workers/DatImporter.cs +++ b/RomRepoMgr.Core/Workers/DatImporter.cs @@ -183,8 +183,9 @@ namespace RomRepoMgr.Core.Workers Message = Localization.RetrievingRomsAndDisks }); - List roms = new List(); - List disks = new List(); + List roms = new List(); + List disks = new List(); + List medias = new List(); foreach(List values in datFile.Items.Values) { @@ -199,6 +200,10 @@ namespace RomRepoMgr.Core.Workers case Disk disk: disks.Add(disk); + continue; + case Media media: + medias.Add(media); + continue; } } @@ -561,6 +566,145 @@ namespace RomRepoMgr.Core.Workers newDisks.Clear(); newDisksByMachine.Clear(); + SetProgressBounds?.Invoke(this, new ProgressBoundsEventArgs + { + Minimum = 0, + Maximum = medias.Count + }); + + SetMessage?.Invoke(this, new MessageEventArgs + { + Message = Localization.AddingMedias + }); + + position = 0; + + Dictionary pendingMediasBySha256 = new Dictionary(); + Dictionary pendingMediasBySha1 = new Dictionary(); + Dictionary pendingMediasByMd5 = new Dictionary(); + List newMedias = new List(); + List newMediasByMachine = new List(); + + foreach(Media media in medias) + { + SetProgress?.Invoke(this, new ProgressEventArgs + { + Value = position + }); + + if(!machines.TryGetValue(media.Machine.Name, out Machine machine)) + { + ErrorOccurred?.Invoke(this, new ErrorEventArgs + { + Message = Localization.FoundMediaWithoutMachine + }); + + return; + } + + if(media.MD5 == null && + media.SHA1 == null && + media.SHA256 == null) + { + position++; + + continue; + } + + DbMedia dbMedia = null; + + if(media.SHA256 != null && + dbMedia == null) + pendingMediasBySha256.TryGetValue(media.SHA256, out dbMedia); + + if(media.SHA1 != null && + dbMedia == null) + pendingMediasBySha1.TryGetValue(media.SHA1, out dbMedia); + + if(media.MD5 != null && + dbMedia == null) + pendingMediasByMd5.TryGetValue(media.MD5, out dbMedia); + + dbMedia ??= + Context.Singleton.Medias.FirstOrDefault(f => + (media.SHA256 != null && + f.Sha256 == media.SHA256) || + (media.SHA1 != null && f.Sha1 == media.SHA1) || + (media.MD5 != null && f.Md5 == media.MD5)); + + // TODO: SpamSum + if(dbMedia == null) + { + dbMedia = new DbMedia + { + CreatedOn = DateTime.UtcNow, + Md5 = media.MD5, + Sha1 = media.SHA1, + Sha256 = media.SHA256, + UpdatedOn = DateTime.UtcNow + }; + + newMedias.Add(dbMedia); + } + + if(string.IsNullOrEmpty(dbMedia.Md5) && + !string.IsNullOrEmpty(media.MD5)) + { + dbMedia.Md5 = media.MD5; + dbMedia.UpdatedOn = DateTime.UtcNow; + } + + if(string.IsNullOrEmpty(dbMedia.Sha1) && + !string.IsNullOrEmpty(media.SHA1)) + { + dbMedia.Sha1 = media.SHA1; + dbMedia.UpdatedOn = DateTime.UtcNow; + } + + if(string.IsNullOrEmpty(dbMedia.Sha256) && + !string.IsNullOrEmpty(media.SHA256)) + { + dbMedia.Sha256 = media.SHA256; + dbMedia.UpdatedOn = DateTime.UtcNow; + } + + newMediasByMachine.Add(new MediaByMachine + { + Media = dbMedia, + Machine = machine, + Name = media.Name + }); + + if(dbMedia.Sha256 != null) + pendingMediasBySha256[dbMedia.Sha256] = dbMedia; + + if(dbMedia.Sha1 != null) + pendingMediasBySha1[dbMedia.Sha1] = dbMedia; + + if(dbMedia.Md5 != null) + pendingMediasByMd5[dbMedia.Md5] = dbMedia; + + position++; + } + + SetMessage?.Invoke(this, new MessageEventArgs + { + Message = Localization.SavingChangesToDatabase + }); + + SetIndeterminateProgress?.Invoke(this, System.EventArgs.Empty); + + Context.Singleton.Medias.AddRange(newMedias); + Context.Singleton.MediasByMachines.AddRange(newMediasByMachine); + + Context.Singleton.SaveChanges(); + + pendingMediasBySha256.Clear(); + pendingMediasBySha1.Clear(); + pendingMediasByMd5.Clear(); + newMedias.Clear(); + newMediasByMachine.Clear(); + WorkFinished?.Invoke(this, System.EventArgs.Empty); romSet = Context.Singleton.RomSets.Find(romSet.Id); @@ -596,11 +740,14 @@ namespace RomRepoMgr.Core.Workers romSet.Machines.Count(m => m.Files.Count > 0 && m.Disks.Count > 0 && (m.Files.Any(f => !f.File.IsInRepo) || m.Disks.Any(f => !f.Disk.IsInRepo))), - TotalRoms = romSet.Machines.Sum(m => m.Files.Count) + romSet.Machines.Sum(m => m.Disks.Count), + TotalRoms = romSet.Machines.Sum(m => m.Files.Count) + romSet.Machines.Sum(m => m.Disks.Count) + + romSet.Machines.Sum(m => m.Medias.Count), HaveRoms = romSet.Machines.Sum(m => m.Files.Count(f => f.File.IsInRepo)) + - romSet.Machines.Sum(m => m.Disks.Count(f => f.Disk.IsInRepo)), + romSet.Machines.Sum(m => m.Disks.Count(f => f.Disk.IsInRepo)) + + romSet.Machines.Sum(m => m.Medias.Count(f => f.Media.IsInRepo)), MissRoms = romSet.Machines.Sum(m => m.Files.Count(f => !f.File.IsInRepo)) + - romSet.Machines.Sum(m => m.Disks.Count(f => !f.Disk.IsInRepo)) + romSet.Machines.Sum(m => m.Disks.Count(f => !f.Disk.IsInRepo)) + + romSet.Machines.Sum(m => m.Medias.Count(f => !f.Media.IsInRepo)) } }); } diff --git a/RomRepoMgr.Core/Workers/FileExporter.cs b/RomRepoMgr.Core/Workers/FileExporter.cs index f0ac612..566c5b7 100644 --- a/RomRepoMgr.Core/Workers/FileExporter.cs +++ b/RomRepoMgr.Core/Workers/FileExporter.cs @@ -107,13 +107,216 @@ namespace RomRepoMgr.Core.Workers Message = machine.Name }); + string machineName = machine.Name; + + Dictionary mediasByMachine = Context.Singleton.MediasByMachines. + Where(f => f.Machine.Id == machine.Id && + f.Media.IsInRepo). + ToDictionary(f => f.Name); + + if(mediasByMachine.Count > 0) + { + SetProgress2Bounds?.Invoke(this, new ProgressBoundsEventArgs + { + Minimum = 0, + Maximum = mediasByMachine.Count + }); + + if(machineName.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase)) + machineName = machineName.Substring(0, machineName.Length - 4); + + string machinePath = Path.Combine(_outPath, machineName); + + if(!Directory.Exists(machinePath)) + Directory.CreateDirectory(machinePath); + + long mediaPosition = 0; + + foreach(KeyValuePair mediaByMachine in mediasByMachine) + { + string outputPath = Path.Combine(machinePath, mediaByMachine.Key); + + if(!outputPath.EndsWith(".aif", StringComparison.InvariantCultureIgnoreCase)) + outputPath += ".aif"; + + SetProgress2?.Invoke(this, new ProgressEventArgs + { + Value = mediaPosition + }); + + string repoPath = null; + string md5Path = null; + string sha1Path = null; + string sha256Path = null; + + DbMedia media = mediaByMachine.Value.Media; + + if(media.Sha256 != null) + { + byte[] sha256Bytes = new byte[32]; + string sha256 = media.Sha256; + + 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(media.Sha1 != null) + { + byte[] sha1Bytes = new byte[20]; + string sha1 = media.Sha1; + + 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(media.Md5 != null) + { + byte[] md5Bytes = new byte[16]; + string md5 = media.Md5; + + 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) + throw new ArgumentException(string.Format(Localization.CannotFindHashInRepository, + media.Sha256 ?? media.Sha1 ?? media.Md5)); + + var inFs = new FileStream(repoPath, FileMode.Open, FileAccess.Read); + var outFs = new FileStream(outputPath, FileMode.Create, FileAccess.Write); + + SetMessage3?.Invoke(this, new MessageEventArgs + { + Message = string.Format(Localization.Copying, Path.GetFileName(outputPath)) + }); + + SetProgress3Bounds?.Invoke(this, new ProgressBoundsEventArgs + { + Minimum = 0, + Maximum = inFs.Length + }); + + byte[] buffer = new byte[BUFFER_SIZE]; + + while(inFs.Position + BUFFER_SIZE <= inFs.Length) + { + SetProgress3?.Invoke(this, new ProgressEventArgs + { + Value = inFs.Position + }); + + inFs.Read(buffer, 0, buffer.Length); + outFs.Write(buffer, 0, buffer.Length); + } + + buffer = new byte[inFs.Length - inFs.Position]; + + SetProgress3?.Invoke(this, new ProgressEventArgs + { + Value = inFs.Position + }); + + inFs.Read(buffer, 0, buffer.Length); + outFs.Write(buffer, 0, buffer.Length); + + inFs.Close(); + outFs.Close(); + + mediaPosition++; + } + } + Dictionary disksByMachine = Context.Singleton.DisksByMachines. Where(f => f.Machine.Id == machine.Id && f.Disk.IsInRepo). ToDictionary(f => f.Name); - string machineName = machine.Name; - if(disksByMachine.Count > 0) { SetProgress2Bounds?.Invoke(this, new ProgressBoundsEventArgs diff --git a/RomRepoMgr.Core/Workers/FileImporter.cs b/RomRepoMgr.Core/Workers/FileImporter.cs index 1d31c79..d952583 100644 --- a/RomRepoMgr.Core/Workers/FileImporter.cs +++ b/RomRepoMgr.Core/Workers/FileImporter.cs @@ -18,28 +18,36 @@ namespace RomRepoMgr.Core.Workers { public class FileImporter { - const long BUFFER_SIZE = 131072; - readonly bool _deleteAfterImport; - readonly List _newDisks; - readonly List _newFiles; - readonly bool _onlyKnown; - readonly Dictionary _pendingDisksByMd5; - readonly Dictionary _pendingDisksBySha1; - readonly Dictionary _pendingFiles; - string _lastMessage; - long _position; - long _totalFiles; + const long BUFFER_SIZE = 131072; + readonly bool _deleteAfterImport; + readonly List _newDisks; + readonly List _newFiles; + readonly List _newMedias; + readonly bool _onlyKnown; + readonly Dictionary _pendingDisksByMd5; + readonly Dictionary _pendingDisksBySha1; + readonly Dictionary _pendingFiles; + readonly Dictionary _pendingMediasByMd5; + readonly Dictionary _pendingMediasBySha1; + readonly Dictionary _pendingMediasBySha256; + string _lastMessage; + long _position; + long _totalFiles; public FileImporter(bool onlyKnown, bool deleteAfterImport) { - _pendingFiles = new Dictionary(); - _pendingDisksByMd5 = new Dictionary(); - _pendingDisksBySha1 = new Dictionary(); - _newFiles = new List(); - _newDisks = new List(); - _onlyKnown = onlyKnown; - _deleteAfterImport = deleteAfterImport; - _position = 0; + _pendingFiles = new Dictionary(); + _pendingDisksByMd5 = new Dictionary(); + _pendingDisksBySha1 = new Dictionary(); + _pendingMediasBySha256 = new Dictionary(); + _pendingMediasBySha1 = new Dictionary(); + _pendingMediasByMd5 = new Dictionary(); + _newFiles = new List(); + _newDisks = new List(); + _newMedias = new List(); + _onlyKnown = onlyKnown; + _deleteAfterImport = deleteAfterImport; + _position = 0; } public event EventHandler SetIndeterminateProgress2; @@ -92,6 +100,44 @@ namespace RomRepoMgr.Core.Workers var fs = new FileStream(file, FileMode.Open, FileAccess.Read); + var aif = AaruFormat.Create(fs); + + if(aif != null) + { + fs.Close(); + + bool ret = ImportMedia(file); + + if(ret) + { + ImportedRom?.Invoke(this, new ImportedRomItemEventArgs + { + Item = new ImportRomItem + { + Filename = Path.GetFileName(file), + Status = Localization.OK + } + }); + } + else + { + ImportedRom?.Invoke(this, new ImportedRomItemEventArgs + { + Item = new ImportRomItem + { + Filename = Path.GetFileName(file), + Status = string.Format(Localization.ErrorWithMessage, _lastMessage) + } + }); + } + + _position++; + + continue; + } + + fs.Position = 0; + var chd = CHDFile.Create(fs); if(chd != null) @@ -128,6 +174,8 @@ namespace RomRepoMgr.Core.Workers continue; } + fs.Close(); + if(processArchives) { SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty); @@ -782,6 +830,346 @@ namespace RomRepoMgr.Core.Workers } } + bool ImportMedia(string path) + { + try + { + var inFs = new FileStream(path, FileMode.Open, FileAccess.Read); + + SetMessage2?.Invoke(this, new MessageEventArgs + { + Message = Localization.HashingFile + }); + + var aif = AaruFormat.Create(path); + + if(aif == null) + { + _lastMessage = Localization.NotAnAaruFormatFile; + + return false; + } + + if(aif.MD5 == null && + aif.SHA1 == null && + aif.SHA256 == null) + { + _lastMessage = Localization.NoChecksumsFound; + + return false; + } + + string md5 = null; + string sha1 = null; + string sha256 = null; + + if(aif.MD5 != null) + { + char[] chdArray = new char[32]; + + for(int i = 0; i < 16; i++) + { + int nibble1 = aif.MD5[i] >> 4; + int nibble2 = aif.MD5[i] & 0xF; + + nibble1 += nibble1 >= 0xA ? 0x37 : 0x30; + nibble2 += nibble2 >= 0xA ? 0x37 : 0x30; + + chdArray[i * 2] = (char)nibble1; + chdArray[(i * 2) + 1] = (char)nibble2; + } + + md5 = new string(chdArray); + } + + if(aif.SHA1 != null) + { + char[] chdArray = new char[40]; + + for(int i = 0; i < 20; i++) + { + int nibble1 = aif.SHA1[i] >> 4; + int nibble2 = aif.SHA1[i] & 0xF; + + nibble1 += nibble1 >= 0xA ? 0x57 : 0x30; + nibble2 += nibble2 >= 0xA ? 0x57 : 0x30; + + chdArray[i * 2] = (char)nibble1; + chdArray[(i * 2) + 1] = (char)nibble2; + } + + sha1 = new string(chdArray); + } + + if(aif.SHA256 != null) + { + char[] chdArray = new char[64]; + + for(int i = 0; i < 32; i++) + { + int nibble1 = aif.SHA256[i] >> 4; + int nibble2 = aif.SHA256[i] & 0xF; + + nibble1 += nibble1 >= 0xA ? 0x57 : 0x30; + nibble2 += nibble2 >= 0xA ? 0x57 : 0x30; + + chdArray[i * 2] = (char)nibble1; + chdArray[(i * 2) + 1] = (char)nibble2; + } + + sha256 = new string(chdArray); + } + + ulong uSize = (ulong)inFs.Length; + bool mediaInDb = true; + DbMedia dbMedia = null; + bool knownMedia = false; + bool knownMediaWasBigger = false; + + if(sha256 != null) + knownMedia = _pendingMediasBySha1.TryGetValue(sha256, out dbMedia); + + if(!knownMedia && + sha1 != null) + knownMedia = _pendingMediasBySha1.TryGetValue(sha1, out dbMedia); + + if(!knownMedia && + md5 != null) + knownMedia = _pendingMediasByMd5.TryGetValue(md5, out dbMedia); + + dbMedia ??= Context.Singleton.Medias.FirstOrDefault(d => (d.Sha256 != null && d.Sha256 == sha256) || + (d.Sha1 != null && d.Sha1 == sha1) || + (d.Md5 != null && d.Md5 == sha1)); + + if(dbMedia == null) + { + if(_onlyKnown) + { + _lastMessage = Localization.UnknownFile; + + return false; + } + + dbMedia = new DbMedia + { + Md5 = md5, + Sha1 = sha1, + Sha256 = sha256, + Size = uSize, + CreatedOn = DateTime.UtcNow, + UpdatedOn = DateTime.UtcNow, + OriginalFileName = Path.GetFileName(path) + }; + + mediaInDb = false; + } + + if(!knownMedia) + { + if(sha256 != null) + _pendingMediasBySha256[sha256] = dbMedia; + else if(sha1 != null) + _pendingMediasBySha1[sha1] = dbMedia; + else if(md5 != null) + _pendingMediasByMd5[md5] = dbMedia; + } + + string sha256B32 = null; + string sha1B32 = null; + string md5B32 = null; + + if(aif.SHA256 != null) + sha256B32 = Base32.ToBase32String(aif.SHA256); + + if(aif.SHA1 != null) + sha1B32 = Base32.ToBase32String(aif.SHA1); + + if(aif.MD5 != null) + md5B32 = Base32.ToBase32String(aif.SHA1); + + if(dbMedia.Md5 == null && + md5 != null) + { + dbMedia.Md5 = md5; + dbMedia.UpdatedOn = DateTime.UtcNow; + } + + if(dbMedia.Sha1 == null && + sha1 != null) + { + dbMedia.Sha1 = sha1; + dbMedia.UpdatedOn = DateTime.UtcNow; + } + + if(dbMedia.Sha256 == null && + sha256 != null) + { + dbMedia.Sha256 = sha256; + dbMedia.UpdatedOn = DateTime.UtcNow; + } + + if(dbMedia.Size > uSize) + { + knownMediaWasBigger = true; + dbMedia.Size = null; + } + + if(dbMedia.Size == null) + { + dbMedia.Size = uSize; + dbMedia.UpdatedOn = DateTime.UtcNow; + } + + string md5Path = null; + string sha1Path = null; + string sha256Path = null; + string repoPath = null; + + if(md5 != null) + { + md5Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "aaru", "md5", + md5B32[0].ToString(), md5B32[1].ToString(), md5B32[2].ToString(), + md5B32[3].ToString(), md5B32[4].ToString()); + + repoPath = md5Path; + + md5Path = Path.Combine(repoPath, md5B32 + ".aif"); + } + + if(sha1 != null) + { + sha1Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "aaru", "sha1", + sha1B32[0].ToString(), sha1B32[1].ToString(), sha1B32[2].ToString(), + sha1B32[3].ToString(), sha1B32[4].ToString()); + + repoPath = sha1Path; + + sha1Path = Path.Combine(repoPath, sha1B32 + ".aif"); + } + + if(sha256 != null) + { + sha256Path = Path.Combine(Settings.Settings.Current.RepositoryPath, "aaru", "sha256", + sha256B32[0].ToString(), sha256B32[1].ToString(), sha256B32[2].ToString(), + sha256B32[3].ToString(), sha256B32[4].ToString()); + + repoPath = sha256Path; + + sha256Path = Path.Combine(repoPath, sha256B32 + ".aif"); + } + + if(!Directory.Exists(repoPath)) + Directory.CreateDirectory(repoPath); + + if(File.Exists(md5Path)) + { + if(sha256Path != null) + File.Move(md5Path, sha256Path); + else if(sha1Path != null) + File.Move(md5Path, sha1Path); + } + + if(File.Exists(sha1Path) && + sha256Path != null) + File.Move(sha1Path, sha256Path); + + if(sha256Path != null) + repoPath = sha256Path; + else if(sha1Path != null) + repoPath = sha1Path; + else if(md5Path != null) + repoPath = md5Path; + + if(File.Exists(repoPath)) + { + if(!knownMediaWasBigger) + File.Move(repoPath, repoPath + ".bak", true); + else + { + dbMedia.IsInRepo = true; + dbMedia.UpdatedOn = DateTime.UtcNow; + + if(!mediaInDb) + _newMedias.Add(dbMedia); + + inFs.Close(); + + if(_deleteAfterImport) + File.Delete(path); + + return true; + } + } + + inFs.Position = 0; + var outFs = new FileStream(repoPath, FileMode.CreateNew, FileAccess.Write); + + SetProgressBounds2?.Invoke(this, new ProgressBoundsEventArgs + { + Minimum = 0, + Maximum = inFs.Length + }); + + SetMessage2?.Invoke(this, new MessageEventArgs + { + Message = Localization.CopyingFile + }); + + byte[] buffer = new byte[BUFFER_SIZE]; + + while(inFs.Position + BUFFER_SIZE <= inFs.Length) + { + SetProgress2?.Invoke(this, new ProgressEventArgs + { + Value = inFs.Position + }); + + inFs.Read(buffer, 0, buffer.Length); + outFs.Write(buffer, 0, buffer.Length); + } + + buffer = new byte[inFs.Length - inFs.Position]; + + SetProgress2?.Invoke(this, new ProgressEventArgs + { + Value = inFs.Position + }); + + inFs.Read(buffer, 0, buffer.Length); + outFs.Write(buffer, 0, buffer.Length); + + SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty); + + SetMessage2?.Invoke(this, new MessageEventArgs + { + Message = Localization.Finishing + }); + + inFs.Close(); + outFs.Close(); + + dbMedia.IsInRepo = true; + dbMedia.UpdatedOn = DateTime.UtcNow; + + if(!mediaInDb) + _newMedias.Add(dbMedia); + + if(_deleteAfterImport) + File.Delete(path); + + if(knownMediaWasBigger) + File.Delete(repoPath + ".bak"); + + return true; + } + catch(Exception e) + { + _lastMessage = Localization.UnhandledExceptionWhenImporting; + + return false; + } + } + void SaveChanges() { SetIndeterminateProgress2?.Invoke(this, System.EventArgs.Empty); diff --git a/RomRepoMgr.Database/Context.cs b/RomRepoMgr.Database/Context.cs index e2a5ab6..67f4d3a 100644 --- a/RomRepoMgr.Database/Context.cs +++ b/RomRepoMgr.Database/Context.cs @@ -54,12 +54,14 @@ namespace RomRepoMgr.Database } } - public DbSet Files { get; set; } - public DbSet RomSets { get; set; } - public DbSet Machines { get; set; } - public DbSet FilesByMachines { get; set; } - public DbSet Disks { get; set; } - public DbSet DisksByMachines { get; set; } + public DbSet Files { get; set; } + public DbSet RomSets { get; set; } + public DbSet Machines { get; set; } + public DbSet FilesByMachines { get; set; } + public DbSet Disks { get; set; } + public DbSet DisksByMachines { get; set; } + public DbSet Medias { get; set; } + public DbSet MediasByMachines { get; set; } public static void ReplaceSingleton(string dbPath) => _singleton = Create(dbPath); @@ -151,6 +153,30 @@ namespace RomRepoMgr.Database entity.HasOne(e => e.Disk).WithMany(e => e.Machines).OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity(entity => + { + entity.HasIndex(e => e.Md5); + + entity.HasIndex(e => e.Sha1); + + entity.HasIndex(e => e.Sha256); + + entity.HasIndex(e => e.SpamSum); + + entity.HasIndex(e => e.Size); + + entity.HasIndex(e => e.IsInRepo); + }); + + modelBuilder.Entity(entity => + { + entity.HasIndex(e => e.Name); + + entity.HasOne(e => e.Machine).WithMany(e => e.Medias).OnDelete(DeleteBehavior.Cascade); + + entity.HasOne(e => e.Media).WithMany(e => e.Machines).OnDelete(DeleteBehavior.Cascade); + }); } } } \ No newline at end of file diff --git a/RomRepoMgr.Database/Migrations/20200904201029_AddMedias.Designer.cs b/RomRepoMgr.Database/Migrations/20200904201029_AddMedias.Designer.cs new file mode 100644 index 0000000..1ea57e9 --- /dev/null +++ b/RomRepoMgr.Database/Migrations/20200904201029_AddMedias.Designer.cs @@ -0,0 +1,408 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RomRepoMgr.Database; + +namespace RomRepoMgr.Database.Migrations +{ + [DbContext(typeof(Context))] + [Migration("20200904201029_AddMedias")] + partial class AddMedias + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.7"); + + modelBuilder.Entity("RomRepoMgr.Database.Models.DbDisk", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("IsInRepo") + .HasColumnType("INTEGER"); + + b.Property("Md5") + .HasColumnType("TEXT") + .HasMaxLength(32); + + b.Property("OriginalFileName") + .HasColumnType("TEXT"); + + b.Property("Sha1") + .HasColumnType("TEXT") + .HasMaxLength(40); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Md5"); + + b.HasIndex("Sha1"); + + b.HasIndex("Size"); + + b.ToTable("Disks"); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.DbFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Crc32") + .HasColumnType("TEXT") + .HasMaxLength(8); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("IsInRepo") + .HasColumnType("INTEGER"); + + b.Property("Md5") + .HasColumnType("TEXT") + .HasMaxLength(32); + + b.Property("OriginalFileName") + .HasColumnType("TEXT"); + + b.Property("Sha1") + .HasColumnType("TEXT") + .HasMaxLength(40); + + b.Property("Sha256") + .HasColumnType("TEXT") + .HasMaxLength(64); + + b.Property("Sha384") + .HasColumnType("TEXT") + .HasMaxLength(96); + + b.Property("Sha512") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Crc32"); + + b.HasIndex("Md5"); + + b.HasIndex("Sha1"); + + b.HasIndex("Sha256"); + + b.HasIndex("Sha384"); + + b.HasIndex("Sha512"); + + b.HasIndex("Size"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.DbMedia", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("IsInRepo") + .HasColumnType("INTEGER"); + + b.Property("Md5") + .HasColumnType("TEXT") + .HasMaxLength(32); + + b.Property("OriginalFileName") + .HasColumnType("TEXT"); + + b.Property("Sha1") + .HasColumnType("TEXT") + .HasMaxLength(40); + + b.Property("Sha256") + .HasColumnType("TEXT") + .HasMaxLength(64); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.Property("SpamSum") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IsInRepo"); + + b.HasIndex("Md5"); + + b.HasIndex("Sha1"); + + b.HasIndex("Sha256"); + + b.HasIndex("Size"); + + b.HasIndex("SpamSum"); + + b.ToTable("Medias"); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.DiskByMachine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DiskId") + .HasColumnType("INTEGER"); + + b.Property("MachineId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DiskId"); + + b.HasIndex("MachineId"); + + b.HasIndex("Name"); + + b.ToTable("DisksByMachines"); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.FileByMachine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FileId") + .HasColumnType("INTEGER"); + + b.Property("MachineId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("FileId"); + + b.HasIndex("MachineId"); + + b.HasIndex("Name"); + + b.ToTable("FilesByMachines"); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.Machine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RomSetId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.HasIndex("RomSetId"); + + b.ToTable("Machines"); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.MediaByMachine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MachineId") + .HasColumnType("INTEGER"); + + b.Property("MediaId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MachineId"); + + b.HasIndex("MediaId"); + + b.HasIndex("Name"); + + b.ToTable("MediasByMachines"); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.RomSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .HasColumnType("TEXT"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Filename") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Homepage") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Sha384") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(96); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Author"); + + b.HasIndex("Comment"); + + b.HasIndex("Date"); + + b.HasIndex("Description"); + + b.HasIndex("Filename"); + + b.HasIndex("Homepage"); + + b.HasIndex("Name"); + + b.HasIndex("Sha384"); + + b.HasIndex("Version"); + + b.ToTable("RomSets"); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.DiskByMachine", b => + { + b.HasOne("RomRepoMgr.Database.Models.DbDisk", "Disk") + .WithMany("Machines") + .HasForeignKey("DiskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RomRepoMgr.Database.Models.Machine", "Machine") + .WithMany("Disks") + .HasForeignKey("MachineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.FileByMachine", b => + { + b.HasOne("RomRepoMgr.Database.Models.DbFile", "File") + .WithMany("Machines") + .HasForeignKey("FileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RomRepoMgr.Database.Models.Machine", "Machine") + .WithMany("Files") + .HasForeignKey("MachineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.Machine", b => + { + b.HasOne("RomRepoMgr.Database.Models.RomSet", "RomSet") + .WithMany("Machines") + .HasForeignKey("RomSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.MediaByMachine", b => + { + b.HasOne("RomRepoMgr.Database.Models.Machine", "Machine") + .WithMany("Medias") + .HasForeignKey("MachineId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RomRepoMgr.Database.Models.DbMedia", "Media") + .WithMany("Machines") + .HasForeignKey("MediaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/RomRepoMgr.Database/Migrations/20200904201029_AddMedias.cs b/RomRepoMgr.Database/Migrations/20200904201029_AddMedias.cs new file mode 100644 index 0000000..c1f5f20 --- /dev/null +++ b/RomRepoMgr.Database/Migrations/20200904201029_AddMedias.cs @@ -0,0 +1,70 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace RomRepoMgr.Database.Migrations +{ + public partial class AddMedias : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("Medias", table => new + { + Id = table.Column(nullable: false).Annotation("Sqlite:Autoincrement", true), + CreatedOn = table.Column(nullable: false), + UpdatedOn = table.Column(nullable: false), + Size = table.Column(nullable: true), + Md5 = table.Column(maxLength: 32, nullable: true), + Sha1 = table.Column(maxLength: 40, nullable: true), + Sha256 = table.Column(maxLength: 64, nullable: true), + SpamSum = table.Column(nullable: true), + IsInRepo = table.Column(nullable: false), + OriginalFileName = table.Column(nullable: true) + }, constraints: table => + { + table.PrimaryKey("PK_Medias", x => x.Id); + }); + + migrationBuilder.CreateTable("MediasByMachines", table => new + { + Id = table.Column(nullable: false).Annotation("Sqlite:Autoincrement", true), + MediaId = table.Column(nullable: false), + MachineId = table.Column(nullable: false), + Name = table.Column(nullable: false) + }, constraints: table => + { + table.PrimaryKey("PK_MediasByMachines", x => x.Id); + + table.ForeignKey("FK_MediasByMachines_Machines_MachineId", x => x.MachineId, "Machines", "Id", + onDelete: ReferentialAction.Cascade); + + table.ForeignKey("FK_MediasByMachines_Medias_MediaId", x => x.MediaId, "Medias", "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex("IX_Medias_IsInRepo", "Medias", "IsInRepo"); + + migrationBuilder.CreateIndex("IX_Medias_Md5", "Medias", "Md5"); + + migrationBuilder.CreateIndex("IX_Medias_Sha1", "Medias", "Sha1"); + + migrationBuilder.CreateIndex("IX_Medias_Sha256", "Medias", "Sha256"); + + migrationBuilder.CreateIndex("IX_Medias_Size", "Medias", "Size"); + + migrationBuilder.CreateIndex("IX_Medias_SpamSum", "Medias", "SpamSum"); + + migrationBuilder.CreateIndex("IX_MediasByMachines_MachineId", "MediasByMachines", "MachineId"); + + migrationBuilder.CreateIndex("IX_MediasByMachines_MediaId", "MediasByMachines", "MediaId"); + + migrationBuilder.CreateIndex("IX_MediasByMachines_Name", "MediasByMachines", "Name"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("MediasByMachines"); + + migrationBuilder.DropTable("Medias"); + } + } +} \ No newline at end of file diff --git a/RomRepoMgr.Database/Migrations/ContextModelSnapshot.cs b/RomRepoMgr.Database/Migrations/ContextModelSnapshot.cs index e24a285..1ec68d7 100644 --- a/RomRepoMgr.Database/Migrations/ContextModelSnapshot.cs +++ b/RomRepoMgr.Database/Migrations/ContextModelSnapshot.cs @@ -88,6 +88,45 @@ namespace RomRepoMgr.Database.Migrations b.ToTable("Files"); }); + modelBuilder.Entity("RomRepoMgr.Database.Models.DbMedia", b => + { + b.Property("Id").ValueGeneratedOnAdd().HasColumnType("INTEGER"); + + b.Property("CreatedOn").HasColumnType("TEXT"); + + b.Property("IsInRepo").HasColumnType("INTEGER"); + + b.Property("Md5").HasColumnType("TEXT").HasMaxLength(32); + + b.Property("OriginalFileName").HasColumnType("TEXT"); + + b.Property("Sha1").HasColumnType("TEXT").HasMaxLength(40); + + b.Property("Sha256").HasColumnType("TEXT").HasMaxLength(64); + + b.Property("Size").HasColumnType("INTEGER"); + + b.Property("SpamSum").HasColumnType("TEXT"); + + b.Property("UpdatedOn").HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IsInRepo"); + + b.HasIndex("Md5"); + + b.HasIndex("Sha1"); + + b.HasIndex("Sha256"); + + b.HasIndex("Size"); + + b.HasIndex("SpamSum"); + + b.ToTable("Medias"); + }); + modelBuilder.Entity("RomRepoMgr.Database.Models.DiskByMachine", b => { b.Property("Id").ValueGeneratedOnAdd().HasColumnType("INTEGER"); @@ -151,6 +190,27 @@ namespace RomRepoMgr.Database.Migrations b.ToTable("Machines"); }); + modelBuilder.Entity("RomRepoMgr.Database.Models.MediaByMachine", b => + { + b.Property("Id").ValueGeneratedOnAdd().HasColumnType("INTEGER"); + + b.Property("MachineId").HasColumnType("INTEGER"); + + b.Property("MediaId").HasColumnType("INTEGER"); + + b.Property("Name").IsRequired().HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MachineId"); + + b.HasIndex("MediaId"); + + b.HasIndex("Name"); + + b.ToTable("MediasByMachines"); + }); + modelBuilder.Entity("RomRepoMgr.Database.Models.RomSet", b => { b.Property("Id").ValueGeneratedOnAdd().HasColumnType("INTEGER"); @@ -223,6 +283,15 @@ namespace RomRepoMgr.Database.Migrations b.HasOne("RomRepoMgr.Database.Models.RomSet", "RomSet").WithMany("Machines").HasForeignKey("RomSetId"). OnDelete(DeleteBehavior.Cascade).IsRequired(); }); + + modelBuilder.Entity("RomRepoMgr.Database.Models.MediaByMachine", b => + { + b.HasOne("RomRepoMgr.Database.Models.Machine", "Machine").WithMany("Medias").HasForeignKey("MachineId"). + OnDelete(DeleteBehavior.Cascade).IsRequired(); + + b.HasOne("RomRepoMgr.Database.Models.DbMedia", "Media").WithMany("Machines").HasForeignKey("MediaId"). + OnDelete(DeleteBehavior.Cascade).IsRequired(); + }); #pragma warning restore 612, 618 } } diff --git a/RomRepoMgr.Database/Models/DbMedia.cs b/RomRepoMgr.Database/Models/DbMedia.cs new file mode 100644 index 0000000..44e9e6d --- /dev/null +++ b/RomRepoMgr.Database/Models/DbMedia.cs @@ -0,0 +1,47 @@ +/****************************************************************************** +// RomRepoMgr - ROM repository manager +// ---------------------------------------------------------------------------- +// +// Author(s) : Natalia Portillo +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2020 Natalia Portillo +*******************************************************************************/ + +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace RomRepoMgr.Database.Models +{ + public class DbMedia : BaseModel + { + public ulong? Size { get; set; } + [StringLength(32, MinimumLength = 32)] + public string Md5 { get; set; } + [StringLength(40, MinimumLength = 40)] + public string Sha1 { get; set; } + [StringLength(64, MinimumLength = 64)] + public string Sha256 { get; set; } + public string SpamSum { get; set; } + [DefaultValue(false)] + public bool IsInRepo { get; set; } + public string OriginalFileName { get; set; } + public virtual IEnumerable Machines { get; set; } + } +} \ No newline at end of file diff --git a/RomRepoMgr.Database/Models/Machine.cs b/RomRepoMgr.Database/Models/Machine.cs index 952957d..0d2bfbe 100644 --- a/RomRepoMgr.Database/Models/Machine.cs +++ b/RomRepoMgr.Database/Models/Machine.cs @@ -33,8 +33,9 @@ namespace RomRepoMgr.Database.Models [Required] public string Name { get; set; } [Required] - public virtual RomSet RomSet { get; set; } - public virtual ICollection Files { get; set; } - public virtual ICollection Disks { get; set; } + public virtual RomSet RomSet { get; set; } + public virtual ICollection Files { get; set; } + public virtual ICollection Disks { get; set; } + public virtual ICollection Medias { get; set; } } } \ No newline at end of file diff --git a/RomRepoMgr.Database/Models/MediaByMachine.cs b/RomRepoMgr.Database/Models/MediaByMachine.cs new file mode 100644 index 0000000..fce268e --- /dev/null +++ b/RomRepoMgr.Database/Models/MediaByMachine.cs @@ -0,0 +1,41 @@ +/****************************************************************************** +// RomRepoMgr - ROM repository manager +// ---------------------------------------------------------------------------- +// +// Author(s) : Natalia Portillo +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2020 Natalia Portillo +*******************************************************************************/ + +using System.ComponentModel.DataAnnotations; + +namespace RomRepoMgr.Database.Models +{ + public class MediaByMachine + { + [Key] + public ulong Id { get; set; } + [Required] + public virtual DbMedia Media { get; set; } + [Required] + public virtual Machine Machine { get; set; } + [Required] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/RomRepoMgr/ViewModels/SplashWindowViewModel.cs b/RomRepoMgr/ViewModels/SplashWindowViewModel.cs index 9845da3..a071f85 100644 --- a/RomRepoMgr/ViewModels/SplashWindowViewModel.cs +++ b/RomRepoMgr/ViewModels/SplashWindowViewModel.cs @@ -345,11 +345,14 @@ namespace RomRepoMgr.ViewModels (m.Files.Any(f => !f.File.IsInRepo) || m.Disks.Any(f => !f.Disk.IsInRepo))), TotalRoms = r.Machines.Sum(m => m.Files.Count) + - r.Machines.Sum(m => m.Disks.Count), + r.Machines.Sum(m => m.Disks.Count) + + r.Machines.Sum(m => m.Medias.Count), HaveRoms = r.Machines.Sum(m => m.Files.Count(f => f.File.IsInRepo)) + - r.Machines.Sum(m => m.Disks.Count(f => f.Disk.IsInRepo)), + r.Machines.Sum(m => m.Disks.Count(f => f.Disk.IsInRepo)) + + r.Machines.Sum(m => m.Medias.Count(f => f.Media.IsInRepo)), MissRoms = r.Machines.Sum(m => m.Files.Count(f => !f.File.IsInRepo)) + - r.Machines.Sum(m => m.Disks.Count(f => !f.Disk.IsInRepo)) + r.Machines.Sum(m => m.Disks.Count(f => !f.Disk.IsInRepo)) + + r.Machines.Sum(m => m.Medias.Count(f => !f.Media.IsInRepo)) }).ToList() });