using System.Text.Json;
namespace AIStudio.Tools.DataBackup;
///
/// What remains to do after the runtime applied a restore, or after a backup or the preparation of a
/// restore stopped halfway: read the report of the runtime, and remove what nobody needs any longer.
///
///
/// Nothing here touches a restore which the runtime has not finished, cf. IsRestoreUnfinished: its
/// journal and the data it replaced are the only way back, and the runtime continues at every start.
///
public static class DataRestoreFollowUp
{
///
/// Whether a restore is prepared completely and waits for the next start.
///
/// The data folder.
/// True when the next start applies a restore.
public static bool IsRestorePending(string dataDirectory) => IsMarkedReady(dataDirectory) && !HasJournal(dataDirectory);
///
/// Whether the runtime has not finished a restore: it failed to move a failed restore back, and
/// tries again at the next start.
///
/// The data folder.
/// True while the runtime still needs the pending and the previous directories.
public static bool IsRestoreUnfinished(string dataDirectory) => IsMarkedReady(dataDirectory) && HasJournal(dataDirectory);
///
/// Reads how the runtime applied a restore at this start. Throws a JsonException when the report
/// cannot be read.
///
/// The data folder.
/// Cancels reading.
/// The report, or null when the runtime applied no restore.
public static async Task ReadReportAsync(string dataDirectory, CancellationToken token)
{
var reportPath = Path.Join(dataDirectory, DataRestoreFormat.RESULT_FILE_NAME);
if (!File.Exists(reportPath))
return null;
await using var reportStream = File.OpenRead(reportPath);
return await JsonSerializer.DeserializeAsync(reportStream, DataBackupFormat.JSON_OPTIONS, token);
}
///
/// Removes the report of the runtime, once the user saw it.
///
/// The data folder.
public static void RemoveReport(string dataDirectory) => File.Delete(Path.Join(dataDirectory, DataRestoreFormat.RESULT_FILE_NAME));
///
/// Removes what a backup or the preparation of a restore left behind when it stopped halfway, e.g.,
/// because AI Studio crashed. Call it only while neither runs.
///
///
/// A pending directory with READY_FILE_NAME stays: the restore waits for the next start, or the
/// runtime has not finished it.
///
/// The data folder.
/// The configuration folder.
public static void RemoveLeftovers(string dataDirectory, string configDirectory)
{
DeleteDirectory(DataRestoreFormat.DirectoryIn(dataDirectory, DataBackupFormat.STAGING_DIRECTORY));
if (IsMarkedReady(dataDirectory))
return;
DeleteDirectory(DataRestoreFormat.DirectoryIn(dataDirectory, DataRestoreFormat.PENDING_DIRECTORY));
DeleteDirectory(DataRestoreFormat.DirectoryIn(configDirectory, DataRestoreFormat.PENDING_DIRECTORY));
}
///
/// Removes the data which a restore replaced. Call it once the restored settings loaded: until
/// then, these data are the way back.
///
/// The data folder.
/// The configuration folder.
/// Whether there were such data.
public static bool RemoveReplacedData(string dataDirectory, string configDirectory)
{
if (IsMarkedReady(dataDirectory))
return false;
var removedData = DeleteDirectory(DataRestoreFormat.DirectoryIn(dataDirectory, DataRestoreFormat.PREVIOUS_DIRECTORY));
var removedConfiguration = DeleteDirectory(DataRestoreFormat.DirectoryIn(configDirectory, DataRestoreFormat.PREVIOUS_DIRECTORY));
return removedData || removedConfiguration;
}
private static bool IsMarkedReady(string dataDirectory) => File.Exists(Path.Join(DataRestoreFormat.DirectoryIn(dataDirectory, DataRestoreFormat.PENDING_DIRECTORY), DataRestoreFormat.READY_FILE_NAME));
private static bool HasJournal(string dataDirectory) => File.Exists(Path.Join(DataRestoreFormat.DirectoryIn(dataDirectory, DataRestoreFormat.PENDING_DIRECTORY), DataRestoreFormat.JOURNAL_FILE_NAME));
///
/// Deletes a directory with everything in it.
///
/// Whether the directory existed.
private static bool DeleteDirectory(string path)
{
if (!Directory.Exists(path))
return false;
Directory.Delete(path, true);
return true;
}
}