summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/release-notes.md1
-rw-r--r--docs/technical-docs.md1
-rw-r--r--src/SMAPI.Installer/InteractiveInstaller.cs1
-rw-r--r--src/SMAPI/Constants.cs7
-rw-r--r--src/SMAPI/Program.cs65
-rw-r--r--src/StardewModdingAPI.Toolkit/Utilities/FileUtilities.cs46
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}");
+ }
+ }
+}