diff --git a/ConvertTask.cs b/ConvertTask.cs deleted file mode 100644 index 99cdc51..0000000 --- a/ConvertTask.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System.Security; -using System.Text; -using Blake3; -using Dalamud.Interface.ImGuiNotification; -using gfoidl.Base64; -using Heliosphere.Model; -using Heliosphere.Model.Penumbra; -using Heliosphere.Util; -using Newtonsoft.Json; -using StrawberryShake; - -namespace Heliosphere; - -internal class ConvertTask { - private HeliosphereMeta Package { get; } - private IActiveNotification Notification { get; } - - internal ConvertTask(HeliosphereMeta pkg, IActiveNotification notification) { - this.Package = pkg; - this.Notification = notification; - } - - internal async Task Run() { - if (!Plugin.Instance.DownloadCodes.TryGetCode(this.Package.Id, out var code)) { - code = null; - } - - this.Notification.AddOrUpdate(Plugin.Instance.NotificationManager, (notif, _) => { - notif.Content = "Downloading package information"; - }); - - var resp = await Plugin.GraphQl.ConvertTask.ExecuteAsync( - this.Package.VersionId, - this.Package.SelectedOptions, - code, - this.Package.FullInstall, - Model.Generated.DownloadKind.Update - ); - resp.EnsureNoErrors(); - - var neededFiles = resp.Data?.GetVersion?.NeededFiles.Files.Files; - if (neededFiles == null) { - throw new Exception("TODO"); - } - - if (!Plugin.Instance.Penumbra.TryGetModDirectory(out var modDirectory)) { - throw new Exception("TODO"); - } - - var dirName = this.Package.ModDirectoryName(); - var penumbraModPath = Path.Join(modDirectory, dirName); - var filesPath = Path.Join(penumbraModPath, "files"); - - this.Notification.AddOrUpdate(Plugin.Instance.NotificationManager, (notif, _) => { - notif.Content = "Checking existing files"; - notif.Progress = 0; - }); - - // gather a list of all files in the files directory - var installedFiles = DirectoryHelper.GetFilesRecursive(filesPath) - .Select(Path.GetFileName) - .Where(path => path != null) - .Cast() - .ToHashSet(); - - // create a mapping of each file and its hash (hash => path) - var finished = 0; - var total = installedFiles.Count; - var hashPaths = new Dictionary(); - using var blake3 = new Blake3HashAlgorithm(); - foreach (var path in installedFiles) { - blake3.Initialize(); - await using var file = FileHelper.OpenSharedReadIfExists(Path.Join(filesPath, path)); - if (file == null) { - continue; - } - - var expected = PathHelper.GetBaseName(path); - if (hashPaths.ContainsKey(expected)) { - continue; - } - - var rawHash = await blake3.ComputeHashAsync(file); - var hash = Base64.Url.Encode(rawHash); - hashPaths[hash] = path; - - finished += 1; - this.Notification.AddOrUpdate(Plugin.Instance.NotificationManager, (notif, _) => { - notif.Progress = (float) finished / total; - }); - } - - this.Notification.AddOrUpdate(Plugin.Instance.NotificationManager, (notif, _) => { - notif.Content = "Converting file layout"; - notif.Progress = 0; - }); - - finished = 0; - total = neededFiles.Count; - - // loop through all needed files and find their corresponding hash, - // then link each output path to it - foreach (var (hash, files) in neededFiles) { - if (!hashPaths.TryGetValue(hash, out var existingPath)) { - Plugin.Log.Warning($"missing a file for {hash}"); - continue; - } - - existingPath = Path.Join(filesPath, existingPath); - var outputPaths = DownloadTask.GetOutputPaths(files); - foreach (var shortOutputPath in outputPaths) { - var outputPath = Path.Join(filesPath, shortOutputPath); - var fullOutputPath = Path.GetFullPath(outputPath); - if (PathHelper.MakeRelativeSub(filesPath, fullOutputPath) == null) { - throw new SecurityException($"path from mod was attempting to leave the files directory: '{fullOutputPath}' is not within '{filesPath}'"); - } - - if (outputPath.Equals(existingPath, StringComparison.InvariantCultureIgnoreCase)) { - continue; - } - - if (File.Exists(outputPath)) { - continue; - } - - var parent = PathHelper.GetParent(outputPath); - Directory.CreateDirectory(parent); - - FileHelper.CreateHardLink(existingPath, outputPath); - } - - finished += 1; - this.Notification.AddOrUpdate(Plugin.Instance.NotificationManager, (notif, _) => { - notif.Progress = (float) finished / total; - }); - } - - this.Notification.AddOrUpdate(Plugin.Instance.NotificationManager, (notif, _) => { - notif.Content = "Removing old files"; - notif.Progress = 0; - }); - - finished = 0; - total = installedFiles.Count; - - // delete all previously-existing files - foreach (var path in installedFiles) { - FileHelper.Delete(Path.Join(filesPath, path)); - - finished += 1; - this.Notification.AddOrUpdate(Plugin.Instance.NotificationManager, (notif, _) => { - notif.Progress = (float) total / finished; - }); - } - - this.Notification.AddOrUpdate(Plugin.Instance.NotificationManager, (notif, _) => { - notif.Content = "Updating package metadata"; - notif.Progress = 0; - }); - - // map group => option => (game path => archive path) - var pathsMap = new Dictionary>>(); - foreach (var (_, files) in neededFiles) { - foreach (var file in files) { - var group = file[0] ?? DownloadTask.DefaultFolder; - var option = file[1] ?? DownloadTask.DefaultFolder; - var gamePath = file[2]!; - var outputPath = file[3]; - - if (!pathsMap.TryGetValue(group, out var groupMap)) { - groupMap = []; - pathsMap[group] = groupMap; - } - - if (!groupMap.TryGetValue(option, out var pathsList)) { - pathsList = []; - groupMap[option] = pathsList; - } - - var pathOnDisk = outputPath == null - ? Path.Join( - DownloadTask.MakeFileNameSafe(group), - DownloadTask.MakeFileNameSafe(option), - DownloadTask.MakePathPartsSafe(gamePath) - ) - : DownloadTask.MakePathPartsSafe(outputPath); - - pathsList[gamePath] = pathOnDisk; - } - } - - var defaultModPath = Path.Join(penumbraModPath, "default_mod.json"); - if (FileHelper.OpenSharedReadIfExists(defaultModPath) is { } defaultModFile) { - var shouldUpdate = false; - string defaultModJson; - await using (defaultModFile) { - defaultModJson = await new StreamReader(defaultModFile).ReadToEndAsync(); - var defaultMod = JsonConvert.DeserializeObject(defaultModJson); - if (defaultMod != null) { - if (pathsMap.TryGetValue(DownloadTask.DefaultFolder, out var groupPaths2)) { - if (groupPaths2.TryGetValue(DownloadTask.DefaultFolder, out var pathList2)) { - UpdatePaths(defaultMod.Files, pathList2); - } - } - - defaultModJson = JsonConvert.SerializeObject(defaultMod, Formatting.Indented); - shouldUpdate = true; - } - } - - if (shouldUpdate) { - await File.WriteAllTextAsync(defaultModPath, defaultModJson); - } - } - - // update groups - var groupPaths = Directory.EnumerateFiles(penumbraModPath) - .Select(Path.GetFileName) - .Where(path => path != null) - .Cast() - .Where(path => path.StartsWith("group_") && path.EndsWith(".json")); - foreach (var groupPath in groupPaths) { - var fullGroupPath = Path.Join(penumbraModPath, groupPath); - var groupJson = await File.ReadAllTextAsync(fullGroupPath); - ModGroup? group; - try { - group = JsonConvert.DeserializeObject(groupJson); - } catch { - group = JsonConvert.DeserializeObject(groupJson); - } - - if (group is not StandardModGroup standard) { - continue; - } - - if (!pathsMap.TryGetValue(group.Name, out var groupMap)) { - continue; - } - - foreach (var option in standard.Options) { - if (!groupMap.TryGetValue(option.Name, out var pathsList)) { - continue; - } - - UpdatePaths(option.Files, pathsList); - } - - groupJson = JsonConvert.SerializeObject(group, Formatting.Indented); - await File.WriteAllTextAsync(fullGroupPath, groupJson); - } - - // update package - this.Package.FileStorageMethod = FileStorageMethod.Original; - var json = JsonConvert.SerializeObject(this.Package, Formatting.Indented); - var metaPath = Path.Join(penumbraModPath, "heliosphere.json"); - await using var metaFile = FileHelper.Create(metaPath); - await metaFile.WriteAsync(Encoding.UTF8.GetBytes(json)); - - this.Notification.AddOrUpdate(Plugin.Instance.NotificationManager, (notif, _) => { - notif.Type = NotificationType.Success; - notif.Content = "Successfully converted file layout"; - notif.InitialDuration = TimeSpan.FromSeconds(3); - }); - - // tell penumbra to reload it - if (!Plugin.Instance.Penumbra.ReloadMod(dirName)) { - throw new Exception($"could not reload mod at {penumbraModPath}"); - } - - return; - - static void UpdatePaths(Dictionary files, Dictionary pathsList) { - var gamePaths = files.Keys.ToList(); - foreach (var gamePath in gamePaths) { - if (!pathsList.TryGetValue(gamePath, out var archivePath)) { - continue; - } - - files[gamePath] = Path.Join("files", archivePath); - } - } - } -} diff --git a/Ui/Tabs/Manager.cs b/Ui/Tabs/Manager.cs index ba28ee0..e60527a 100644 --- a/Ui/Tabs/Manager.cs +++ b/Ui/Tabs/Manager.cs @@ -29,7 +29,6 @@ internal class Manager : IDisposable { private bool _downloadingUpdates; private bool _checkingForUpdates; - private bool _converting; private string _filter = string.Empty; private bool _forced; @@ -385,38 +384,6 @@ await InstallerWindow.OpenAndAdd(new InstallerWindow.OpenOptions { this.Plugin.Penumbra.OpenMod(pkg.ModDirectoryName()); } - if (pkg.FileStorageMethod == FileStorageMethod.Hash) { - using var disabled = ImGuiHelper.DisabledIf(this._converting); - if (ImGuiHelper.CentredWideButton("Convert to original file layout") && !this._converting) { - this._converting = true; - Task.Run(async () => { - try { - var notif = this.Plugin.NotificationManager.AddNotification(new Notification { - Type = NotificationType.Info, - Title = pkg.Name, - Content = "Converting mod to original file layout...", - InitialDuration = TimeSpan.MaxValue, - Minimized = false, - }); - - try { - await new ConvertTask(pkg, notif).Run(); - } catch (Exception ex) { - Plugin.Log.Error(ex, "Failed to convert package"); - notif.AddOrUpdate(this.Plugin.NotificationManager, (notif, _) => { - notif.InitialDuration = TimeSpan.FromSeconds(5); - notif.Type = NotificationType.Error; - notif.Content = "An error occured while converting."; - }); - } - await this.Plugin.State.UpdatePackages(); - } finally { - this._converting = false; - } - }); - } - } - if (ImGuiHelper.CentredWideButton("Open on Heliosphere website")) { var url = $"https://heliosphere.app/mod/{pkg.Id.ToCrockford()}"; Process.Start(new ProcessStartInfo(url) { diff --git a/queries/ConvertTask.graphql b/queries/ConvertTask.graphql deleted file mode 100644 index 0a99514..0000000 --- a/queries/ConvertTask.graphql +++ /dev/null @@ -1,7 +0,0 @@ -query ConvertTask($versionId: UUID!, $options: Options, $key: String, $full: Boolean, $downloadKind: DownloadKind!) { - getVersion(id: $versionId) { - neededFiles(options: $options, full: $full, downloadKey: $key, downloadKind: $downloadKind) { - files - } - } -}