diff --git a/Cargo.lock b/Cargo.lock index 745377c..a154307 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1192,10 +1192,8 @@ name = "neutauri_bundler" version = "0.1.0" dependencies = [ "anyhow", - "bincode", - "brotli", "gumdrop", - "image", + "neutauri_data", "new_mime_guess", "rcedit", "serde", @@ -1205,12 +1203,23 @@ dependencies = [ ] [[package]] -name = "neutauri_runtime" +name = "neutauri_data" version = "0.1.0" dependencies = [ "bincode", "brotli", + "image", + "new_mime_guess", "serde", + "toml", + "wry", +] + +[[package]] +name = "neutauri_runtime" +version = "0.1.0" +dependencies = [ + "neutauri_data", "winres", "wry", ] diff --git a/Cargo.toml b/Cargo.toml index e21b0dc..f9093e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,4 +2,5 @@ members = [ "neutauri_runtime", "neutauri_bundler", + "neutauri_data", ] diff --git a/neutauri_bundler/Cargo.toml b/neutauri_bundler/Cargo.toml index eea9118..5548242 100644 --- a/neutauri_bundler/Cargo.toml +++ b/neutauri_bundler/Cargo.toml @@ -5,10 +5,8 @@ version = "0.1.0" [dependencies] anyhow = "1.0" -bincode = "1.3" -brotli = "3.3" gumdrop = "0.8" -image = "0.24" +neutauri_data = {path = "../neutauri_data", features = ["bundler"]} new_mime_guess = "4.0" serde = {version = "1.0", features = ["derive"]} toml = "0.5" diff --git a/neutauri_bundler/src/bundle.rs b/neutauri_bundler/src/bundle.rs index f5faaa0..4922e8f 100644 --- a/neutauri_bundler/src/bundle.rs +++ b/neutauri_bundler/src/bundle.rs @@ -1,16 +1,15 @@ -use crate::data; -use std::{ - fs, - io::{self, Write}, -}; - #[cfg(windows)] use anyhow::Context; +use neutauri_data as data; #[cfg(windows)] use std::{ env, hash::{Hash, Hasher}, }; +use std::{ + fs, + io::{self, Write}, +}; fn options() -> fs::OpenOptions { #[cfg(not(windows))] diff --git a/neutauri_bundler/src/dev.rs b/neutauri_bundler/src/dev.rs index 93504e3..dced770 100644 --- a/neutauri_bundler/src/dev.rs +++ b/neutauri_bundler/src/dev.rs @@ -1,5 +1,5 @@ +use neutauri_data as data; use std::{fs, io::Read, path::PathBuf}; - use wry::{ application::{ dpi::{PhysicalSize, Size}, @@ -10,8 +10,6 @@ use wry::{ webview::{WebContext, WebViewBuilder}, }; -use crate::data; - const PROTOCOL_PREFIX: &str = "{PROTOCOL}://"; const PROTOCOL: &str = "dev"; diff --git a/neutauri_bundler/src/main.rs b/neutauri_bundler/src/main.rs index e55abe1..a322d2f 100644 --- a/neutauri_bundler/src/main.rs +++ b/neutauri_bundler/src/main.rs @@ -1,8 +1,6 @@ use gumdrop::Options; - mod bundle; mod dev; -mod data; #[derive(Debug, Options)] struct Args { @@ -80,7 +78,7 @@ fn main() -> anyhow::Result<()> { } let config_path = opts.config.unwrap_or_else(|| "neutauri.toml".to_string()); dev::dev(config_path)?; - }, + } }, None => print_help_and_exit(args), } diff --git a/neutauri_data/Cargo.toml b/neutauri_data/Cargo.toml new file mode 100644 index 0000000..7dea036 --- /dev/null +++ b/neutauri_data/Cargo.toml @@ -0,0 +1,17 @@ +[package] +edition = "2021" +name = "neutauri_data" +version = "0.1.0" + +[dependencies] +bincode = "1.3" +brotli = "3.3" +image = {version = "0.24", optional = true} +new_mime_guess = {version = "4.0", optional = true} +serde = {version = "1.0", features = ["derive"]} +toml = {version = "0.5", optional = true} +wry = {version = "0.15", default-features = false, features = ["protocol", "tray", "gtk-tray", "transparent", "fullscreen"]} + +[features] +bundler = ["new_mime_guess", "toml", "image"] +runtime = [] \ No newline at end of file diff --git a/neutauri_bundler/src/data.rs b/neutauri_data/src/lib.rs similarity index 71% rename from neutauri_bundler/src/data.rs rename to neutauri_data/src/lib.rs index 62d9740..8d37aaf 100644 --- a/neutauri_bundler/src/data.rs +++ b/neutauri_data/src/lib.rs @@ -2,7 +2,7 @@ use bincode::Options; use serde::{Deserialize, Serialize}; use std::{ fs, - io::{self, Read, Result}, + io::{self, Read, Result, SeekFrom, Seek}, path::{self, Component, Path, PathBuf}, }; use wry::application::dpi::Position; @@ -104,6 +104,102 @@ pub struct WebViewAttr { pub initialization_script: Option, } +#[cfg(feature = "runtime")] +impl File { + pub fn decompressed_data(&mut self) -> Result> { + let mut data = Vec::with_capacity(self.data.len()); + let mut r = brotli::Decompressor::new(self.data.as_slice(), 4096); + r.read_to_end(&mut data)?; + Ok(data) + } + pub fn mimetype(&self) -> String { + self.mime.clone() + } +} + +#[cfg(feature = "runtime")] +impl Data { + pub fn new + Copy>(path: P) -> Result { + let mut base = fs::File::open(path)?; + let base_length = base.metadata()?.len(); + let mut magic_number_start_data = [0; MAGIC_NUMBER_START.len()]; + let mut data_length_data = [0; USIZE_LEN]; + let mut data = Vec::new(); + let mut magic_number_end_data = [0; MAGIC_NUMBER_END.len()]; + base.seek(SeekFrom::Start(base_length - MAGIC_NUMBER_END.len() as u64))?; + // 此时指针指向 MAGIC_NUMBER_END 之前 + base.read_exact(&mut magic_number_end_data)?; + if &magic_number_end_data != MAGIC_NUMBER_END { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "MAGIC_NUMBER_END not found", + )); + } + base.seek(SeekFrom::Start( + base_length - MAGIC_NUMBER_END.len() as u64 - USIZE_LEN as u64, + ))?; + // 此时指针指向 data_length 之前 + base.read_exact(&mut data_length_data)?; + base.seek(SeekFrom::Start( + base_length - u64::from_be_bytes(data_length_data), + ))?; + // 此时指针指向 MAGIC_NUMBER_START + base.read_exact(&mut magic_number_start_data)?; + if &magic_number_start_data != MAGIC_NUMBER_START { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "MAGIC_NUMBER_START not found", + )); + } + base.read_exact(&mut data_length_data)?; + // 此时指针指向 Data 前 + base.take(u64::from_be_bytes(data_length_data)) + .read_to_end(&mut data)?; + let serialize_options = bincode::DefaultOptions::new() + .with_fixint_encoding() + .allow_trailing_bytes() + .with_limit(104857600 /* 100MiB */); + let fs: Self = match serialize_options.deserialize(&data) { + Ok(fs) => fs, + Err(e) => { + return Err(io::Error::new(io::ErrorKind::InvalidData, e)); + } + }; + Ok(fs) + } + + fn open_file(&self, current_dir: &Dir, mut path: path::Iter) -> Result { + let next_path = match path.next() { + Some(str) => str.to_string_lossy().to_string(), + None => return Err(io::Error::new(io::ErrorKind::NotFound, "file not found")), + }; + for (name, file) in ¤t_dir.files { + if next_path == *name { + return Ok(file.clone()); + } + } + for (name, dir) in ¤t_dir.dirs { + if next_path == *name { + return self.open_file(dir, path); + } + } + Err(io::Error::new(io::ErrorKind::NotFound, "file not found")) + } + + pub fn open>(&self, path: P) -> Result { + let path = normalize_path(path.as_ref()); + let path = if path.starts_with("/") { + path.strip_prefix("/") + .unwrap_or_else(|_| Path::new("")) + .to_path_buf() + } else { + path + }; + self.open_file(&self.fs, path.iter()) + } +} + +#[cfg(feature = "bundler")] impl Dir { // 使用本地文件系统填充 Dir 结构体 fn fill_with>( @@ -155,6 +251,7 @@ impl Dir { } } +#[cfg(feature = "bundler")] impl Data { pub fn build_from_dir>( source: P, @@ -229,6 +326,7 @@ impl Data { } } +#[cfg(feature = "bundler")] impl Default for Config { fn default() -> Self { Self { @@ -255,6 +353,7 @@ impl Default for Config { } } +#[cfg(feature = "bundler")] impl Config { pub fn window_attr(&self) -> Result { Ok(WindowAttr { @@ -294,10 +393,17 @@ impl Config { } } +#[cfg(feature = "runtime")] +pub fn load + Copy>(path: P) -> Result { + Data::new(path) +} + +#[cfg(feature = "bundler")] pub fn pack>(config: P) -> Result<()> { Data::pack(config) } +#[cfg(feature = "bundler")] fn load_icon(path: &Path) -> Result { let image = image::open(path) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))? diff --git a/neutauri_runtime/Cargo.toml b/neutauri_runtime/Cargo.toml index da45101..06eedf9 100644 --- a/neutauri_runtime/Cargo.toml +++ b/neutauri_runtime/Cargo.toml @@ -4,9 +4,7 @@ name = "neutauri_runtime" version = "0.1.0" [dependencies] -bincode = "1.3" -brotli = "3.3" -serde = {version = "1.0", features = ["derive"]} +neutauri_data = {path = "../neutauri_data", features = ["runtime"]} wry = {version = "0.15", default-features = false, features = ["protocol", "tray", "gtk-tray", "transparent", "fullscreen"]} [target.'cfg(windows)'.build-dependencies] diff --git a/neutauri_runtime/src/data.rs b/neutauri_runtime/src/data.rs deleted file mode 100644 index 2d4617e..0000000 --- a/neutauri_runtime/src/data.rs +++ /dev/null @@ -1,255 +0,0 @@ -use bincode::Options; -use serde::{Deserialize, Serialize}; -use std::{ - fs, - io::{self, Read, Result, Seek, SeekFrom}, - path::{self, Component, Path, PathBuf}, -}; -use wry::application::dpi::Position; - -const MAGIC_NUMBER_START: &[u8; 9] = b"NEUTFSv01"; -const MAGIC_NUMBER_END: &[u8; 9] = b"NEUTFSEnd"; -const USIZE_LEN: usize = usize::MAX.to_be_bytes().len(); - -#[non_exhaustive] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum Compress { - Brotli, - None, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct File { - mime: String, - data: Vec, - compress: Compress, -} - -#[derive(Serialize, Deserialize, Debug)] -struct Dir { - files: Vec<(String, File)>, - dirs: Vec<(String, Dir)>, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Data { - pub window_attr: WindowAttr, - pub webview_attr: WebViewAttr, - fs: Dir, -} - -#[derive(Serialize, Deserialize, Copy, Clone, Debug)] -pub enum WindowSize { - Large, - Medium, - Small, - Fixed { width: f64, height: f64 }, - Scale { factor: f64 }, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Config { - pub source: PathBuf, - pub target: PathBuf, - pub inner_size: Option, - pub min_inner_size: Option, - pub max_inner_size: Option, - pub resizable: bool, - pub fullscreen: bool, - pub title: String, - pub maximized: bool, - pub visible: bool, - pub transparent: bool, - pub decorations: bool, - pub always_on_top: bool, - pub icon: Option, - pub spa: bool, - pub url: Option, - pub html: Option, - pub initialization_script: Option, - pub manifest: Option, -} - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct Icon { - pub rgba: Vec, - pub width: u32, - pub height: u32, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct WindowAttr { - pub inner_size: Option, - pub min_inner_size: Option, - pub max_inner_size: Option, - pub position: Option, - pub resizable: bool, - pub fullscreen: bool, - pub title: String, - pub maximized: bool, - pub visible: bool, - pub transparent: bool, - pub decorations: bool, - pub always_on_top: bool, - pub icon: Option, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct WebViewAttr { - pub visible: bool, - pub transparent: bool, - pub spa: bool, - pub url: Option, - pub html: Option, - pub initialization_script: Option, -} - -impl File { - pub fn decompressed_data(&mut self) -> Result> { - let mut data = Vec::with_capacity(self.data.len()); - let mut r = brotli::Decompressor::new(self.data.as_slice(), 4096); - r.read_to_end(&mut data)?; - Ok(data) - } - pub fn mimetype(&self) -> String { - self.mime.clone() - } -} - -impl Data { - pub fn new + Copy>(path: P) -> Result { - let mut base = fs::File::open(path)?; - let base_length = base.metadata()?.len(); - let mut magic_number_start_data = [0; MAGIC_NUMBER_START.len()]; - let mut data_length_data = [0; USIZE_LEN]; - let mut data = Vec::new(); - let mut magic_number_end_data = [0; MAGIC_NUMBER_END.len()]; - base.seek(SeekFrom::Start(base_length - MAGIC_NUMBER_END.len() as u64))?; - // 此时指针指向 MAGIC_NUMBER_END 之前 - base.read_exact(&mut magic_number_end_data)?; - if &magic_number_end_data != MAGIC_NUMBER_END { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "MAGIC_NUMBER_END not found", - )); - } - base.seek(SeekFrom::Start( - base_length - MAGIC_NUMBER_END.len() as u64 - USIZE_LEN as u64, - ))?; - // 此时指针指向 data_length 之前 - base.read_exact(&mut data_length_data)?; - base.seek(SeekFrom::Start( - base_length - u64::from_be_bytes(data_length_data), - ))?; - // 此时指针指向 MAGIC_NUMBER_START - base.read_exact(&mut magic_number_start_data)?; - if &magic_number_start_data != MAGIC_NUMBER_START { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "MAGIC_NUMBER_START not found", - )); - } - base.read_exact(&mut data_length_data)?; - // 此时指针指向 Data 前 - base.take(u64::from_be_bytes(data_length_data)) - .read_to_end(&mut data)?; - let serialize_options = bincode::DefaultOptions::new() - .with_fixint_encoding() - .allow_trailing_bytes() - .with_limit(104857600 /* 100MiB */); - let fs: Self = match serialize_options.deserialize(&data) { - Ok(fs) => fs, - Err(e) => { - return Err(io::Error::new(io::ErrorKind::InvalidData, e)); - } - }; - Ok(fs) - } - - fn open_file(&self, current_dir: &Dir, mut path: path::Iter) -> Result { - let next_path = match path.next() { - Some(str) => str.to_string_lossy().to_string(), - None => return Err(io::Error::new(io::ErrorKind::NotFound, "file not found")), - }; - for (name, file) in ¤t_dir.files { - if next_path == *name { - return Ok(file.clone()); - } - } - for (name, dir) in ¤t_dir.dirs { - if next_path == *name { - return self.open_file(dir, path); - } - } - Err(io::Error::new(io::ErrorKind::NotFound, "file not found")) - } - - pub fn open>(&self, path: P) -> Result { - let path = normalize_path(path.as_ref()); - let path = if path.starts_with("/") { - path.strip_prefix("/") - .unwrap_or_else(|_| Path::new("")) - .to_path_buf() - } else { - path - }; - self.open_file(&self.fs, path.iter()) - } -} - -impl Default for Config { - fn default() -> Self { - Self { - source: PathBuf::from("."), - target: PathBuf::from("app.neu"), - inner_size: Some(WindowSize::Medium), - min_inner_size: None, - max_inner_size: None, - resizable: true, - fullscreen: false, - title: "".into(), - maximized: false, - visible: true, - transparent: false, - decorations: true, - always_on_top: false, - icon: None, - spa: false, - url: Some("/index.html".into()), - html: None, - initialization_script: None, - manifest: None, - } - } -} - -pub fn load + Copy>(path: P) -> Result { - Data::new(path) -} - -pub fn normalize_path(path: &Path) -> PathBuf { - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { - components.next(); - PathBuf::from(c.as_os_str()) - } else { - PathBuf::new() - }; - - for component in components { - match component { - Component::Prefix(..) => {} - Component::RootDir => { - ret.push(component.as_os_str()); - } - Component::CurDir => {} - Component::ParentDir => { - ret.pop(); - } - Component::Normal(c) => { - ret.push(c); - } - } - } - ret -} diff --git a/neutauri_runtime/src/main.rs b/neutauri_runtime/src/main.rs index 785615e..f06effa 100644 --- a/neutauri_runtime/src/main.rs +++ b/neutauri_runtime/src/main.rs @@ -1,7 +1,7 @@ #![windows_subsystem = "windows"] +use neutauri_data as data; use std::path::PathBuf; - use wry::{ application::{ dpi::{PhysicalSize, Size}, @@ -11,7 +11,6 @@ use wry::{ }, webview::{WebContext, WebViewBuilder}, }; -mod data; const PROTOCOL_PREFIX: &str = "{PROTOCOL}://"; const PROTOCOL: &str = "neu";