Files
OmniNX-Installer-Payload/desktop/OmniNX.DesktopInstaller/Services/DeletionService.cs
T
niklascfw d573f6cca4 Add a Windows desktop installer for PC-based OmniNX installs.
This lets mass deployments and modchip setups apply the same install/update flow from a PC, with generated deletion lists kept in sync with the payload headers.
2026-09-13 21:10:58 +02:00

188 lines
6.9 KiB
C#

using CleanLists = OmniNX.DesktopInstaller.Data.DeletionLists.Clean;
using UpdateLists = OmniNX.DesktopInstaller.Data.DeletionLists.Update;
namespace OmniNX.DesktopInstaller.Services;
public sealed class DeletionService
{
private readonly PreserveService _preserve = new();
public void RunCleanDeletion(string driveRoot, IProgress<string>? progress = null)
{
DeleteGroup(driveRoot, "atmosphere", CleanLists.clean_atmosphere_dirs_to_delete, progress);
DeleteGroup(driveRoot, "bootloader", CleanLists.clean_bootloader_dirs_to_delete, progress);
DeleteFiles(driveRoot, CleanLists.clean_bootloader_files_to_delete, progress);
DeleteGroup(driveRoot, "config", CleanLists.clean_config_dirs_to_delete, progress);
DeleteGroup(driveRoot, "switch", CleanLists.clean_switch_dirs_to_delete, progress);
DeleteFiles(driveRoot, CleanLists.clean_root_files_to_delete, progress);
DeleteGroup(driveRoot, "misc", CleanLists.clean_misc_dirs_to_delete, progress);
DeleteFiles(driveRoot, CleanLists.clean_misc_files_to_delete, progress);
DeleteFiles(driveRoot, CleanLists.old_version_files_to_delete, progress);
PathUtils.EnsureDirectory(PathUtils.ToDrivePath(driveRoot, @"switch"));
}
public void RunUpdateDeletion(string driveRoot, IProgress<string>? progress = null)
{
_preserve.Load(driveRoot, progress);
DeleteGroup(driveRoot, "atmosphere", UpdateLists.atmosphere_dirs_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "atmosphere/contents", UpdateLists.atmosphere_contents_dirs_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.atmosphere_files_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "bootloader", UpdateLists.bootloader_dirs_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.bootloader_files_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "config", UpdateLists.config_dirs_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "switch", UpdateLists.switch_dirs_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.switch_files_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.root_files_to_delete, progress, _preserve);
DeleteGroup(driveRoot, "misc", UpdateLists.misc_dirs_to_delete, progress, _preserve);
DeleteFiles(driveRoot, UpdateLists.misc_files_to_delete, progress, _preserve);
_preserve.Clear();
}
public void RunPostRestoreCleanup(string driveRoot)
{
DeleteGroup(driveRoot, "switch (post-restore)", CleanLists.clean_post_restore_dirs_to_delete, null);
}
private static void DeleteGroup(
string driveRoot,
string label,
string[] paths,
IProgress<string>? progress,
PreserveService? preserve = null)
{
if (paths.Length == 0)
return;
progress?.Report($"Deleting {label}...");
foreach (var rel in paths)
{
var full = PathUtils.ToDrivePath(driveRoot, rel);
if (!PathUtils.PathExists(full))
continue;
if (preserve?.IsBlocked(full, driveRoot) == true)
{
progress?.Report($" Skipped (preserve): {rel}");
continue;
}
progress?.Report($" Delete: {rel}");
PathUtils.DeletePath(full);
}
}
private static void DeleteFiles(
string driveRoot,
string[] paths,
IProgress<string>? progress,
PreserveService? preserve = null)
{
foreach (var rel in paths)
{
var full = PathUtils.ToDrivePath(driveRoot, rel);
if (!File.Exists(full))
continue;
if (preserve?.IsBlocked(full, driveRoot) == true)
{
progress?.Report($" Skipped (preserve): {rel}");
continue;
}
progress?.Report($" Delete: {rel}");
PathUtils.DeletePath(full);
}
}
}
public sealed class PreserveService
{
private readonly List<string> _paths = new();
public void Load(string driveRoot, IProgress<string>? progress = null)
{
_paths.Clear();
var iniPath = PathUtils.ToDrivePath(driveRoot, @"config\omninx\preserve.ini");
if (!File.Exists(iniPath))
return;
var inSection = false;
foreach (var rawLine in File.ReadLines(iniPath))
{
var line = rawLine.Trim();
if (line.StartsWith('[') && line.EndsWith(']'))
{
inSection = line[1..^1].Equals("Preserve", StringComparison.Ordinal);
continue;
}
if (!inSection || !line.Contains('='))
continue;
var eq = line.IndexOf('=');
var key = line[..eq].Trim().Trim('\'', '"');
var val = line[(eq + 1)..].Trim();
if (!IsTruthy(val))
continue;
var normalized = NormalizePreservePath(key, driveRoot);
if (normalized is null)
continue;
if (!_paths.Contains(normalized, StringComparer.OrdinalIgnoreCase))
_paths.Add(normalized);
}
if (_paths.Count > 0)
progress?.Report($"preserve.ini: {_paths.Count} path(s) excluded from update cleanup.");
}
public bool IsBlocked(string deleteFullPath, string driveRoot)
{
if (_paths.Count == 0)
return false;
var normalizedDelete = Path.GetFullPath(deleteFullPath).TrimEnd(Path.DirectorySeparatorChar);
foreach (var preserve in _paths)
{
var normalizedPreserve = Path.GetFullPath(preserve).TrimEnd(Path.DirectorySeparatorChar);
if (normalizedDelete.Equals(normalizedPreserve, StringComparison.OrdinalIgnoreCase))
return true;
if (IsPrefix(normalizedPreserve, normalizedDelete))
return true;
if (IsPrefix(normalizedDelete, normalizedPreserve))
return true;
}
return false;
}
public void Clear() => _paths.Clear();
private static string? NormalizePreservePath(string key, string driveRoot)
{
var path = key.Replace("sd:/", string.Empty).Replace("sd:\\", string.Empty)
.Replace('/', Path.DirectorySeparatorChar).TrimEnd(Path.DirectorySeparatorChar);
if (path.Length < 2)
return null;
return PathUtils.ToDrivePath(driveRoot, path);
}
private static bool IsPrefix(string prefix, string path)
{
if (!path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
return false;
return path.Length == prefix.Length ||
path[prefix.Length] == Path.DirectorySeparatorChar;
}
private static bool IsTruthy(string val)
{
if (string.IsNullOrEmpty(val))
return false;
var c = char.ToUpperInvariant(val[0]);
return c is '1' or 'T' or 'Y';
}
}