From b27d61d2ad3b2592bfab8ee3e630e67216c233e7 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Tue, 7 May 2019 16:20:30 -0700 Subject: [PATCH 1/5] Find -> FindFile --- ModuleManager/Extensions/UrlDirExtensions.cs | 4 +-- ModuleManager/MMPatchLoader.cs | 2 +- .../Extensions/UrlDirExtensionsTest.cs | 32 +++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ModuleManager/Extensions/UrlDirExtensions.cs b/ModuleManager/Extensions/UrlDirExtensions.cs index 3a57bf6a..7ffa31fc 100644 --- a/ModuleManager/Extensions/UrlDirExtensions.cs +++ b/ModuleManager/Extensions/UrlDirExtensions.cs @@ -5,7 +5,7 @@ namespace ModuleManager.Extensions { public static class UrlDirExtensions { - public static UrlDir.UrlFile Find(this UrlDir urlDir, string url) + public static UrlDir.UrlFile FindFile(this UrlDir urlDir, string url) { if (urlDir == null) throw new ArgumentNullException(nameof(urlDir)); if (url == null) throw new ArgumentNullException(nameof(url)); @@ -18,7 +18,7 @@ public static UrlDir.UrlFile Find(this UrlDir urlDir, string url) currentDir = currentDir.children.FirstOrDefault(subDir => subDir.name == splits[i]); if (currentDir == null) return null; } - + string fileName = splits[splits.Length - 1]; string fileExtension = null; diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index b7e28179..f709b5ce 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -528,7 +528,7 @@ private IEnumerable LoadCache() { string parentUrl = node.GetValue("parentUrl"); - UrlDir.UrlFile parent = gameDataDir.Find(parentUrl); + UrlDir.UrlFile parent = gameDataDir.FindFile(parentUrl); if (parent != null) { databaseConfigs.Add(new ProtoUrlConfig(parent, node.nodes[0])); diff --git a/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs index d7ff2316..41871e5f 100644 --- a/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs @@ -9,77 +9,77 @@ public class UrlDirExtensionsTest { [Fact] - public void TestFind__IndirectChild() + public void TestFindFile__IndirectChild() { UrlDir urlDir = UrlBuilder.CreateDir("abc"); UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def/ghi.cfg", urlDir); - Assert.Equal(urlFile, urlDir.Find("def/ghi")); + Assert.Equal(urlFile, urlDir.FindFile("def/ghi")); } [Fact] - public void TestFind__DirectChild() + public void TestFindFile__DirectChild() { UrlDir urlDir = UrlBuilder.CreateDir("abc"); UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def.cfg", urlDir); - Assert.Equal(urlFile, urlDir.Find("def")); + Assert.Equal(urlFile, urlDir.FindFile("def")); } [Fact] - public void TestFind__Extension() + public void TestFindFile__Extension() { UrlDir urlDir = UrlBuilder.CreateDir("abc"); UrlBuilder.CreateFile("def/ghi.yyy", urlDir); UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def/ghi.cfg", urlDir); UrlBuilder.CreateFile("def/ghi.zzz", urlDir); - Assert.Equal(urlFile, urlDir.Find("def/ghi.cfg")); + Assert.Equal(urlFile, urlDir.FindFile("def/ghi.cfg")); } [Fact] - public void TestFind__NotFound() + public void TestFindFile__NotFound() { UrlDir urlDir = UrlBuilder.CreateDir("abc"); UrlBuilder.CreateDir("def", urlDir); - Assert.Null(urlDir.Find("def/ghi")); + Assert.Null(urlDir.FindFile("def/ghi")); } [Fact] - public void TestFind__Extension__NotFound() + public void TestFindFile__Extension__NotFound() { UrlDir urlDir = UrlBuilder.CreateDir("abc"); UrlBuilder.CreateFile("def/ghi.yyy", urlDir); UrlBuilder.CreateFile("def/ghi.zzz", urlDir); - Assert.Null(urlDir.Find("def/ghi.cfg")); + Assert.Null(urlDir.FindFile("def/ghi.cfg")); } [Fact] - public void TestFind__IntermediateDirectoryNotFound() + public void TestFindFile__IntermediateDirectoryNotFound() { UrlDir urlDir = UrlBuilder.CreateDir("abc"); - Assert.Null(urlDir.Find("def/ghi")); + Assert.Null(urlDir.FindFile("def/ghi")); } [Fact] - public void TestFind__UrlDirNull() + public void TestFindFile__UrlDirNull() { ArgumentNullException ex = Assert.Throws(delegate { - UrlDirExtensions.Find(null, "abc"); + UrlDirExtensions.FindFile(null, "abc"); }); Assert.Equal("urlDir", ex.ParamName); } [Fact] - public void TestFind__UrlNull() + public void TestFindFile__UrlNull() { ArgumentNullException ex = Assert.Throws(delegate { - UrlDirExtensions.Find(UrlBuilder.CreateDir("abc"), null); + UrlDirExtensions.FindFile(UrlBuilder.CreateDir("abc"), null); }); Assert.Equal("url", ex.ParamName); From 24995f4ff6d1f98fb1f8a6be18bbfe2afd043d07 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Tue, 7 May 2019 16:47:46 -0700 Subject: [PATCH 2/5] remove unnecessary newline --- ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs index 41871e5f..d5fd9e5c 100644 --- a/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs @@ -7,7 +7,6 @@ namespace ModuleManagerTests.Extensions { public class UrlDirExtensionsTest { - [Fact] public void TestFindFile__IndirectChild() { From a1f0b614c34905edc3f2a202d66587c9fbb26574 Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Tue, 7 May 2019 16:51:29 -0700 Subject: [PATCH 3/5] add optional FileType in UrlDir.FindFile --- ModuleManager/Extensions/UrlDirExtensions.cs | 3 ++- .../Extensions/UrlDirExtensionsTest.cs | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/ModuleManager/Extensions/UrlDirExtensions.cs b/ModuleManager/Extensions/UrlDirExtensions.cs index 7ffa31fc..775cfa6e 100644 --- a/ModuleManager/Extensions/UrlDirExtensions.cs +++ b/ModuleManager/Extensions/UrlDirExtensions.cs @@ -5,7 +5,7 @@ namespace ModuleManager.Extensions { public static class UrlDirExtensions { - public static UrlDir.UrlFile FindFile(this UrlDir urlDir, string url) + public static UrlDir.UrlFile FindFile(this UrlDir urlDir, string url, UrlDir.FileType? fileType = null) { if (urlDir == null) throw new ArgumentNullException(nameof(urlDir)); if (url == null) throw new ArgumentNullException(nameof(url)); @@ -34,6 +34,7 @@ public static UrlDir.UrlFile FindFile(this UrlDir urlDir, string url) { if (file.name != fileName) continue; if (fileExtension != null && fileExtension != file.fileExtension) continue; + if (fileType != null && file.fileType != fileType) continue; return file; } diff --git a/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs index d5fd9e5c..91d15c5f 100644 --- a/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs +++ b/ModuleManagerTests/Extensions/UrlDirExtensionsTest.cs @@ -36,6 +36,17 @@ public void TestFindFile__Extension() Assert.Equal(urlFile, urlDir.FindFile("def/ghi.cfg")); } + [Fact] + public void TestFindFile__FileType() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlBuilder.CreateFile("def/ghi.yyy", urlDir); + UrlDir.UrlFile urlFile = UrlBuilder.CreateFile("def/ghi.cfg", urlDir); + UrlBuilder.CreateFile("def/ghi.zzz", urlDir); + + Assert.Equal(urlFile, urlDir.FindFile("def/ghi", UrlDir.FileType.Config)); + } + [Fact] public void TestFindFile__NotFound() { @@ -55,6 +66,16 @@ public void TestFindFile__Extension__NotFound() Assert.Null(urlDir.FindFile("def/ghi.cfg")); } + [Fact] + public void TestFindFile__FileType__NotFound() + { + UrlDir urlDir = UrlBuilder.CreateDir("abc"); + UrlBuilder.CreateFile("def/ghi.yyy", urlDir); + UrlBuilder.CreateFile("def/ghi.zzz", urlDir); + + Assert.Null(urlDir.FindFile("def/ghi", UrlDir.FileType.Config)); + } + [Fact] public void TestFindFile__IntermediateDirectoryNotFound() { From 38f439fa20f989173b6a344c42ec99cc94d90a2a Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Tue, 7 May 2019 17:36:04 -0700 Subject: [PATCH 4/5] add GetGameData extension method to GameDatabase --- ModuleManager/Extensions/GameDatabaseExtensions.cs | 10 ++++++++++ ModuleManager/MMPatchLoader.cs | 6 +++--- ModuleManager/ModListGenerator.cs | 2 +- ModuleManager/ModuleManager.csproj | 1 + 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 ModuleManager/Extensions/GameDatabaseExtensions.cs diff --git a/ModuleManager/Extensions/GameDatabaseExtensions.cs b/ModuleManager/Extensions/GameDatabaseExtensions.cs new file mode 100644 index 00000000..95769521 --- /dev/null +++ b/ModuleManager/Extensions/GameDatabaseExtensions.cs @@ -0,0 +1,10 @@ +using System; +using System.Linq; + +public static class GameDatabaseExtensions +{ + public static UrlDir GetGameData(this GameDatabase gameDatabase) + { + return gameDatabase.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == ""); + } +} diff --git a/ModuleManager/MMPatchLoader.cs b/ModuleManager/MMPatchLoader.cs index f709b5ce..cdc456f5 100644 --- a/ModuleManager/MMPatchLoader.cs +++ b/ModuleManager/MMPatchLoader.cs @@ -114,7 +114,7 @@ public IEnumerable Run() status = "Extracting patches"; patchLogger.Info(status); - UrlDir gameData = GameDatabase.Instance.root.children.First(dir => dir.type == UrlDir.DirectoryType.GameData && dir.name == ""); + UrlDir gameData = GameDatabase.Instance.GetGameData(); INeedsChecker needsChecker = new NeedsChecker(mods, gameData, progress, patchLogger); ITagListParser tagListParser = new TagListParser(progress); IProtoPatchBuilder protoPatchBuilder = new ProtoPatchBuilder(progress); @@ -248,7 +248,7 @@ public IEnumerable Run() private void LoadPhysicsConfig() { logger.Info("Loading Physics.cfg"); - UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith("GameData") && d.name == "" && d.url == ""); + UrlDir gameDataDir = GameDatabase.Instance.GetGameData(); // need to use a file with a cfg extension to get the right fileType or you can't AddConfig on it UrlDir.UrlFile physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath)); // Since it loaded the default config badly (sub node only) we clear it first @@ -517,7 +517,7 @@ private IEnumerable LoadCache() status = "ModuleManager: " + patchedNodeCount + " patch" + (patchedNodeCount != 1 ? "es" : "") + " loaded from cache"; // Create the fake file where we load the physic config cache - UrlDir gameDataDir = GameDatabase.Instance.root.AllDirectories.First(d => d.path.EndsWith("GameData") && d.name == "" && d.url == ""); + UrlDir gameDataDir = GameDatabase.Instance.GetGameData(); // need to use a file with a cfg extension to get the right fileType or you can't AddConfig on it UrlDir.UrlFile physicsUrlFile = new UrlDir.UrlFile(gameDataDir, new FileInfo(defaultPhysicsPath)); gameDataDir.files.Add(physicsUrlFile); diff --git a/ModuleManager/ModListGenerator.cs b/ModuleManager/ModListGenerator.cs index 5d017232..7f776dca 100644 --- a/ModuleManager/ModListGenerator.cs +++ b/ModuleManager/ModListGenerator.cs @@ -117,7 +117,7 @@ public static IEnumerable GenerateModList(IEnumerable dir.type == UrlDir.DirectoryType.GameData); + UrlDir gameData = GameDatabase.Instance.GetGameData(); foreach (UrlDir subDir in gameData.children) { string cleanName = subDir.name.RemoveWS(); diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index ac7665b4..d3c9b8da 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -42,6 +42,7 @@ + From 23559d47780af1db1042bb98cab969cf259c0f5a Mon Sep 17 00:00:00 2001 From: blowfishpro Date: Mon, 27 May 2019 16:05:53 -0700 Subject: [PATCH 5/5] wip --- ModuleManager/FilePathRepository.cs | 2 + ModuleManager/ModuleManager.csproj | 5 + ModuleManager/PartDatabaseCache.cs | 101 ++++++++++++++++++ ModuleManager/PartShaGenerator.cs | 81 ++++++++++++++ ModuleManager/Utils/FileShaGenerator.cs | 56 ++++++++++ ModuleManager/Utils/ReadableObject.cs | 56 ++++++++++ .../Utils/UrlFileToReadableObjectConverter.cs | 19 ++++ 7 files changed, 320 insertions(+) create mode 100644 ModuleManager/PartDatabaseCache.cs create mode 100644 ModuleManager/PartShaGenerator.cs create mode 100644 ModuleManager/Utils/FileShaGenerator.cs create mode 100644 ModuleManager/Utils/ReadableObject.cs create mode 100644 ModuleManager/Utils/UrlFileToReadableObjectConverter.cs diff --git a/ModuleManager/FilePathRepository.cs b/ModuleManager/FilePathRepository.cs index 4ba34321..5462b51d 100644 --- a/ModuleManager/FilePathRepository.cs +++ b/ModuleManager/FilePathRepository.cs @@ -21,5 +21,7 @@ internal static class FilePathRepository internal static readonly string logsDirPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "Logs"), "ModuleManager"); internal static readonly string logPath = Path.Combine(logsDirPath, "ModuleManager.log"); internal static readonly string patchLogPath = Path.Combine(logsDirPath, "MMPatch.log"); + + internal static readonly string partDatabaseShaPath = Path.Combine(Path.Combine(KSPUtil.ApplicationRootPath, "GameData"), "ModuleManager.PartDatabasSha"); } } diff --git a/ModuleManager/ModuleManager.csproj b/ModuleManager/ModuleManager.csproj index d3c9b8da..9ffde56e 100644 --- a/ModuleManager/ModuleManager.csproj +++ b/ModuleManager/ModuleManager.csproj @@ -72,6 +72,8 @@ + + @@ -106,7 +108,10 @@ + + + diff --git a/ModuleManager/PartDatabaseCache.cs b/ModuleManager/PartDatabaseCache.cs new file mode 100644 index 00000000..77d0e3e9 --- /dev/null +++ b/ModuleManager/PartDatabaseCache.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using ModuleManager.Extensions; +using ModuleManager.Logging; +using ModuleManager.Utils; + +using static ModuleManager.FilePathRepository; + +namespace ModuleManager +{ + public class PartDatabaseCache + { + private readonly IPartShaGenerator partShaGenerator; + private readonly IBasicLogger logger; + + public PartDatabaseCache(IPartShaGenerator partShaGenerator, IBasicLogger logger) + { + this.partShaGenerator = partShaGenerator ?? throw new ArgumentNullException(nameof(partShaGenerator)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public void DoTheThing(IEnumerable urlConfigs) + { + UrlDir gameData = GameDatabase.Instance.GetGameData(); + ConfigNode newPartDatabaseSha = new ConfigNode(); + + foreach (IProtoUrlConfig urlConfig in urlConfigs) + { + if (urlConfig.NodeType != "PART") continue; + string partSha = partShaGenerator.ComputePartSha(urlConfig); + if (partSha == null) + { + logger.Error($"Could not compute SHA for part '{urlConfig.FullUrl}'"); + continue; + } + newPartDatabaseSha.AddValue($"{urlConfig.UrlFile.url}/{urlConfig.Node.GetValue("name")}", partSha); + } + + ConfigNode partDatabaseSha = null; + if (File.Exists(partDatabaseShaPath)) + partDatabaseSha = ConfigNode.Load(partDatabaseShaPath, true); + + newPartDatabaseSha.Save(partDatabaseShaPath); + + if (!File.Exists(partDatabasePath)) + { + logger.Info("PartDatabase.cfg does not exist"); + return; + } + else if (partDatabaseSha == null) + { + logger.Info("Part databse SHA does not exist, removing PartDatabase.cfg"); + File.Delete(partDatabasePath); + return; + } + + ConfigNode partDatabase = ConfigNode.Load(partDatabasePath, true); + + foreach (ConfigNode partDatabaseNode in partDatabase.nodes) + { + if (partDatabaseNode.name != "PART") continue; + + if (!(partDatabaseNode.GetValue("url") is string partUrl)) + { + logger.Error("Part database node is missing url"); + partDatabase.RemoveNode(partDatabaseNode); + continue; + } + + int slashIndex = partUrl.LastIndexOf('/'); + + if (slashIndex == -1 || slashIndex == 0 || slashIndex == partUrl.Length - 1) + { + logger.Error($"Malformed part url in part database: '{partUrl}'"); + partDatabase.RemoveNode(partDatabaseNode); + continue; + } + + string cachedPartSha = partDatabaseSha.GetValue(partUrl); + string partSha = newPartDatabaseSha.GetValue(partUrl); + + if (cachedPartSha == null) + { + logger.Info($"No cached SHA found for part '{partUrl}'"); + partDatabase.RemoveNode(partDatabaseNode); + continue; + } + else if (partSha != cachedPartSha) + { + logger.Info($"Part SHAs differ on part '{partUrl}' - cached: '{cachedPartSha}', actual: '{partSha}'"); + partDatabase.RemoveNode(partDatabaseNode); + continue; + } + } + + partDatabase.Save(partDatabasePath); + } + } +} diff --git a/ModuleManager/PartShaGenerator.cs b/ModuleManager/PartShaGenerator.cs new file mode 100644 index 00000000..95b65685 --- /dev/null +++ b/ModuleManager/PartShaGenerator.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using System.Text; +using ModuleManager.Extensions; +using ModuleManager.Logging; +using ModuleManager.Utils; + +namespace ModuleManager +{ + public interface IPartShaGenerator + { + string ComputePartSha(IProtoUrlConfig partUrlConfig); + } + + public class PartShaGenerator : IPartShaGenerator + { + private readonly IFileShaGenerator fileShaGenerator; + private readonly IUrlFileToReadableObjectConverter urlFileToReadableObjectConverter; + private readonly UrlDir gameData; + private readonly IBasicLogger logger; + + public PartShaGenerator(IFileShaGenerator fileShaGenerator, IUrlFileToReadableObjectConverter urlFileToReadableObjectConverter, UrlDir gameData, IBasicLogger logger) + { + this.fileShaGenerator = fileShaGenerator ?? throw new ArgumentNullException(nameof(fileShaGenerator)); + this.urlFileToReadableObjectConverter = urlFileToReadableObjectConverter ?? throw new ArgumentNullException(nameof(urlFileToReadableObjectConverter)); + this.gameData = gameData ?? throw new ArgumentNullException(nameof(gameData)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public string ComputePartSha(IProtoUrlConfig partUrlConfig) + { + System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); + byte[] configBytes = Encoding.UTF8.GetBytes(partUrlConfig.Node.ToString()); + sha.TransformBlock(configBytes, 0, configBytes.Length, configBytes, 0); + + if (partUrlConfig.Node.GetValue("model") is string modelName) + { + UrlDir.UrlFile modelFile = partUrlConfig.UrlFile.parent.files.FirstOrDefault(file => file.fileType == UrlDir.FileType.Model); + + if (modelFile == null) + { + logger.Error($"Unable to find model for part '{partUrlConfig.FullUrl}'"); + return null; + } + else + { + IReadableObject modelReadableObject = urlFileToReadableObjectConverter.ConvertToReadableObject(modelFile); + fileShaGenerator.TransformBlock(sha, modelReadableObject); + } + } + + foreach (ConfigNode subNode in partUrlConfig.Node.nodes) + { + if (subNode.name != "MODEL") continue; + if (!(subNode.GetValue("model") is string modelUrl)) + { + logger.Error($"Part has MODEL node without model value: {partUrlConfig.FullUrl}"); + return null; + } + if (!(gameData.FindFile(modelUrl, UrlDir.FileType.Model) is UrlDir.UrlFile modelFile)) + { + logger.Error($"Unable to find model file for part '{partUrlConfig.FullUrl}' model url '{modelUrl}'"); + return null; + } + else + { + IReadableObject modelReadableObject = urlFileToReadableObjectConverter.ConvertToReadableObject(modelFile); + fileShaGenerator.TransformBlock(sha, modelReadableObject); + } + } + + byte[] godsFinalMessageToHisCreation = Encoding.UTF8.GetBytes("We apologize for the inconvenience."); + sha.TransformFinalBlock(godsFinalMessageToHisCreation, 0, godsFinalMessageToHisCreation.Length); + + string partSha = BitConverter.ToString(sha.Hash); + sha.Clear(); + + return partSha; + } + } +} diff --git a/ModuleManager/Utils/FileShaGenerator.cs b/ModuleManager/Utils/FileShaGenerator.cs new file mode 100644 index 00000000..53b2e96e --- /dev/null +++ b/ModuleManager/Utils/FileShaGenerator.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace ModuleManager.Utils +{ + public interface IFileShaGenerator + { + void TransformBlock(SHA256 sha, IReadableObject readableObject); + string ComputeSha(IReadableObject readableObject); + } + + public class FileShaGenerator + { + public void TransformBlock(SHA256 sha, IReadableObject readableObject) + { + if (sha == null) throw new ArgumentNullException(nameof(sha)); + if (readableObject == null) throw new ArgumentNullException(nameof(readableObject)); + + byte[] contentBytes = readableObject.ReadAllBytes(); + sha.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); + } + + public string ComputeSha(IReadableObject readableObject) + { + if (readableObject == null) throw new ArgumentNullException(nameof(readableObject)); + + byte[] shaBytes = null; + + using (SHA256 sha = SHA256.Create()) + { + using (Stream stream = readableObject.OpenRead()) + { + shaBytes = sha.ComputeHash(stream); + } + } + + char[] result = new char[shaBytes.Length * 2]; + + for (int i = 0; i < shaBytes.Length; i++) + { + result[i * 2] = GetHexValue(shaBytes[i] / 16); + result[i * 2 + 1] = GetHexValue(shaBytes[i] % 16); + } + + return new string(result); + } + + private static char GetHexValue(int i) { + if (i < 10) + return (char)(i + '0'); + else + return (char)(i - 10 + 'a'); + } + } +} diff --git a/ModuleManager/Utils/ReadableObject.cs b/ModuleManager/Utils/ReadableObject.cs new file mode 100644 index 00000000..8924c871 --- /dev/null +++ b/ModuleManager/Utils/ReadableObject.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; + +namespace ModuleManager.Utils +{ + public interface IReadableObject + { + long Length { get; } + Stream OpenRead(); + } + + public static class ReadableObjectExtensions + { + public static byte[] ReadAllBytes(this IReadableObject readableObject) + { + if (readableObject == null) throw new ArgumentNullException(nameof(readableObject)); + + long fileLength = readableObject.Length; + if (fileLength > Int32.MaxValue) + throw new IOException($"Object is too large to be read (should be less than 2 GB): {readableObject}"); + + byte[] bytes; + using(Stream stream = readableObject.OpenRead()) + { + int count = (int) fileLength; + int index = 0; + bytes = new byte[count]; + while(count > 0) + { + int nBytesRead = stream.Read(bytes, index, count); + if (nBytesRead == 0) + throw new EndOfStreamException("Read beyond the end of the file, this shouldn't be possible!"); + index += nBytesRead; + count -= nBytesRead; + } + } + return bytes; + } + } + + public class ReadableObjectFile : IReadableObject + { + private readonly FileInfo fileInfo; + + public ReadableObjectFile(FileInfo fileInfo) + { + this.fileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo)); + } + + public long Length => fileInfo.Length; + + public Stream OpenRead() => fileInfo.OpenRead(); + + // public static implicit operator ReadableObjectFile(FileInfo fileInfo) => new ReadableObjectFile(fileInfo); + } +} diff --git a/ModuleManager/Utils/UrlFileToReadableObjectConverter.cs b/ModuleManager/Utils/UrlFileToReadableObjectConverter.cs new file mode 100644 index 00000000..598338c1 --- /dev/null +++ b/ModuleManager/Utils/UrlFileToReadableObjectConverter.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace ModuleManager.Utils +{ + public interface IUrlFileToReadableObjectConverter + { + IReadableObject ConvertToReadableObject(UrlDir.UrlFile urlFile); + } + + public class UrlFileToReadableObjectConverter : IUrlFileToReadableObjectConverter + { + public IReadableObject ConvertToReadableObject(UrlDir.UrlFile urlFile) + { + if (urlFile == null) throw new ArgumentNullException(nameof(urlFile)); + return new ReadableObjectFile(new FileInfo(urlFile.fullPath)); + } + } +}