namespace WiiCompiled.Setup;
///
/// Owns one temporary directory for an install operation. The name carries the installation's scope
/// id, so the next locked operation can remove trees left by forced process termination without
/// touching scratch belonging to another installation.
///
internal sealed class InstallScratchSpace : IDisposable
{
private readonly IInstallReporter? _reporter;
private bool _disposed;
private InstallScratchSpace(string root, IInstallReporter? reporter)
{
Root = root;
_reporter = reporter;
}
public string Root { get; }
/// Creates staging beside the installation, suitable even when the install is absent.
public static InstallScratchSpace CreateSibling(string installDirectory, IInstallReporter? reporter = null) =>
Create(InstallOperationPaths.GetParent(InstallOperationPaths.NormalizeRoot(installDirectory)),
installDirectory, reporter);
/// Creates build/update output inside an existing installation.
public static InstallScratchSpace CreateInsideInstall(string installDirectory,
IInstallReporter? reporter = null)
{
var installRoot = InstallOperationPaths.NormalizeRoot(installDirectory);
if (!Directory.Exists(installRoot))
throw new DirectoryNotFoundException(
"An in-install scratch directory requires an existing installation root.");
return Create(installRoot, installDirectory, reporter);
}
/// Deletes scratch left by an interrupted operation. Call only while locked.
public static void Recover(string installDirectory, IInstallReporter? reporter = null)
{
var installRoot = InstallOperationPaths.NormalizeRoot(installDirectory);
foreach (var container in new[] { InstallOperationPaths.GetParent(installRoot), installRoot })
{
if (!Directory.Exists(container)) continue;
foreach (var leftover in Directory.EnumerateDirectories(container, Pattern(installRoot)))
{
reporter?.Diagnostic("Removing scratch space from an interrupted operation: " + leftover);
FileSystemUtilities.DeleteDirectoryIfExists(leftover);
}
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
try
{
FileSystemUtilities.DeleteDirectoryIfExists(Root);
}
catch (Exception ex)
{
// The next locked operation removes this exact tree. A transient AV/file-indexer handle
// must not turn an already committed installation into a reported failure.
_reporter?.Diagnostic("Scratch cleanup is pending and will be retried: " + ex.Message);
}
}
private static InstallScratchSpace Create(string container, string installDirectory,
IInstallReporter? reporter)
{
var installRoot = InstallOperationPaths.NormalizeRoot(installDirectory);
var root = Path.Combine(container,
$".mkwc-scratch-{InstallOperationPaths.GetScopeId(installRoot)}-{Guid.NewGuid():N}");
Directory.CreateDirectory(root);
reporter?.Diagnostic("Prepared exact update scratch space at " + root);
return new InstallScratchSpace(root, reporter);
}
private static string Pattern(string installRoot) =>
$".mkwc-scratch-{InstallOperationPaths.GetScopeId(installRoot)}-*";
}