2020-02-19 14:49:06 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Alphaleonis.Win32.Filesystem;
|
|
|
|
|
using CommandLine;
|
|
|
|
|
using Wabbajack.Common;
|
|
|
|
|
using Wabbajack.Lib;
|
|
|
|
|
using Directory = Alphaleonis.Win32.Filesystem.Directory;
|
|
|
|
|
using File = Alphaleonis.Win32.Filesystem.File;
|
|
|
|
|
using Path = Alphaleonis.Win32.Filesystem.Path;
|
|
|
|
|
|
|
|
|
|
namespace Wabbajack.CLI.Verbs
|
|
|
|
|
{
|
|
|
|
|
[Verb("change-download", HelpText = "Move or Copy all used Downloads from a Modlist to another directory")]
|
|
|
|
|
public class ChangeDownload : AVerb
|
|
|
|
|
{
|
2020-04-06 12:59:38 +00:00
|
|
|
|
[IsDirectory(CustomMessage = "Downloads folder %1 does not exist!")]
|
2020-02-19 15:24:50 +00:00
|
|
|
|
[Option("input", Required = true, HelpText = "Input folder containing the downloads you want to move")]
|
2020-07-13 03:50:12 +00:00
|
|
|
|
public string _input { get; set; } = "";
|
|
|
|
|
public AbsolutePath Input => (AbsolutePath)_input;
|
|
|
|
|
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-04-06 12:57:37 +00:00
|
|
|
|
[IsDirectory(Create = true)]
|
2020-02-19 15:24:50 +00:00
|
|
|
|
[Option("output", Required = true, HelpText = "Output folder the downloads should be transferred to")]
|
2020-07-13 03:50:12 +00:00
|
|
|
|
public string _output { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
public AbsolutePath Output => (AbsolutePath)_output;
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-04-06 12:59:38 +00:00
|
|
|
|
[IsFile(CustomMessage = "Modlist file %1 does not exist!")]
|
2020-02-19 14:49:06 +00:00
|
|
|
|
[Option("modlist", Required = true, HelpText = "The Modlist, can either be a .wabbajack or a modlist.txt file")]
|
2020-07-13 03:50:12 +00:00
|
|
|
|
public string _modlist { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
public AbsolutePath ModList => (AbsolutePath)_modlist;
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-04-09 22:36:07 +00:00
|
|
|
|
[Option("mods", Required = false,
|
|
|
|
|
HelpText = "Mods folder location if the provided modlist file is an MO2 modlist.txt")]
|
2020-07-13 03:50:12 +00:00
|
|
|
|
public string _mods { get; set; } = "";
|
|
|
|
|
|
|
|
|
|
public AbsolutePath Mods => (AbsolutePath)_mods;
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-02-20 10:40:20 +00:00
|
|
|
|
[Option("copy", Default = true, HelpText = "Whether to copy the files", SetName = "copy")]
|
2020-02-19 14:49:06 +00:00
|
|
|
|
public bool Copy { get; set; }
|
|
|
|
|
|
2020-02-20 10:40:20 +00:00
|
|
|
|
[Option("move", Default = false, HelpText = "Whether to move the files", SetName = "move")]
|
2020-02-19 14:49:06 +00:00
|
|
|
|
public bool Move { get; set; }
|
|
|
|
|
|
|
|
|
|
[Option("overwrite", Default = false, HelpText = "Whether to overwrite the file if it already exists")]
|
|
|
|
|
public bool Overwrite { get; set; }
|
|
|
|
|
|
|
|
|
|
[Option("meta", Default = true, HelpText = "Whether to also transfer the meta file for the archive")]
|
|
|
|
|
public bool IncludeMeta { get; set; }
|
2020-07-13 03:50:12 +00:00
|
|
|
|
|
|
|
|
|
private interface ITransferFile
|
|
|
|
|
{
|
|
|
|
|
public Task PerformCopy();
|
|
|
|
|
public AbsolutePath Output { get; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private struct FileCopy : ITransferFile
|
|
|
|
|
{
|
|
|
|
|
private AbsolutePath _src;
|
|
|
|
|
private AbsolutePath _dest;
|
|
|
|
|
|
|
|
|
|
public FileCopy(AbsolutePath src, AbsolutePath dest)
|
|
|
|
|
{
|
|
|
|
|
_src = src;
|
|
|
|
|
_dest = dest;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task PerformCopy()
|
|
|
|
|
{
|
|
|
|
|
CLIUtils.Log($"Copying {_src} to {_dest}");
|
|
|
|
|
await _src.CopyToAsync(_dest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AbsolutePath Output => _dest;
|
|
|
|
|
}
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
private struct StringCopy : ITransferFile
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
private string _src;
|
|
|
|
|
private AbsolutePath _dest;
|
|
|
|
|
|
|
|
|
|
public StringCopy(string src, AbsolutePath dest)
|
|
|
|
|
{
|
|
|
|
|
_src = src;
|
|
|
|
|
_dest = dest;
|
|
|
|
|
}
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
public async Task PerformCopy()
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
CLIUtils.Log($"Writing data to {_src}");
|
|
|
|
|
await _dest.WriteAllTextAsync(_src);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
2020-07-13 03:50:12 +00:00
|
|
|
|
public AbsolutePath Output => _dest;
|
|
|
|
|
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-06 17:14:46 +00:00
|
|
|
|
protected override async Task<ExitCode> Run()
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
if (ModList.Extension != Consts.ModListExtension && ModList.FileName != (RelativePath)"modlist.txt")
|
|
|
|
|
return CLIUtils.Exit($"The file {ModList} is not a valid modlist file!", ExitCode.BadArguments);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-02-19 14:56:43 +00:00
|
|
|
|
if (Copy && Move)
|
2020-04-06 13:09:17 +00:00
|
|
|
|
return CLIUtils.Exit("You can't set both copy and move flags!", ExitCode.BadArguments);
|
2020-02-19 14:56:43 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var isModlist = ModList.Extension == Consts.ModListExtension;
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var list = new List<ITransferFile>();
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
|
|
|
|
if (isModlist)
|
|
|
|
|
{
|
|
|
|
|
ModList modlist;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
modlist = AInstaller.LoadFromFile(ModList);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
2020-04-06 13:09:17 +00:00
|
|
|
|
return CLIUtils.Exit($"Error while loading the Modlist!\n{e}", ExitCode.Error);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (modlist == null)
|
|
|
|
|
{
|
2020-04-06 13:09:17 +00:00
|
|
|
|
return CLIUtils.Exit("The Modlist could not be loaded!", ExitCode.Error);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Modlist contains {modlist.Archives.Count} archives.");
|
2020-07-13 03:50:12 +00:00
|
|
|
|
|
|
|
|
|
using var queue = new WorkQueue();
|
|
|
|
|
|
|
|
|
|
CLIUtils.Log($"Hashing Downloads (this may take some time)");
|
|
|
|
|
var hashes = (await Input.EnumerateFiles().PMap(queue, async f =>
|
|
|
|
|
{
|
|
|
|
|
CLIUtils.Log($"Hashing {f}");
|
|
|
|
|
return (f, await f.FileHashCachedAsync());
|
|
|
|
|
}))
|
|
|
|
|
.GroupBy(d => d.Item2)
|
|
|
|
|
.ToDictionary(d => d.Key, d => d.First().f);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
|
|
|
|
modlist.Archives.Do(a =>
|
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
if (!hashes.TryGetValue(a.Hash, out var inputPath))
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
CLIUtils.Log($"Archive not found for hash {a.Hash}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var outputPath = Output.Combine(a.Name);
|
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Adding {inputPath} to the transfer list.");
|
2020-07-13 03:50:12 +00:00
|
|
|
|
list.Add(new FileCopy(inputPath, outputPath));
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var metaInputPath = inputPath.WithExtension(Consts.MetaFileExtension);
|
|
|
|
|
var metaOutputPath = outputPath.WithExtension(Consts.MetaFileExtension);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
if (metaInputPath.Exists)
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Found meta file {metaInputPath}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
if (IncludeMeta)
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Adding {metaInputPath} to the transfer list.");
|
2020-07-13 03:50:12 +00:00
|
|
|
|
list.Add(new FileCopy(metaInputPath, metaOutputPath));
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Meta file {metaInputPath} will be ignored.");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Found no meta file for {inputPath}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
if (IncludeMeta)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(a.Meta))
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Meta for {a.Name} is empty, this should not be possible but whatever.");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log("Adding meta from archive info the transfer list");
|
2020-07-13 03:50:12 +00:00
|
|
|
|
list.Add(new StringCopy(a.Meta, metaOutputPath));
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Meta will be ignored for {a.Name}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
if (!Mods.Exists)
|
2020-04-06 13:09:17 +00:00
|
|
|
|
return CLIUtils.Exit($"Mods directory {Mods} does not exist!", ExitCode.BadArguments);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
CLIUtils.Log($"Reading modlist.txt from {ModList}");
|
|
|
|
|
var modlist = await ModList.ReadAllLinesAsync();
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
if (modlist == null || !modlist.Any())
|
|
|
|
|
return CLIUtils.Exit($"Provided modlist.txt file at {ModList} is empty or could not be read!", ExitCode.BadArguments);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var mods = modlist.Where(s => s.StartsWith("+")).Select(s => s.Substring(1)).Select(f => (RelativePath)f).ToHashSet();
|
2020-02-24 17:05:42 +00:00
|
|
|
|
|
2020-02-19 14:49:06 +00:00
|
|
|
|
if (mods.Count == 0)
|
2020-04-06 13:09:17 +00:00
|
|
|
|
return CLIUtils.Exit("Counted mods from modlist.txt are 0!", ExitCode.BadArguments);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Found {mods.Count} mods in modlist.txt");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
|
|
|
|
var downloads = new HashSet<string>();
|
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
Mods.EnumerateDirectories(recursive:false)
|
|
|
|
|
.Where(d => mods.Contains(d.Parent.FileName))
|
|
|
|
|
.Do(async d =>
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var meta = d.Combine("meta.ini");
|
|
|
|
|
if (!meta.Exists)
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Mod meta file {meta} does not exist, skipping");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var ini = await meta.ReadAllLinesAsync();
|
|
|
|
|
if (ini == null || !ini.Any())
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Mod meta file {meta} could not be read or is empty!");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ini.Where(i => !string.IsNullOrWhiteSpace(i) && i.StartsWith("installationFile="))
|
|
|
|
|
.Select(i => i.Replace("installationFile=", ""))
|
|
|
|
|
.Do(i =>
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Found installationFile {i}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
downloads.Add(i);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Found {downloads.Count} installationFiles from mod metas.");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
Input.EnumerateFiles()
|
|
|
|
|
.Where(f => downloads.Contains(f.FileNameWithoutExtension.ToString()))
|
2020-02-19 14:49:06 +00:00
|
|
|
|
.Do(f =>
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Found archive {f}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var outputPath = Output.Combine(f.FileName);
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Adding {f} to the transfer list");
|
2020-07-13 03:50:12 +00:00
|
|
|
|
list.Add(new FileCopy(f, outputPath));
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var metaInputPath = f.WithExtension(Consts.MetaFileExtension);
|
|
|
|
|
if (metaInputPath.Exists)
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Found meta file for {f} at {metaInputPath}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
if (IncludeMeta)
|
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
var metaOutputPath = outputPath.WithExtension(Consts.MetaFileExtension);
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Adding {metaInputPath} to the transfer list.");
|
2020-07-13 03:50:12 +00:00
|
|
|
|
list.Add(new FileCopy(metaInputPath, metaOutputPath));
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log("Meta file will be ignored");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Found no meta file for {f}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Transfer list contains {list.Count} items");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
var success = 0;
|
|
|
|
|
var failed = 0;
|
|
|
|
|
var skipped = 0;
|
2020-07-13 03:50:12 +00:00
|
|
|
|
list.Do(async f =>
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
if (f.Output.Exists)
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
|
|
|
|
if (Overwrite)
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Output file {f.Output} already exists, it will be overwritten");
|
2020-07-13 03:50:12 +00:00
|
|
|
|
if (f is StringCopy || Move)
|
2020-02-19 14:49:06 +00:00
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Deleting file at {f.Output}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2020-07-13 03:50:12 +00:00
|
|
|
|
if (f.Output.Exists)
|
|
|
|
|
f.Output.Delete();
|
2020-02-19 14:49:06 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Could not delete file {f.Output}!\n{e}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
failed++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Output file {f.Output} already exists, skipping");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
skipped++;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 03:50:12 +00:00
|
|
|
|
await f.PerformCopy();
|
2020-02-19 14:49:06 +00:00
|
|
|
|
});
|
|
|
|
|
|
2020-02-24 16:50:58 +00:00
|
|
|
|
CLIUtils.Log($"Skipped transfers: {skipped}");
|
|
|
|
|
CLIUtils.Log($"Failed transfers: {failed}");
|
|
|
|
|
CLIUtils.Log($"Successful transfers: {success}");
|
2020-02-19 14:49:06 +00:00
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|