Skip to main content

webview_bundle_protocol/
source.rs

1use async_trait::async_trait;
2use std::io;
3use std::path::{Path, PathBuf};
4use tokio::fs::File;
5use tokio::io::{AsyncRead, AsyncSeek};
6use webview_bundle::{AsyncBundleReader, AsyncReader, Bundle, BundleManifest};
7
8#[async_trait]
9pub trait Source: Send + Sync {
10  type Reader: AsyncRead + AsyncSeek + Unpin + Send + Sync;
11
12  async fn reader(&self, name: &str) -> crate::Result<Self::Reader>;
13  async fn fetch(&self, name: &str) -> crate::Result<Bundle>;
14  async fn fetch_manifest(&self, name: &str) -> crate::Result<BundleManifest>;
15}
16
17#[derive(Clone)]
18pub struct FileSource {
19  base_dir: PathBuf,
20}
21
22impl FileSource {
23  pub fn new<P: AsRef<Path>>(base_dir: P) -> Self {
24    Self {
25      base_dir: base_dir.as_ref().to_path_buf(),
26    }
27  }
28
29  pub fn file_path(&self, name: &str) -> PathBuf {
30    self.base_dir.join(name).with_extension("wvb")
31  }
32}
33
34#[async_trait]
35impl Source for FileSource {
36  type Reader = File;
37
38  async fn reader(&self, name: &str) -> crate::Result<Self::Reader> {
39    let file = File::open(&self.file_path(name))
40      .await
41      .map_err(map_io_err)?;
42    Ok(file)
43  }
44
45  async fn fetch(&self, name: &str) -> crate::Result<Bundle> {
46    let mut file = self.reader(name).await?;
47    let bundle = AsyncReader::<Bundle>::read(&mut AsyncBundleReader::new(&mut file)).await?;
48    Ok(bundle)
49  }
50
51  async fn fetch_manifest(&self, name: &str) -> crate::Result<BundleManifest> {
52    let mut file = self.reader(name).await?;
53    let manifest =
54      AsyncReader::<BundleManifest>::read(&mut AsyncBundleReader::new(&mut file)).await?;
55    Ok(manifest)
56  }
57}
58
59fn map_io_err(e: io::Error) -> crate::Error {
60  if e.kind() == io::ErrorKind::NotFound {
61    return crate::Error::BundleNotFound;
62  }
63  crate::Error::from(e)
64}
65
66#[cfg(test)]
67mod tests {
68  use super::*;
69
70  #[tokio::test]
71  async fn file_source_fetch() {
72    let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
73      .join("tests")
74      .join("fixtures");
75    let source = FileSource::new(base_dir);
76    let bundle = source.fetch("nextjs.wvb").await.unwrap();
77    bundle.get_data("/index.html").unwrap().unwrap();
78  }
79
80  #[tokio::test]
81  async fn file_source_fetch_manifest() {
82    let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
83      .join("tests")
84      .join("fixtures");
85    let source = FileSource::new(base_dir);
86    let manifest = source.fetch_manifest("nextjs.wvb").await.unwrap();
87    assert!(manifest.index().contains_path("/index.html"));
88    let reader = source.reader("nextjs.wvb").await.unwrap();
89    manifest
90      .async_get_data(reader, "/index.html")
91      .await
92      .unwrap()
93      .unwrap();
94  }
95
96  #[tokio::test]
97  async fn file_source_fetch_many_times() {
98    let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
99      .join("tests")
100      .join("fixtures");
101    let source = FileSource::new(base_dir);
102    let mut handles = Vec::new();
103    for _i in 0..10 {
104      let s = source.clone();
105      let handle = tokio::spawn(async move {
106        let bundle = s.fetch("nextjs.wvb").await.unwrap();
107        bundle.get_data("/index.html").unwrap().unwrap();
108      });
109      handles.push(handle);
110    }
111    for h in handles {
112      h.await.unwrap();
113    }
114  }
115
116  #[tokio::test]
117  async fn bundle_not_found() {
118    let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
119      .join("tests")
120      .join("fixtures");
121    let source = FileSource::new(base_dir);
122    let bundle = source.fetch("not-found.wvb").await;
123    assert!(matches!(bundle.unwrap_err(), crate::Error::BundleNotFound));
124  }
125}