anni-provider 0.1.3

Storage providers for Project Anni.
Documentation
use crate::{FileEntry, FileSystemProvider, ProviderError, Range, ResourceReader};
use async_trait::async_trait;
use std::io::SeekFrom;
use std::path::PathBuf;
use std::pin::Pin;
use tokio::fs::read_dir;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use tokio_stream::{self as stream, Stream};

pub struct LocalFileSystemProvider;

#[async_trait]
impl FileSystemProvider for LocalFileSystemProvider {
    async fn children(
        &self,
        path: &PathBuf,
    ) -> crate::Result<Pin<Box<dyn Stream<Item = FileEntry> + Send>>> {
        Ok(Box::pin(stream::iter(std::fs::read_dir(path)?.filter_map(
            |entry| {
                let entry = entry.ok()?;
                let path = entry.path();
                let name = path.file_name()?.to_string_lossy().to_string();
                if path.is_dir() {
                    Some(FileEntry { name, path })
                } else {
                    None
                }
            },
        ))))
    }

    async fn get_file_entry_by_prefix(
        &self,
        parent: &PathBuf,
        prefix: &str,
    ) -> crate::Result<FileEntry> {
        let mut dir = read_dir(parent).await?;
        loop {
            match dir.next_entry().await? {
                Some(entry) if entry.file_name().to_string_lossy().starts_with(prefix) => {
                    return Ok(FileEntry {
                        name: entry.file_name().to_string_lossy().to_string(),
                        path: entry.path(),
                    });
                }
                None => return Err(ProviderError::FileNotFound),
                _ => {}
            }
        }
    }

    async fn get_file(&self, path: &PathBuf, range: Range) -> crate::Result<ResourceReader> {
        let mut file = tokio::fs::File::open(path).await?;
        let metadata = file.metadata().await?;
        let file_size = metadata.len();

        file.seek(SeekFrom::Start(range.start)).await?;
        let file = file.take(range.length_limit(file_size));
        Ok(Box::pin(file))
    }

    async fn get_audio_info(&self, path: &PathBuf) -> crate::Result<(String, usize)> {
        let extension = path
            .extension()
            .map(|e| e.to_string_lossy().to_string())
            .unwrap_or_default();
        let size = tokio::fs::metadata(path).await.map(|m| m.len())?;
        Ok((extension, size as usize))
    }

    async fn reload(&mut self) -> crate::Result<()> {
        Ok(())
    }
}