/picture/missing, /picture/put, /picture/get: the master copy of the pictures. Devices ask
which of theirs are missing here, send those, and fetch the ones they lack.
POST /picture/missing, /picture/put and /picture/get. Needs the picture shelf.
13pub struct Pictures;
15impl sealed::Sealed for Pictures {} 16 17impl<C: HasPictures> Route<C> for Pictures { 18 const PATHS: &'static [&'static str] = &["/picture/missing", "/picture/put", "/picture/get"]; 19 async fn answer(&self, adapter: &C, route: &str, body: &str) -> Response { 20 pictures(adapter, route, body).await 21 } 22} 23 24fn reply(status: u16, json: Value) -> Response { 25 Response::json(status, json.to_string()) 26} 27 28fn failed(route: &str, e: &dyn std::fmt::Display) -> Response { 29 error!("{route}: the shelf failed: {e}"); 30 reply(500, json!({ "error": e.to_string() })) 31} 32 33async fn pictures<C: HasPictures>(adapter: &C, route: &str, body: &str) -> Response { 34 let Ok(v) = serde_json::from_str::<Value>(body) else { 35 warn!("{route}: the body ({} bytes) is not JSON", body.len()); 36 return reply(400, json!({ "error": "bad json" })); 37 }; 38 match route { 39 "/picture/missing" => pictures_missing(adapter, &v).await, 40 "/picture/put" => picture_put(adapter, &v).await, 41 _ => picture_get(adapter, &v).await, 42 } 43}
Which of the named pictures are not kept here. A name that is not a picture name is not reported as missing: it could never be sent.
47async fn pictures_missing<C: HasPictures>(adapter: &C, v: &Value) -> Response { 48 let ids = v["ids"].as_array().cloned().unwrap_or_default(); 49 let mut missing: Vec<String> = Vec::new(); 50 for name in ids.iter().filter_map(Value::as_str) { 51 let Ok(id) = SafePictureId::parse(name) else { 52 trace!("pictures: a name of {} chars is not a picture name; not asked about", name.len()); 53 continue; 54 }; 55 match adapter.pictures().has(&id).await { 56 Ok(true) => {} 57 Ok(false) => missing.push(name.to_owned()), 58 Err(e) => return failed("/picture/missing", &e), 59 } 60 } 61 debug!("pictures: {} asked about, {} missing", ids.len(), missing.len()); 62 reply(200, json!({ "missing": missing })) 63}
65async fn picture_put<C: HasPictures>(adapter: &C, v: &Value) -> Response { 66 let (Some(name), Some(data)) = (v["id"].as_str(), v["data_base64"].as_str()) else { 67 warn!("picture put without an id and data"); 68 return reply(400, json!({ "error": "id and data_base64" })); 69 }; 70 let Ok(id) = SafePictureId::parse(name) else { 71 warn!("picture put refused: unsafe name of {} chars", name.len()); 72 return reply(400, json!({ "error": "not a picture name" })); 73 }; 74 let Ok(bytes) = base64::engine::general_purpose::STANDARD.decode(data) else { 75 warn!("picture put {name} refused: bad base64 ({} chars)", data.len()); 76 return reply(400, json!({ "error": "bad base64" })); 77 }; 78 match adapter.pictures().put(&id, &bytes).await { 79 Ok(Put::Stored) => { 80 info!("picture {name} stored ({} bytes)", bytes.len()); 81 reply(200, json!({ "ok": true })) 82 } 83 Ok(Put::AlreadyKept) => { 84 trace!("picture {name} already kept"); 85 reply(200, json!({ "ok": true })) 86 } 87 Err(e) => failed("/picture/put", &e), 88 } 89} 90 91async fn picture_get<C: HasPictures>(adapter: &C, v: &Value) -> Response { 92 let Some(id) = v["id"].as_str().and_then(|n| SafePictureId::parse(n).ok()) else { 93 warn!("picture get refused: no safe name"); 94 return reply(400, json!({ "error": "not a picture name" })); 95 }; 96 match adapter.pictures().get(&id).await { 97 Ok(Some(bytes)) => { 98 debug!("picture {} served ({} bytes)", id.as_str(), bytes.len()); 99 reply(200, json!({ "data_base64": base64::engine::general_purpose::STANDARD.encode(bytes) })) 100 } 101 Ok(None) => { 102 debug!("picture {} not found", id.as_str()); 103 reply(404, json!({ "error": "no such picture" })) 104 } 105 Err(e) => failed("/picture/get", &e), 106 } 107}