79 lines
3.3 KiB
C#
79 lines
3.3 KiB
C#
using MongoDB.Entities;
|
|||
|
|
|
||
|
|
using PrivaPub.ClientModels.User;
|
||
|
|
|
||
|
|
namespace PrivaPub.Domain.Portability
|
||
|
|
{
|
||
|
|
// A persona's archive, made and restored in jobs (owner decision 2026-10-07): where each stands, one row per persona
|
||
|
|
// (its ID is the persona's). The files live in <backups>/.personas/<avatarId>/: the export, kept a week, and the upload
|
||
|
|
// being imported.
|
||
|
|
public class PersonaArchive : Entity
|
||
|
|
{
|
||
|
|
public string ExportState { get; set; } = "none";
|
||
|
|
public DateTime? ExportAskedAt { get; set; }
|
||
|
|
public DateTime? ExportReadyAt { get; set; }
|
||
|
|
public string ExportFile { get; set; }
|
||
|
|
public long ExportBytes { get; set; }
|
||
|
|
public string ExportError { get; set; }
|
||
|
|
|
||
|
|
public string ImportState { get; set; } = "none";
|
||
|
|
public DateTime? ImportAskedAt { get; set; }
|
||
|
|
public DateTime? ImportEndedAt { get; set; }
|
||
|
|
public string ImportFile { get; set; }
|
||
|
|
public List<string> ImportParts { get; set; } = [];
|
||
|
|
public long ImportDone { get; set; }
|
||
|
|
public long ImportTotal { get; set; }
|
||
|
|
public Dictionary<string, int> ImportCounts { get; set; } = [];
|
||
|
|
public string ImportError { get; set; }
|
||
|
|
public string ImportFrom { get; set; }
|
||
|
|
public bool ImportSameActor { get; set; }
|
||
|
|
public bool StopAsked { get; set; }
|
||
|
|
}
|
||
|
|
|
||
|
|
public static class PersonaArchives
|
||
|
|
{
|
||
|
|
public static readonly TimeSpan Kept = TimeSpan.FromDays(7);
|
||
|
|
|
||
|
|
/// <summary>Every part an import can take; the last three only when asked (they act, or tell others).</summary>
|
||
|
|
public static readonly string[] Parts = ["posts", "profile", "follows", "blocks", "mutes", "domainblocks", "lists", "bookmarks", "filters", "tags",
|
||
|
|
"notificationpolicy", "pins", "located", "scheduled", "likes"];
|
||
|
|
|
||
|
|
public static string Folder(string backupsRoot, string avatarId) => Path.Combine(backupsRoot, ".personas", avatarId);
|
||
|
|
|
||
|
|
public static async Task<PersonaArchive> Of(string avatarId, CancellationToken token) =>
|
||
|
|
await DB.Default.Find<PersonaArchive>().MatchID(avatarId).ExecuteFirstAsync(token) ?? new PersonaArchive { ID = avatarId };
|
||
|
|
|
||
|
|
public static ViewPersonaArchive View(PersonaArchive archive) => new()
|
||
|
|
{
|
||
|
|
ExportState = archive.ExportState,
|
||
|
|
ExportAskedAt = archive.ExportAskedAt,
|
||
|
|
ExportReadyAt = archive.ExportReadyAt,
|
||
|
|
ExportExpiresAt = archive.ExportReadyAt + Kept,
|
||
|
|
ExportBytes = archive.ExportBytes,
|
||
|
|
ExportError = archive.ExportError,
|
||
|
|
ImportState = archive.ImportState,
|
||
|
|
ImportAskedAt = archive.ImportAskedAt,
|
||
|
|
ImportEndedAt = archive.ImportEndedAt,
|
||
|
|
ImportParts = archive.ImportParts,
|
||
|
|
ImportDone = archive.ImportDone,
|
||
|
|
ImportTotal = archive.ImportTotal,
|
||
|
|
ImportCounts = archive.ImportCounts,
|
||
|
|
ImportError = archive.ImportError,
|
||
|
|
ImportFrom = archive.ImportFrom,
|
||
|
|
ImportSameActor = archive.ImportSameActor
|
||
|
|
};
|
||
|
|
|
||
|
|
/// <summary>Exports older than a week go, as do imports' files left behind.</summary>
|
||
|
|
public static async Task Forget(string backupsRoot, CancellationToken token)
|
||
|
|
{
|
||
|
|
var folder = Path.Combine(backupsRoot, ".personas");
|
||
|
|
if (!Directory.Exists(folder))
|
||
|
|
return;
|
||
|
|
var before = DateTime.UtcNow - Kept;
|
||
|
|
foreach (var file in Directory.EnumerateFiles(folder, "*", SearchOption.AllDirectories).Where(f => File.GetLastWriteTimeUtc(f) < before))
|
||
|
|
File.Delete(file);
|
||
|
|
await DB.Default.Update<PersonaArchive>().Match(a => a.ExportState == "ready" && a.ExportReadyAt < before)
|
||
|
|
.Modify(a => a.ExportState, "none").Modify(a => a.ExportFile, null).ExecuteAsync(token);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|