Browse Source

feat: add `AppHandle::remove_plugin` and plugin `on_drop`, closes #4361 (#4443)

Lucas Fernandes Nogueira 3 years ago
parent
commit
be4bb391a9
4 changed files with 160 additions and 5 deletions
  1. 5 0
      .changes/plugin-on-drop.md
  2. 5 0
      .changes/remove-plugin.md
  3. 101 2
      core/tauri/src/app.rs
  4. 49 3
      core/tauri/src/plugin.rs

+ 5 - 0
.changes/plugin-on-drop.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Added `on_drop` hook to the `plugin::Builder`.

+ 5 - 0
.changes/remove-plugin.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+Added `AppHandle::remove_plugin`.

+ 101 - 2
core/tauri/src/app.rs

@@ -410,7 +410,31 @@ impl<R: Runtime> AppHandle<R> {
     self.runtime_handle.remove_system_tray().map_err(Into::into)
   }
 
-  /// Adds a plugin to the runtime.
+  /// Adds a Tauri application plugin.
+  /// This function can be used to register a plugin that is loaded dynamically e.g. after login.
+  /// For plugins that are created when the app is started, prefer [`Builder::plugin`].
+  ///
+  /// See [`Builder::plugin`] for more information.
+  ///
+  /// # Examples
+  ///
+  /// ```
+  /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, Runtime};
+  ///
+  /// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {
+  ///   PluginBuilder::new("dummy").build()
+  /// }
+  ///
+  /// tauri::Builder::default()
+  ///   .setup(move |app| {
+  ///     let handle = app.handle();
+  ///     std::thread::spawn(move || {
+  ///       handle.plugin(init_plugin());
+  ///     });
+  ///
+  ///     Ok(())
+  ///   });
+  /// ```
   pub fn plugin<P: Plugin<R> + 'static>(&self, mut plugin: P) -> crate::Result<()> {
     plugin
       .initialize(
@@ -434,6 +458,41 @@ impl<R: Runtime> AppHandle<R> {
     Ok(())
   }
 
+  /// Removes the plugin with the given name.
+  ///
+  /// # Examples
+  ///
+  /// ```
+  /// use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin, Plugin}, Runtime};
+  ///
+  /// fn init_plugin<R: Runtime>() -> TauriPlugin<R> {
+  ///   PluginBuilder::new("dummy").build()
+  /// }
+  ///
+  /// let plugin = init_plugin();
+  /// // `.name()` requires the `PLugin` trait import
+  /// let plugin_name = plugin.name();
+  /// tauri::Builder::default()
+  ///   .plugin(plugin)
+  ///   .setup(move |app| {
+  ///     let handle = app.handle();
+  ///     std::thread::spawn(move || {
+  ///       handle.remove_plugin(plugin_name);
+  ///     });
+  ///
+  ///     Ok(())
+  ///   });
+  /// ```
+  pub fn remove_plugin(&self, plugin: &'static str) -> bool {
+    self
+      .manager()
+      .inner
+      .plugins
+      .lock()
+      .unwrap()
+      .unregister(plugin)
+  }
+
   /// Exits the app. This is the same as [`std::process::exit`], but it performs cleanup on this application.
   pub fn exit(&self, exit_code: i32) {
     self.cleanup_before_exit();
@@ -940,7 +999,47 @@ impl<R: Runtime> Builder<R> {
     self
   }
 
-  /// Adds a plugin to the runtime.
+  /// Adds a Tauri application plugin.
+  ///
+  /// A plugin is created using the [`crate::plugin::Builder`] struct.Check its documentation for more information.
+  ///
+  /// # Examples
+  ///
+  /// ```
+  /// mod plugin {
+  ///   use tauri::{plugin::{Builder as PluginBuilder, TauriPlugin}, RunEvent, Runtime};
+  ///
+  ///   // this command can be called in the frontend using `invoke('plugin:window|do_something')`.
+  ///   #[tauri::command]
+  ///   async fn do_something<R: Runtime>(app: tauri::AppHandle<R>, window: tauri::Window<R>) -> Result<(), String> {
+  ///     println!("command called");
+  ///     Ok(())
+  ///   }
+  ///   pub fn init<R: Runtime>() -> TauriPlugin<R> {
+  ///     PluginBuilder::new("window")
+  ///       .setup(|app| {
+  ///         // initialize the plugin here
+  ///         Ok(())
+  ///       })
+  ///       .on_event(|app, event| {
+  ///         match event {
+  ///           RunEvent::Ready => {
+  ///             println!("app is ready");
+  ///           }
+  ///           RunEvent::WindowEvent { label, event, .. } => {
+  ///             println!("window {} received an event: {:?}", label, event);
+  ///           }
+  ///           _ => (),
+  ///         }
+  ///       })
+  ///       .invoke_handler(tauri::generate_handler![do_something])
+  ///       .build()
+  ///   }
+  /// }
+  ///
+  /// tauri::Builder::default()
+  ///   .plugin(plugin::init());
+  /// ```
   #[must_use]
   pub fn plugin<P: Plugin<R> + 'static>(mut self, plugin: P) -> Self {
     self.plugins.register(plugin);

+ 49 - 3
core/tauri/src/plugin.rs

@@ -59,6 +59,7 @@ type SetupWithConfigHook<R, T> = dyn FnOnce(&AppHandle<R>, T) -> Result<()> + Se
 type OnWebviewReady<R> = dyn FnMut(Window<R>) + Send;
 type OnEvent<R> = dyn FnMut(&AppHandle<R>, &RunEvent) + Send;
 type OnPageLoad<R> = dyn FnMut(Window<R>, PageLoadPayload) + Send;
+type OnDrop<R> = dyn FnOnce(AppHandle<R>) + Send;
 
 /// Builds a [`TauriPlugin`].
 ///
@@ -141,6 +142,7 @@ pub struct Builder<R: Runtime, C: DeserializeOwned = ()> {
   on_page_load: Box<OnPageLoad<R>>,
   on_webview_ready: Box<OnWebviewReady<R>>,
   on_event: Box<OnEvent<R>>,
+  on_drop: Option<Box<OnDrop<R>>>,
 }
 
 impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
@@ -155,6 +157,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
       on_page_load: Box::new(|_, _| ()),
       on_webview_ready: Box::new(|_| ()),
       on_event: Box::new(|_, _| ()),
+      on_drop: None,
     }
   }
 
@@ -311,7 +314,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
   #[must_use]
   pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
   where
-    F: FnMut(Window<R>, PageLoadPayload) + Send + Sync + 'static,
+    F: FnMut(Window<R>, PageLoadPayload) + Send + 'static,
   {
     self.on_page_load = Box::new(on_page_load);
     self
@@ -335,7 +338,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
   #[must_use]
   pub fn on_webview_ready<F>(mut self, on_webview_ready: F) -> Self
   where
-    F: FnMut(Window<R>) + Send + Sync + 'static,
+    F: FnMut(Window<R>) + Send + 'static,
   {
     self.on_webview_ready = Box::new(on_webview_ready);
     self
@@ -367,16 +370,42 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
   #[must_use]
   pub fn on_event<F>(mut self, on_event: F) -> Self
   where
-    F: FnMut(&AppHandle<R>, &RunEvent) + Send + Sync + 'static,
+    F: FnMut(&AppHandle<R>, &RunEvent) + Send + 'static,
   {
     self.on_event = Box::new(on_event);
     self
   }
 
+  /// Callback invoked when the plugin is dropped.
+  ///
+  /// # Examples
+  ///
+  /// ```rust
+  /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime};
+  ///
+  /// fn init<R: Runtime>() -> TauriPlugin<R> {
+  ///   Builder::new("example")
+  ///     .on_drop(|app| {
+  ///       println!("plugin has been dropped and is no longer running");
+  ///       // you can run cleanup logic here
+  ///     })
+  ///     .build()
+  /// }
+  /// ```
+  #[must_use]
+  pub fn on_drop<F>(mut self, on_drop: F) -> Self
+  where
+    F: FnOnce(AppHandle<R>) + Send + 'static,
+  {
+    self.on_drop.replace(Box::new(on_drop));
+    self
+  }
+
   /// Builds the [TauriPlugin].
   pub fn build(self) -> TauriPlugin<R, C> {
     TauriPlugin {
       name: self.name,
+      app: None,
       invoke_handler: self.invoke_handler,
       setup: self.setup,
       setup_with_config: self.setup_with_config,
@@ -384,6 +413,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
       on_page_load: self.on_page_load,
       on_webview_ready: self.on_webview_ready,
       on_event: self.on_event,
+      on_drop: self.on_drop,
     }
   }
 }
@@ -391,6 +421,7 @@ impl<R: Runtime, C: DeserializeOwned> Builder<R, C> {
 /// Plugin struct that is returned by the [`Builder`]. Should only be constructed through the builder.
 pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
   name: &'static str,
+  app: Option<AppHandle<R>>,
   invoke_handler: Box<InvokeHandler<R>>,
   setup: Option<Box<SetupHook<R>>>,
   setup_with_config: Option<Box<SetupWithConfigHook<R, C>>>,
@@ -398,6 +429,15 @@ pub struct TauriPlugin<R: Runtime, C: DeserializeOwned = ()> {
   on_page_load: Box<OnPageLoad<R>>,
   on_webview_ready: Box<OnWebviewReady<R>>,
   on_event: Box<OnEvent<R>>,
+  on_drop: Option<Box<OnDrop<R>>>,
+}
+
+impl<R: Runtime, C: DeserializeOwned> Drop for TauriPlugin<R, C> {
+  fn drop(&mut self) {
+    if let (Some(on_drop), Some(app)) = (self.on_drop.take(), self.app.take()) {
+      on_drop(app);
+    }
+  }
 }
 
 impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
@@ -406,6 +446,7 @@ impl<R: Runtime, C: DeserializeOwned> Plugin<R> for TauriPlugin<R, C> {
   }
 
   fn initialize(&mut self, app: &AppHandle<R>, config: JsonValue) -> Result<()> {
+    self.app.replace(app.clone());
     if let Some(s) = self.setup.take() {
       (s)(app)?;
     }
@@ -466,6 +507,11 @@ impl<R: Runtime> PluginStore<R> {
     self.store.insert(plugin.name(), Box::new(plugin)).is_some()
   }
 
+  /// Removes the plugin with the given name from the store.
+  pub fn unregister(&mut self, plugin: &'static str) -> bool {
+    self.store.remove(plugin).is_some()
+  }
+
   /// Initializes all plugins in the store.
   pub(crate) fn initialize(
     &mut self,