From 86a3f8dd460f329fad903770231016813e750168 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 8 Jun 2018 18:46:58 -0400 Subject: allow launching multiple instances without manually changing log path (#494) --- src/SMAPI.Installer/InteractiveInstaller.cs | 1 + src/SMAPI/Constants.cs | 7 ++- src/SMAPI/Program.cs | 65 ++++++++++++++++------ .../Utilities/FileUtilities.cs | 46 +++++++++++++++ 4 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs (limited to 'src') diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 6e4cb95d..02dd6891 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -518,6 +518,7 @@ namespace StardewModdingApi.Installer /// Delete a file or folder regardless of file permissions, and block until deletion completes. /// The file or folder to reset. + /// This method is mirred from FileUtilities.ForceDelete in the toolkit. private void ForceDelete(FileSystemInfo entry) { // ignore if already deleted diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 786c1a39..867e01ea 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -82,8 +82,11 @@ namespace StardewModdingAPI /// The file path for the SMAPI metadata file. internal static string ApiMetadataPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.metadata.json"); - /// The file path to the log where the latest output should be saved. - internal static string DefaultLogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); + /// The filename prefix for SMAPI log files. + internal static string LogNamePrefix { get; } = "SMAPI-latest"; + + /// The filename extension for SMAPI log files. + internal static string LogNameExtension { get; } = "txt"; /// A copy of the log leading up to the previous fatal crash, if any. internal static string FatalCrashLog => Path.Combine(Constants.LogDir, "SMAPI-crash.txt"); diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index cc59c0cd..570a3c55 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -117,30 +117,19 @@ namespace StardewModdingAPI // get flags from arguments bool writeToConsole = !args.Contains("--no-terminal"); - // get log path from arguments - string logPath = null; - { - int pathIndex = Array.LastIndexOf(args, "--log-path") + 1; - if (pathIndex >= 1 && args.Length >= pathIndex) - { - logPath = args[pathIndex]; - if (!Path.IsPathRooted(logPath)) - logPath = Path.Combine(Constants.LogDir, logPath); - } - } - if (string.IsNullOrWhiteSpace(logPath)) - logPath = Constants.DefaultLogPath; - // load SMAPI - using (Program program = new Program(writeToConsole, logPath)) + using (Program program = new Program(writeToConsole)) program.RunInteractively(); } /// Construct an instance. /// Whether to output log messages to the console. - /// The full file path to which to write log messages. - public Program(bool writeToConsole, string logPath) + public Program(bool writeToConsole) { + // init log file + this.PurgeLogFiles(); + string logPath = this.GetLogPath(); + // init basics this.Settings = JsonConvert.DeserializeObject(File.ReadAllText(Constants.ApiConfigPath)); this.LogFile = new LogFileManager(logPath); @@ -1258,5 +1247,47 @@ namespace StardewModdingAPI if (this.Settings.VerboseLogging) this.Monitor.Log(message, LogLevel.Trace); } + + /// Get the absolute path to the next available log file. + private string GetLogPath() + { + // default path + { + FileInfo defaultFile = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.{Constants.LogNameExtension}")); + if (!defaultFile.Exists) + return defaultFile.FullName; + } + + // get first disambiguated path + for (int i = 2; i < int.MaxValue; i++) + { + FileInfo file = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.player-{i}.{Constants.LogNameExtension}")); + if (!file.Exists) + return file.FullName; + } + + // should never happen + throw new InvalidOperationException("Could not find an available log path."); + } + + /// Delete all log files created by SMAPI. + private void PurgeLogFiles() + { + DirectoryInfo logsDir = new DirectoryInfo(Constants.LogDir); + foreach (FileInfo logFile in logsDir.EnumerateFiles("*.txt")) + { + if (logFile.Name.StartsWith(Constants.LogNamePrefix, StringComparison.InvariantCultureIgnoreCase)) + { + try + { + FileUtilities.ForceDelete(logFile); + } + catch (Exception ex) + { + // leave file if it's locked + } + } + } + } } } diff --git a/src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs b/src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs new file mode 100644 index 00000000..7856fdb1 --- /dev/null +++ b/src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs @@ -0,0 +1,46 @@ +using System.IO; +using System.Threading; + +namespace StardewModdingAPI.Toolkit.Utilities +{ + /// Provides utilities for dealing with files. + public static class FileUtilities + { + /********* + ** Public methods + *********/ + /// Delete a file or folder regardless of file permissions, and block until deletion completes. + /// The file or folder to reset. + public static void ForceDelete(FileSystemInfo entry) + { + // ignore if already deleted + entry.Refresh(); + if (!entry.Exists) + return; + + // delete children + if (entry is DirectoryInfo folder) + { + foreach (FileSystemInfo child in folder.GetFileSystemInfos()) + FileUtilities.ForceDelete(child); + } + + // reset permissions & delete + entry.Attributes = FileAttributes.Normal; + entry.Delete(); + + // wait for deletion to finish + for (int i = 0; i < 10; i++) + { + entry.Refresh(); + if (entry.Exists) + Thread.Sleep(500); + } + + // throw exception if deletion didn't happen before timeout + entry.Refresh(); + if (entry.Exists) + throw new IOException($"Timed out trying to delete {entry.FullName}"); + } + } +} -- cgit