webview_bundle_protocol/
source.rs1use 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}