An import reads the archive's followers once, finds threads in linear time, and imports a located post once

A post's visibility no longer parses actor.json again for every outbox item; a reply's conversation is its thread's
first post's, found from what the first pass read instead of searching every planned post (quadratic on a large
archive); and a located post always records where it came from, so importing a persona's own archive twice made it
twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-07 12:51:58 +02:00
1 parent 44a5895b0c
commit 163f7e4e2b
2 files changed
+36 -17

No files matched your search

+10 -6
View File
@@ -157,26 +157,30 @@ namespace PrivaPub.Tests.Http
})).Ok().Body.Text("id");
var first = await alice.Status("the first words #moving", ("media_ids[]", picture));
await alice.Status("and a reply to them", ("in_reply_to_id", first.Text("id")));
await _host.Located(alice, "here and nowhere else");
var zip = await Exported(alice.Persona);
var carol = await _host.Mastodon("carol");
await follower.Client.Post($"/api/v1/accounts/{carol.Persona.Id}/follow");
var actor = $"/peasants/{carol.Persona.UserName}";
var jobs = await DB.Default.CountAsync<Job>(j => j.Kind == JobKind.Deliver && j.Payload.Contains(actor), Token);
var (_, state) = await Imported(carol.Persona, zip, "posts", "pins", "tags");
var (_, state) = await Imported(carol.Persona, zip, "posts", "pins", "tags", "located");
Assert.Equal("done", state["importState"]!.GetValue<string>());
Assert.False(state["importSameActor"]!.GetValue<bool>());
Assert.Equal(2, Count(state, "posts"));
Assert.Equal(1, Count(state, "media"));
var copies = await DB.Default.Find<PostEntity>().Match(p => p.GroupUserId == carol.Persona.Id).ExecuteAsync(Token);
Assert.Equal(1, Count(state, "located posts"));
var copies = await DB.Default.Find<PostEntity>().Match(p => p.GroupUserId == carol.Persona.Id && p.Visibility != Models.Post.PostVisibility.LocalGeo).ExecuteAsync(Token);
Assert.Equal(2, copies.Count);
var copy = copies.Single(p => p.ImportedFromURI == first.Text("uri"));
Assert.StartsWith($"{PrivaPubHost.Base}/peasants/{carol.Persona.UserName}/scribbles/", copy.ObjectURI);
Assert.Equal(["moving"], copy.Tags);
Assert.Single(copy.Media);
Assert.Equal(carol.Persona.Id, (await DB.Default.Find<MediaAttachment>().MatchID(copy.Media[0].AttachmentId).ExecuteFirstAsync(Token)).OwnerAvatarId);
Assert.Equal(copy.ID, copies.Single(p => p.ID != copy.ID).AnsweringToPostId);
var reply = copies.Single(p => p.ID != copy.ID);
Assert.Equal(copy.ID, reply.AnsweringToPostId);
Assert.Equal(copy.ObjectURI + "/context", reply.ContextURI);
// nobody told: no delivery, no home, no notification; on the profile all the same
Assert.Equal(jobs, await DB.Default.CountAsync<Job>(j => j.Kind == JobKind.Deliver && j.Payload.Contains(actor), Token));
@@ -185,9 +189,9 @@ namespace PrivaPub.Tests.Http
Assert.Contains((await carol.Client.Get($"/api/v1/accounts/{carol.Persona.Id}/statuses")).Ok().Body!.AsArray(), s => s!.Text("id") == copy.ID);
// twice changes nothing
var (_, again) = await Imported(carol.Persona, zip, "posts");
Assert.Equal((0, 2), (Count(again, "posts"), Count(again, "posts already here")));
Assert.Equal(2, await DB.Default.CountAsync<PostEntity>(p => p.GroupUserId == carol.Persona.Id, Token));
var (_, again) = await Imported(carol.Persona, zip, "posts", "located");
Assert.Equal((0, 2, 1), (Count(again, "posts"), Count(again, "posts already here"), Count(again, "located posts already here")));
Assert.Equal(3, await DB.Default.CountAsync<PostEntity>(p => p.GroupUserId == carol.Persona.Id, Token));
}
// Mastodon's own archive: its posts, not its boosts nor its direct messages