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
12 changes: 2 additions & 10 deletions StabilityMatrix.Avalonia/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 41 additions & 2 deletions StabilityMatrix.Avalonia/Helpers/IOCommands.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -37,7 +43,7 @@ public static class IOCommands
if (string.IsNullOrWhiteSpace(path))
return;

await ProcessRunner.OpenFileBrowser(path);
await OpenFileBrowserAsync(path);
},
path => !string.IsNullOrWhiteSpace(path)
);
Expand All @@ -49,8 +55,41 @@ public static class IOCommands
if (string.IsNullOrWhiteSpace(path))
return;

await ProcessRunner.OpenFolderBrowser(path);
await OpenFolderBrowserAsync(path);
},
path => !string.IsNullOrWhiteSpace(path)
);

/// <summary>
/// Opens the folder in the system file explorer, or shows a notification if it does not exist.
/// </summary>
public static async Task OpenFolderBrowserAsync(string directoryPath)
{
if (!Directory.Exists(directoryPath))
{
ShowNotFound(Resources.Label_FolderNotFound, directoryPath);
return;
}

await ProcessRunner.OpenFolderBrowser(directoryPath);
}

/// <summary>
/// Opens the file within its folder in the system file explorer, or shows a notification if it does not exist.
/// </summary>
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<INotificationService>().Show(title, path, NotificationType.Warning);
}
}
18 changes: 18 additions & 0 deletions StabilityMatrix.Avalonia/Languages/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions StabilityMatrix.Avalonia/Languages/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,12 @@
<data name="Label_AppData" xml:space="preserve">
<value>App Data</value>
</data>
<data name="Label_FolderNotFound" xml:space="preserve">
<value>Folder not found</value>
</data>
<data name="Label_FileNotFound" xml:space="preserve">
<value>File not found</value>
</data>
<data name="TextTemplate_PackageUpdatedToSelected" xml:space="preserve">
<value>{0} has been updated to the selected version</value>
</data>
Expand Down
29 changes: 29 additions & 0 deletions StabilityMatrix.Avalonia/Logging/LogFileTarget.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using NLog.Layouts;
using NLog.Targets;
using StabilityMatrix.Core.Models.FileInterfaces;

namespace StabilityMatrix.Avalonia.Logging;

public static class LogFileTarget
{
/// <summary>
/// Creates the app's rolling file target, writing app.log and its archives under <paramref name="logDir"/>.
/// </summary>
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,
};
}
}
4 changes: 3 additions & 1 deletion StabilityMatrix.Avalonia/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,15 @@ public static async Task<string> 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,7 @@ private async Task LoadInstalledWorkflowsAsync()
{
workflowsCache.Clear();

if (!Directory.Exists(settingsManager.WorkflowDirectory))
{
Directory.CreateDirectory(settingsManager.WorkflowDirectory);
}
settingsManager.EnsureAppDirectories();

var count = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ protected override void OnInitialLoaded()
if (!settingsManager.IsLibraryDirSet)
return;

Directory.CreateDirectory(settingsManager.ImagesDirectory);
settingsManager.EnsureAppDirectories();

RefreshCategories();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ private async Task CheckPackagesForUpdates()

private IEnumerable<UnknownInstalledPackage> IndexUnknownPackages()
{
var packageDir = settingsManager.LibraryDir.JoinDir("Packages");
var packageDir = settingsManager.PackagesDirectory;

if (!packageDir.Exists)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions StabilityMatrix.Core/Helper/Compat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public static class Compat
/// </summary>
public static DirectoryPath AppDataHome { get; private set; }

/// <summary>
/// Log files directory (AppDataHome/Logs)
/// </summary>
public static DirectoryPath LogsDir => AppDataHome.JoinDir("Logs");

/// <summary>
/// Set AppDataHome to a custom path. Used for testing.
/// </summary>
Expand Down
9 changes: 9 additions & 0 deletions StabilityMatrix.Core/Services/ISettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public interface ISettingsManager
List<string> PackageInstallsInProgress { get; set; }
DirectoryPath WorkflowDirectory { get; }
DirectoryPath ExtensionPackDirectory { get; }
DirectoryPath PackagesDirectory { get; }

/// <summary>
/// Event fired when the library directory is changed
Expand Down Expand Up @@ -118,6 +119,14 @@ Action<T> onPropertyChanged
/// </summary>
void SetPortableMode();

/// <summary>
/// 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.
/// </summary>
void EnsureAppDirectories();

void SaveLaunchArgs(Guid packageId, IEnumerable<LaunchOption> launchArgs);
bool IsEulaAccepted();
void SetEulaAccepted();
Expand Down
55 changes: 45 additions & 10 deletions StabilityMatrix.Core/Services/SettingsManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reactive.Disposables;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -362,9 +359,7 @@ public bool TryFindLibrary(bool forceReload = false)
&& Directory.Exists(librarySettings.LibraryPath)
)
{
LibraryDir = librarySettings.LibraryPath;
SetStaticLibraryPaths();
LoadSettings();
LoadLibrary(librarySettings.LibraryPath);
return true;
}
}
Expand All @@ -375,6 +370,46 @@ public bool TryFindLibrary(bool forceReload = false)
return false;
}

private void LoadLibrary(DirectoryPath libraryDirectory)
{
LibraryDir = libraryDirectory;
SetStaticLibraryPaths();
LoadSettings();
// After LoadSettings, so a ModelDirectoryOverride is known
EnsureAppDirectories();
}

/// <inheritdoc />
public void EnsureAppDirectories()
{
List<DirectoryPath> 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()
{
Expand Down
Loading
Loading