Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
- Add man page output to the command-line programs (gmipf)
- Add a man page for the graphical frontend (gmipf)
- Normalize CLI feature handling
- Root the default output path when the current folder is unwritable (gmipf)

### 3.8.3 (2026-07-06)

Expand Down
79 changes: 79 additions & 0 deletions MPF.Frontend.Test/OptionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.IO;
using SabreTools.IO;
using Xunit;

namespace MPF.Frontend.Test
{
public class OptionsTests
{
/// <summary>
/// A writable current directory keeps the relative default
/// </summary>
[Fact]
public void GetDefaultOutputPath_WritableDirectory_ReturnsRelativePath()
{
string currentDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(currentDirectory);

try
{
string actual = DumpSettings.GetDefaultOutputPath(currentDirectory);
Assert.Equal("ISO", actual);
}
finally
{
Directory.Delete(currentDirectory);
}
}

/// <summary>
/// A current directory that can not hold a file falls back to the home directory
/// </summary>
/// <remarks>
/// A file stands in for the directory because that is the only way of making the creation
/// fail on every platform. Denied permissions end up in the same branch.
/// </remarks>
[Fact]
public void GetDefaultOutputPath_UnwritableDirectory_ReturnsHomePath()
{
string currentDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
File.Create(currentDirectory).Dispose();

try
{
string actual = DumpSettings.GetDefaultOutputPath(currentDirectory);
Assert.Equal(Path.Combine(PathTool.GetHomeDirectory(), "ISO"), actual);
}
finally
{
File.Delete(currentDirectory);
}
}

/// <summary>
/// A missing current directory falls back to the home directory
/// </summary>
[Fact]
public void GetDefaultOutputPath_MissingDirectory_ReturnsHomePath()
{
string currentDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

string actual = DumpSettings.GetDefaultOutputPath(currentDirectory);

Assert.Equal(Path.Combine(PathTool.GetHomeDirectory(), "ISO"), actual);
}

/// <summary>
/// Clearing the default output path restores the default instead of leaving it empty
/// </summary>
[Fact]
public void DefaultOutputPath_SetToNull_RestoresDefault()
{
var options = new Options();

options.Dumping.DefaultOutputPath = null;

Assert.Equal(DumpSettings.DefaultOutputPathValue, options.Dumping.DefaultOutputPath);
}
}
}
61 changes: 59 additions & 2 deletions MPF.Frontend/Options.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.IO;
using Newtonsoft.Json;
using SabreTools.IO;
using SabreTools.RedumpLib.Data;
using AaruDumpSettings = MPF.ExecutionContexts.Aaru.DumpSettings;
using DiscImageCreatorDumpSettings = MPF.ExecutionContexts.DiscImageCreator.DumpSettings;
Expand Down Expand Up @@ -179,6 +180,11 @@ public Options(Options? source)
/// </summary>
public class DumpSettings
{
/// <summary>
/// Default output directory name
/// </summary>
private const string DefaultOutputDirectoryName = "ISO";

#region Default Paths

/// <summary>
Expand Down Expand Up @@ -281,6 +287,57 @@ internal static string DefaultRedumperPath
}
}

/// <summary>
/// Default output path for dumps
/// </summary>
/// <remarks>
/// The relative path is kept wherever the current directory can be written to, which is the
/// portable layout. It only resolves to the home directory when the current directory can
/// not be written to, such as an installed copy launched from a read-only location.
/// </remarks>
[JsonIgnore]
internal static string DefaultOutputPathValue
{
get
{
if (field is not null)
return field;

field = GetDefaultOutputPath(Environment.CurrentDirectory);
return field;
}
}

/// <summary>
/// Attempt to determine the default output path
/// </summary>
/// <param name="currentDirectory">Directory that a relative path would resolve against</param>
internal static string GetDefaultOutputPath(string currentDirectory)
Comment thread
mnadareski marked this conversation as resolved.
{
// Portable output -- keep the relative path wherever the current directory can hold a
// file. Creating one is the only portable way of asking; the probe is removed again, and
// only the creation decides so that a failed cleanup does not read as unwritable.
if (!string.IsNullOrEmpty(currentDirectory))
{
try
{
string probePath = Path.Combine(currentDirectory, Path.GetRandomFileName());
File.Create(probePath).Dispose();
try { File.Delete(probePath); } catch { }
return DefaultOutputDirectoryName;
}
catch { }
}

// User output
string homeDir = PathTool.GetHomeDirectory();
if (!string.IsNullOrEmpty(homeDir))
return Path.Combine(homeDir, DefaultOutputDirectoryName);

// This should not happen
return DefaultOutputDirectoryName;
}

#endregion

#region Internal Program
Expand Down Expand Up @@ -350,9 +407,9 @@ public string? DefaultOutputPath
get;
set
{
field = value ?? "ISO";
field = value ?? DefaultOutputPathValue;
}
} = "ISO";
} = DefaultOutputPathValue;

/// <summary>
/// Default system if none can be detected
Expand Down
2 changes: 1 addition & 1 deletion MPF.Frontend/Tools/OptionsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ private static Options ConvertFromDictionary(Dictionary<string, string?>? source
options.Dumping.DreamdumpPath = GetStringSetting(source, "DreamdumpPath", DumpSettings.DefaultDreamdumpPath) ?? DumpSettings.DefaultDreamdumpPath;
options.Dumping.RedumperPath = GetStringSetting(source, "RedumperPath", DumpSettings.DefaultRedumperPath) ?? DumpSettings.DefaultRedumperPath;

options.Dumping.DefaultOutputPath = GetStringSetting(source, "DefaultOutputPath", "ISO");
options.Dumping.DefaultOutputPath = GetStringSetting(source, "DefaultOutputPath", DumpSettings.DefaultOutputPathValue);
valueString = GetStringSetting(source, "DefaultSystem", PhysicalSystem.IBMPCcompatible.ToString());
options.Dumping.DefaultSystem = (valueString ?? string.Empty).ToPhysicalSystem();
options.Dumping.DumpSpeeds.CD = GetInt32Setting(source, "PreferredDumpSpeedCD", 24);
Expand Down