瀏覽代碼

refactor(core): add setup() to the Assets trait (#9147)

* feat(core): allow swapping the assets implemenetation

* refactor(core): add setup() to the Assets trait

* code review
Lucas Fernandes Nogueira 1 年之前
父節點
當前提交
490a6b424e

+ 5 - 0
.changes/assets-setup.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch:feat
+---
+
+The `Assets` trait now include a `setup` method that lets you run initialization code for your custom asset provider.

+ 5 - 0
.changes/context-assets-runtime-generic.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch:breaking
+---
+
+The `Context` struct and the `Assets` trait now takes a `R: Runtime` generic.

+ 5 - 0
.changes/context-remove-assets-mut.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch:breaking
+---
+
+Removed `Context::assets_mut` and added `Context::set_assets`.

+ 5 - 0
.changes/utils-remove-asset-trait.md

@@ -0,0 +1,5 @@
+---
+"tauri-utils": patch:breaking
+---
+
+Removed the `assets::Assets` trait which is now part of the `tauri` crate.

+ 2 - 2
core/tauri-codegen/src/context.rs

@@ -345,10 +345,10 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
   let info_plist = quote!(());
 
   let pattern = match &options.pattern {
-    PatternKind::Brownfield => quote!(#root::Pattern::Brownfield(std::marker::PhantomData)),
+    PatternKind::Brownfield => quote!(#root::Pattern::Brownfield),
     #[cfg(not(feature = "isolation"))]
     PatternKind::Isolation { dir: _ } => {
-      quote!(#root::Pattern::Brownfield(std::marker::PhantomData))
+      quote!(#root::Pattern::Brownfield)
     }
     #[cfg(feature = "isolation")]
     PatternKind::Isolation { dir } => {

+ 1 - 1
core/tauri-plugin/Cargo.toml

@@ -30,7 +30,7 @@ runtime = [ ]
 [dependencies]
 anyhow = { version = "1", optional = true }
 serde = { version = "1", optional = true }
-tauri-utils = { version = "2.0.0-beta.8", default-features = false, path = "../tauri-utils" }
+tauri-utils = { version = "2.0.0-beta.8", default-features = false, features = [ "build" ], path = "../tauri-utils" }
 serde_json = { version = "1", optional = true }
 glob = { version = "0.3", optional = true }
 toml = { version = "0.8", optional = true }

+ 9 - 19
core/tauri-utils/src/assets.rs

@@ -104,18 +104,6 @@ impl CspHash<'_> {
   }
 }
 
-/// Represents a container of file assets that are retrievable during runtime.
-pub trait Assets: Send + Sync + 'static {
-  /// Get the content of the passed [`AssetKey`].
-  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>>;
-
-  /// Iterator for the assets.
-  fn iter(&self) -> Box<dyn Iterator<Item = (&&str, &&[u8])> + '_>;
-
-  /// Gets the hashes for the CSP tag of the HTML on the given path.
-  fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_>;
-}
-
 /// [`Assets`] implementation that only contains compile-time compressed and embedded assets.
 #[derive(Debug)]
 pub struct EmbeddedAssets {
@@ -139,11 +127,10 @@ impl EmbeddedAssets {
       html_hashes,
     }
   }
-}
 
-impl Assets for EmbeddedAssets {
+  /// Get an asset by key.
   #[cfg(feature = "compression")]
-  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
+  pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
     self
       .assets
       .get(key.as_ref())
@@ -157,8 +144,9 @@ impl Assets for EmbeddedAssets {
       .map(Cow::Owned)
   }
 
+  /// Get an asset by key.
   #[cfg(not(feature = "compression"))]
-  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
+  pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
     self
       .assets
       .get(key.as_ref())
@@ -166,11 +154,13 @@ impl Assets for EmbeddedAssets {
       .map(|a| Cow::Owned(a.to_vec()))
   }
 
-  fn iter(&self) -> Box<dyn Iterator<Item = (&&str, &&[u8])> + '_> {
-    Box::new(self.assets.into_iter())
+  /// Iterate on the assets.
+  pub fn iter(&self) -> Box<dyn Iterator<Item = (&str, &[u8])> + '_> {
+    Box::new(self.assets.into_iter().map(|(k, b)| (*k, *b)))
   }
 
-  fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
+  /// CSP hashes for the given asset.
+  pub fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
     Box::new(
       self
         .global_hashes

+ 5 - 3
core/tauri/src/app.rs

@@ -266,7 +266,7 @@ impl<R: Runtime> AssetResolver<R> {
   }
 
   /// Iterate on all assets.
-  pub fn iter(&self) -> Box<dyn Iterator<Item = (&&str, &&[u8])> + '_> {
+  pub fn iter(&self) -> Box<dyn Iterator<Item = (&str, &[u8])> + '_> {
     self.manager.assets.iter()
   }
 }
@@ -1581,7 +1581,7 @@ tauri::Builder::default()
     feature = "tracing",
     tracing::instrument(name = "app::build", skip_all)
   )]
-  pub fn build(mut self, context: Context) -> crate::Result<App<R>> {
+  pub fn build(mut self, context: Context<R>) -> crate::Result<App<R>> {
     #[cfg(target_os = "macos")]
     if self.menu.is_none() && self.enable_macos_default_menu {
       self.menu = Some(Box::new(|app_handle| {
@@ -1749,7 +1749,7 @@ tauri::Builder::default()
   }
 
   /// Runs the configured Tauri application.
-  pub fn run(self, context: Context) -> crate::Result<()> {
+  pub fn run(self, context: Context<R>) -> crate::Result<()> {
     self.build(context)?.run(|_, _| {});
     Ok(())
   }
@@ -1824,6 +1824,8 @@ fn setup<R: Runtime>(app: &mut App<R>) -> crate::Result<()> {
       .build_internal(&window_labels, &webview_labels)?;
   }
 
+  app.manager.assets.setup(app);
+
   if let Some(setup) = app.setup.take() {
     (setup)(app).map_err(|e| crate::Error::Setup(e.into()))?;
   }

+ 44 - 10
core/tauri/src/lib.rs

@@ -192,10 +192,12 @@ pub type SyncTask = Box<dyn FnOnce() + Send>;
 
 use serde::Serialize;
 use std::{
+  borrow::Cow,
   collections::HashMap,
   fmt::{self, Debug},
   sync::MutexGuard,
 };
+use utils::assets::{AssetKey, CspHash, EmbeddedAssets};
 
 #[cfg(feature = "wry")]
 #[cfg_attr(docsrs, doc(cfg(feature = "wry")))]
@@ -224,7 +226,6 @@ pub use {
   },
   self::state::{State, StateManager},
   self::utils::{
-    assets::Assets,
     config::{Config, WebviewUrl},
     Env, PackageInfo, Theme,
   },
@@ -338,14 +339,47 @@ pub fn dev() -> bool {
   !cfg!(feature = "custom-protocol")
 }
 
+/// Represents a container of file assets that are retrievable during runtime.
+pub trait Assets<R: Runtime>: Send + Sync + 'static {
+  /// Initialize the asset provider.
+  fn setup(&self, app: &App<R>) {
+    let _ = app;
+  }
+
+  /// Get the content of the passed [`AssetKey`].
+  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>>;
+
+  /// Iterator for the assets.
+  fn iter(&self) -> Box<dyn Iterator<Item = (&str, &[u8])> + '_>;
+
+  /// Gets the hashes for the CSP tag of the HTML on the given path.
+  fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_>;
+}
+
+impl<R: Runtime> Assets<R> for EmbeddedAssets {
+  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
+    EmbeddedAssets::get(self, key)
+  }
+
+  fn iter(&self) -> Box<dyn Iterator<Item = (&str, &[u8])> + '_> {
+    EmbeddedAssets::iter(self)
+  }
+
+  fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
+    EmbeddedAssets::csp_hashes(self, html_path)
+  }
+}
+
 /// User supplied data required inside of a Tauri application.
 ///
 /// # Stability
 /// This is the output of the [`generate_context`] macro, and is not considered part of the stable API.
 /// Unless you know what you are doing and are prepared for this type to have breaking changes, do not create it yourself.
-pub struct Context {
+#[tauri_macros::default_runtime(Wry, wry)]
+pub struct Context<R: Runtime> {
   pub(crate) config: Config,
-  pub(crate) assets: Box<dyn Assets>,
+  /// Asset provider.
+  pub assets: Box<dyn Assets<R>>,
   pub(crate) default_window_icon: Option<image::Image<'static>>,
   pub(crate) app_icon: Option<Vec<u8>>,
   #[cfg(all(desktop, feature = "tray-icon"))]
@@ -356,7 +390,7 @@ pub struct Context {
   pub(crate) runtime_authority: RuntimeAuthority,
 }
 
-impl fmt::Debug for Context {
+impl<R: Runtime> fmt::Debug for Context<R> {
   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     let mut d = f.debug_struct("Context");
     d.field("config", &self.config)
@@ -372,7 +406,7 @@ impl fmt::Debug for Context {
   }
 }
 
-impl Context {
+impl<R: Runtime> Context<R> {
   /// The config the application was prepared with.
   #[inline(always)]
   pub fn config(&self) -> &Config {
@@ -387,14 +421,14 @@ impl Context {
 
   /// The assets to be served directly by Tauri.
   #[inline(always)]
-  pub fn assets(&self) -> &dyn Assets {
+  pub fn assets(&self) -> &dyn Assets<R> {
     self.assets.as_ref()
   }
 
-  /// A mutable reference to the assets to be served directly by Tauri.
+  /// Replace the [`Assets`] implementation and returns the previous value so you can use it as a fallback if desired.
   #[inline(always)]
-  pub fn assets_mut(&mut self) -> &mut Box<dyn Assets> {
-    &mut self.assets
+  pub fn set_assets(&mut self, assets: Box<dyn Assets<R>>) -> Box<dyn Assets<R>> {
+    std::mem::replace(&mut self.assets, assets)
   }
 
   /// The default window icon Tauri should use when creating windows.
@@ -459,7 +493,7 @@ impl Context {
   #[allow(clippy::too_many_arguments)]
   pub fn new(
     config: Config,
-    assets: Box<dyn Assets>,
+    assets: Box<dyn Assets<R>>,
     default_window_icon: Option<image::Image<'static>>,
     app_icon: Option<Vec<u8>>,
     package_info: PackageInfo,

+ 5 - 5
core/tauri/src/manager/mod.rs

@@ -24,8 +24,8 @@ use crate::{
   event::{assert_event_name_is_valid, Event, EventId, EventTarget, Listeners},
   ipc::{Invoke, InvokeHandler, InvokeResponder, RuntimeAuthority},
   plugin::PluginStore,
-  utils::{assets::Assets, config::Config, PackageInfo},
-  Context, Pattern, Runtime, StateManager, Window,
+  utils::{config::Config, PackageInfo},
+  Assets, Context, Pattern, Runtime, StateManager, Window,
 };
 use crate::{event::EmitArgs, resources::ResourceTable, Webview};
 
@@ -48,7 +48,7 @@ struct CspHashStrings {
 #[allow(clippy::borrowed_box)]
 pub(crate) fn set_csp<R: Runtime>(
   asset: &mut String,
-  assets: &impl std::borrow::Borrow<dyn Assets>,
+  assets: &impl std::borrow::Borrow<dyn Assets<R>>,
   asset_path: &AssetKey,
   manager: &AppManager<R>,
   csp: Csp,
@@ -179,7 +179,7 @@ pub struct AppManager<R: Runtime> {
   pub listeners: Listeners,
   pub state: Arc<StateManager>,
   pub config: Config,
-  pub assets: Box<dyn Assets>,
+  pub assets: Box<dyn Assets<R>>,
 
   pub app_icon: Option<Vec<u8>>,
 
@@ -216,7 +216,7 @@ impl<R: Runtime> fmt::Debug for AppManager<R> {
 impl<R: Runtime> AppManager<R> {
   #[allow(clippy::too_many_arguments, clippy::type_complexity)]
   pub(crate) fn with_handlers(
-    #[allow(unused_mut)] mut context: Context,
+    #[allow(unused_mut)] mut context: Context<R>,
     plugins: PluginStore<R>,
     invoke_handler: Box<InvokeHandler<R>>,
     on_page_load: Option<Arc<OnPageLoad<R>>>,

+ 4 - 7
core/tauri/src/pattern.rs

@@ -2,28 +2,25 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
-use std::marker::PhantomData;
 #[cfg(feature = "isolation")]
 use std::sync::Arc;
 
 use serde::Serialize;
 use serialize_to_javascript::{default_template, Template};
 
-use tauri_utils::assets::{Assets, EmbeddedAssets};
-
 /// The domain of the isolation iframe source.
 pub const ISOLATION_IFRAME_SRC_DOMAIN: &str = "localhost";
 
 /// An application pattern.
 #[derive(Debug)]
-pub enum Pattern<A: Assets = EmbeddedAssets> {
+pub enum Pattern {
   /// The brownfield pattern.
-  Brownfield(PhantomData<A>),
+  Brownfield,
   /// Isolation pattern. Recommended for security purposes.
   #[cfg(feature = "isolation")]
   Isolation {
     /// The HTML served on `isolation://index.html`.
-    assets: Arc<A>,
+    assets: Arc<tauri_utils::assets::EmbeddedAssets>,
 
     /// The schema used for the isolation frames.
     schema: String,
@@ -55,7 +52,7 @@ pub(crate) enum PatternObject {
 impl From<&Pattern> for PatternObject {
   fn from(pattern: &Pattern) -> Self {
     match pattern {
-      Pattern::Brownfield(_) => Self::Brownfield,
+      Pattern::Brownfield => Self::Brownfield,
       #[cfg(feature = "isolation")]
       Pattern::Isolation { .. } => Self::Isolation {
         side: IsolationSide::default(),

+ 3 - 5
core/tauri/src/protocol/isolation.rs

@@ -2,12 +2,10 @@
 // SPDX-License-Identifier: Apache-2.0
 // SPDX-License-Identifier: MIT
 
+use crate::Assets;
 use http::header::CONTENT_TYPE;
 use serialize_to_javascript::Template;
-use tauri_utils::{
-  assets::{Assets, EmbeddedAssets},
-  config::Csp,
-};
+use tauri_utils::{assets::EmbeddedAssets, config::Csp};
 
 use std::sync::Arc;
 
@@ -29,7 +27,7 @@ pub fn get<R: Runtime>(
     format!("{schema}:")
   };
 
-  let assets = assets as Arc<dyn Assets>;
+  let assets = assets as Arc<dyn Assets<R>>;
 
   Box::new(move |request, responder| {
     let response = match request_to_path(&request).as_str() {

+ 8 - 8
core/tauri/src/test/mod.rs

@@ -57,27 +57,27 @@ use std::{borrow::Cow, collections::HashMap, fmt::Debug};
 use crate::{
   ipc::{InvokeBody, InvokeError, InvokeResponse, RuntimeAuthority},
   webview::InvokeRequest,
-  App, Builder, Context, Pattern, Webview,
+  App, Assets, Builder, Context, Pattern, Runtime, Webview,
 };
 use tauri_utils::{
   acl::resolved::Resolved,
-  assets::{AssetKey, Assets, CspHash},
+  assets::{AssetKey, CspHash},
   config::{AppConfig, Config},
 };
 
 /// An empty [`Assets`] implementation.
 pub struct NoopAsset {
-  assets: HashMap<&'static str, &'static [u8]>,
+  assets: HashMap<String, Vec<u8>>,
   csp_hashes: Vec<CspHash<'static>>,
 }
 
-impl Assets for NoopAsset {
+impl<R: Runtime> Assets<R> for NoopAsset {
   fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
     None
   }
 
-  fn iter(&self) -> Box<dyn Iterator<Item = (&&str, &&[u8])> + '_> {
-    Box::new(self.assets.iter())
+  fn iter(&self) -> Box<dyn Iterator<Item = (&str, &[u8])> + '_> {
+    Box::new(self.assets.iter().map(|(k, b)| (k.as_str(), b.as_slice())))
   }
 
   fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
@@ -94,7 +94,7 @@ pub fn noop_assets() -> NoopAsset {
 }
 
 /// Creates a new [`crate::Context`] for testing.
-pub fn mock_context<A: Assets>(assets: A) -> crate::Context {
+pub fn mock_context<R: Runtime, A: Assets<R>>(assets: A) -> crate::Context<R> {
   Context {
     config: Config {
       schema: None,
@@ -125,7 +125,7 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context {
       crate_name: "test",
     },
     _info_plist: (),
-    pattern: Pattern::Brownfield(std::marker::PhantomData),
+    pattern: Pattern::Brownfield,
     runtime_authority: RuntimeAuthority::new(Default::default(), Resolved::default()),
   }
 }