2021-12-17 14:20:40 +00:00
|
|
|
using System;
|
|
|
|
using System.CommandLine;
|
|
|
|
using System.CommandLine.Invocation;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
2021-12-17 23:40:45 +00:00
|
|
|
using System.Net.Http;
|
2021-12-17 14:20:40 +00:00
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using FluentFTP.Helpers;
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
using Wabbajack.Common;
|
|
|
|
using Wabbajack.Compiler.PatchCache;
|
|
|
|
using Wabbajack.Downloaders;
|
|
|
|
using Wabbajack.DTOs;
|
|
|
|
using Wabbajack.DTOs.ModListValidation;
|
|
|
|
using Wabbajack.DTOs.ServerResponses;
|
2021-12-17 23:40:45 +00:00
|
|
|
using Wabbajack.Hashing.xxHash64;
|
2021-12-17 14:20:40 +00:00
|
|
|
using Wabbajack.Installer;
|
2021-12-17 23:40:45 +00:00
|
|
|
using Wabbajack.Networking.Http;
|
2021-12-17 14:20:40 +00:00
|
|
|
using Wabbajack.Networking.WabbajackClientApi;
|
|
|
|
using Wabbajack.Paths;
|
|
|
|
using Wabbajack.Paths.IO;
|
|
|
|
using Wabbajack.VFS;
|
|
|
|
|
|
|
|
namespace Wabbajack.CLI.Verbs;
|
|
|
|
|
2021-12-17 23:40:45 +00:00
|
|
|
public class ForceHeal : IVerb
|
2021-12-17 14:20:40 +00:00
|
|
|
{
|
|
|
|
private readonly ILogger<ForceHeal> _logger;
|
|
|
|
private readonly Client _client;
|
|
|
|
private readonly DownloadDispatcher _downloadDispatcher;
|
|
|
|
private readonly FileHashCache _fileHashCache;
|
2021-12-17 23:40:45 +00:00
|
|
|
private readonly HttpClient _httpClient;
|
2021-12-17 14:20:40 +00:00
|
|
|
|
2021-12-17 23:40:45 +00:00
|
|
|
public ForceHeal(ILogger<ForceHeal> logger, Client client, DownloadDispatcher downloadDispatcher, FileHashCache hashCache,
|
|
|
|
HttpClient httpClient)
|
2021-12-17 14:20:40 +00:00
|
|
|
{
|
|
|
|
_logger = logger;
|
|
|
|
_client = client;
|
|
|
|
_downloadDispatcher = downloadDispatcher;
|
|
|
|
_fileHashCache = hashCache;
|
2021-12-17 23:40:45 +00:00
|
|
|
_httpClient = httpClient;
|
2021-12-17 14:20:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public Command MakeCommand()
|
|
|
|
{
|
|
|
|
var command = new Command("force-heal");
|
2021-12-18 17:59:47 +00:00
|
|
|
command.Add(new Option<AbsolutePath>(new[] {"-n", "-new-file"}, "New File"));
|
|
|
|
command.Add(new Option<string>(new[] {"-o", "-old-file"}, "Old File"));
|
2021-12-17 14:20:40 +00:00
|
|
|
command.Description = "Creates a patch from New file to Old File and uploads it";
|
|
|
|
command.Handler = CommandHandler.Create(Run);
|
|
|
|
return command;
|
|
|
|
}
|
|
|
|
|
2021-12-18 17:35:08 +00:00
|
|
|
public async Task<int> Run(AbsolutePath oldFile, AbsolutePath newFile)
|
2021-12-17 14:20:40 +00:00
|
|
|
{
|
2021-12-18 17:35:08 +00:00
|
|
|
var oldResolved = await Resolve(oldFile);
|
|
|
|
var newResolved = await Resolve(newFile);
|
2021-12-17 14:20:40 +00:00
|
|
|
|
|
|
|
_logger.LogInformation("Creating patch");
|
|
|
|
var outData = new MemoryStream();
|
2021-12-18 17:35:08 +00:00
|
|
|
OctoDiff.Create( await @newFile.ReadAllBytesAsync(), await oldFile.ReadAllBytesAsync(), outData);
|
2021-12-17 14:20:40 +00:00
|
|
|
|
|
|
|
_logger.LogInformation("Created {Size} patch", outData.Length.FileSizeToString());
|
|
|
|
|
|
|
|
outData.Position = 0;
|
|
|
|
|
|
|
|
var validated = new ValidatedArchive
|
|
|
|
{
|
2021-12-18 17:35:08 +00:00
|
|
|
Original = oldResolved,
|
|
|
|
PatchedFrom = newResolved,
|
2021-12-17 14:20:40 +00:00
|
|
|
Status = ArchiveStatus.Updated
|
|
|
|
};
|
|
|
|
|
|
|
|
validated = await _client.UploadPatch(validated, outData);
|
2021-12-17 23:40:45 +00:00
|
|
|
_logger.LogInformation("Patch Updated, validating result by downloading patch");
|
|
|
|
|
2021-12-18 00:14:45 +00:00
|
|
|
_logger.LogInformation("Checking URL {Url}", validated.PatchUrl);
|
2021-12-17 23:40:45 +00:00
|
|
|
using var patchStream = await _httpClient.GetAsync(validated.PatchUrl);
|
|
|
|
if (!patchStream.IsSuccessStatusCode)
|
|
|
|
throw new HttpException(patchStream);
|
|
|
|
|
|
|
|
outData.Position = 0;
|
|
|
|
var originalHash = outData.HashingCopy(Stream.Null, CancellationToken.None);
|
|
|
|
var hash = await (await patchStream.Content.ReadAsStreamAsync()).HashingCopy(Stream.Null, CancellationToken.None);
|
|
|
|
if (hash != await originalHash)
|
|
|
|
{
|
|
|
|
throw new Exception($"Patch on server does not match patch hash {await originalHash} vs {hash}");
|
|
|
|
}
|
2021-12-17 14:20:40 +00:00
|
|
|
|
|
|
|
_logger.LogInformation("Adding patch to forced_healing.json");
|
|
|
|
await _client.AddForceHealedPatch(validated);
|
|
|
|
_logger.LogInformation("Done, validation should trigger soon");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
private async Task<Archive> Resolve(AbsolutePath file)
|
|
|
|
{
|
|
|
|
var meta = file.WithExtension(Ext.Meta);
|
|
|
|
if (!meta.FileExists())
|
|
|
|
throw new Exception($"Meta not found {meta}");
|
|
|
|
|
|
|
|
var ini = meta.LoadIniFile();
|
|
|
|
var state = await _downloadDispatcher.ResolveArchive(ini["General"].ToDictionary(d => d.KeyName, d => d.Value));
|
2022-01-03 22:46:28 +00:00
|
|
|
if (state == null)
|
|
|
|
{
|
|
|
|
_logger.LogError("Cannot resolve state from meta for {File}", file);
|
|
|
|
throw new Exception($"Cannot resolve state from meta for {file}");
|
|
|
|
}
|
2021-12-17 14:20:40 +00:00
|
|
|
|
|
|
|
_logger.LogInformation("Hashing {File}", file.FileName);
|
|
|
|
var hash = await _fileHashCache.FileHashCachedAsync(file, CancellationToken.None);
|
|
|
|
|
|
|
|
return new Archive
|
|
|
|
{
|
|
|
|
Hash = hash,
|
|
|
|
Name = file.FileName.ToString(),
|
|
|
|
Size = file.Size(),
|
|
|
|
State = state!
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|