a sync that makes an avatar's sizes publishes them
Signed-off-by: dawn <90008@klbr.net>
1003 unmodified linesExpand all1004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082397 unmodified linesExpand all1003 unmodified linesExpand all );}
#[test]fn sizes_made_after_a_generation_are_served_by_the_next_sync() { let env = setup_test_env(); assert!(knot_static(&env.config, ["generate"]).status.success()); let first = get_only_generation_dir(&env.output); let first_id = first.file_name().unwrap().to_str().unwrap().to_string(); assert!( knot_static(&env.config, ["activate", &first_id]) .status .success() ); let cid = "bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku"; let avatars = env.output.join("avatars"); std::fs::create_dir_all(&avatars).unwrap(); std::fs::write(avatars.join(format!("{cid}.png")), b"\x89PNG an avatar").unwrap(); std::fs::write( env.output.join("owner-handles.json"), serde_json::to_vec(&serde_json::json!({ "version": 1, "entries": { OWNER: { "handle": "ptr.pet", "verified_at_unix": 1_700_000_000u64, "avatar_cid": cid, "avatar_ext": "png", } } })) .unwrap(), ) .unwrap(); let served_at = |size: u32| { let maps = env .output .join("generations") .join(active_generation(&env.output)) .join("nginx/maps/63_social.conf"); let maps = std::fs::read_to_string(maps).unwrap(); let row = format!("/avatar/{OWNER}|{size}"); maps.lines() .find_map(|line| { let mut fields = line.split_whitespace(); (fields.next()? == format!("\"{row}\"") || line.trim_start().starts_with(&row)) .then(|| { line.split_whitespace() .last() .unwrap() .trim_end_matches(';') .trim_matches('"') .to_string() }) }) .unwrap_or_else(|| panic!("no {row} in\n{maps}")) }; let _ = knot_static(&env.config, ["post-activate"]); assert_eq!(served_at(42), format!("{cid}.png"), "no sizes yet");
for size in [42, 96, 192] { std::fs::write(avatars.join(format!("{cid}-{size}.webp")), b"RIFF").unwrap(); } let before = active_generation(&env.output); let _ = knot_static(&env.config, ["post-activate"]); assert_ne!( active_generation(&env.output), before, "the sizes are published" ); assert_eq!(served_at(42), format!("{cid}-42.webp")); assert_eq!(served_at(96), format!("{cid}-96.webp"));}
#[test]fn a_write_waiting_to_activate_keeps_its_candidate_through_another_writes_gc() { let env = setup_test_env(); let mut config = std::fs::read_to_string(&env.config).unwrap(); config.push_str("keep_generations = 1\n");397 unmodified linesExpand all293 unmodified linesExpand all294295296297298299298299300301302303304305441 unmodified linesExpand all293 unmodified linesExpand all }, ) .collect(); trace.record("plan: unchanged pass", t_outcomes); let handles = crate::index_page::served_digest(&crate::index_page::cached_handles(&output_root)); let handles = crate::index_page::served_digest( &crate::index_page::cached_handles(&output_root), &output_root.join(crate::xrpc::social::AVATARS_DIR), ); let answers_handles = active .as_ref() .is_some_and(|active| active.manifest.handles_digest.as_deref() == Some(handles.as_str())); if answers_handles441 unmodified linesExpand all20 unmodified lines2122232425252627282953 unmodified lines8384858687888987888990919293949596979893949910010110210310410510610710810911011111283 unmodified lines196197198199186200201202203204449 unmodified linesExpand all20 unmodified linesuse crate::config::ValidatedConfig;use crate::datastore::{Datastore, schema};use crate::manifest::{GenerationManifest, RepoEntry};use crate::plan::repo_key;use crate::xrpc::social::{ACCOUNTS_FILE, AVATAR_ORIGINAL_MAX, AVATARS_DIR};use crate::xrpc::social::{ACCOUNTS_FILE, AVATAR_ORIGINAL_MAX, AVATARS_DIR, Avatar};
use super::avatar_sizes::has_sized;
const REFRESH_TIMEOUT: Duration = Duration::from_secs(3);53 unmodified lines self.avatar_file().into_iter().chain(sized) }}
/// what a generation answers from the cache. verification times and failures don't countpub(crate) fn served_digest(stored: &StoredHandles) -> String { let served: BTreeMap<&str, (&str, Option<String>, &Option<Map<String, Value>>)> = stored/// what a generation answers from the cache, including which avatar files it finds on disk./// verification times and failures don't countpub(crate) fn served_digest(stored: &StoredHandles, avatars: &Path) -> String { type Answered<'a> = ( &'a str, Option<(String, bool)>, &'a Option<Map<String, Value>>, ); let served: BTreeMap<&str, Answered> = stored .entries .iter() .map(|(did, entry)| { let answered = (entry.handle.as_str(), entry.avatar_file(), &entry.profile); (did.as_str(), answered) let avatar = entry .avatar_cid .as_deref() .zip(entry.avatar_ext.as_deref()) .and_then(|(cid, ext)| Avatar::of(avatars, cid, ext)) .map(|avatar| (avatar.file(), avatar.sized)); ( did.as_str(), (entry.handle.as_str(), avatar, &entry.profile), ) }) .collect(); crate::cas::Digest::of_bytes(&serde_json::to_vec(&served).expect("handles serialize")).hex()}83 unmodified lines refresh_handles_with(&atproto, &pds, &avatars, &named, &mut stored, now).await; if stored != before && record.put(&stored).is_ok() { sweep_avatars(&avatars, &stored); } let digest = served_digest(&stored); let digest = served_digest(&stored, &avatars); if manifest.handles_digest.as_deref() == Some(digest.as_str()) { return; } let attempt = FailedPublish {449 unmodified linesExpand all75 unmodified lines767778798080818283843 unmodified lines8889909192929394959638 unmodified lines135136137138139140139140141142143144145546 unmodified linesExpand all75 unmodified lines format!("{source}-{size}.webp")}
impl Avatar { fn file(&self) -> String { pub(crate) fn file(&self) -> String { format!("{}.{}", self.cid, self.ext) }
/// what the knot keeps of it3 unmodified lines false => sized_avatar(&self.source, AVATAR_SIZES[AVATAR_SIZES.len() - 1]), } }
fn of(avatars: &Path, source: &str, ext: &str) -> Option<Self> { pub(crate) fn of(avatars: &Path, source: &str, ext: &str) -> Option<Self> { let sized = AVATAR_SIZES .iter() .all(|&size| avatars.join(sized_avatar(source, size)).is_file()); let original = avatars.join(format!("{source}.{ext}"));38 unmodified lines
impl Social { pub fn of(config: &ValidatedConfig) -> Self { let stored = crate::index_page::cached_handles(&config.output_root); let handles_digest = crate::index_page::served_digest(&stored); let entries = stored.entries; let avatars_dir = config.output_root.join(AVATARS_DIR); let handles_digest = crate::index_page::served_digest(&stored, &avatars_dir); let entries = stored.entries; let avatars = entries .iter() .filter_map(|(did, entry)| { let avatar = Avatar::of(546 unmodified linesExpand all