cranpose-services 0.18.0

Multiplatform service contracts and platform integrations for Cranpose apps
Documentation
use std::{
    cell::Cell,
    fs::ReadDir,
    path::PathBuf,
    rc::Rc,
    sync::{Arc, Mutex},
};

use super::{FileContent, PathInfo, io_error, metadata_for};
use crate::content::{
    ContentError, ContentFuture, ContentHandle, ContentMetadata, ContentStream, ContentStreamRef,
    run_threaded,
};

pub(super) fn open(root: PathBuf) -> ContentStreamRef {
    Rc::new(Stream {
        scope: Arc::new(super::folder_reference::FolderReference::new(root.clone())),
        walk: Arc::new(Mutex::new(Walk {
            pending: vec![root],
            current: None,
        })),
        produced: Cell::new(0),
    })
}

struct Stream {
    scope: Arc<super::folder_reference::FolderReference>,
    walk: Arc<Mutex<Walk>>,
    produced: Cell<usize>,
}

impl ContentStream for Stream {
    fn next(&self) -> ContentFuture<'_, Result<Option<ContentHandle>, ContentError>> {
        let walk = Arc::clone(&self.walk);
        Box::pin(async move {
            let entry = run_threaded(move || {
                let mut walk = walk
                    .lock()
                    .map_err(|_| ContentError::Io("folder scan failed".into()))?;
                walk.next()
            })
            .await?;
            Ok(entry.map(|(path, metadata)| {
                self.produced.set(self.produced.get() + 1);
                Rc::new(FileContent {
                    scope: Some(Arc::clone(&self.scope)),
                    source: PathInfo {
                        path,
                        metadata: Some(metadata),
                    },
                }) as ContentHandle
            }))
        })
    }

    fn produced(&self) -> Option<usize> {
        Some(self.produced.get())
    }
}

struct Walk {
    pending: Vec<PathBuf>,
    current: Option<(PathBuf, ReadDir)>,
}

impl Walk {
    fn next(&mut self) -> Result<Option<(PathBuf, ContentMetadata)>, ContentError> {
        loop {
            if self.current.is_none() {
                let Some(path) = self.pending.pop() else {
                    return Ok(None);
                };
                let entries = std::fs::read_dir(&path).map_err(|error| io_error(&path, error))?;
                self.current = Some((path, entries));
            }
            let (directory, entries) = self.current.as_mut().expect("directory opened");
            let Some(entry) = entries.next() else {
                self.current = None;
                continue;
            };
            let entry = entry.map_err(|error| io_error(directory, error))?;
            let path = entry.path();
            let kind = entry.file_type().map_err(|error| io_error(&path, error))?;
            if kind.is_dir() {
                self.pending.push(path);
            } else {
                let metadata = metadata_for(&path);
                return Ok(Some((path, metadata)));
            }
        }
    }
}