1use std::io;
10use std::path::{Path, PathBuf};
11
12use crate::load::{DirEntry, FileKind, LoadedPlugin, Rejection};
13use crate::path::PackagePath;
14use crate::source::{self, Source};
15
16#[derive(Debug, Clone)]
18pub struct DirSource {
19 root: PathBuf,
20 canonical_root: PathBuf,
21}
22
23impl DirSource {
24 pub fn open(root: impl AsRef<Path>) -> io::Result<Self> {
27 let root = root.as_ref().to_path_buf();
28 let canonical_root = root.canonicalize()?;
29 Ok(Self { root, canonical_root })
30 }
31
32 pub fn root(&self) -> &Path {
34 &self.root
35 }
36
37 pub fn canonical_root(&self) -> &Path {
40 &self.canonical_root
41 }
42
43 fn native(&self, path: &PackagePath) -> PathBuf {
44 path.to_native(&self.root)
45 }
46}
47
48impl Source for DirSource {
49 type Error = io::Error;
50
51 fn kind(&mut self, path: &PackagePath) -> Result<Option<FileKind>, io::Error> {
52 match std::fs::metadata(self.native(path)) {
53 Ok(meta) => Ok(Some(if meta.is_file() {
54 FileKind::File
55 } else if meta.is_dir() {
56 FileKind::Directory
57 } else {
58 FileKind::Other
59 })),
60 Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None),
61 Err(e) => Err(e),
62 }
63 }
64
65 fn confined(&mut self, path: &PackagePath) -> Result<bool, io::Error> {
66 let resolved = self.native(path).canonicalize()?;
67 Ok(resolved.starts_with(&self.canonical_root))
68 }
69
70 fn read(&mut self, path: &PackagePath) -> Result<Vec<u8>, io::Error> {
71 std::fs::read(self.native(path))
72 }
73
74 fn list(&mut self, path: &PackagePath) -> Result<Vec<DirEntry>, io::Error> {
75 let mut entries = Vec::new();
76 for entry in std::fs::read_dir(self.native(path))? {
77 let entry = entry?;
78 let Ok(name) = entry.file_name().into_string() else {
79 continue; };
81 let kind = match std::fs::metadata(entry.path()) {
83 Ok(meta) if meta.is_file() => FileKind::File,
84 Ok(meta) if meta.is_dir() => FileKind::Directory,
85 _ => FileKind::Other,
86 };
87 entries.push(DirEntry::new(name, kind));
88 }
89 entries.sort_by(|a, b| a.name.cmp(&b.name));
90 Ok(entries)
91 }
92}
93
94pub fn load_dir(path: impl AsRef<Path>) -> Result<LoadedPlugin, Rejection> {
104 let mut source = DirSource::open(path)
105 .map_err(|e| Rejection::Source { detail: format!("cannot open plugin root: {e}") })?;
106 source::load(&mut source)
107}