1use std::{
10 collections::VecDeque,
11 sync::{
12 Arc, Mutex, OnceLock,
13 atomic::{AtomicU64, Ordering},
14 },
15};
16
17use cranpose_core::{EventStream, rememberEventStream};
18
19use crate::content::{BytesContent, ContentHandle, ContentMetadata, resolve_content};
20
21#[derive(Clone, Debug, PartialEq, Eq)]
23pub enum IncomingSource {
24 Bytes(Vec<u8>),
26 Uri(String),
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct IncomingContent {
38 pub name: Option<String>,
40 pub mime_type: Option<String>,
42 pub source: IncomingSource,
44}
45
46impl IncomingContent {
47 pub fn from_bytes(bytes: Vec<u8>) -> Self {
49 Self {
50 name: None,
51 mime_type: None,
52 source: IncomingSource::Bytes(bytes),
53 }
54 }
55
56 pub fn from_uri(uri: impl Into<String>) -> Self {
58 Self {
59 name: None,
60 mime_type: None,
61 source: IncomingSource::Uri(uri.into()),
62 }
63 }
64
65 pub fn with_name(mut self, name: impl Into<String>) -> Self {
67 self.name = Some(name.into());
68 self
69 }
70
71 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
73 self.mime_type = Some(mime_type.into());
74 self
75 }
76
77 pub fn display_name(&self) -> String {
79 if let Some(name) = &self.name {
80 return name.clone();
81 }
82 match &self.source {
83 IncomingSource::Bytes(_) => "shared".to_string(),
84 IncomingSource::Uri(uri) => uri
85 .rsplit(['/', ':'])
86 .find(|segment| !segment.is_empty())
87 .unwrap_or(uri)
88 .to_string(),
89 }
90 }
91
92 pub fn content(self) -> Option<ContentHandle> {
97 let name = self.display_name();
98 match self.source {
99 IncomingSource::Bytes(bytes) => {
100 let mut metadata = ContentMetadata::named(name);
101 metadata.mime_type = self.mime_type;
102 Some(BytesContent::new(metadata, bytes).handle())
103 }
104 IncomingSource::Uri(uri) => resolve_content(&uri),
105 }
106 }
107}
108
109type Observer = Arc<dyn Fn(IncomingContent) + Send + Sync>;
110
111struct Inbox {
112 observers: Vec<(u64, Observer)>,
113 backlog: VecDeque<IncomingContent>,
114}
115
116impl Inbox {
117 fn new() -> Self {
118 Self {
119 observers: Vec::new(),
120 backlog: VecDeque::new(),
121 }
122 }
123
124 fn observe(&mut self, id: u64, observer: Observer) -> Vec<IncomingContent> {
125 self.observers.push((id, observer));
126 self.backlog.drain(..).collect()
127 }
128
129 fn publish(&mut self, content: IncomingContent) -> Option<Vec<Observer>> {
130 if self.observers.is_empty() {
131 self.backlog.push_back(content);
132 return None;
133 }
134 Some(
135 self.observers
136 .iter()
137 .map(|(_, observer)| Arc::clone(observer))
138 .collect(),
139 )
140 }
141
142 fn remove_observer(&mut self, id: u64) {
143 self.observers.retain(|(existing, _)| *existing != id);
144 }
145
146 fn clear(&mut self) {
147 self.backlog.clear();
148 }
149}
150
151fn inbox() -> &'static Mutex<Inbox> {
152 static INBOX: OnceLock<Mutex<Inbox>> = OnceLock::new();
153 INBOX.get_or_init(|| Mutex::new(Inbox::new()))
154}
155
156static NEXT_ID: AtomicU64 = AtomicU64::new(1);
157
158pub struct IncomingContentObserver {
160 id: u64,
161}
162
163impl Drop for IncomingContentObserver {
164 fn drop(&mut self) {
165 if let Ok(mut inbox) = inbox().lock() {
166 inbox.remove_observer(self.id);
167 }
168 }
169}
170
171pub fn observe_incoming_content(
177 observer: impl Fn(IncomingContent) + Send + Sync + 'static,
178) -> IncomingContentObserver {
179 let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
180 let observer: Observer = Arc::new(observer);
181 let replay = {
182 let Ok(mut inbox) = inbox().lock() else {
183 return IncomingContentObserver { id };
184 };
185 inbox.observe(id, Arc::clone(&observer))
186 };
187 log::info!(
188 "incoming share: observer {id} registered, replays {} item(s)",
189 replay.len()
190 );
191 for item in replay {
192 observer(item);
193 }
194 IncomingContentObserver { id }
195}
196
197pub fn publish_incoming_content(content: IncomingContent) {
200 let Some(observers) = ({
201 let Ok(mut inbox) = inbox().lock() else {
202 return;
203 };
204 inbox.publish(content.clone())
205 }) else {
206 log::info!(
207 "incoming share: no observer yet, item {} waits in the backlog",
208 content.display_name()
209 );
210 return;
211 };
212 log::info!(
213 "incoming share: item {} goes to {} observer(s)",
214 content.display_name(),
215 observers.len()
216 );
217 for observer in observers {
218 observer(content.clone());
219 }
220}
221
222pub fn clear_incoming_content() {
224 if let Ok(mut inbox) = inbox().lock() {
225 inbox.clear();
226 }
227}
228
229#[allow(non_snake_case)]
245#[track_caller]
246pub fn rememberIncomingContent() -> EventStream<IncomingContent> {
247 rememberEventStream((), |sender| {
248 observe_incoming_content(move |content| sender.send(content))
249 })
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 fn recording_observer() -> (Observer, Arc<Mutex<Vec<String>>>) {
257 let seen = Arc::new(Mutex::new(Vec::new()));
258 let recorder = Arc::clone(&seen);
259 let observer: Observer = Arc::new(move |item: IncomingContent| {
260 recorder
261 .lock()
262 .unwrap_or_else(|error| error.into_inner())
263 .push(item.display_name())
264 });
265 (observer, seen)
266 }
267
268 #[test]
269 fn an_item_published_before_anyone_listens_is_backlogged_once() {
270 let mut inbox = Inbox::new();
271
272 assert!(
273 inbox
274 .publish(IncomingContent::from_bytes(vec![1, 2, 3]).with_name("scan.jpg"))
275 .is_none(),
276 "with nobody listening the item belongs in the backlog, not delivered"
277 );
278
279 let (first, _first_seen) = recording_observer();
280 assert_eq!(
281 inbox.observe(1, first).len(),
282 1,
283 "the first observer to register receives the backlog"
284 );
285
286 let (second, _second_seen) = recording_observer();
287 assert!(
288 inbox.observe(2, second).is_empty(),
289 "the backlog drains into that observer and is not replayed to the next"
290 );
291 }
292
293 #[test]
294 fn the_backlog_reaches_the_first_observer_that_registers() {
295 let mut inbox = Inbox::new();
296 inbox.publish(IncomingContent::from_bytes(vec![1, 2, 3]).with_name("scan.jpg"));
297
298 let (observer, seen) = recording_observer();
299 let replay = inbox.observe(1, Arc::clone(&observer));
300 for item in replay {
301 observer(item);
302 }
303
304 assert_eq!(
305 seen.lock().unwrap_or_else(|e| e.into_inner()).as_slice(),
306 ["scan.jpg"]
307 );
308 }
309
310 #[test]
311 fn observers_stop_receiving_once_dropped() {
312 let mut inbox = Inbox::new();
313 let (observer, _seen) = recording_observer();
314 inbox.observe(7, observer);
315
316 let delivered = inbox
317 .publish(IncomingContent::from_bytes(vec![1]))
318 .expect("a registered observer receives the item");
319 assert_eq!(delivered.len(), 1);
320
321 inbox.remove_observer(7);
322 assert!(
323 inbox
324 .publish(IncomingContent::from_bytes(vec![2]))
325 .is_none(),
326 "once the last observer is gone the item goes back to the backlog"
327 );
328 }
329
330 #[test]
331 fn clearing_discards_the_backlog() {
332 let mut inbox = Inbox::new();
333 inbox.publish(IncomingContent::from_bytes(vec![1]));
334 inbox.clear();
335
336 let (observer, _seen) = recording_observer();
337 assert!(
338 inbox.observe(1, observer).is_empty(),
339 "a cleared backlog has nothing left to replay"
340 );
341 }
342
343 #[test]
344 fn bytes_become_readable_content_with_the_reported_name() {
345 let item = IncomingContent::from_bytes(b"payload".to_vec())
346 .with_name("note.txt")
347 .with_mime_type("text/plain");
348 let content = item.content().expect("bytes always resolve");
349 assert_eq!(content.metadata().name, "note.txt");
350 assert_eq!(content.metadata().mime_type.as_deref(), Some("text/plain"));
351 assert_eq!(
352 pollster::block_on(content.read_all()).expect("the bytes read back"),
353 b"payload"
354 );
355 }
356
357 #[test]
358 fn a_uri_item_names_itself_from_its_last_segment() {
359 let item = IncomingContent::from_uri("content://media/external/images/42");
360 assert_eq!(item.display_name(), "42");
361 }
362}