@inherits LocalizableComponentBase @* The persona's follows, lists, blocks, mutes, blocked servers and bookmarks, exported and imported as the CSV files Mastodon writes, which most fediverse servers read too: a persona moves here from another server, or away, with what it gathered there. An import acts one row at a time, through PrivaPub, and can be stopped. *@

@Localizer["Export"]

@Localizer["Files in Mastodon's format, which most fediverse servers import."]

@foreach (var data in Kinds) { @if (exporting == data) { } else { } @Name(data) }

@Localizer["Import"]

@if (importing is null) { @Localizer["Import"] } else { @Localizer["Stop"] }
@if (report is not null) {
@Localizer["{0} of {1} done, {2} could not be.", report.Done, report.Total, report.Failed.Count]
}
@code { [Inject] PortabilityService PortabilityService { get; set; } [Inject] IBlazorDownloadFileService DownloadFile { get; set; } [Inject] ILoggingService Logger { get; set; } // an export file of 5000 rows stays well under this const long MaximumFileSize = 4 * 1024 * 1024; static readonly PortableData[] Kinds = Enum.GetValues(); PortableData? exporting; PortableData? importing; PortableData importKind = PortableData.Follows; string file; ImportReport report; CancellationTokenSource stopping; protected override void OnInitialized() => IsLoading = false; string Name(PortableData data) => data switch { PortableData.Follows => Localizer["Follows"], PortableData.Lists => Localizer["Lists"], PortableData.Blocks => Localizer["Blocked accounts"], PortableData.Mutes => Localizer["Muted accounts"], PortableData.ServerBlocks => Localizer["Blocked servers"], _ => Localizer["Bookmarks"] }; async Task Export(PortableData data) { exporting = data; var result = await PortabilityService.Export(data, CascadingState.PersonaId); exporting = null; if (result.IsInvalid) { Toast.AddResponseError(result); return; } await DownloadFile.DownloadFileAsync(PortabilityService.FileName(data), (byte[])result.Data, "text/csv"); } async Task Pick(InputFileChangeEventArgs eventArgs) { file = null; report = null; if (eventArgs.File.Size > MaximumFileSize) { Toast.AddWarning(Localizer["Supplied file \"{0}\" with size {1} exceeds the maximum of {2}.", eventArgs.File.Name, eventArgs.File.Size.GetFileSize(Localizer), MaximumFileSize.GetFileSize(Localizer)]); return; } try { using var reader = new StreamReader(eventArgs.File.OpenReadStream(MaximumFileSize)); file = await reader.ReadToEndAsync(); } catch (Exception ex) { await Logger.ProcessError(ex, $"{nameof(DataTransfer)}.{nameof(Pick)}:{eventArgs.File.Name}"); Toast.AddWarning(Localizer["The file \"{0}\" could not be read.", eventArgs.File.Name]); } } async Task Import() { importing = importKind; stopping = new(); report = new(); var progress = new Progress(step => { report = step; StateHasChanged(); }); report = await PortabilityService.Import(importKind, file, progress, stopping.Token); importing = null; stopping.Dispose(); stopping = null; Toast.AddResponseSuccess(Localizer["{0} of {1} done, {2} could not be.", report.Done, report.Total, report.Failed.Count]); } void Stop() => stopping?.Cancel(); public override async ValueTask DisposeAsync() { stopping?.Cancel(); await base.DisposeAsync(); } }