diff options
-rw-r--r-- | docs/release-notes.md | 1 | ||||
-rw-r--r-- | docs/technical-docs.md | 1 | ||||
-rw-r--r-- | src/SMAPI.Installer/InteractiveInstaller.cs | 1 | ||||
-rw-r--r-- | src/SMAPI/Constants.cs | 7 | ||||
-rw-r--r-- | src/SMAPI/Program.cs | 65 | ||||
-rw-r--r-- | src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs | 46 |
6 files changed, 101 insertions, 20 deletions
diff --git a/docs/release-notes.md b/docs/release-notes.md index 11a2a04c..b46e345f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -29,6 +29,7 @@ * Added `Context.IsMultiplayer` and `Context.IsMainPlayer` flags. * Added `Constants.TargetPlatform` which says whether the game is running on Linux, Mac, or Windows. * Added `semanticVersion.IsPrerelease()` method. + * Added support for launching multiple instances transparently. This removes the former `--log-path` command-line argument. * Fixed error if a mod loads a PNG while the game is loading (e.g. custom map tilesheets via `IAssetLoader`). * Fixed assets loaded by temporary content managers not being editable by mods. * Fixed assets not reloaded consistently when the player switches language. diff --git a/docs/technical-docs.md b/docs/technical-docs.md index a988eefc..f4358e31 100644 --- a/docs/technical-docs.md +++ b/docs/technical-docs.md @@ -137,7 +137,6 @@ change without warning. argument | purpose -------- | ------- -`--log-path "path"` | The relative or absolute path of the log file SMAPI should write. `--no-terminal` | SMAPI won't write anything to the console window. (Messages will still be written to the log file.) ### Compile flags 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 /// <summary>Delete a file or folder regardless of file permissions, and block until deletion completes.</summary> /// <param name="entry">The file or folder to reset.</param> + /// <remarks>This method is mirred from <c>FileUtilities.ForceDelete</c> in the toolkit.</remarks> 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 /// <summary>The file path for the SMAPI metadata file.</summary> internal static string ApiMetadataPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.metadata.json"); - /// <summary>The file path to the log where the latest output should be saved.</summary> - internal static string DefaultLogPath => Path.Combine(Constants.LogDir, "SMAPI-latest.txt"); + /// <summary>The filename prefix for SMAPI log files.</summary> + internal static string LogNamePrefix { get; } = "SMAPI-latest"; + + /// <summary>The filename extension for SMAPI log files.</summary> + internal static string LogNameExtension { get; } = "txt"; /// <summary>A copy of the log leading up to the previous fatal crash, if any.</summary> 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(); } /// <summary>Construct an instance.</summary> /// <param name="writeToConsole">Whether to output log messages to the console.</param> - /// <param name="logPath">The full file path to which to write log messages.</param> - 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<SConfig>(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); } + + /// <summary>Get the absolute path to the next available log file.</summary> + 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."); + } + + /// <summary>Delete all log files created by SMAPI.</summary> + 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 +{ + /// <summary>Provides utilities for dealing with files.</summary> + public static class FileUtilities + { + /********* + ** Public methods + *********/ + /// <summary>Delete a file or folder regardless of file permissions, and block until deletion completes.</summary> + /// <param name="entry">The file or folder to reset.</param> + 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}"); + } + } +} |