Skip to main content

tauri_plugin_ota_self_update/
lib.rs

1use std::{
2  borrow::Cow,
3  cell::OnceCell,
4  collections::HashMap,
5  sync::{Arc, Mutex},
6};
7
8use serde::{Deserialize, Deserializer};
9use tauri::{
10  plugin::{Builder, TauriPlugin},
11  utils::assets::{AssetKey, CspHash},
12  App, Assets, Context, Manager, Runtime,
13};
14
15pub use models::*;
16
17mod commands;
18mod core;
19mod error;
20mod models;
21mod runtime;
22
23pub use error::{Error, Result};
24
25use runtime::OtaSelfUpdate;
26
27const DEFAULT_CHANNEL: &str = "stable";
28
29#[derive(Clone, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct Config {
32  pub base_url: String,
33  pub pubkey: String,
34  #[serde(default, deserialize_with = "channel_deserializer")]
35  pub channel: Option<String>,
36  #[serde(default)]
37  pub request_headers: HashMap<String, String>,
38  #[serde(default)]
39  pub timeout_secs: Option<u64>,
40  #[serde(default)]
41  pub activation_policy: ActivationPolicy,
42}
43
44fn channel_deserializer<'de, D>(deserializer: D) -> std::result::Result<Option<String>, D::Error>
45where
46  D: Deserializer<'de>,
47{
48  let s = Option::<String>::deserialize(deserializer)?;
49  Ok(match s {
50    Some(channel) if channel == DEFAULT_CHANNEL => None,
51    Some(channel) if channel.is_empty() => None,
52    other => other,
53  })
54}
55
56/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the ota-self-update APIs.
57pub trait OtaSelfUpdateExt<R: Runtime> {
58  fn ota_self_update(&self) -> &OtaSelfUpdate<R>;
59}
60
61impl<R: Runtime, T: Manager<R>> crate::OtaSelfUpdateExt<R> for T {
62  fn ota_self_update(&self) -> &OtaSelfUpdate<R> {
63    self.state::<OtaSelfUpdate<R>>().inner()
64  }
65}
66
67pub(crate) struct OtaAssets<R: Runtime> {
68  overlay_assets: Arc<Mutex<HashMap<AssetKey, Vec<u8>>>>,
69  embedded_assets: OnceCell<Box<dyn Assets<R>>>,
70  csp_hashes: Vec<CspHash<'static>>,
71}
72
73unsafe impl<R: Runtime> Sync for OtaAssets<R> {}
74
75impl<R: Runtime> Assets<R> for OtaAssets<R> {
76  fn setup(&self, app: &App<R>) {
77    let ota = app.state::<OtaSelfUpdate<R>>();
78    self.embedded_assets.get_or_init(|| {
79      let assets = ota.embedded_assets.lock().unwrap().take().unwrap();
80      assets.setup(app);
81      assets
82    });
83  }
84
85  fn csp_hashes(&self, _html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
86    Box::new(self.csp_hashes.iter().copied())
87  }
88
89  fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
90    self
91      .overlay_assets
92      .lock()
93      .unwrap()
94      .get(key)
95      .map(|bytes| Cow::Owned(bytes.clone()))
96      .or_else(|| self.embedded_assets.get().unwrap().get(key))
97  }
98
99  fn iter(&self) -> Box<dyn Iterator<Item = (Cow<'_, str>, Cow<'_, [u8]>)> + '_> {
100    Box::new(
101      self
102        .overlay_assets
103        .lock()
104        .unwrap()
105        .clone()
106        .into_iter()
107        .map(|(k, v)| (Cow::Owned(k.as_ref().to_string()), Cow::Owned(v))),
108    )
109  }
110}
111
112pub fn init<R: Runtime>(context: Context<R>) -> (TauriPlugin<R, Config>, Context<R>) {
113  let overlay_assets = Arc::new(Mutex::new(HashMap::<AssetKey, Vec<u8>>::new()));
114  let mut context = context;
115  let embedded_assets = context.set_assets(Box::new(OtaAssets {
116    overlay_assets: overlay_assets.clone(),
117    embedded_assets: Default::default(),
118    csp_hashes: Default::default(),
119  }));
120
121  let plugin = Builder::<R, Config>::new("ota-self-update")
122    .invoke_handler(tauri::generate_handler![
123      commands::check_for_updates,
124      commands::apply_update,
125      commands::set_channel,
126      commands::get_current_version
127    ])
128    .setup(move |app, api| {
129      let ota_self_update = runtime::init(app, api, overlay_assets.clone(), embedded_assets)?;
130      app.manage(ota_self_update);
131      Ok(())
132    })
133    .build();
134  (plugin, context)
135}