diff --git a/Cmdline/Action/Import.cs b/Cmdline/Action/Import.cs new file mode 100644 index 0000000000..8d0819d5cc --- /dev/null +++ b/Cmdline/Action/Import.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; +using System.Collections.Generic; +using log4net; + +namespace CKAN.CmdLine +{ + + /// + /// Handler for "ckan import" command. + /// Imports manually downloaded ZIP files into the cache. + /// + public class Import : ICommand + { + + /// + /// Initialize the command + /// + /// IUser object for user interaction + public Import(IUser user) + { + this.user = user; + } + + /// + /// Execute an import command + /// + /// Game instance into which to import + /// Command line parameters from the user + /// + /// Process exit code + /// + public int RunCommand(CKAN.KSP ksp, object options) + { + try + { + ImportOptions opts = options as ImportOptions; + HashSet toImport = GetFiles(opts); + if (toImport.Count < 1) + { + user.RaiseMessage("Usage: ckan import path [path2, ...]"); + return Exit.ERROR; + } + else + { + log.InfoFormat("Importing {0} files", toImport.Count); + List toInstall = new List(); + ModuleInstaller inst = ModuleInstaller.GetInstance(ksp, user); + inst.ImportFiles(toImport, user, id => toInstall.Add(id), !opts.Headless); + if (toInstall.Count > 0) + { + inst.InstallList( + toInstall, + new RelationshipResolverOptions() + ); + } + return Exit.OK; + } + } + catch (Exception ex) + { + user.RaiseError("Import error: {0}", ex.Message); + return Exit.ERROR; + } + } + + private HashSet GetFiles(ImportOptions options) + { + HashSet files = new HashSet(); + foreach (string filename in options.paths) + { + if (Directory.Exists(filename)) + { + // Import everything in this folder + log.InfoFormat("{0} is a directory", filename); + foreach (string dirfile in Directory.EnumerateFiles(filename)) + { + AddFile(files, dirfile); + } + } + else + { + AddFile(files, filename); + } + } + return files; + } + + private void AddFile(HashSet files, string filename) + { + if (File.Exists(filename)) + { + log.InfoFormat("Attempting import of {0}", filename); + files.Add(new FileInfo(filename)); + } + else + { + user.RaiseMessage("File not found: {0}", filename); + } + } + + private readonly IUser user; + private static readonly ILog log = LogManager.GetLogger(typeof(Import)); + } + +} diff --git a/Cmdline/CKAN-cmdline.csproj b/Cmdline/CKAN-cmdline.csproj index 409b389768..2ca70b235e 100644 --- a/Cmdline/CKAN-cmdline.csproj +++ b/Cmdline/CKAN-cmdline.csproj @@ -57,6 +57,7 @@ + diff --git a/Cmdline/Main.cs b/Cmdline/Main.cs index f2e19adde9..a35b916f92 100644 --- a/Cmdline/Main.cs +++ b/Cmdline/Main.cs @@ -189,6 +189,9 @@ private static int RunSimpleAction(Options cmdline, CommonOptions options, strin Scan(GetGameInstance(manager), user, cmdline.action); return (new Upgrade(user)).RunCommand(GetGameInstance(manager), cmdline.options); + case "import": + return (new Import(user)).RunCommand(GetGameInstance(manager), options); + case "clean": return Clean(GetGameInstance(manager)); diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs index e721506cb8..064553b015 100644 --- a/Cmdline/Options.cs +++ b/Cmdline/Options.cs @@ -67,6 +67,9 @@ internal class Actions : VerbCommandOptions [VerbOption("remove", HelpText = "Remove an installed mod")] public RemoveOptions Remove { get; set; } + [VerbOption("import", HelpText = "Import manually downloaded mods")] + public ImportOptions Import { get; set; } + [VerbOption("scan", HelpText = "Scan for manually installed KSP mods")] public ScanOptions Scan { get; set; } @@ -132,6 +135,9 @@ public string GetUsage(string verb) case "compare": ht.AddPreOptionsLine($"Usage: ckan {verb} [options] version1 version2"); break; + case "import": + ht.AddPreOptionsLine($"Usage: ckan {verb} [options] paths"); + break; // Now the commands with only --flag type options case "gui": @@ -433,6 +439,12 @@ internal class RemoveOptions : InstanceSpecificOptions public bool rmall { get; set; } } + internal class ImportOptions : InstanceSpecificOptions + { + [ValueList(typeof(List))] + public List paths { get; set; } + } + internal class ShowOptions : InstanceSpecificOptions { [ValueOption(0)] public string Modname { get; set; } diff --git a/Core/ModuleInstaller.cs b/Core/ModuleInstaller.cs index b616cafff9..4f3ed0d43c 100644 --- a/Core/ModuleInstaller.cs +++ b/Core/ModuleInstaller.cs @@ -1092,7 +1092,8 @@ private void DownloadModules(IEnumerable mods, IDownloader downloade /// Set of files to import /// Object for user interaction /// Function to call to mark a mod for installation - public void ImportFiles(HashSet files, IUser user, Action installMod) + /// True to ask user whether to delete imported files, false to leave the files as is + public void ImportFiles(HashSet files, IUser user, Action installMod, bool allowDelete = true) { Registry registry = registry_manager.registry; HashSet installable = new HashSet(); @@ -1134,7 +1135,7 @@ public void ImportFiles(HashSet files, IUser user, Action inst } ++i; } - if (installable.Count > 0 && user.RaiseYesNoDialog($"Install {installable.Count} compatible imported mods?")) + if (installable.Count > 0 && user.RaiseYesNoDialog($"Install {installable.Count} compatible imported mods in game instance {ksp.Name} ({ksp.GameDir()})?")) { // Install the imported mods foreach (string identifier in installable) @@ -1142,7 +1143,7 @@ public void ImportFiles(HashSet files, IUser user, Action inst installMod(identifier); } } - if (user.RaiseYesNoDialog($"Import complete. Delete {deletable.Count} old files?")) + if (allowDelete && deletable.Count > 0 && user.RaiseYesNoDialog($"Import complete. Delete {deletable.Count} old files?")) { // Delete old files foreach (FileInfo f in deletable) diff --git a/Core/Net/NetModuleCache.cs b/Core/Net/NetModuleCache.cs index 7b298f1300..12c3cc3752 100644 --- a/Core/Net/NetModuleCache.cs +++ b/Core/Net/NetModuleCache.cs @@ -142,7 +142,7 @@ public string Store(CkanModule module, string path, string description = null, b } // If no exceptions, then everything is fine - return cache.Store(module.download, path, description, move); + return cache.Store(module.download, path, description ?? module.StandardName(), move); } private NetFileCache cache;