@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"]
@Localizer["What the file holds"]
@foreach (var data in Kinds)
{
@Name(data)
}
@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();
}
}