Name pass: only holders near the newest; merges drop duplicate candidates
Test and deploy / test (push) Successful in 50s
Test and deploy / edge (push) Successful in 1m53s
Test and deploy / deploy (push) Successful in 57s

The name pass put every holder of a set into its merge: a par2 posted
again years later ("Auto Bild Klassik Februar 02-2014", held by a 2014
and a 2026 release) would have joined the repost to the old upload. Now
the newest holder anchors it and only holders in its group within 12
hours take part. Every round, three sets failed on the probe candidates'
unique key: a member held a candidate of an article the target had.
merge() now drops such a candidate (the target's, or a lower member's,
stays), as it already does for par2 sets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-23 14:59:09 +02:00
1 parent 66d08a0e92
commit fb6b00f230
2 files changed
+38 -6

No files matched your search

+18 -6
View File
@@ -288,19 +288,23 @@ export class Par2Merger {
}
/**
* The releases a set's file names find (mergeByName without merging): its holders, and the releases of the set's
* poster in its group posted within NAME_SPREAD_MS whose files the set all names, besides sidecars, par2 and images.
* Null when there's nothing to merge.
* The releases a set's file names find (mergeByName without merging): its newest holder and the holders near it,
* and the releases of its poster in its group posted within NAME_SPREAD_MS whose files the set all names, besides
* sidecars, par2 and images. Null when there's nothing to merge. Not a holder further off: the same par2 posted
* again years later ("Auto Bild Klassik Februar 02-2014": 2014 and 2026) is a repost (decision d).
*/
async nameMatches(trx: any, setId: string): Promise<Map<string, Member> | null> {
const { rows: holders } = await trx.rawQuery(
const { rows: all } = await trx.rawQuery(
`SELECT r.id, r.release_key, r.group_id, r.files, r.posted_at, r.readable, r.poster FROM par2_sets s JOIN releases r ON r.id = s.release_id
WHERE s.set_id = ? ORDER BY r.id`,
WHERE s.set_id = ? ORDER BY r.posted_at DESC NULLS LAST, r.id`,
[setId]
)
const anchor: Member | undefined = holders[0]
const anchor: Member | undefined = all[0]
// A random poster (one per file) bounds nothing: those uploads are the hash pass's.
if (!anchor?.poster || !anchor.posted_at || randomPoster(anchor.poster)) return null
const holders = all.filter(
(h: Member) => h.group_id === anchor.group_id && h.posted_at && Math.abs(new Date(h.posted_at).getTime() - new Date(anchor.posted_at!).getTime()) <= NAME_SPREAD_MS
)
const { rows: described } = await trx.rawQuery(`SELECT name FROM par2_files WHERE set_id = ?`, [setId])
if (described.length === 0) return null
const names = new Set(described.map((d: { name: string }) => fileKey(d.name)))
@@ -425,6 +429,14 @@ export class Par2Merger {
SELECT id, release_key, ?, ?, files, ? FROM releases WHERE id = ANY(?)`,
[target.id, key, via, others]
)
// A candidate of an article the target, or another member of lower id, has already (the same file in two releases)
// isn't moved: the name pass met them (every round of 3 sets failed on the unique key; LOG.md 2026-10-23 (99)).
await trx.rawQuery(
`DELETE FROM probe_candidates p WHERE p.release_id = ANY(?)
AND (EXISTS (SELECT 1 FROM probe_candidates t WHERE t.release_id = ? AND t.kind = p.kind AND t.message_id = p.message_id)
OR EXISTS (SELECT 1 FROM probe_candidates o WHERE o.release_id = ANY(?) AND o.release_id < p.release_id AND o.kind = p.kind AND o.message_id = p.message_id))`,
[others, target.id, others]
)
for (const table of ['release_chunks', 'probe_candidates', 'release_inspections', 'file_hashes']) {
await trx.rawQuery(`UPDATE ${table} SET release_id = ? WHERE release_id = ANY(?)`, [target.id, others])
}
+20
View File
@@ -65,4 +65,24 @@ test.group('Par2Merger / by file names', (group) => {
// Nothing more to find.
assert.equal(await new Par2Merger().mergeByName(setId), 0)
})
test('not a holder years off (a repost of the par2); a member with an article the target has merges without it', async ({ assert }) => {
await store([...file('Some.Album-WEB-2026.par2', 100_010, 0), ...file('Track One.flac', 100_020, 1)])
// The same par2 posted again a year later, in a range of its own.
await store(file('Some.Album-WEB-2026.par2', 200_010, 365 * 24 * 60))
const releases = await db.from('releases').orderBy('posted_at').select('id', 'name', 'posted_at')
const [index, track, repost] = releases.map((r) => Number(r.id))
const setId = 'b'.repeat(32)
await db.table('par2_files').insert({ set_id: setId, file_id: '1'.repeat(32), hash16k: 'f'.repeat(32), length: 600_000, name: 'Track One.flac' })
await db.table('par2_sets').multiInsert([index, repost].map((id) => ({ set_id: setId, release_id: id, files: 1 })))
// The repost is the newest holder: nothing near it to join.
assert.equal(await new Par2Merger().mergeByName(setId), 0)
await db.from('par2_sets').where('release_id', repost).delete()
// Both releases hold a candidate of the same article.
await db.table('probe_candidates').multiInsert([index, track].map((id) => ({ release_id: id, kind: 'par2', message_id: '<same@x>', file: 'x.par2' })))
assert.equal(await new Par2Merger().mergeByName(setId), 1)
assert.lengthOf(await db.from('probe_candidates').where('message_id', '<same@x>'), 1)
assert.lengthOf(await db.from('releases').whereIn('id', [index, track]), 1)
assert.lengthOf(await db.from('releases').where('id', repost), 1)
})
})