|
| 1 | +use crate::error::{AsyncTiffError, AsyncTiffResult}; |
| 2 | +use crate::tiff::tags::Tag; |
| 3 | +use crate::tiff::Value; |
| 4 | +use std::collections::{HashMap, HashSet}; |
| 5 | +use std::fmt::Debug; |
| 6 | +use std::sync::Arc; |
| 7 | + |
| 8 | +/// Trait to implement for custom tags, such as Geo, EXIF, OME, etc |
| 9 | +/// your type should also implement `Clone` |
| 10 | +// Send + Sync are required for Python, where `dyn ExtraTags` needs `Send` and `Sync` |
| 11 | +pub trait ExtraTags: ExtraTagsCloneArc + std::any::Any + Debug + Send + Sync { |
| 12 | + /// a list of tags this entry processes |
| 13 | + /// e.g. for Geo this would be [34735, 34736, 34737] |
| 14 | + fn tags(&self) -> &'static [Tag]; |
| 15 | + /// process a single tag |
| 16 | + fn process_tag(&mut self, tag: u16, value: Value) -> AsyncTiffResult<()>; |
| 17 | +} |
| 18 | + |
| 19 | +// we need to do a little dance to do an object-safe deep clone |
| 20 | +// https://stackoverflow.com/a/30353928/14681457 |
| 21 | +pub trait ExtraTagsCloneArc { |
| 22 | + fn clone_arc(&self) -> Arc<dyn ExtraTags>; |
| 23 | +} |
| 24 | + |
| 25 | +impl<T> ExtraTagsCloneArc for T |
| 26 | +where |
| 27 | + T: 'static + ExtraTags + Clone, |
| 28 | +{ |
| 29 | + fn clone_arc(&self) -> Arc<dyn ExtraTags> { |
| 30 | + Arc::new(self.clone()) |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +/// The registry in which extra tags (parsers) are registered |
| 35 | +/// This is passed to TODO |
| 36 | +#[derive(Debug, Clone)] |
| 37 | +pub struct ExtraTagsRegistry(HashMap<Tag, Arc<dyn ExtraTags>>); |
| 38 | + |
| 39 | +impl ExtraTagsRegistry { |
| 40 | + /// Create a new, empty `ExtraTagsRegistry` |
| 41 | + pub fn new() -> Self { |
| 42 | + Self(HashMap::new()) |
| 43 | + } |
| 44 | + /// Register an ExtraTags so their tags are parsed and stored in the ifd's `extra_tags`` |
| 45 | + pub fn register(&mut self, tags: Arc<dyn ExtraTags>) -> AsyncTiffResult<()> { |
| 46 | + // check for duplicates |
| 47 | + for tag in tags.tags() { |
| 48 | + if self.0.contains_key(tag) { |
| 49 | + return Err(AsyncTiffError::General(format!( |
| 50 | + "Tag {tag:?} already registered in {self:?}!" |
| 51 | + ))); |
| 52 | + } |
| 53 | + } |
| 54 | + // add to self |
| 55 | + for tag in tags.tags() { |
| 56 | + self.0.insert(*tag, tags.clone()); |
| 57 | + } |
| 58 | + Ok(()) |
| 59 | + } |
| 60 | + |
| 61 | + /// deep clone so we have different registries per IFD |
| 62 | + pub(crate) fn deep_clone(&self) -> Self { |
| 63 | + let mut new_registry = ExtraTagsRegistry::new(); |
| 64 | + |
| 65 | + // we need to do some magic, because we can have multiple tags pointing to the same arc |
| 66 | + let mut seen = HashSet::new(); |
| 67 | + for extra_tags in self.0.values() { |
| 68 | + // only add if this is the first encountered reference to this arc |
| 69 | + // (using thin pointer equality: https://stackoverflow.com/a/67114787/14681457 ; https://github.com/rust-lang/rust/issues/46139#issuecomment-346971153) |
| 70 | + if seen.insert(Arc::as_ptr(extra_tags) as *const ()) { |
| 71 | + if let Err(e) = new_registry.register(extra_tags.clone_arc()) { |
| 72 | + panic!("{e}"); |
| 73 | + } |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + new_registry |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +impl Default for ExtraTagsRegistry { |
| 82 | + fn default() -> Self { |
| 83 | + Self::new() // add e.g. geo tags later |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +#[cfg(test)] |
| 88 | +mod tests { |
| 89 | + use super::*; |
| 90 | + use std::sync::LazyLock; |
| 91 | + |
| 92 | + #[derive(Debug, Clone, PartialEq)] |
| 93 | + struct TestyTag; |
| 94 | + |
| 95 | + static TESTY_TAGS: LazyLock<Vec<Tag>> = LazyLock::new(|| { |
| 96 | + vec![ |
| 97 | + Tag::from_u16_exhaustive(u16::MAX), |
| 98 | + Tag::from_u16_exhaustive(u16::MAX - 1), |
| 99 | + ] |
| 100 | + }); |
| 101 | + |
| 102 | + impl ExtraTags for TestyTag { |
| 103 | + fn tags(&self) -> &'static [Tag] { |
| 104 | + &TESTY_TAGS |
| 105 | + } |
| 106 | + |
| 107 | + fn process_tag( |
| 108 | + &mut self, |
| 109 | + tag: u16, |
| 110 | + value: crate::tiff::Value, |
| 111 | + ) -> crate::error::AsyncTiffResult<()> { |
| 112 | + println!("received {tag:?}: {value:?}"); |
| 113 | + Ok(()) |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + #[test] |
| 118 | + fn test_register() { |
| 119 | + let mut registry = ExtraTagsRegistry::new(); |
| 120 | + assert!(registry.0.is_empty()); |
| 121 | + let a1: Arc<dyn ExtraTags> = Arc::new(TestyTag); |
| 122 | + registry.register(a1.clone()).unwrap(); |
| 123 | + assert_eq!(registry.0.len(), TestyTag.tags().len()); |
| 124 | + for tag in a1.tags() { |
| 125 | + // very strict equality check |
| 126 | + assert!(Arc::ptr_eq(®istry.0[tag], &a1)); |
| 127 | + } |
| 128 | + } |
| 129 | + |
| 130 | + #[test] |
| 131 | + fn test_overlap_err() { |
| 132 | + let mut registry = ExtraTagsRegistry::new(); |
| 133 | + assert!(registry.0.is_empty()); |
| 134 | + registry.register(Arc::new(TestyTag)).unwrap(); |
| 135 | + assert!(matches!( |
| 136 | + registry.register(Arc::new(TestyTag)).unwrap_err(), |
| 137 | + AsyncTiffError::General(_) |
| 138 | + )); |
| 139 | + } |
| 140 | + |
| 141 | + #[test] |
| 142 | + fn test_deep_clone() { |
| 143 | + let mut registry = ExtraTagsRegistry::new(); |
| 144 | + let a1: Arc<dyn ExtraTags> = Arc::new(TestyTag); |
| 145 | + registry.register(a1.clone()).unwrap(); |
| 146 | + let r2 = registry.deep_clone(); |
| 147 | + for tags in a1.tags().windows(2) { |
| 148 | + // all should refer to the same Arc |
| 149 | + assert!(Arc::ptr_eq(&r2.0[&tags[0]], &r2.0[&tags[1]])); |
| 150 | + // which is different from the previous |
| 151 | + assert!(!Arc::ptr_eq(&a1, &r2.0[&tags[0]])); |
| 152 | + assert!(!Arc::ptr_eq(&a1, &r2.0[&tags[1]])); |
| 153 | + } |
| 154 | + } |
| 155 | +} |
0 commit comments