1use std::{
15 cell::RefCell,
16 collections::VecDeque,
17 future::Future,
18 pin::Pin,
19 rc::Rc,
20 task::{Context, Poll, Waker},
21};
22
23pub const DEFAULT_CHUNK_LEN: usize = 256 * 1024;
25
26pub type ContentFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
29
30#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
32pub enum ContentError {
33 #[error("content not found: {0}")]
35 NotFound(String),
36 #[error("content access denied: {0}")]
38 PermissionDenied(String),
39 #[error("content i/o failed: {0}")]
41 Io(String),
42 #[error("{0} is not supported on this platform")]
44 Unsupported(&'static str),
45}
46
47#[derive(Clone, Debug, Default, PartialEq, Eq)]
49pub struct ContentMetadata {
50 pub name: String,
52 pub mime_type: Option<String>,
54 pub len: Option<u64>,
56 pub modified_millis: Option<u64>,
58 pub identifier: String,
61}
62
63impl ContentMetadata {
64 pub fn named(name: impl Into<String>) -> Self {
66 let name = name.into();
67 Self {
68 identifier: name.clone(),
69 name,
70 ..Self::default()
71 }
72 }
73
74 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
76 self.mime_type = Some(mime_type.into());
77 self
78 }
79
80 pub fn with_len(mut self, len: u64) -> Self {
82 self.len = Some(len);
83 self
84 }
85
86 pub fn with_modified_millis(mut self, modified_millis: u64) -> Self {
88 self.modified_millis = Some(modified_millis);
89 self
90 }
91
92 pub fn with_identifier(mut self, identifier: impl Into<String>) -> Self {
94 self.identifier = identifier.into();
95 self
96 }
97
98 pub fn extension(&self) -> Option<String> {
100 let (_, extension) = self.name.rsplit_once('.')?;
101 if extension.is_empty() {
102 None
103 } else {
104 Some(extension.to_ascii_lowercase())
105 }
106 }
107}
108
109pub trait ContentReader {
115 fn read_chunk(&self) -> ContentFuture<'_, Result<Option<Vec<u8>>, ContentError>>;
117}
118
119pub type ContentReaderRef = Rc<dyn ContentReader>;
121
122pub trait Content {
124 fn metadata(&self) -> ContentMetadata;
126
127 fn media_uri(&self) -> Result<String, ContentError> {
131 Err(ContentError::Unsupported(
132 "media references from this provider",
133 ))
134 }
135
136 fn media_uri_async(&self) -> ContentFuture<'_, Result<String, ContentError>> {
140 Box::pin(async move { self.media_uri() })
141 }
142
143 fn open(&self) -> ContentFuture<'_, Result<ContentReaderRef, ContentError>>;
145
146 fn read_all(&self) -> ContentFuture<'_, Result<Vec<u8>, ContentError>> {
149 Box::pin(async move {
150 let reader = self.open().await?;
151 drain_reader(&reader).await
152 })
153 }
154}
155
156pub type ContentHandle = Rc<dyn Content>;
158
159pub async fn drain_reader(reader: &ContentReaderRef) -> Result<Vec<u8>, ContentError> {
161 let mut bytes = Vec::new();
162 while let Some(chunk) = reader.read_chunk().await? {
163 bytes.extend_from_slice(&chunk);
164 }
165 Ok(bytes)
166}
167
168pub trait ContentSink {
170 fn write_chunk(&self, bytes: Vec<u8>) -> ContentFuture<'_, Result<(), ContentError>>;
172
173 fn finish(&self) -> ContentFuture<'_, Result<(), ContentError>>;
176}
177
178pub type ContentSinkRef = Rc<dyn ContentSink>;
180
181pub async fn write_all(sink: &ContentSinkRef, bytes: Vec<u8>) -> Result<(), ContentError> {
183 sink.write_chunk(bytes).await?;
184 sink.finish().await
185}
186
187pub enum ContentEntry {
189 File(ContentHandle),
191 Folder(ContentFolderRef),
193}
194
195impl ContentEntry {
196 pub fn metadata(&self) -> ContentMetadata {
198 match self {
199 Self::File(file) => file.metadata(),
200 Self::Folder(folder) => folder.metadata(),
201 }
202 }
203}
204
205pub trait ContentFolder {
207 fn metadata(&self) -> ContentMetadata;
209
210 fn entries(&self) -> ContentFuture<'_, Result<Vec<ContentEntry>, ContentError>>;
212
213 fn stream_files(&self) -> Option<ContentStreamRef> {
217 None
218 }
219}
220
221pub type ContentFolderRef = Rc<dyn ContentFolder>;
223
224pub trait ContentStream {
229 fn next(&self) -> ContentFuture<'_, Result<Option<ContentHandle>, ContentError>>;
231
232 fn produced(&self) -> Option<usize> {
234 None
235 }
236}
237
238pub type ContentStreamRef = Rc<dyn ContentStream>;
240
241pub fn folder_files(folder: ContentFolderRef) -> ContentStreamRef {
244 folder
245 .stream_files()
246 .unwrap_or_else(|| Rc::new(WalkStream::new(folder)))
247}
248
249pub async fn collect_stream(stream: &ContentStreamRef) -> Result<Vec<ContentHandle>, ContentError> {
252 let mut items = Vec::new();
253 while let Some(item) = stream.next().await? {
254 items.push(item);
255 }
256 Ok(items)
257}
258
259struct WalkStream {
260 pending: RefCell<Vec<ContentFolderRef>>,
261 ready: RefCell<VecDeque<ContentHandle>>,
262 produced: std::cell::Cell<usize>,
263}
264
265impl WalkStream {
266 fn new(root: ContentFolderRef) -> Self {
267 Self {
268 pending: RefCell::new(vec![root]),
269 ready: RefCell::new(VecDeque::new()),
270 produced: std::cell::Cell::new(0),
271 }
272 }
273}
274
275impl ContentStream for WalkStream {
276 fn next(&self) -> ContentFuture<'_, Result<Option<ContentHandle>, ContentError>> {
277 Box::pin(async move {
278 loop {
279 if let Some(file) = self.ready.borrow_mut().pop_front() {
280 self.produced.set(self.produced.get() + 1);
281 return Ok(Some(file));
282 }
283 let Some(folder) = self.pending.borrow_mut().pop() else {
284 return Ok(None);
285 };
286 for entry in folder.entries().await? {
287 match entry {
288 ContentEntry::File(file) => self.ready.borrow_mut().push_back(file),
289 ContentEntry::Folder(child) => self.pending.borrow_mut().push(child),
290 }
291 }
292 }
293 })
294 }
295
296 fn produced(&self) -> Option<usize> {
297 Some(self.produced.get())
298 }
299}
300
301pub struct ContentChannel {
308 shared: Rc<ChannelShared>,
309}
310
311struct ChannelShared {
312 ready: RefCell<VecDeque<ContentHandle>>,
313 error: RefCell<Option<ContentError>>,
314 closed: std::cell::Cell<bool>,
315 produced: std::cell::Cell<usize>,
316 waker: RefCell<Option<Waker>>,
317}
318
319impl ChannelShared {
320 fn wake(&self) {
321 if let Some(waker) = self.waker.borrow_mut().take() {
322 waker.wake();
323 }
324 }
325}
326
327impl Default for ContentChannel {
328 fn default() -> Self {
329 Self::new()
330 }
331}
332
333impl ContentChannel {
334 pub fn new() -> Self {
336 Self {
337 shared: Rc::new(ChannelShared {
338 ready: RefCell::new(VecDeque::new()),
339 error: RefCell::new(None),
340 closed: std::cell::Cell::new(false),
341 produced: std::cell::Cell::new(0),
342 waker: RefCell::new(None),
343 }),
344 }
345 }
346
347 pub fn stream(&self) -> ContentStreamRef {
349 Rc::new(ChannelStream {
350 shared: Rc::clone(&self.shared),
351 })
352 }
353
354 pub fn push(&self, content: ContentHandle) {
356 if self.shared.closed.get() {
357 return;
358 }
359 self.shared.ready.borrow_mut().push_back(content);
360 self.shared.wake();
361 }
362
363 pub fn fail(&self, error: ContentError) {
365 if self.shared.closed.get() {
366 return;
367 }
368 *self.shared.error.borrow_mut() = Some(error);
369 self.shared.closed.set(true);
370 self.shared.wake();
371 }
372
373 pub fn close(&self) {
375 if self.shared.closed.get() {
376 return;
377 }
378 self.shared.closed.set(true);
379 self.shared.wake();
380 }
381
382 pub fn is_closed(&self) -> bool {
384 self.shared.closed.get()
385 }
386}
387
388struct ChannelStream {
389 shared: Rc<ChannelShared>,
390}
391
392impl ContentStream for ChannelStream {
393 fn next(&self) -> ContentFuture<'_, Result<Option<ContentHandle>, ContentError>> {
394 Box::pin(ChannelNext {
395 shared: Rc::clone(&self.shared),
396 })
397 }
398
399 fn produced(&self) -> Option<usize> {
400 Some(self.shared.produced.get())
401 }
402}
403
404struct ChannelNext {
405 shared: Rc<ChannelShared>,
406}
407
408impl Future for ChannelNext {
409 type Output = Result<Option<ContentHandle>, ContentError>;
410
411 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
412 if let Some(item) = self.shared.ready.borrow_mut().pop_front() {
413 self.shared.produced.set(self.shared.produced.get() + 1);
414 return Poll::Ready(Ok(Some(item)));
415 }
416 if let Some(error) = self.shared.error.borrow_mut().take() {
417 return Poll::Ready(Err(error));
418 }
419 if self.shared.closed.get() {
420 return Poll::Ready(Ok(None));
421 }
422 *self.shared.waker.borrow_mut() = Some(context.waker().clone());
423 Poll::Pending
424 }
425}
426
427pub struct BytesContent {
429 metadata: ContentMetadata,
430 bytes: Rc<Vec<u8>>,
431}
432
433impl BytesContent {
434 pub fn new(metadata: ContentMetadata, bytes: Vec<u8>) -> Self {
436 let len = bytes.len() as u64;
437 Self {
438 metadata: ContentMetadata {
439 len: Some(len),
440 ..metadata
441 },
442 bytes: Rc::new(bytes),
443 }
444 }
445
446 pub fn named(name: impl Into<String>, bytes: Vec<u8>) -> Self {
448 Self::new(ContentMetadata::named(name), bytes)
449 }
450
451 pub fn handle(self) -> ContentHandle {
453 Rc::new(self)
454 }
455}
456
457impl Content for BytesContent {
458 fn metadata(&self) -> ContentMetadata {
459 self.metadata.clone()
460 }
461
462 fn open(&self) -> ContentFuture<'_, Result<ContentReaderRef, ContentError>> {
463 let bytes = Rc::clone(&self.bytes);
464 Box::pin(async move {
465 Ok(Rc::new(BytesReader {
466 bytes,
467 offset: std::cell::Cell::new(0),
468 }) as ContentReaderRef)
469 })
470 }
471
472 fn read_all(&self) -> ContentFuture<'_, Result<Vec<u8>, ContentError>> {
473 let bytes = Rc::clone(&self.bytes);
474 Box::pin(async move { Ok(bytes.as_ref().clone()) })
475 }
476}
477
478struct BytesReader {
479 bytes: Rc<Vec<u8>>,
480 offset: std::cell::Cell<usize>,
481}
482
483impl ContentReader for BytesReader {
484 fn read_chunk(&self) -> ContentFuture<'_, Result<Option<Vec<u8>>, ContentError>> {
485 Box::pin(async move {
486 let start = self.offset.get();
487 if start >= self.bytes.len() {
488 return Ok(None);
489 }
490 let end = (start + DEFAULT_CHUNK_LEN).min(self.bytes.len());
491 self.offset.set(end);
492 Ok(Some(self.bytes[start..end].to_vec()))
493 })
494 }
495}
496
497pub struct ReadyFolder {
499 metadata: ContentMetadata,
500 entries: RefCell<Option<Vec<ContentEntry>>>,
501}
502
503impl ReadyFolder {
504 pub fn new(metadata: ContentMetadata, entries: Vec<ContentEntry>) -> Self {
506 Self {
507 metadata,
508 entries: RefCell::new(Some(entries)),
509 }
510 }
511
512 pub fn handle(self) -> ContentFolderRef {
514 Rc::new(self)
515 }
516}
517
518impl ContentFolder for ReadyFolder {
519 fn metadata(&self) -> ContentMetadata {
520 self.metadata.clone()
521 }
522
523 fn entries(&self) -> ContentFuture<'_, Result<Vec<ContentEntry>, ContentError>> {
524 Box::pin(async move {
525 self.entries
526 .borrow_mut()
527 .take()
528 .ok_or_else(|| ContentError::Io("folder entries already consumed".into()))
529 })
530 }
531}
532
533pub trait ContentResolver {
540 fn resolve(&self, uri: &str) -> Option<ContentHandle>;
542}
543
544pub type ContentResolverRef = Rc<dyn ContentResolver>;
546
547pub fn set_platform_content_resolver(resolver: ContentResolverRef) {
550 crate::thread_state::with(|state| *state.content_resolver.borrow_mut() = Some(resolver));
551}
552
553pub fn clear_platform_content_resolver() {
555 crate::thread_state::with(|state| *state.content_resolver.borrow_mut() = None);
556}
557
558pub fn resolve_content(uri: &str) -> Option<ContentHandle> {
561 if let Some(resolver) =
562 crate::thread_state::with(|state| state.content_resolver.borrow().clone())
563 && let Some(content) = resolver.resolve(uri)
564 {
565 return Some(content);
566 }
567 #[cfg(not(target_arch = "wasm32"))]
568 {
569 if let Some(path) = crate::media::path_from_uri(uri) {
570 return Some(file_content(path));
571 }
572 }
573 let _ = uri;
574 None
575}
576
577#[cfg(not(target_arch = "wasm32"))]
578pub use file::{FileContent, FileFolder, FileSink, file_content, file_folder};
579#[cfg(not(target_arch = "wasm32"))]
580pub use threaded::{
581 BlockingSink, copy_content, open_threaded, read_full, run_threaded, threaded_sink,
582};
583
584#[cfg(all(target_arch = "wasm32", feature = "file-picker-web"))]
585pub(crate) mod blob;
586#[cfg(not(target_arch = "wasm32"))]
587mod file;
588#[cfg(not(target_arch = "wasm32"))]
589mod threaded;
590
591pub fn percent_decode(input: &str) -> Option<String> {
605 let bytes = input.as_bytes();
606 let mut out = Vec::with_capacity(bytes.len());
607 let mut index = 0;
608 while index < bytes.len() {
609 if bytes[index] == b'%' {
610 let high = bytes.get(index + 1).copied().and_then(hex_value)?;
611 let low = bytes.get(index + 2).copied().and_then(hex_value)?;
612 out.push((high << 4) | low);
613 index += 3;
614 } else {
615 out.push(bytes[index]);
616 index += 1;
617 }
618 }
619 String::from_utf8(out).ok()
620}
621
622pub fn percent_decode_lossy(input: &str) -> String {
629 let bytes = input.as_bytes();
630 let mut out = Vec::with_capacity(bytes.len());
631 let mut index = 0;
632 while index < bytes.len() {
633 if bytes[index] == b'%' {
634 let pair = hex_value(bytes.get(index + 1).copied().unwrap_or(0))
635 .zip(hex_value(bytes.get(index + 2).copied().unwrap_or(0)));
636 if let Some((high, low)) = pair {
637 out.push((high << 4) | low);
638 index += 3;
639 continue;
640 }
641 }
642 out.push(bytes[index]);
643 index += 1;
644 }
645 String::from_utf8_lossy(&out).into_owned()
646}
647
648fn hex_value(byte: u8) -> Option<u8> {
649 match byte {
650 b'0'..=b'9' => Some(byte - b'0'),
651 b'a'..=b'f' => Some(byte - b'a' + 10),
652 b'A'..=b'F' => Some(byte - b'A' + 10),
653 _ => None,
654 }
655}
656
657#[cfg(test)]
658#[path = "tests/content_tests.rs"]
659mod tests;