diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs
index 07a196cf2..3910cc874 100644
--- a/StabilityMatrix.Avalonia/App.axaml.cs
+++ b/StabilityMatrix.Avalonia/App.axaml.cs
@@ -1327,16 +1327,8 @@ private static LoggingConfiguration ConfigureLogging()
builder.ForLogger().FilterMinLevel(NLog.LogLevel.Trace).WriteTo(consoleTarget).WithAsync();
// File logging
- var fileTarget = new FileTarget("logfile")
- {
- Layout = "${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}",
- FileName = "${specialfolder:folder=ApplicationData}/StabilityMatrix/Logs/app.log",
- ArchiveOldFileOnStartup = true,
- ArchiveFileName = "${specialfolder:folder=ApplicationData}/StabilityMatrix/Logs/app.{#}.log",
- ArchiveDateFormat = "yyyy-MM-dd HH_mm_ss",
- ArchiveNumbering = ArchiveNumberingMode.Date,
- MaxArchiveFiles = 9,
- };
+ // Use Compat.AppDataHome so logs follow --home-dir overrides
+ var fileTarget = LogFileTarget.Create(Compat.LogsDir);
builder.ForLogger().FilterMinLevel(NLog.LogLevel.Debug).WriteTo(fileTarget).WithAsync();
#if SM_LOG_WINDOW
diff --git a/StabilityMatrix.Avalonia/Helpers/IOCommands.cs b/StabilityMatrix.Avalonia/Helpers/IOCommands.cs
index 6877ec14c..43f7a098a 100644
--- a/StabilityMatrix.Avalonia/Helpers/IOCommands.cs
+++ b/StabilityMatrix.Avalonia/Helpers/IOCommands.cs
@@ -1,5 +1,11 @@
using System;
+using System.IO;
+using System.Threading.Tasks;
+using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.Input;
+using Microsoft.Extensions.DependencyInjection;
+using StabilityMatrix.Avalonia.Languages;
+using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Core.Processes;
namespace StabilityMatrix.Avalonia.Helpers;
@@ -37,7 +43,7 @@ public static class IOCommands
if (string.IsNullOrWhiteSpace(path))
return;
- await ProcessRunner.OpenFileBrowser(path);
+ await OpenFileBrowserAsync(path);
},
path => !string.IsNullOrWhiteSpace(path)
);
@@ -49,8 +55,41 @@ public static class IOCommands
if (string.IsNullOrWhiteSpace(path))
return;
- await ProcessRunner.OpenFolderBrowser(path);
+ await OpenFolderBrowserAsync(path);
},
path => !string.IsNullOrWhiteSpace(path)
);
+
+ ///
+ /// Opens the folder in the system file explorer, or shows a notification if it does not exist.
+ ///
+ public static async Task OpenFolderBrowserAsync(string directoryPath)
+ {
+ if (!Directory.Exists(directoryPath))
+ {
+ ShowNotFound(Resources.Label_FolderNotFound, directoryPath);
+ return;
+ }
+
+ await ProcessRunner.OpenFolderBrowser(directoryPath);
+ }
+
+ ///
+ /// Opens the file within its folder in the system file explorer, or shows a notification if it does not exist.
+ ///
+ public static async Task OpenFileBrowserAsync(string filePath)
+ {
+ if (!File.Exists(filePath))
+ {
+ ShowNotFound(Resources.Label_FileNotFound, filePath);
+ return;
+ }
+
+ await ProcessRunner.OpenFileBrowser(filePath);
+ }
+
+ private static void ShowNotFound(string title, string path)
+ {
+ App.Services.GetRequiredService().Show(title, path, NotificationType.Warning);
+ }
}
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
index b11dbd975..44bca5b52 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
+++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
@@ -2103,6 +2103,15 @@ public static string Label_FileNamePattern {
}
}
+ ///
+ /// Looks up a localized string similar to File not found.
+ ///
+ public static string Label_FileNotFound {
+ get {
+ return ResourceManager.GetString("Label_FileNotFound", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Files.
///
@@ -2166,6 +2175,15 @@ public static string Label_Folder {
}
}
+ ///
+ /// Looks up a localized string similar to Folder not found.
+ ///
+ public static string Label_FolderNotFound {
+ get {
+ return ResourceManager.GetString("Label_FolderNotFound", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Frames Per Second.
///
diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx
index 8b3915911..ede9d4b94 100644
--- a/StabilityMatrix.Avalonia/Languages/Resources.resx
+++ b/StabilityMatrix.Avalonia/Languages/Resources.resx
@@ -1182,6 +1182,12 @@
App Data
+
+ Folder not found
+
+
+ File not found
+
{0} has been updated to the selected version
diff --git a/StabilityMatrix.Avalonia/Logging/LogFileTarget.cs b/StabilityMatrix.Avalonia/Logging/LogFileTarget.cs
new file mode 100644
index 000000000..893d6c504
--- /dev/null
+++ b/StabilityMatrix.Avalonia/Logging/LogFileTarget.cs
@@ -0,0 +1,29 @@
+using NLog.Layouts;
+using NLog.Targets;
+using StabilityMatrix.Core.Models.FileInterfaces;
+
+namespace StabilityMatrix.Avalonia.Logging;
+
+public static class LogFileTarget
+{
+ ///
+ /// Creates the app's rolling file target, writing app.log and its archives under .
+ ///
+ public static FileTarget Create(DirectoryPath logDir)
+ {
+ // FileName and ArchiveFileName are NLog layouts, so escape the literal path
+ // (e.g. a home directory named "${shortdate}" must not be rendered as a date)
+ var escapedLogDir = SimpleLayout.Escape(logDir.FullPath);
+
+ return new FileTarget("logfile")
+ {
+ Layout = "${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}",
+ FileName = $"{escapedLogDir}/app.log",
+ ArchiveOldFileOnStartup = true,
+ ArchiveFileName = $"{escapedLogDir}/app.{{#}}.log",
+ ArchiveDateFormat = "yyyy-MM-dd HH_mm_ss",
+ ArchiveNumbering = ArchiveNumberingMode.Date,
+ MaxArchiveFiles = 9,
+ };
+ }
+}
diff --git a/StabilityMatrix.Avalonia/Program.cs b/StabilityMatrix.Avalonia/Program.cs
index 838b018d0..6c834c055 100644
--- a/StabilityMatrix.Avalonia/Program.cs
+++ b/StabilityMatrix.Avalonia/Program.cs
@@ -87,8 +87,10 @@ x.Tag is ErrorType.HelpRequestedError or ErrorType.VersionRequestedError
Args = parseResult.Value ?? new AppArgs();
- if (Args.HomeDirectoryOverride is { } homeDir)
+ if (Args.HomeDirectoryOverride is { } homeDirArg)
{
+ // Resolve relative paths now, so later consumers (file browser, file:// URIs) get absolute paths
+ var homeDir = Path.GetFullPath(homeDirArg);
Compat.SetAppDataHome(homeDir);
GlobalConfig.HomeDir = homeDir;
}
diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ExceptionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ExceptionViewModel.cs
index 650f2a021..5e92d46b7 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/ExceptionViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/ExceptionViewModel.cs
@@ -66,13 +66,15 @@ public static async Task CreateLogFolderZip()
using var suspend = LogManager.SuspendLogging();
- var logDir = Compat.AppDataHome.JoinDir("Logs");
+ var logDir = Compat.LogsDir;
// Copy logs to temp directory
using var tempDir = new TempDirectoryPath();
var tempLogDir = tempDir.JoinDir("Logs");
tempLogDir.Create();
- foreach (var logFile in logDir.EnumerateFiles("*.log"))
+ // Logs may have been deleted since NLog last wrote, zip whatever exists
+ var logFiles = logDir.Exists ? logDir.EnumerateFiles("*.log") : [];
+ foreach (var logFile in logFiles)
{
// Need FileShare.ReadWrite since NLog keeps the file open
await logFile.CopyToAsync(
diff --git a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
index 8c0a3a454..65f74ef7c 100644
--- a/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
@@ -76,10 +76,7 @@ private async Task LoadInstalledWorkflowsAsync()
{
workflowsCache.Clear();
- if (!Directory.Exists(settingsManager.WorkflowDirectory))
- {
- Directory.CreateDirectory(settingsManager.WorkflowDirectory);
- }
+ settingsManager.EnsureAppDirectories();
var count = 0;
diff --git a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
index aa7deddcd..71e6fa72b 100644
--- a/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
@@ -226,7 +226,7 @@ protected override void OnInitialLoaded()
if (!settingsManager.IsLibraryDirSet)
return;
- Directory.CreateDirectory(settingsManager.ImagesDirectory);
+ settingsManager.EnsureAppDirectories();
RefreshCategories();
diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs
index 49f28a0c5..83207c1de 100644
--- a/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/PackageManager/MainPackageManagerViewModel.cs
@@ -209,7 +209,7 @@ private async Task CheckPackagesForUpdates()
private IEnumerable IndexUnknownPackages()
{
- var packageDir = settingsManager.LibraryDir.JoinDir("Packages");
+ var packageDir = settingsManager.PackagesDirectory;
if (!packageDir.Exists)
{
diff --git a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
index ef8d91fbb..efb77e2e8 100644
--- a/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
+++ b/StabilityMatrix.Avalonia/ViewModels/Settings/MainSettingsViewModel.cs
@@ -814,31 +814,35 @@ partial void OnShowAllAvailablePythonVersionsChanged(bool value)
public CommandItem[] DirectoryShortcutCommands =>
[
new CommandItem(
- new AsyncRelayCommand(() => ProcessRunner.OpenFolderBrowser(Compat.AppDataHome)),
+ new AsyncRelayCommand(() => OpenDirectoryShortcut(Compat.AppDataHome)),
Resources.Label_AppData
),
new CommandItem(
- new AsyncRelayCommand(() =>
- ProcessRunner.OpenFolderBrowser(Compat.AppDataHome.JoinDir("Logs"))
- ),
+ new AsyncRelayCommand(() => OpenDirectoryShortcut(Compat.LogsDir)),
Resources.Label_Logs
),
new CommandItem(
- new AsyncRelayCommand(() => ProcessRunner.OpenFolderBrowser(settingsManager.LibraryDir)),
+ new AsyncRelayCommand(() => OpenDirectoryShortcut(settingsManager.LibraryDir)),
Resources.Label_DataDirectory
),
new CommandItem(
- new AsyncRelayCommand(() => ProcessRunner.OpenFolderBrowser(settingsManager.ModelsDirectory)),
+ new AsyncRelayCommand(() => OpenDirectoryShortcut(settingsManager.ModelsDirectory)),
Resources.Label_Checkpoints
),
new CommandItem(
- new AsyncRelayCommand(() =>
- ProcessRunner.OpenFolderBrowser(settingsManager.LibraryDir.JoinDir("Packages"))
- ),
+ new AsyncRelayCommand(() => OpenDirectoryShortcut(settingsManager.PackagesDirectory)),
Resources.Label_Packages
),
];
+ private Task OpenDirectoryShortcut(string directoryPath)
+ {
+ // Recreate app folders in case they were deleted since startup
+ settingsManager.EnsureAppDirectories();
+
+ return IOCommands.OpenFolderBrowserAsync(directoryPath);
+ }
+
#endregion
#region System
diff --git a/StabilityMatrix.Core/Helper/Compat.cs b/StabilityMatrix.Core/Helper/Compat.cs
index 1a619bab8..63b11ac77 100644
--- a/StabilityMatrix.Core/Helper/Compat.cs
+++ b/StabilityMatrix.Core/Helper/Compat.cs
@@ -49,6 +49,11 @@ public static class Compat
///
public static DirectoryPath AppDataHome { get; private set; }
+ ///
+ /// Log files directory (AppDataHome/Logs)
+ ///
+ public static DirectoryPath LogsDir => AppDataHome.JoinDir("Logs");
+
///
/// Set AppDataHome to a custom path. Used for testing.
///
diff --git a/StabilityMatrix.Core/Services/ISettingsManager.cs b/StabilityMatrix.Core/Services/ISettingsManager.cs
index 42c8b7a8d..cf5baf917 100644
--- a/StabilityMatrix.Core/Services/ISettingsManager.cs
+++ b/StabilityMatrix.Core/Services/ISettingsManager.cs
@@ -24,6 +24,7 @@ public interface ISettingsManager
List PackageInstallsInProgress { get; set; }
DirectoryPath WorkflowDirectory { get; }
DirectoryPath ExtensionPackDirectory { get; }
+ DirectoryPath PackagesDirectory { get; }
///
/// Event fired when the library directory is changed
@@ -118,6 +119,14 @@ Action onPropertyChanged
///
void SetPortableMode();
+ ///
+ /// Creates the app's well-known directories (AppDataHome, Logs, and library folders such as Packages)
+ /// if they are missing. Library folders are skipped when the library is not set.
+ /// A custom models directory override is never created.
+ /// Safe to call repeatedly; failures are logged, not thrown.
+ ///
+ void EnsureAppDirectories();
+
void SaveLaunchArgs(Guid packageId, IEnumerable launchArgs);
bool IsEulaAccepted();
void SetEulaAccepted();
diff --git a/StabilityMatrix.Core/Services/SettingsManager.cs b/StabilityMatrix.Core/Services/SettingsManager.cs
index a07ad9108..4511887bf 100644
--- a/StabilityMatrix.Core/Services/SettingsManager.cs
+++ b/StabilityMatrix.Core/Services/SettingsManager.cs
@@ -1,4 +1,4 @@
-using System.ComponentModel;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reactive.Disposables;
@@ -71,6 +71,7 @@ private set
public DirectoryPath ImagesInferenceDirectory => ImagesDirectory.JoinDir("Inference");
public DirectoryPath ConsolidatedImagesDirectory => ImagesDirectory.JoinDir("Consolidated");
public DirectoryPath ExtensionPackDirectory => LibraryDir.JoinDir("ExtensionPacks");
+ public DirectoryPath PackagesDirectory => LibraryDir.JoinDir("Packages");
public Settings Settings { get; private set; } = new();
@@ -330,9 +331,7 @@ public bool TryFindLibrary(bool forceReload = false)
{
logger.LogInformation("Using library override path {Path}", libraryDirOverride.FullPath);
- LibraryDir = libraryDirOverride;
- SetStaticLibraryPaths();
- LoadSettings();
+ LoadLibrary(libraryDirOverride);
return true;
}
@@ -341,9 +340,7 @@ public bool TryFindLibrary(bool forceReload = false)
IsPortableMode = File.Exists(Path.Combine(appDir, "Data", ".sm-portable"));
if (IsPortableMode)
{
- LibraryDir = appDir + "Data";
- SetStaticLibraryPaths();
- LoadSettings();
+ LoadLibrary(appDir + "Data");
return true;
}
@@ -362,9 +359,7 @@ public bool TryFindLibrary(bool forceReload = false)
&& Directory.Exists(librarySettings.LibraryPath)
)
{
- LibraryDir = librarySettings.LibraryPath;
- SetStaticLibraryPaths();
- LoadSettings();
+ LoadLibrary(librarySettings.LibraryPath);
return true;
}
}
@@ -375,6 +370,44 @@ public bool TryFindLibrary(bool forceReload = false)
return false;
}
+ private void LoadLibrary(DirectoryPath libraryDirectory)
+ {
+ LibraryDir = libraryDirectory;
+ SetStaticLibraryPaths();
+ LoadSettings();
+ }
+
+ ///
+ public void EnsureAppDirectories()
+ {
+ List directories = [Compat.AppDataHome, Compat.LogsDir];
+
+ if (IsLibraryDirSet)
+ {
+ directories.AddRange(
+ [PackagesDirectory, WorkflowDirectory, ImagesDirectory, ExtensionPackDirectory]
+ );
+
+ // Don't create an override, it may be on a drive that isn't mounted
+ if (Settings.ModelDirectoryOverride is null)
+ {
+ directories.Add(ModelsDirectory);
+ }
+ }
+
+ foreach (var directory in directories)
+ {
+ try
+ {
+ directory.Create();
+ }
+ catch (Exception e) when (e is IOException or UnauthorizedAccessException)
+ {
+ logger.LogWarning(e, "Failed to create directory {Path}", directory.FullPath);
+ }
+ }
+ }
+
// Set static classes requiring library path
private void SetStaticLibraryPaths()
{
diff --git a/StabilityMatrix.Tests/Avalonia/LogFileTargetTests.cs b/StabilityMatrix.Tests/Avalonia/LogFileTargetTests.cs
new file mode 100644
index 000000000..2815abcd6
--- /dev/null
+++ b/StabilityMatrix.Tests/Avalonia/LogFileTargetTests.cs
@@ -0,0 +1,25 @@
+using NLog;
+using StabilityMatrix.Avalonia.Logging;
+using StabilityMatrix.Core.Models.FileInterfaces;
+
+namespace StabilityMatrix.Tests.Avalonia;
+
+[TestClass]
+public class LogFileTargetTests
+{
+ [DataTestMethod]
+ [DataRow("home")]
+ [DataRow("${shortdate}")]
+ [DataRow("${x}")]
+ [DataRow(@"home\name")]
+ public void Create_FileNamesUseLiteralLogDirectory(string homeDirName)
+ {
+ var logDir = new DirectoryPath(Path.GetTempPath(), homeDirName, "Logs");
+
+ var target = LogFileTarget.Create(logDir);
+ var logEvent = LogEventInfo.CreateNullEvent();
+
+ Assert.AreEqual($"{logDir.FullPath}/app.log", target.FileName.Render(logEvent));
+ Assert.AreEqual($"{logDir.FullPath}/app.{{#}}.log", target.ArchiveFileName.Render(logEvent));
+ }
+}
diff --git a/StabilityMatrix.Tests/Core/SettingsManagerDirectoriesTests.cs b/StabilityMatrix.Tests/Core/SettingsManagerDirectoriesTests.cs
new file mode 100644
index 000000000..badc8e429
--- /dev/null
+++ b/StabilityMatrix.Tests/Core/SettingsManagerDirectoriesTests.cs
@@ -0,0 +1,112 @@
+using System.Text.Json;
+using Microsoft.Extensions.Logging.Abstractions;
+using StabilityMatrix.Core.Helper;
+using StabilityMatrix.Core.Models.FileInterfaces;
+using StabilityMatrix.Core.Services;
+
+namespace StabilityMatrix.Tests.Core;
+
+[TestClass]
+public class SettingsManagerDirectoriesTests
+{
+ private DirectoryPath tempDir = null!;
+ private DirectoryPath libraryDir = null!;
+ private DirectoryPath originalAppDataHome = null!;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ tempDir = new DirectoryPath(Path.GetTempPath(), $"SMTest_{Guid.NewGuid():N}");
+ libraryDir = tempDir.JoinDir("Library");
+ libraryDir.Create();
+
+ originalAppDataHome = Compat.AppDataHome;
+ Compat.SetAppDataHome(tempDir.JoinDir("AppDataHome"));
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ Compat.SetAppDataHome(originalAppDataHome);
+ tempDir.Delete(true);
+ }
+
+ private SettingsManager CreateWithLibrary()
+ {
+ var settingsManager = new SettingsManager(NullLogger.Instance);
+ settingsManager.SetLibraryDirOverride(libraryDir);
+
+ Assert.IsTrue(settingsManager.TryFindLibrary());
+
+ return settingsManager;
+ }
+
+ [TestMethod]
+ public void EnsureAppDirectories_CreatesAppDirectories()
+ {
+ var settingsManager = CreateWithLibrary();
+
+ settingsManager.EnsureAppDirectories();
+
+ Assert.IsTrue(Compat.AppDataHome.Exists);
+ Assert.IsTrue(Compat.LogsDir.Exists);
+ Assert.IsTrue(settingsManager.PackagesDirectory.Exists);
+ Assert.IsTrue(settingsManager.WorkflowDirectory.Exists);
+ Assert.IsTrue(settingsManager.ImagesDirectory.Exists);
+ Assert.IsTrue(settingsManager.ExtensionPackDirectory.Exists);
+ Assert.IsTrue(Directory.Exists(settingsManager.ModelsDirectory));
+ }
+
+ [TestMethod]
+ public void EnsureAppDirectories_DoesNotCreateConsolidatedImagesDirectory()
+ {
+ // Subfolders of Images show as categories in the Outputs page
+ var settingsManager = CreateWithLibrary();
+
+ settingsManager.EnsureAppDirectories();
+
+ Assert.IsFalse(settingsManager.ConsolidatedImagesDirectory.Exists);
+ }
+
+ [TestMethod]
+ public void EnsureAppDirectories_DoesNotCreateModelsDirectoryOverride()
+ {
+ var modelsOverride = tempDir.JoinDir("UnmountedDrive", "Models");
+ libraryDir
+ .JoinFile("settings.json")
+ .WriteAllText(JsonSerializer.Serialize(new { ModelDirectoryOverride = modelsOverride.FullPath }));
+
+ var settingsManager = CreateWithLibrary();
+
+ settingsManager.EnsureAppDirectories();
+
+ Assert.AreEqual(modelsOverride.FullPath, settingsManager.ModelsDirectory);
+ Assert.IsFalse(modelsOverride.Exists);
+ Assert.IsFalse(libraryDir.JoinDir("Models").Exists);
+ }
+
+ [TestMethod]
+ public void EnsureAppDirectories_RecreatesDeletedDirectory()
+ {
+ var settingsManager = CreateWithLibrary();
+ settingsManager.EnsureAppDirectories();
+ settingsManager.PackagesDirectory.Delete(true);
+
+ settingsManager.EnsureAppDirectories();
+ settingsManager.EnsureAppDirectories();
+
+ Assert.IsTrue(settingsManager.PackagesDirectory.Exists);
+ }
+
+ [TestMethod]
+ public void EnsureAppDirectories_WithoutLibrary_OnlyCreatesAppDataDirectories()
+ {
+ var settingsManager = new SettingsManager(NullLogger.Instance);
+
+ settingsManager.EnsureAppDirectories();
+
+ Assert.IsFalse(settingsManager.IsLibraryDirSet);
+ Assert.IsTrue(Compat.LogsDir.Exists);
+ Assert.IsFalse(libraryDir.JoinDir("Packages").Exists);
+ }
+}