From a4a9838af6132fc0279723307ee0f24f1f0825a7 Mon Sep 17 00:00:00 2001 From: Reherc Date: Fri, 14 May 2021 03:32:54 +0200 Subject: [PATCH] Addd manifest listing from both folders and crab files --- .../Tasks/ExportProjectTask.cs | 4 +- .../Tasks/ImportProjectTask.cs | 2 +- .../Loader/CampaignLoaderLogic.cs | 5 +- .../Loader/Steps/CampaignExtractor.cs | 5 + .../Loader/Steps/CampaignImporter.cs | 4 + .../Loader/Steps/CampaignListing.cs | 230 +++++++++++++++++- .../Loader/Steps/CampaignWorkspaceSetup.cs | 18 +- 7 files changed, 249 insertions(+), 19 deletions(-) diff --git a/App.AdventureMaker.Core/Tasks/ExportProjectTask.cs b/App.AdventureMaker.Core/Tasks/ExportProjectTask.cs index 31e57ef..e573500 100644 --- a/App.AdventureMaker.Core/Tasks/ExportProjectTask.cs +++ b/App.AdventureMaker.Core/Tasks/ExportProjectTask.cs @@ -3,7 +3,6 @@ using Distance.AdventureMaker.Common.Models; using Distance.AdventureMaker.Common.Models.Resources; using Newtonsoft.Json; -using SharpCompress.Archives; using SharpCompress.Archives.Zip; using System; using System.Collections.Generic; @@ -78,7 +77,8 @@ void hash(string resource) archive.AddEntry("readme.txt", Resources.GetText("archive_readme.txt").GetStream()); progress.Value++; archive.AddEntry("$campaign", string.Empty.GetStream()); - progress.Value++; + progress.Value++; + editor.Document.Metadata.Version = DateTime.Now.TimeOfDay.Ticks; archive.AddEntry("project.json", JsonConvert.SerializeObject(editor.Document).GetStream()); progress.Value++; archive.AddEntry("hashes.json", JsonConvert.SerializeObject(hashes).GetStream()); diff --git a/App.AdventureMaker.Core/Tasks/ImportProjectTask.cs b/App.AdventureMaker.Core/Tasks/ImportProjectTask.cs index e35d1df..53beea0 100644 --- a/App.AdventureMaker.Core/Tasks/ImportProjectTask.cs +++ b/App.AdventureMaker.Core/Tasks/ImportProjectTask.cs @@ -93,7 +93,7 @@ string hash(string entry) } } - foreach (var hashEntry in hashes) + foreach (KeyValuePair hashEntry in hashes) { progress.Status = $"Checking data validity... ({progress.Value + 1}/{progress.Maximum})"; diff --git a/Distance.AdventureMaker/Loader/CampaignLoaderLogic.cs b/Distance.AdventureMaker/Loader/CampaignLoaderLogic.cs index 939b5b6..44c0beb 100644 --- a/Distance.AdventureMaker/Loader/CampaignLoaderLogic.cs +++ b/Distance.AdventureMaker/Loader/CampaignLoaderLogic.cs @@ -1,6 +1,5 @@ using Centrifuge.Distance.Game; using Distance.AdventureMaker.Loader.Steps; -using System; using System.Collections; using System.Collections.Generic; using UnityEngine; @@ -45,7 +44,7 @@ public sealed class CampaignLoader : IEnumerable { private readonly Queue tasks; - public CampaignWorkspaceSetup WorkspaceSetup { get; } + public CampaignWorkspaceSetup Workspace { get; } public CampaignListing Listing { get; } @@ -57,7 +56,7 @@ public CampaignLoader() { tasks = new Queue(); - tasks.Enqueue(WorkspaceSetup = new CampaignWorkspaceSetup(this)); + tasks.Enqueue(Workspace = new CampaignWorkspaceSetup(this)); tasks.Enqueue(Listing = new CampaignListing(this)); tasks.Enqueue(Extractor = new CampaignExtractor(this)); tasks.Enqueue(Importer = new CampaignImporter(this)); diff --git a/Distance.AdventureMaker/Loader/Steps/CampaignExtractor.cs b/Distance.AdventureMaker/Loader/Steps/CampaignExtractor.cs index f971f4c..9100639 100644 --- a/Distance.AdventureMaker/Loader/Steps/CampaignExtractor.cs +++ b/Distance.AdventureMaker/Loader/Steps/CampaignExtractor.cs @@ -12,6 +12,11 @@ public CampaignExtractor(CampaignLoader loader) : base(loader) public override IEnumerator Run(Task.Status status) { + foreach (var item in loader.Listing) + { + Mod.Instance.Logger.Info($"[{item.Value.source}]\t {item.Key} : {item.Value.path.FullName}"); + } + yield break; } } diff --git a/Distance.AdventureMaker/Loader/Steps/CampaignImporter.cs b/Distance.AdventureMaker/Loader/Steps/CampaignImporter.cs index 834cfaa..1323922 100644 --- a/Distance.AdventureMaker/Loader/Steps/CampaignImporter.cs +++ b/Distance.AdventureMaker/Loader/Steps/CampaignImporter.cs @@ -15,6 +15,9 @@ public CampaignImporter(CampaignLoader loader) : base(loader) public override IEnumerator Run(Task.Status status) { + yield break; + + /* DiscordRpc.RichPresence rpc = new DiscordRpc.RichPresence(); rpc.details = "Waiting..."; rpc.state = "In Main Menu"; @@ -39,6 +42,7 @@ public override IEnumerator Run(Task.Status status) DiscordRpc.UpdatePresence(ref rpc); yield return null; } + */ } } } diff --git a/Distance.AdventureMaker/Loader/Steps/CampaignListing.cs b/Distance.AdventureMaker/Loader/Steps/CampaignListing.cs index 7cc89eb..f18849e 100644 --- a/Distance.AdventureMaker/Loader/Steps/CampaignListing.cs +++ b/Distance.AdventureMaker/Loader/Steps/CampaignListing.cs @@ -1,36 +1,246 @@ using Centrifuge.Distance.Game; +using Distance.AdventureMaker.Common.Models; +using Newtonsoft.Json; +using SharpCompress.Archives; +using SharpCompress.Archives.Zip; +using System; using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; using static Distance.AdventureMaker.Loader.CampaignLoaderLogic; namespace Distance.AdventureMaker.Loader.Steps { - public class CampaignListing : LoaderTask + public class CampaignListing : LoaderTask, IEnumerable> { + private readonly CampaignItem.Collection Manifests; + public CampaignListing(CampaignLoader loader) : base(loader) { + Manifests = new CampaignItem.Collection(); } public override IEnumerator Run(Task.Status status) { - const int max = 100; + /* + List all .crab files and extracted folders + For each folder, check project.json and fill the map + Proceed to scan every .crab file + If + .crab file cid is not in map + or cid is in map but version is higher + Then update extraction map with higher version + + For each item in extraction map + + If update was made, set flag to true + Later: if flag is true: ask if the mod should destroy the source .crab file + */ + + status.SetText("Scanning workspace..."); + status.SetProgress(0, 1); + + foreach (DirectoryInfo workspace in loader.Workspace) + { + // Scan already extracted campaigns first + foreach (DirectoryInfo directory in workspace.GetDirectories()) + { + FileInfo projectFile = new FileInfo(Path.Combine(directory.FullName, "project.json")); + + if (projectFile.Exists) + { + CampaignFile manifest = Json.Load(projectFile, null); + + if (manifest != null) + { + Manifests.Add(manifest, directory); + } + } + } + + // Scan archive files + foreach (FileInfo file in workspace.GetFiles("*.crab")) + { + using (Stream archiveStream = File.OpenRead(file.FullName)) + { + if (!ZipArchive.IsZipFile(archiveStream)) + { + continue; + } + + using (IArchive archive = ZipArchive.Open(archiveStream)) + { + Dictionary entries = archive.GetFileEntries(); + + bool required_entries_present = + entries.ContainsKey("$campaign") + && entries.ContainsKey("project.json") + && entries.ContainsKey("hashes.json"); + + if (!required_entries_present) + { + continue; + } + + Dictionary hashes = JsonConvert.DeserializeObject>(entries["hashes.json"].GetText()); + + string hash(string entry) + { + using (HashAlgorithm ha = SHA512.Create()) + { + byte[] hashed = ha.ComputeHash(entries[$"resources/{entry.Replace("\\", "/")}"].OpenEntryStream()); + return BitConverter.ToString(hashed).Replace("-", ""); + } + } + + if (!hashes.All(hashEntry => string.Equals(hashEntry.Value, hash(hashEntry.Key)))) + { + continue; + } - status.SetText("Setting up..."); + try + { + CampaignFile manifest = JsonConvert.DeserializeObject(entries["project.json"].GetText()); + + if (manifest != null) + { + Manifests.Add(manifest, file); + } + } + catch (Exception) + { + continue; + } + } + } + } + } + + status.SetText(""); status.SetProgress(0, 1); yield return Task.Wait(1.5f); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + { + return Manifests.GetEnumerator(); + } + + public struct CampaignItem : IEquatable, IComparable + { + public enum Source : byte + { + Archive = 0, + Folder = 1 + } + + public class Collection : Dictionary + { + public void Add(CampaignFile manifest, DirectoryInfo directory) + { + Add(manifest, directory, Source.Folder); + } - for (uint i = 1; i <= max; ++i) + public void Add(CampaignFile manifest, FileInfo file) + { + Add(manifest, file, Source.Archive); + } + + public void Add(CampaignFile manifest, FileSystemInfo fsi, Source source) + { + CampaignMetadata metadata = manifest.Metadata; + CampaignItem item = new CampaignItem(source, fsi, metadata.Version); + + if (!ContainsKey(metadata.Guid)) + { + this[metadata.Guid] = item; + } + else + { + if (item > this[metadata.Guid]) + { + this[metadata.Guid] = item; + } + } + } + } + + public Source source; + public FileSystemInfo path; + public long version; + + public CampaignItem(Source source, FileSystemInfo path, long version) + { + this.source = source; + this.path = path; + this.version = version; + } + + public int CompareTo(CampaignItem other) + { + int versionCompare = version.CompareTo(other.version); + if (versionCompare == 0) + { + return source.CompareTo(other.source); + } + return versionCompare; + } + + public override bool Equals(object obj) + { + return obj is CampaignItem info && Equals(info); + } + + public bool Equals(CampaignItem other) + { + return version == other.version && source == other.source; + } + + public override int GetHashCode() + { + int hashCode = -1235800969; + hashCode = (hashCode * -1521134295) + version.GetHashCode(); + hashCode = (hashCode * -1521134295) + source.GetHashCode(); + return hashCode; + } + + public static bool operator ==(CampaignItem left, CampaignItem right) + { + return left.Equals(right); + } + + public static bool operator !=(CampaignItem left, CampaignItem right) + { + return !(left == right); + } + + public static bool operator >(CampaignItem left, CampaignItem right) { - status.SetText($"Running task {i} of {max}..."); - status.SetProgress(i, max); + return left.CompareTo(right) == 1; + } - yield return Task.Wait(0.05f); + public static bool operator <(CampaignItem left, CampaignItem right) + { + return right.CompareTo(left) == 1; } - status.SetText("Finishing..."); - status.SetProgress(1, 1); + public static bool operator >=(CampaignItem left, CampaignItem right) + { + return left > right || left == right; + } - yield return Task.Wait(2.0f); + public static bool operator <=(CampaignItem left, CampaignItem right) + { + return left < right || left == right; + } } } } diff --git a/Distance.AdventureMaker/Loader/Steps/CampaignWorkspaceSetup.cs b/Distance.AdventureMaker/Loader/Steps/CampaignWorkspaceSetup.cs index 35eefa2..5fb39c3 100644 --- a/Distance.AdventureMaker/Loader/Steps/CampaignWorkspaceSetup.cs +++ b/Distance.AdventureMaker/Loader/Steps/CampaignWorkspaceSetup.cs @@ -1,20 +1,27 @@ using Centrifuge.Distance.Game; using Reactor.API.Storage; using System.Collections; +using System.Collections.Generic; using System.IO; using static Distance.AdventureMaker.Loader.CampaignLoaderLogic; namespace Distance.AdventureMaker.Loader.Steps { - public class CampaignWorkspaceSetup : LoaderTask + public class CampaignWorkspaceSetup : LoaderTask, IEnumerable { - DirectoryInfo LocalCampaignsFolder; - DirectoryInfo DocumentsCampaignsFolder; + private DirectoryInfo LocalCampaignsFolder; + private DirectoryInfo DocumentsCampaignsFolder; public CampaignWorkspaceSetup(CampaignLoader loader) : base(loader) { } + public IEnumerator GetEnumerator() + { + yield return LocalCampaignsFolder; + yield return DocumentsCampaignsFolder; + } + public override IEnumerator Run(Task.Status status) { status.SetText("Creating directory structure..."); @@ -29,5 +36,10 @@ public override IEnumerator Run(Task.Status status) yield break; } + + IEnumerator IEnumerable.GetEnumerator() + { + yield return GetEnumerator(); + } } }