Nothing enters the catalog without a usable name: the pending store (decision f amended)
Test and deploy / test (push) Successful in 56s
Test and deploy / edge (push) Successful in 2m2s
Test and deploy / deploy (push) Skipped

An unnamed release (no readable file name) is written to pending_releases
(migration 6300, the HDD) with its keys, counts, article span, upload tool
and probe targets, instead of releases, release_texts, posters and a chunk.
The edge sends it with an empty payload and its tool; the batch store splits
a batch by readable. The pending ladder (probes cheapest first, signature
verdicts, the move into the catalog) comes next. The replay report shows the
pending releases.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Fable 5.1 committed 2026-10-26 13:18:14 +01:00
1 parent 6860e6ccaf
commit 9cf85e09d8
8 files changed
+286 -15

No files matched your search

+34 -2
View File
@@ -53,6 +53,11 @@ pub struct ReleaseChunk {
pub edge: bool,
/// A file misses parts.
pub partial: bool,
/// The upload tool from the first article's message-id (names::upload_tool), for an unnamed release: with the
/// group its signature, what the pending ladder learns verdicts by. None for a named one.
pub tool: Option<&'static str>,
/// The files' names and segments (codec); empty for an unnamed release (decision f: nothing in the catalog
/// without a usable name; the pending store keeps its keys and probe targets until a probe names it).
pub payload: Vec<u8>,
/// The first files' subjects (at most SUBJECTS): edge-bench's --releases dump for the grouping sweeps.
pub subjects: Vec<String>,
@@ -1154,17 +1159,26 @@ impl RangeIndexer {
let mut chunk = chunk_of(key, &list, upload);
chunk.finished = finished;
chunk.edge = edge;
let album_none = album.is_none();
if let Some(name) = album {
chunk.name = name;
chunk.source = "album";
chunk.file_total = None;
} else if let Some(total) = chunk.file_total.filter(|&t| t >= BATCH_COUNTER_MIN_TOTAL && (chunk.files as i64) < t) {
}
if !chunk.readable {
// Unnamed: no chunk (its segments are read again once a probe names it), the tool for its signature.
chunk.tool = Some(names::upload_tool(&list[0][0].message_id));
chunk.payload = encode_chunk(&[]);
}
if chunk.readable {
self.pending_files += chunk.files;
}
if let Some(total) = chunk.file_total.filter(|&t| album_none && t >= BATCH_COUNTER_MIN_TOTAL && (chunk.files as i64) < t) {
// A batch counter: no file total (the release is as complete as its files are).
if self.batch_totals.get(&(chunk.poster.clone(), total)).is_some_and(|(n, _)| *n >= BATCH_COUNTER_RELEASES) {
chunk.file_total = None;
}
}
self.pending_files += chunk.files;
self.batch.releases.push(chunk);
self.batch.candidates.extend(candidates);
}
@@ -1267,6 +1281,7 @@ fn chunk_of(key: &str, files: &[Vec<Seg>], upload: usize) -> ReleaseChunk {
finished: true,
edge: false,
partial: files.iter().any(|f| (f.len() as u32) < f[0].file.total_parts),
tool: None,
payload: encode_chunk(&chunk_files),
subjects: chunk_files.iter().take(SUBJECTS).map(|f| f.subject.to_string()).collect(),
}
@@ -1411,6 +1426,23 @@ mod tests {
assert!(ix.albums.is_empty());
}
#[test]
fn an_unnamed_release_keeps_no_chunk_but_its_tool_and_probe_target() {
let mut priorities = FxHashMap::default();
priorities.insert(GROUP.to_string(), 1);
let mut ix = RangeIndexer::new(GROUP, 1, 10_000, 1, Rules::defaults(priorities), 5);
let lines: Vec<String> = (0..4).map(|i| line(100 + i, &format!(r#"[1/1] - "q3B9OrpKiCxUnzgtUGnp.part01.rar" yEnc ({}/4)"#, i + 1), i)).collect();
ix.add_text(&lines.join("\n"), 10_001);
ix.finish();
let batch = ix.take();
assert_eq!(batch.releases.len(), 1);
let r = &batch.releases[0];
assert!(!r.readable && r.tool == Some("other") && r.files == 1 && r.segments == 4);
assert_eq!(r.payload, encode_chunk(&[]));
assert_eq!(batch.candidates.iter().map(|c| c.kind).collect::<Vec<_>>(), vec!["archive"]);
assert_eq!(ix.pending(), 0);
}
#[test]
fn album_templates_and_names() {
assert_eq!(album_template("1481439079025_014.jpg.data").as_deref(), Some("#_#.jpg.data"));
+3
View File
@@ -27,6 +27,8 @@ struct WireRelease<'a> {
finished: bool,
edge: bool,
partial: bool,
#[serde(skip_serializing_if = "Option::is_none")]
tool: Option<&'a str>,
payload: String,
#[serde(skip_serializing_if = "Option::is_none")]
rule: Option<&'a str>,
@@ -87,6 +89,7 @@ fn release<'a>(r: &'a ReleaseChunk, rule: Option<(&'a str, &'a str)>) -> WireRel
finished: r.finished,
edge: r.edge,
partial: r.partial,
tool: r.tool,
payload: base64::engine::general_purpose::STANDARD.encode(&r.payload),
rule: rule.map(|(rule, _)| rule),
rule_key: rule.map(|(_, key)| key),