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.
188 lines
6.9 KiB
C#
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';
|
|
}
|
|
}
|