atagen

macltok

public
Star0
Watch0

Commit d8e76b56867c

tarball

atagen <boss@atagen.co>2026-10-25 09:30 UTC

4 files changed+88-60
packs: keep unknown usage on clone and reject empty usage

Cloning dropped msc2545 usage values macltok does not know, so a copy stopped meaning what the original meant. Some(vec![]) read back as inheriting, which None and Patch::Clear already say, so it is now rejected as Invalid.
Modified file: src/engine/packs.rs+74 additions−24 deletions
@@ -18,7 +18,9 @@ use super::{
1818 };
1919 use crate::{
2020 CommandOutput, Error, ErrorKind, ImagePackSource, PackCommand, PackImageInfo, PackUsage, Patch,
21- normalise::{ENABLED_ROOMS, ROOM_PACKS, USER_PACK, resolve_pack_id, stored_pack},
21+ normalise::{
22+ ENABLED_ROOMS, ROOM_PACKS, USER_PACK, effective_usage, resolve_pack_id, stored_pack,
23+ },
2224 transport::Responder,
2325 };
2426
@@ -59,6 +61,14 @@ pub(super) async fn edit_pack(
5961 pack: &ImagePackSource,
6062 edit: PackEdit,
6163 ) -> Result<CommandOutput, Error> {
64+ match &edit {
65+ PackEdit::Meta {
66+ usage: Patch::Set(usage),
67+ ..
68+ } => validate_usage(Some(usage))?,
69+ PackEdit::Put { usage, .. } => validate_usage(usage.as_ref())?,
70+ _ => {}
71+ }
6272 let current = fetch_pack(client, pack).await?;
6373 let mut content = current.clone().unwrap_or_default();
6474 let mut output = CommandOutput::Done;
@@ -205,6 +215,12 @@ pub(super) async fn run_detached(
205215 max_bytes: usize,
206216 command: PackCommand,
207217 ) -> Result<CommandOutput, Error> {
218+ if let PackCommand::CloneImage { usage, .. }
219+ | PackCommand::CloneSticker { usage, .. }
220+ | PackCommand::ClonePack { usage, .. } = &command
221+ {
222+ validate_usage(usage.as_ref())?;
223+ }
208224 match command {
209225 PackCommand::SendSticker {
210226 room_id,
@@ -506,11 +522,25 @@ async fn write_pack(
506522 written.map_err(|source| Error::from_sdk("writing the pack failed", source))
507523 }
508524
525+/// values this crate doesn't know ride along untouched, they may mean something to someone
509526 fn with_pack_usage(mut image: JsonObject, meta: &JsonObject) -> JsonObject {
510- let usage = known_usage(image.get("usage"))
511- .or_else(|| known_usage(meta.get("usage")))
512- .unwrap_or_else(|| vec![PackUsage::Emoticon, PackUsage::Sticker]);
513- image.insert("usage".to_owned(), usage_json(usage));
527+ if known_usage(image.get("usage")).is_some() {
528+ return image;
529+ }
530+ let mut usage = match meta.get("usage") {
531+ Some(JsonValue::Array(values)) if known_usage(meta.get("usage")).is_some() => {
532+ values.clone()
533+ }
534+ _ => usage_values(effective_usage(None, None)),
535+ };
536+ if let Some(JsonValue::Array(own)) = image.get("usage") {
537+ for value in own {
538+ if !usage.contains(value) {
539+ usage.push(value.clone());
540+ }
541+ }
542+ }
543+ image.insert("usage".to_owned(), usage.into());
514544 image
515545 }
516546
@@ -531,6 +561,17 @@ fn known_usage(value: Option<&JsonValue>) -> Option<Vec<PackUsage>> {
531561 (!usage.is_empty()).then_some(usage)
532562 }
533563
564+/// `Some` of nothing would read back as inheriting, which `None` and `Patch::Clear` already say
565+fn validate_usage(usage: Option<&Vec<PackUsage>>) -> Result<(), Error> {
566+ if usage.is_some_and(Vec::is_empty) {
567+ return Err(Error::new(
568+ ErrorKind::Invalid,
569+ "usage needs at least one value, leave it unset to inherit",
570+ ));
571+ }
572+ Ok(())
573+}
574+
534575 fn apply_usage(image: &mut JsonObject, usage: Option<Vec<PackUsage>>) {
535576 if let Some(usage) = usage {
536577 image.insert("usage".to_owned(), usage_json(usage));
@@ -585,14 +626,17 @@ fn patch<T>(
585626 }
586627
587628 fn usage_json(usage: Vec<PackUsage>) -> JsonValue {
629+ usage_values(usage).into()
630+}
631+
632+fn usage_values(usage: Vec<PackUsage>) -> Vec<JsonValue> {
588633 usage
589634 .into_iter()
590635 .map(|usage| match usage {
591- PackUsage::Emoticon => "emoticon",
592- PackUsage::Sticker => "sticker",
636+ PackUsage::Emoticon => "emoticon".into(),
637+ PackUsage::Sticker => "sticker".into(),
593638 })
594- .collect::<Vec<_>>()
595- .into()
639+ .collect()
596640 }
597641
598642 fn info_json(info: PackImageInfo) -> JsonValue {
@@ -624,23 +668,40 @@ fn object(value: JsonValue) -> JsonObject {
624668 }
625669 }
626670
671+fn parse(json: &str) -> Result<JsonObject, Error> {
672+ serde_json::from_str(json)
673+ .map_err(|source| Error::with_source(ErrorKind::Server, "the pack is malformed", source))
674+}
675+
676+fn raw<T>(content: &JsonObject) -> Raw<T> {
677+ Raw::new(content)
678+ .expect("a json object always serialises")
679+ .cast_unchecked()
680+}
681+
627682 #[cfg(test)]
628683 mod tests {
629684 use super::*;
630685
631686 #[test]
632- fn pack_usage_inherits_known_values_then_defaults() {
687+ fn pack_usage_inherits_known_values_then_defaults_and_keeps_unknown_ones() {
633688 let pack = object(json!({ "usage": ["sticker", "unknown"] }));
634689 let inherited = with_pack_usage(object(json!({ "usage": [] })), &pack);
635- assert_eq!(inherited.get("usage"), Some(&json!(["sticker"])));
690+ assert_eq!(inherited.get("usage"), Some(&json!(["sticker", "unknown"])));
636691
637692 let pack = object(json!({ "usage": ["emoticon"] }));
638693 let inherited = with_pack_usage(object(json!({ "usage": ["unknown"] })), &pack);
639- assert_eq!(inherited.get("usage"), Some(&json!(["emoticon"])));
694+ assert_eq!(
695+ inherited.get("usage"),
696+ Some(&json!(["emoticon", "unknown"]))
697+ );
640698
641699 let pack = object(json!({ "usage": ["sticker"] }));
642700 let inherited = with_pack_usage(object(json!({ "usage": ["emoticon", "unknown"] })), &pack);
643- assert_eq!(inherited.get("usage"), Some(&json!(["emoticon"])));
701+ assert_eq!(
702+ inherited.get("usage"),
703+ Some(&json!(["emoticon", "unknown"]))
704+ );
644705
645706 let pack = object(json!({ "usage": [] }));
646707 let inherited = with_pack_usage(object(json!({})), &pack);
@@ -671,14 +732,3 @@ mod tests {
671732 );
672733 }
673734 }
674-
675-fn parse(json: &str) -> Result<JsonObject, Error> {
676- serde_json::from_str(json)
677- .map_err(|source| Error::with_source(ErrorKind::Server, "the pack is malformed", source))
678-}
679-
680-fn raw<T>(content: &JsonObject) -> Raw<T> {
681- Raw::new(content)
682- .expect("a json object always serialises")
683- .cast_unchecked()
684-}
Modified file: src/normalise/image_pack.rs+11 additions−3 deletions
@@ -184,9 +184,7 @@ fn pack(
184184 mimetype: info.and_then(|info| info.mimetype.clone()),
185185 width: info.and_then(|info| info.width).map(u64::from),
186186 height: info.and_then(|info| info.height).map(u64::from),
187- usage: usage(&image.usage)
188- .or_else(|| pack_usage.clone())
189- .unwrap_or_else(|| vec![PackUsage::Emoticon, PackUsage::Sticker]),
187+ usage: effective_usage(usage(&image.usage), pack_usage.clone()),
190188 })
191189 })
192190 .collect();
@@ -210,6 +208,16 @@ fn pack(
210208 })
211209 }
212210
211+/// an image's own known usage, else its pack's, else both. unknown values never count
212+pub(crate) fn effective_usage(
213+ image: Option<Vec<PackUsage>>,
214+ pack: Option<Vec<PackUsage>>,
215+) -> Vec<PackUsage> {
216+ image
217+ .or(pack)
218+ .unwrap_or_else(|| vec![PackUsage::Emoticon, PackUsage::Sticker])
219+}
220+
213221 fn usage(usage: &BTreeSet<SourceUsage>) -> Option<Vec<PackUsage>> {
214222 let usage: Vec<PackUsage> = usage
215223 .iter()
Modified file: src/normalise/mod.rs+2 additions−1 deletions
@@ -9,7 +9,8 @@ mod timeline;
99
1010 pub(crate) use context::{apply_profiles, normalise_context, resolve_replies};
1111 pub(crate) use image_pack::{
12- ENABLED_ROOMS, ROOM_PACKS, USER_PACK, can_edit_packs, image_packs, resolve_pack_id, stored_pack,
12+ ENABLED_ROOMS, ROOM_PACKS, USER_PACK, can_edit_packs, effective_usage, image_packs,
13+ resolve_pack_id, stored_pack,
1314 };
1415 pub(crate) use link_preview::{ROOM_PREVIEW_URLS, server_preview, url_previews_allowed};
1516 pub(crate) use media::{MediaRegistration, MediaScope, MediaSourceInfo};
Modified file: src/transport.rs+1 additions−32 deletions
@@ -1038,22 +1038,12 @@ impl ClientHandle {
10381038 }
10391039 }
10401040
1041- /// hands back the shortcode it ended up under
1041+ /// hands back the shortcode it ended up under. `usage` overrides what the image had
10421042 pub async fn clone_pack_image(
10431043 &self,
10441044 from_pack: String,
10451045 shortcode: String,
10461046 to: ImagePackSource,
1047- ) -> Result<String, Error> {
1048- self.clone_pack_image_with_usage(from_pack, shortcode, to, None)
1049- .await
1050- }
1051-
1052- pub async fn clone_pack_image_with_usage(
1053- &self,
1054- from_pack: String,
1055- shortcode: String,
1056- to: ImagePackSource,
10571047 usage: Option<Vec<PackUsage>>,
10581048 ) -> Result<String, Error> {
10591049 let (mut shortcodes, _) = self
@@ -1076,17 +1066,6 @@ impl ClientHandle {
10761066 event_id: OwnedEventId,
10771067 to: ImagePackSource,
10781068 shortcode: String,
1079- ) -> Result<String, Error> {
1080- self.clone_sticker_with_usage(room_id, event_id, to, shortcode, None)
1081- .await
1082- }
1083-
1084- pub async fn clone_sticker_with_usage(
1085- &self,
1086- room_id: OwnedRoomId,
1087- event_id: OwnedEventId,
1088- to: ImagePackSource,
1089- shortcode: String,
10901069 usage: Option<Vec<PackUsage>>,
10911070 ) -> Result<String, Error> {
10921071 let (mut shortcodes, _) = self
@@ -1109,16 +1088,6 @@ impl ClientHandle {
11091088 from_pack: String,
11101089 to: ImagePackSource,
11111090 display_name: Option<String>,
1112- ) -> Result<Vec<String>, Error> {
1113- self.clone_pack_with_usage(from_pack, to, display_name, None)
1114- .await
1115- }
1116-
1117- pub async fn clone_pack_with_usage(
1118- &self,
1119- from_pack: String,
1120- to: ImagePackSource,
1121- display_name: Option<String>,
11221091 usage: Option<Vec<PackUsage>>,
11231092 ) -> Result<Vec<String>, Error> {
11241093 let (_, skipped) = self