mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-06-06 13:53:54 +02:00
Merge remote-tracking branch 'origin/dev' into next
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-authenticator"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Use hardware security-keys in your Tauri App."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
@@ -18,7 +18,7 @@ thiserror.workspace = true
|
||||
authenticator = "0.3.1"
|
||||
once_cell = "1"
|
||||
sha2 = "0.10"
|
||||
base64 = { version = "^0.13" }
|
||||
base64 = "0.21"
|
||||
u2f = "0.2"
|
||||
chrono = "0.4"
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -6,7 +6,7 @@ use authenticator::{
|
||||
authenticatorservice::AuthenticatorService, statecallback::StateCallback,
|
||||
AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate,
|
||||
};
|
||||
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Serialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
@@ -75,9 +75,9 @@ pub fn register(application: String, timeout: u64, challenge: String) -> crate::
|
||||
|
||||
let (key_handle, public_key) =
|
||||
_u2f_get_key_handle_and_public_key_from_register_response(®ister_data).unwrap();
|
||||
let key_handle_base64 = encode_config(key_handle, URL_SAFE_NO_PAD);
|
||||
let public_key_base64 = encode_config(public_key, URL_SAFE_NO_PAD);
|
||||
let register_data_base64 = encode_config(®ister_data, URL_SAFE_NO_PAD);
|
||||
let key_handle_base64 = URL_SAFE_NO_PAD.encode(key_handle);
|
||||
let public_key_base64 = URL_SAFE_NO_PAD.encode(public_key);
|
||||
let register_data_base64 = URL_SAFE_NO_PAD.encode(®ister_data);
|
||||
println!("Key Handle: {}", &key_handle_base64);
|
||||
println!("Public Key: {}", &public_key_base64);
|
||||
|
||||
@@ -108,7 +108,7 @@ pub fn sign(
|
||||
challenge: String,
|
||||
key_handle: String,
|
||||
) -> crate::Result<String> {
|
||||
let credential = match decode_config(key_handle, URL_SAFE_NO_PAD) {
|
||||
let credential = match URL_SAFE_NO_PAD.decode(key_handle) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(e.into());
|
||||
@@ -152,19 +152,16 @@ pub fn sign(
|
||||
|
||||
let (_, handle_used, sign_data, device_info) = sign_result.unwrap();
|
||||
|
||||
let sig = encode_config(sign_data, URL_SAFE_NO_PAD);
|
||||
let sig = URL_SAFE_NO_PAD.encode(sign_data);
|
||||
|
||||
println!("Sign result: {sig}");
|
||||
println!(
|
||||
"Key handle used: {}",
|
||||
encode_config(&handle_used, URL_SAFE_NO_PAD)
|
||||
);
|
||||
println!("Key handle used: {}", URL_SAFE_NO_PAD.encode(&handle_used));
|
||||
println!("Device info: {}", &device_info);
|
||||
println!("Done.");
|
||||
|
||||
let res = serde_json::to_string(&Signature {
|
||||
sign_data: sig,
|
||||
key_handle: encode_config(&handle_used, URL_SAFE_NO_PAD),
|
||||
key_handle: URL_SAFE_NO_PAD.encode(&handle_used),
|
||||
})?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
||||
use chrono::prelude::*;
|
||||
use serde::Serialize;
|
||||
use std::convert::Into;
|
||||
@@ -15,7 +15,7 @@ static VERSION: &str = "U2F_V2";
|
||||
pub fn make_challenge(app_id: &str, challenge_bytes: Vec<u8>) -> Challenge {
|
||||
let utc: DateTime<Utc> = Utc::now();
|
||||
Challenge {
|
||||
challenge: encode_config(challenge_bytes, URL_SAFE_NO_PAD),
|
||||
challenge: URL_SAFE_NO_PAD.encode(challenge_bytes),
|
||||
timestamp: format!("{utc:?}"),
|
||||
app_id: app_id.to_string(),
|
||||
}
|
||||
@@ -35,10 +35,10 @@ pub fn verify_registration(
|
||||
register_data: String,
|
||||
client_data: String,
|
||||
) -> crate::Result<String> {
|
||||
let challenge_bytes = decode_config(challenge, URL_SAFE_NO_PAD)?;
|
||||
let challenge_bytes = URL_SAFE_NO_PAD.decode(challenge)?;
|
||||
let challenge = make_challenge(&app_id, challenge_bytes);
|
||||
let client_data_bytes: Vec<u8> = client_data.as_bytes().into();
|
||||
let client_data_base64 = encode_config(client_data_bytes, URL_SAFE_NO_PAD);
|
||||
let client_data_base64 = URL_SAFE_NO_PAD.encode(client_data_bytes);
|
||||
let client = U2f::new(app_id);
|
||||
match client.register_response(
|
||||
challenge,
|
||||
@@ -50,8 +50,8 @@ pub fn verify_registration(
|
||||
) {
|
||||
Ok(v) => {
|
||||
let rv = RegistrationVerification {
|
||||
key_handle: encode_config(&v.key_handle, URL_SAFE_NO_PAD),
|
||||
pubkey: encode_config(&v.pub_key, URL_SAFE_NO_PAD),
|
||||
key_handle: URL_SAFE_NO_PAD.encode(&v.key_handle),
|
||||
pubkey: URL_SAFE_NO_PAD.encode(&v.pub_key),
|
||||
device_name: v.device_name,
|
||||
};
|
||||
Ok(serde_json::to_string(&rv)?)
|
||||
@@ -74,12 +74,12 @@ pub fn verify_signature(
|
||||
key_handle: String,
|
||||
pub_key: String,
|
||||
) -> crate::Result<u32> {
|
||||
let challenge_bytes = decode_config(challenge, URL_SAFE_NO_PAD)?;
|
||||
let challenge_bytes = URL_SAFE_NO_PAD.decode(challenge)?;
|
||||
let chal = make_challenge(&app_id, challenge_bytes);
|
||||
let client_data_bytes: Vec<u8> = client_data.as_bytes().into();
|
||||
let client_data_base64 = encode_config(client_data_bytes, URL_SAFE_NO_PAD);
|
||||
let key_handle_bytes = decode_config(&key_handle, URL_SAFE_NO_PAD)?;
|
||||
let pubkey_bytes = decode_config(pub_key, URL_SAFE_NO_PAD)?;
|
||||
let client_data_base64 = URL_SAFE_NO_PAD.encode(client_data_bytes);
|
||||
let key_handle_bytes = URL_SAFE_NO_PAD.decode(&key_handle)?;
|
||||
let pubkey_bytes = URL_SAFE_NO_PAD.decode(pub_key)?;
|
||||
let client = U2f::new(app_id);
|
||||
let mut _counter: u32 = 0;
|
||||
match client.sign_response(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-autostart"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Automatically launch your application at startup."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-cli"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-clipboard"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-fs-watch"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Watch files and directories for changes."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Access the file system."
|
||||
edition = "2021"
|
||||
#authors.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-global-shortcut"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-http"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
#edition.workspace = true
|
||||
#authors.workspace = true
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-log"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Configurable logging for your Tauri app."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
@@ -19,7 +19,7 @@ tauri.workspace = true
|
||||
serde_repr = "0.1"
|
||||
byte-unit = "4.0"
|
||||
log = { workspace = true, features = ["kv_unstable"] }
|
||||
time = { version = "0.3", features = ["formatting"] }
|
||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
fern = "0.6"
|
||||
|
||||
[target."cfg(target_os = \"android\")".dependencies]
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
+48
-3
@@ -21,6 +21,7 @@ use tauri::{
|
||||
};
|
||||
|
||||
pub use fern;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
mod ios {
|
||||
@@ -56,6 +57,7 @@ mod ios {
|
||||
|
||||
const DEFAULT_MAX_FILE_SIZE: u128 = 40000;
|
||||
const DEFAULT_ROTATION_STRATEGY: RotationStrategy = RotationStrategy::KeepOne;
|
||||
const DEFAULT_TIMEZONE_STRATEGY: TimezoneStrategy = TimezoneStrategy::UseUtc;
|
||||
const DEFAULT_LOG_TARGETS: [LogTarget; 2] = [LogTarget::Stdout, LogTarget::LogDir];
|
||||
|
||||
/// An enum representing the available verbosity levels of the logger.
|
||||
@@ -115,6 +117,23 @@ pub enum RotationStrategy {
|
||||
KeepOne,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TimezoneStrategy {
|
||||
UseUtc,
|
||||
UseLocal,
|
||||
}
|
||||
|
||||
impl TimezoneStrategy {
|
||||
pub fn get_now(&self) -> OffsetDateTime {
|
||||
match self {
|
||||
TimezoneStrategy::UseUtc => OffsetDateTime::now_utc(),
|
||||
TimezoneStrategy::UseLocal => {
|
||||
OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc())
|
||||
} // Fallback to UTC since Rust cannot determine local timezone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
struct RecordPayload {
|
||||
message: String,
|
||||
@@ -177,6 +196,7 @@ fn log(
|
||||
pub struct Builder {
|
||||
dispatch: fern::Dispatch,
|
||||
rotation_strategy: RotationStrategy,
|
||||
timezone_strategy: TimezoneStrategy,
|
||||
max_file_size: u128,
|
||||
targets: Vec<LogTarget>,
|
||||
}
|
||||
@@ -194,7 +214,7 @@ impl Default for Builder {
|
||||
#[cfg(desktop)]
|
||||
format_args!(
|
||||
"{}[{}][{}] {}",
|
||||
time::OffsetDateTime::now_utc().format(&format).unwrap(),
|
||||
DEFAULT_TIMEZONE_STRATEGY.get_now().format(&format).unwrap(),
|
||||
record.target(),
|
||||
record.level(),
|
||||
message
|
||||
@@ -204,6 +224,7 @@ impl Default for Builder {
|
||||
Self {
|
||||
dispatch,
|
||||
rotation_strategy: DEFAULT_ROTATION_STRATEGY,
|
||||
timezone_strategy: DEFAULT_TIMEZONE_STRATEGY,
|
||||
max_file_size: DEFAULT_MAX_FILE_SIZE,
|
||||
targets: DEFAULT_LOG_TARGETS.into(),
|
||||
}
|
||||
@@ -220,6 +241,24 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn timezone_strategy(mut self, timezone_strategy: TimezoneStrategy) -> Self {
|
||||
self.timezone_strategy = timezone_strategy.clone();
|
||||
|
||||
let format =
|
||||
time::format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]")
|
||||
.unwrap();
|
||||
self.dispatch = fern::Dispatch::new().format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}[{}][{}] {}",
|
||||
timezone_strategy.get_now().format(&format).unwrap(),
|
||||
record.target(),
|
||||
record.level(),
|
||||
message
|
||||
))
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_file_size(mut self, max_file_size: u128) -> Self {
|
||||
self.max_file_size = max_file_size;
|
||||
self
|
||||
@@ -266,10 +305,12 @@ impl Builder {
|
||||
let format =
|
||||
time::format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]")
|
||||
.unwrap();
|
||||
|
||||
let timezone_strategy = self.timezone_strategy.clone();
|
||||
self.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}[{}][{}] {}",
|
||||
time::OffsetDateTime::now_utc().format(&format).unwrap(),
|
||||
timezone_strategy.get_now().format(&format).unwrap(),
|
||||
record.target(),
|
||||
colors.color(record.level()),
|
||||
message
|
||||
@@ -319,6 +360,7 @@ impl Builder {
|
||||
&path,
|
||||
app_name,
|
||||
&self.rotation_strategy,
|
||||
&self.timezone_strategy,
|
||||
self.max_file_size,
|
||||
)?)?
|
||||
.into()
|
||||
@@ -336,6 +378,7 @@ impl Builder {
|
||||
&path,
|
||||
app_name,
|
||||
&self.rotation_strategy,
|
||||
&self.timezone_strategy,
|
||||
self.max_file_size,
|
||||
)?)?
|
||||
.into()
|
||||
@@ -370,6 +413,7 @@ fn get_log_file_path(
|
||||
dir: &impl AsRef<Path>,
|
||||
app_name: &str,
|
||||
rotation_strategy: &RotationStrategy,
|
||||
timezone_strategy: &TimezoneStrategy,
|
||||
max_file_size: u128,
|
||||
) -> plugin::Result<PathBuf> {
|
||||
let path = dir.as_ref().join(format!("{app_name}.log"));
|
||||
@@ -382,7 +426,8 @@ fn get_log_file_path(
|
||||
let to = dir.as_ref().join(format!(
|
||||
"{}_{}.log",
|
||||
app_name,
|
||||
time::OffsetDateTime::now_utc()
|
||||
timezone_strategy
|
||||
.get_now()
|
||||
.format(
|
||||
&time::format_description::parse(
|
||||
"[year]-[month]-[day]_[hour]-[minute]-[second]"
|
||||
|
||||
@@ -15,6 +15,7 @@ serde_json.workspace = true
|
||||
tauri.workspace = true
|
||||
log.workspace = true
|
||||
thiserror.workspace = true
|
||||
aho-corasick = "1.0"
|
||||
bincode = "1"
|
||||
|
||||
[features]
|
||||
|
||||
@@ -2,19 +2,34 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use aho_corasick::AhoCorasick;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
FsScopeEvent, Manager, Runtime,
|
||||
AppHandle, FsScopeEvent, Manager, Runtime,
|
||||
};
|
||||
|
||||
use std::{
|
||||
fs::{create_dir_all, File},
|
||||
io::Write,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
const SCOPE_STATE_FILENAME: &str = ".persisted-scope";
|
||||
|
||||
// Most of these patterns are just added to try to fix broken files in the wild.
|
||||
// After a while we can hopefully reduce it to something like [r"[?]", r"[*]", r"\\?\\\?\"]
|
||||
const PATTERNS: &[&str] = &[
|
||||
r"[[]",
|
||||
r"[]]",
|
||||
r"[?]",
|
||||
r"[*]",
|
||||
r"\?\?",
|
||||
r"\\?\\?\",
|
||||
r"\\?\\\?\",
|
||||
];
|
||||
const REPLACE_WITH: &[&str] = &[r"[", r"]", r"?", r"*", r"\?", r"\\?\", r"\\?\"];
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum Error {
|
||||
#[error(transparent)]
|
||||
@@ -33,6 +48,41 @@ struct Scope {
|
||||
forbidden_patterns: Vec<String>,
|
||||
}
|
||||
|
||||
fn fix_pattern(ac: &AhoCorasick, s: &str) -> String {
|
||||
let s = ac.replace_all(s, REPLACE_WITH);
|
||||
|
||||
if ac.find(&s).is_some() {
|
||||
return fix_pattern(ac, &s);
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
fn save_scopes<R: Runtime>(app: &AppHandle<R>, app_dir: &Path, scope_state_path: &Path) {
|
||||
let fs_scope = app.fs_scope();
|
||||
|
||||
let scope = Scope {
|
||||
allowed_paths: fs_scope
|
||||
.allowed_patterns()
|
||||
.into_iter()
|
||||
.map(|p| p.to_string())
|
||||
.collect(),
|
||||
forbidden_patterns: fs_scope
|
||||
.forbidden_patterns()
|
||||
.into_iter()
|
||||
.map(|p| p.to_string())
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let _ = create_dir_all(app_dir)
|
||||
.and_then(|_| File::create(scope_state_path))
|
||||
.map_err(Error::Io)
|
||||
.and_then(|mut f| {
|
||||
f.write_all(&bincode::serialize(&scope).map_err(Error::from)?)
|
||||
.map_err(Into::into)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("persisted-scope")
|
||||
.setup(|app, _api| {
|
||||
@@ -49,49 +99,38 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
#[cfg(feature = "protocol-asset")]
|
||||
let _ = asset_protocol_scope.forbid_file(&scope_state_path);
|
||||
|
||||
// We're trying to fix broken .persisted-scope files seamlessly, so we'll be running this on the values read on the saved file.
|
||||
// We will still save some semi-broken values because the scope events are quite spammy and we don't want to reduce runtime performance any further.
|
||||
let ac = AhoCorasick::new(PATTERNS).unwrap(/* This should be impossible to fail since we're using a small static input */);
|
||||
|
||||
if scope_state_path.exists() {
|
||||
let scope: Scope = tauri::api::file::read_binary(&scope_state_path)
|
||||
.map_err(Error::from)
|
||||
.and_then(|scope| bincode::deserialize(&scope).map_err(Into::into))
|
||||
.unwrap_or_default();
|
||||
for allowed in &scope.allowed_paths {
|
||||
// allows the path as is
|
||||
let _ = fs_scope.allow_file(allowed);
|
||||
let allowed = fix_pattern(&ac, allowed);
|
||||
|
||||
let _ = fs_scope.allow_file(&allowed);
|
||||
#[cfg(feature = "protocol-asset")]
|
||||
let _ = asset_protocol_scope.allow_file(allowed);
|
||||
let _ = asset_protocol_scope.allow_file(&allowed);
|
||||
}
|
||||
for forbidden in &scope.forbidden_patterns {
|
||||
// forbid the path as is
|
||||
let _ = fs_scope.forbid_file(forbidden);
|
||||
let forbidden = fix_pattern(&ac, forbidden);
|
||||
|
||||
let _ = fs_scope.forbid_file(&forbidden);
|
||||
#[cfg(feature = "protocol-asset")]
|
||||
let _ = asset_protocol_scope.forbid_file(forbidden);
|
||||
let _ = asset_protocol_scope.forbid_file(&forbidden);
|
||||
}
|
||||
|
||||
// Manually save the fixed scopes to disk once.
|
||||
// This is needed to fix broken .peristed-scope files in case the app doesn't update the scope itself.
|
||||
save_scopes(&app, &app_dir, &scope_state_path);
|
||||
}
|
||||
|
||||
fs_scope.listen(move |event| {
|
||||
let fs_scope = app.fs_scope();
|
||||
if let FsScopeEvent::PathAllowed(_) = event {
|
||||
let scope = Scope {
|
||||
allowed_paths: fs_scope
|
||||
.allowed_patterns()
|
||||
.into_iter()
|
||||
.map(|p| p.to_string())
|
||||
.collect(),
|
||||
forbidden_patterns: fs_scope
|
||||
.forbidden_patterns()
|
||||
.into_iter()
|
||||
.map(|p| p.to_string())
|
||||
.collect(),
|
||||
};
|
||||
let scope_state_path = scope_state_path.clone();
|
||||
|
||||
let _ = create_dir_all(&app_dir)
|
||||
.and_then(|_| File::create(scope_state_path))
|
||||
.map_err(Error::Io)
|
||||
.and_then(|mut f| {
|
||||
f.write_all(&bincode::serialize(&scope).map_err(Error::from)?)
|
||||
.map_err(Into::into)
|
||||
});
|
||||
save_scopes(&app, &app_dir, &scope_state_path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-positioner"
|
||||
version = "0.2.7"
|
||||
version = "1.0.4"
|
||||
description = "Position your windows at well-known locations."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tauri-plugin-positioner-api",
|
||||
"version": "0.0.0",
|
||||
"version": "0.2.7",
|
||||
"description": "Position your windows at well-known locations.",
|
||||
"license": "MIT or APACHE-2.0",
|
||||
"authors": [
|
||||
@@ -25,7 +25,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Ensure a single instance of your tauri app is running."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
@@ -18,7 +18,7 @@ log.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||
version = "0.42"
|
||||
version = "0.48"
|
||||
features = [
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_DataExchange",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
Ensure a single instance of your tauri app is running.
|
||||
|
||||
@@ -38,7 +38,7 @@ struct Payload {
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(auri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
println!("{}, {argv:?}, {cwd}", app.package_info().name);
|
||||
|
||||
app.emit_all("single-instance", Payload { args: argv, cwd }).unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-sql"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Interface with SQL databases."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
@@ -15,9 +15,10 @@ serde_json.workspace = true
|
||||
tauri.workspace = true
|
||||
log.workspace = true
|
||||
thiserror.workspace = true
|
||||
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "json"] }
|
||||
futures-core = "0.3"
|
||||
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "json", "time"] }
|
||||
time = "0.3"
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
futures = "0.3"
|
||||
|
||||
[features]
|
||||
sqlite = ["sqlx/sqlite"]
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#[cfg(feature = "mysql")]
|
||||
mod mysql;
|
||||
#[cfg(feature = "postgres")]
|
||||
mod postgres;
|
||||
#[cfg(feature = "sqlite")]
|
||||
mod sqlite;
|
||||
|
||||
#[cfg(feature = "mysql")]
|
||||
pub(crate) use mysql::to_json;
|
||||
|
||||
#[cfg(feature = "postgres")]
|
||||
pub(crate) use postgres::to_json;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub(crate) use sqlite::to_json;
|
||||
@@ -0,0 +1,90 @@
|
||||
use serde_json::Value as JsonValue;
|
||||
use sqlx::{mysql::MySqlValueRef, TypeInfo, Value, ValueRef};
|
||||
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
pub(crate) fn to_json(v: MySqlValueRef) -> Result<JsonValue, Error> {
|
||||
if v.is_null() {
|
||||
return Ok(JsonValue::Null);
|
||||
}
|
||||
|
||||
let res = match v.type_info().name() {
|
||||
"CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "ENUM" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
|
||||
JsonValue::String(v)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"FLOAT" | "DOUBLE" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<f64>() {
|
||||
JsonValue::from(v)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"TINYINT" | "SMALLINT" | "INT" | "MEDIUMINT" | "BIGINT" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<i64>() {
|
||||
JsonValue::Number(v.into())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"TINYINT UNSIGNED" | "SMALLINT UNSIGNED" | "INT UNSIGNED" | "MEDIUMINT UNSIGNED"
|
||||
| "BIGINT UNSIGNED" | "YEAR" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<u64>() {
|
||||
JsonValue::Number(v.into())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"BOOLEAN" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
|
||||
JsonValue::Bool(v)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"DATE" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Date>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"TIME" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Time>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"DATETIME" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<PrimitiveDateTime>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"TIMESTAMP" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<OffsetDateTime>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"JSON" => ValueRef::to_owned(&v).try_decode().unwrap_or_default(),
|
||||
"TINIYBLOB" | "MEDIUMBLOB" | "BLOB" | "LONGBLOB" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Vec<u8>>() {
|
||||
JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"NULL" => JsonValue::Null,
|
||||
_ => return Err(Error::UnsupportedDatatype(v.type_info().name().to_string())),
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use serde_json::Value as JsonValue;
|
||||
use sqlx::{postgres::PgValueRef, TypeInfo, Value, ValueRef};
|
||||
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
pub(crate) fn to_json(v: PgValueRef) -> Result<JsonValue, Error> {
|
||||
if v.is_null() {
|
||||
return Ok(JsonValue::Null);
|
||||
}
|
||||
|
||||
let res = match v.type_info().name() {
|
||||
"CHAR" | "VARCHAR" | "TEXT" | "NAME" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
|
||||
JsonValue::String(v)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"FLOAT4" | "FLOAT8" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<f64>() {
|
||||
JsonValue::from(v)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"INT2" | "INT4" | "INT8" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<i64>() {
|
||||
JsonValue::Number(v.into())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"BOOL" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
|
||||
JsonValue::Bool(v)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"DATE" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Date>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"TIME" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Time>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"TIMESTAMP" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<PrimitiveDateTime>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"TIMESTAMPTZ" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<OffsetDateTime>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"JSON" | "JSONB" => ValueRef::to_owned(&v).try_decode().unwrap_or_default(),
|
||||
"BYTEA" => {
|
||||
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Vec<u8>>() {
|
||||
JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"VOID" => JsonValue::Null,
|
||||
_ => return Err(Error::UnsupportedDatatype(v.type_info().name().to_string())),
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use serde_json::Value as JsonValue;
|
||||
use sqlx::{sqlite::SqliteValueRef, TypeInfo, Value, ValueRef};
|
||||
use time::{Date, PrimitiveDateTime, Time};
|
||||
|
||||
use crate::Error;
|
||||
|
||||
pub(crate) fn to_json(v: SqliteValueRef) -> Result<JsonValue, Error> {
|
||||
if v.is_null() {
|
||||
return Ok(JsonValue::Null);
|
||||
}
|
||||
|
||||
let res = match v.type_info().name() {
|
||||
"TEXT" => {
|
||||
if let Ok(v) = v.to_owned().try_decode() {
|
||||
JsonValue::String(v)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"REAL" => {
|
||||
if let Ok(v) = v.to_owned().try_decode::<f64>() {
|
||||
JsonValue::from(v)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"INTEGER" | "NUMERIC" => {
|
||||
if let Ok(v) = v.to_owned().try_decode::<i64>() {
|
||||
JsonValue::Number(v.into())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"BOOLEAN" => {
|
||||
if let Ok(v) = v.to_owned().try_decode() {
|
||||
JsonValue::Bool(v)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"DATE" => {
|
||||
if let Ok(v) = v.to_owned().try_decode::<Date>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"TIME" => {
|
||||
if let Ok(v) = v.to_owned().try_decode::<Time>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"DATETIME" => {
|
||||
if let Ok(v) = v.to_owned().try_decode::<PrimitiveDateTime>() {
|
||||
JsonValue::String(v.to_string())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"BLOB" => {
|
||||
if let Ok(v) = v.to_owned().try_decode::<Vec<u8>>() {
|
||||
JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"NULL" => JsonValue::Null,
|
||||
_ => return Err(Error::UnsupportedDatatype(v.type_info().name().to_string())),
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
+1
-10
@@ -14,15 +14,6 @@ compile_error!(
|
||||
"Database driver not defined. Please set the feature flag for the driver of your choice."
|
||||
);
|
||||
|
||||
#[cfg(any(
|
||||
all(feature = "sqlite", not(any(feature = "mysql", feature = "postgres"))),
|
||||
all(feature = "mysql", not(any(feature = "sqlite", feature = "postgres"))),
|
||||
all(feature = "postgres", not(any(feature = "sqlite", feature = "mysql"))),
|
||||
))]
|
||||
mod decode;
|
||||
mod plugin;
|
||||
#[cfg(any(
|
||||
all(feature = "sqlite", not(any(feature = "mysql", feature = "postgres"))),
|
||||
all(feature = "mysql", not(any(feature = "sqlite", feature = "postgres"))),
|
||||
all(feature = "postgres", not(any(feature = "sqlite", feature = "mysql"))),
|
||||
))]
|
||||
pub use plugin::*;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures_core::future::BoxFuture;
|
||||
use serde::{ser::Serializer, Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use sqlx::{
|
||||
@@ -10,7 +10,7 @@ use sqlx::{
|
||||
migrate::{
|
||||
MigrateDatabase, Migration as SqlxMigration, MigrationSource, MigrationType, Migrator,
|
||||
},
|
||||
Column, Pool, Row, TypeInfo, ValueRef,
|
||||
Column, Pool, Row,
|
||||
};
|
||||
use tauri::{
|
||||
command,
|
||||
@@ -207,7 +207,9 @@ async fn execute(
|
||||
let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?;
|
||||
let mut query = sqlx::query(&query);
|
||||
for value in values {
|
||||
if value.is_string() {
|
||||
if value.is_null() {
|
||||
query = query.bind(None::<JsonValue>);
|
||||
} else if value.is_string() {
|
||||
query = query.bind(value.as_str().unwrap().to_owned())
|
||||
} else {
|
||||
query = query.bind(value);
|
||||
@@ -234,7 +236,9 @@ async fn select(
|
||||
let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?;
|
||||
let mut query = sqlx::query(&query);
|
||||
for value in values {
|
||||
if value.is_string() {
|
||||
if value.is_null() {
|
||||
query = query.bind(None::<JsonValue>);
|
||||
} else if value.is_string() {
|
||||
query = query.bind(value.as_str().unwrap().to_owned())
|
||||
} else {
|
||||
query = query.bind(value);
|
||||
@@ -247,58 +251,7 @@ async fn select(
|
||||
for (i, column) in row.columns().iter().enumerate() {
|
||||
let v = row.try_get_raw(i)?;
|
||||
|
||||
let v = if v.is_null() {
|
||||
JsonValue::Null
|
||||
} else {
|
||||
// TODO: postgresql's JSON type
|
||||
match v.type_info().name() {
|
||||
"VARCHAR" | "STRING" | "TEXT" | "TINYTEXT" | "LONGTEXT" | "NVARCHAR"
|
||||
| "BIGVARCHAR" | "CHAR" | "BIGCHAR" | "NCHAR" | "DATETIME" | "DATE"
|
||||
| "TIME" | "YEAR" | "TIMESTAMP" => {
|
||||
if let Ok(s) = row.try_get(i) {
|
||||
JsonValue::String(s)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"BOOL" | "BOOLEAN" => {
|
||||
if let Ok(b) = row.try_get(i) {
|
||||
JsonValue::Bool(b)
|
||||
} else {
|
||||
let x: String = row.get(i);
|
||||
JsonValue::Bool(x.to_lowercase() == "true")
|
||||
}
|
||||
}
|
||||
"INT" | "NUMBER" | "INTEGER" | "BIGINT" | "INT2" | "INT4" | "INT8"
|
||||
| "NUMERIC" | "TINYINT" | "SMALLINT" | "MEDIUMINT" | "TINYINT UNSINGED"
|
||||
| "SMALLINT UNSINGED" | "INT UNSINGED" | "MEDIUMINT UNSINGED"
|
||||
| "BIGINT UNSINGED" => {
|
||||
if let Ok(n) = row.try_get::<i64, usize>(i) {
|
||||
JsonValue::Number(n.into())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"REAL" | "FLOAT" | "DOUBLE" | "FLOAT4" | "FLOAT8" => {
|
||||
if let Ok(n) = row.try_get::<f64, usize>(i) {
|
||||
JsonValue::from(n)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"BLOB" | "TINYBLOB" | "MEDIUMBLOB" | "LONGBLOB" | "BINARY" | "VARBINARY"
|
||||
| "BYTEA" => {
|
||||
if let Ok(n) = row.try_get::<Vec<u8>, usize>(i) {
|
||||
JsonValue::Array(
|
||||
n.into_iter().map(|n| JsonValue::Number(n.into())).collect(),
|
||||
)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::UnsupportedDatatype(v.type_info().name().to_string())),
|
||||
}
|
||||
};
|
||||
let v = crate::decode::to_json(v)?;
|
||||
|
||||
value.insert(column.name().to_string(), v);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-store"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Simple, persistent key-value store."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -59,6 +59,48 @@ await store.set("some-key", { value: 5 });
|
||||
|
||||
const val = await store.get("some-key");
|
||||
assert(val, { value: 5 });
|
||||
|
||||
await store.save(); // this manually saves the store, otherwise the store is only saved when your app is closed
|
||||
```
|
||||
|
||||
### Persisting values
|
||||
|
||||
Values added to the store are not persisted between application loads unless:
|
||||
|
||||
1. The application is closed gracefully (plugin automatically saves)
|
||||
2. The store is manually saved (using `store.save()`)
|
||||
|
||||
## Usage from Rust
|
||||
|
||||
You can also access Stores from Rust, you can create new stores:
|
||||
|
||||
```rust
|
||||
use tauri_plugin_store::StoreBuilder;
|
||||
use serde_json::json;
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_store::Builder::default().build())
|
||||
.setup(|app| {
|
||||
let mut store = StoreBuilder::new(app.handle(), "path/to/store.bin".parse()?).build();
|
||||
|
||||
store.insert("a".to_string(), json!("b")) // note that values must be serd_json::Value to be compatible with JS
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
```
|
||||
|
||||
As you may have noticed, the Store crated above isn't accessible to the frontend. To interoperate with stores created by JS use the exported `with_store` method:
|
||||
|
||||
```rust
|
||||
use tauri::Wry;
|
||||
use tauri_plugin_store::with_store;
|
||||
|
||||
let stores = app.state::<StoreCollection<Wry>>();
|
||||
let path = PathBuf::from("path/to/the/storefile");
|
||||
|
||||
with_store(app_handle, stores, path, |store| store.insert("a".to_string(), json!("b")))
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
import { listen, UnlistenFn } from "@tauri-apps/api/event";
|
||||
|
||||
interface ChangePayload<T> {
|
||||
path: string;
|
||||
@@ -180,14 +179,11 @@ export class Store {
|
||||
key: string,
|
||||
cb: (value: T | null) => void
|
||||
): Promise<UnlistenFn> {
|
||||
return await appWindow.listen<ChangePayload<T>>(
|
||||
"store://change",
|
||||
(event) => {
|
||||
if (event.payload.path === this.path && event.payload.key === key) {
|
||||
cb(event.payload.value);
|
||||
}
|
||||
return await listen<ChangePayload<T>>("store://change", (event) => {
|
||||
if (event.payload.path === this.path && event.payload.key === key) {
|
||||
cb(event.payload.value);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,13 +194,10 @@ export class Store {
|
||||
async onChange<T>(
|
||||
cb: (key: string, value: T | null) => void
|
||||
): Promise<UnlistenFn> {
|
||||
return await appWindow.listen<ChangePayload<T>>(
|
||||
"store://change",
|
||||
(event) => {
|
||||
if (event.payload.path === this.path) {
|
||||
cb(event.payload.key, event.payload.value);
|
||||
}
|
||||
return await listen<ChangePayload<T>>("store://change", (event) => {
|
||||
if (event.payload.path === this.path) {
|
||||
cb(event.payload.key, event.payload.value);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -10,9 +10,9 @@ use std::path::PathBuf;
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
#[error("Failed to serialize store. {0}")]
|
||||
Serialize(Box<dyn std::error::Error>),
|
||||
Serialize(Box<dyn std::error::Error + Send + Sync>),
|
||||
#[error("Failed to deserialize store. {0}")]
|
||||
Deserialize(Box<dyn std::error::Error>),
|
||||
Deserialize(Box<dyn std::error::Error + Send + Sync>),
|
||||
/// JSON error.
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
@@ -22,6 +22,9 @@ pub enum Error {
|
||||
/// Store not found
|
||||
#[error("Store \"{0}\" not found")]
|
||||
NotFound(PathBuf),
|
||||
/// Some Tauri API failed
|
||||
#[error(transparent)]
|
||||
Tauri(#[from] tauri::Error),
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
|
||||
+73
-123
@@ -5,251 +5,201 @@
|
||||
pub use error::Error;
|
||||
use log::warn;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{collections::HashMap, path::PathBuf, sync::Mutex};
|
||||
pub use serde_json::Value as JsonValue;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::Mutex,
|
||||
};
|
||||
pub use store::{Store, StoreBuilder};
|
||||
use tauri::{
|
||||
plugin::{self, TauriPlugin},
|
||||
AppHandle, Manager, RunEvent, Runtime, State, Window,
|
||||
AppHandle, Manager, RunEvent, Runtime, State,
|
||||
};
|
||||
|
||||
mod error;
|
||||
mod store;
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
struct ChangePayload {
|
||||
path: PathBuf,
|
||||
key: String,
|
||||
value: JsonValue,
|
||||
struct ChangePayload<'a> {
|
||||
path: &'a Path,
|
||||
key: &'a str,
|
||||
value: &'a JsonValue,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StoreCollection {
|
||||
stores: Mutex<HashMap<PathBuf, Store>>,
|
||||
pub struct StoreCollection<R: Runtime> {
|
||||
stores: Mutex<HashMap<PathBuf, Store<R>>>,
|
||||
frozen: bool,
|
||||
}
|
||||
|
||||
fn with_store<R: Runtime, T, F: FnOnce(&mut Store) -> Result<T, Error>>(
|
||||
app: &AppHandle<R>,
|
||||
collection: State<'_, StoreCollection>,
|
||||
path: PathBuf,
|
||||
pub fn with_store<R: Runtime, T, F: FnOnce(&mut Store<R>) -> Result<T, Error>>(
|
||||
app: AppHandle<R>,
|
||||
collection: State<'_, StoreCollection<R>>,
|
||||
path: impl AsRef<Path>,
|
||||
f: F,
|
||||
) -> Result<T, Error> {
|
||||
let mut stores = collection.stores.lock().expect("mutex poisoned");
|
||||
|
||||
if !stores.contains_key(&path) {
|
||||
let path = path.as_ref();
|
||||
if !stores.contains_key(path) {
|
||||
if collection.frozen {
|
||||
return Err(Error::NotFound(path));
|
||||
return Err(Error::NotFound(path.to_path_buf()));
|
||||
}
|
||||
let mut store = StoreBuilder::new(path.clone()).build();
|
||||
let mut store = StoreBuilder::new(app, path.to_path_buf()).build();
|
||||
// ignore loading errors, just use the default
|
||||
if let Err(err) = store.load(app) {
|
||||
if let Err(err) = store.load() {
|
||||
warn!(
|
||||
"Failed to load store {:?} from disk: {}. Falling back to default values.",
|
||||
path, err
|
||||
);
|
||||
}
|
||||
stores.insert(path.clone(), store);
|
||||
stores.insert(path.to_path_buf(), store);
|
||||
}
|
||||
|
||||
f(stores
|
||||
.get_mut(&path)
|
||||
.get_mut(path)
|
||||
.expect("failed to retrieve store. This is a bug!"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn set<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
window: Window<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
key: String,
|
||||
value: JsonValue,
|
||||
) -> Result<(), Error> {
|
||||
with_store(&app, stores, path.clone(), |store| {
|
||||
store.cache.insert(key.clone(), value.clone());
|
||||
let _ = window.emit("store://change", ChangePayload { path, key, value });
|
||||
Ok(())
|
||||
})
|
||||
with_store(app, stores, path, |store| store.insert(key, value))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
key: String,
|
||||
) -> Result<Option<JsonValue>, Error> {
|
||||
with_store(&app, stores, path, |store| {
|
||||
Ok(store.cache.get(&key).cloned())
|
||||
})
|
||||
with_store(app, stores, path, |store| Ok(store.get(key).cloned()))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn has<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
key: String,
|
||||
) -> Result<bool, Error> {
|
||||
with_store(&app, stores, path, |store| {
|
||||
Ok(store.cache.contains_key(&key))
|
||||
})
|
||||
with_store(app, stores, path, |store| Ok(store.has(key)))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn delete<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
window: Window<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
key: String,
|
||||
) -> Result<bool, Error> {
|
||||
with_store(&app, stores, path.clone(), |store| {
|
||||
let flag = store.cache.remove(&key).is_some();
|
||||
if flag {
|
||||
let _ = window.emit(
|
||||
"store://change",
|
||||
ChangePayload {
|
||||
path,
|
||||
key,
|
||||
value: JsonValue::Null,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(flag)
|
||||
})
|
||||
with_store(app, stores, path, |store| store.delete(key))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn clear<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
window: Window<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
) -> Result<(), Error> {
|
||||
with_store(&app, stores, path.clone(), |store| {
|
||||
let keys = store.cache.keys().cloned().collect::<Vec<String>>();
|
||||
store.cache.clear();
|
||||
for key in keys {
|
||||
let _ = window.emit(
|
||||
"store://change",
|
||||
ChangePayload {
|
||||
path: path.clone(),
|
||||
key,
|
||||
value: JsonValue::Null,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
with_store(app, stores, path, |store| store.clear())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn reset<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
window: Window<R>,
|
||||
collection: State<'_, StoreCollection>,
|
||||
collection: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
) -> Result<(), Error> {
|
||||
let has_defaults = collection
|
||||
.stores
|
||||
.lock()
|
||||
.expect("mutex poisoned")
|
||||
.get(&path)
|
||||
.map(|store| store.defaults.is_some());
|
||||
|
||||
if Some(true) == has_defaults {
|
||||
with_store(&app, collection, path.clone(), |store| {
|
||||
if let Some(defaults) = &store.defaults {
|
||||
for (key, value) in &store.cache {
|
||||
if defaults.get(key) != Some(value) {
|
||||
let _ = window.emit(
|
||||
"store://change",
|
||||
ChangePayload {
|
||||
path: path.clone(),
|
||||
key: key.clone(),
|
||||
value: defaults.get(key).cloned().unwrap_or(JsonValue::Null),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
store.cache = defaults.clone();
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
clear(app, window, collection, path).await
|
||||
}
|
||||
with_store(app, collection, path, |store| store.reset())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn keys<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
) -> Result<Vec<String>, Error> {
|
||||
with_store(&app, stores, path, |store| {
|
||||
Ok(store.cache.keys().cloned().collect())
|
||||
with_store(app, stores, path, |store| {
|
||||
Ok(store.keys().cloned().collect())
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn values<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
) -> Result<Vec<JsonValue>, Error> {
|
||||
with_store(&app, stores, path, |store| {
|
||||
Ok(store.cache.values().cloned().collect())
|
||||
with_store(app, stores, path, |store| {
|
||||
Ok(store.values().cloned().collect())
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn entries<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
) -> Result<Vec<(String, JsonValue)>, Error> {
|
||||
with_store(&app, stores, path, |store| {
|
||||
Ok(store.cache.clone().into_iter().collect())
|
||||
with_store(app, stores, path, |store| {
|
||||
Ok(store
|
||||
.entries()
|
||||
.map(|(k, v)| (k.to_owned(), v.to_owned()))
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn length<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
) -> Result<usize, Error> {
|
||||
with_store(&app, stores, path, |store| Ok(store.cache.len()))
|
||||
with_store(app, stores, path, |store| Ok(store.len()))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn load<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
) -> Result<(), Error> {
|
||||
with_store(&app, stores, path, |store| store.load(&app))
|
||||
with_store(app, stores, path, |store| store.load())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn save<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
stores: State<'_, StoreCollection>,
|
||||
stores: State<'_, StoreCollection<R>>,
|
||||
path: PathBuf,
|
||||
) -> Result<(), Error> {
|
||||
with_store(&app, stores, path, |store| store.save(&app))
|
||||
with_store(app, stores, path, |store| store.save())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Builder {
|
||||
stores: HashMap<PathBuf, Store>,
|
||||
// #[derive(Default)]
|
||||
pub struct Builder<R: Runtime> {
|
||||
stores: HashMap<PathBuf, Store<R>>,
|
||||
frozen: bool,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
impl<R: Runtime> Default for Builder<R> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stores: Default::default(),
|
||||
frozen: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Builder<R> {
|
||||
/// Registers a store with the plugin.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -265,7 +215,7 @@ impl Builder {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn store(mut self, store: Store) -> Self {
|
||||
pub fn store(mut self, store: Store<R>) -> Self {
|
||||
self.stores.insert(store.path.clone(), store);
|
||||
self
|
||||
}
|
||||
@@ -285,7 +235,7 @@ impl Builder {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn stores<T: IntoIterator<Item = Store>>(mut self, stores: T) -> Self {
|
||||
pub fn stores<T: IntoIterator<Item = Store<R>>>(mut self, stores: T) -> Self {
|
||||
self.stores = stores
|
||||
.into_iter()
|
||||
.map(|store| (store.path.clone(), store))
|
||||
@@ -331,7 +281,7 @@ impl Builder {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
|
||||
pub fn build(mut self) -> TauriPlugin<R> {
|
||||
plugin::Builder::new("store")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
set, get, has, delete, clear, reset, keys, values, length, entries, load, save
|
||||
@@ -339,7 +289,7 @@ impl Builder {
|
||||
.setup(move |app_handle, _api| {
|
||||
for (path, store) in self.stores.iter_mut() {
|
||||
// ignore loading errors, just use the default
|
||||
if let Err(err) = store.load(app_handle) {
|
||||
if let Err(err) = store.load() {
|
||||
warn!(
|
||||
"Failed to load store {:?} from disk: {}. Falling back to default values.",
|
||||
path, err
|
||||
@@ -356,10 +306,10 @@ impl Builder {
|
||||
})
|
||||
.on_event(|app_handle, event| {
|
||||
if let RunEvent::Exit = event {
|
||||
let collection = app_handle.state::<StoreCollection>();
|
||||
let collection = app_handle.state::<StoreCollection<R>>();
|
||||
|
||||
for store in collection.stores.lock().expect("mutex poisoned").values() {
|
||||
if let Err(err) = store.save(app_handle) {
|
||||
if let Err(err) = store.save() {
|
||||
eprintln!("failed to save store {:?} with error {:?}", store.path, err);
|
||||
}
|
||||
}
|
||||
|
||||
+120
-15
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::Error;
|
||||
use crate::{ChangePayload, Error};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -12,23 +12,26 @@ use std::{
|
||||
};
|
||||
use tauri::{AppHandle, Manager, Runtime};
|
||||
|
||||
type SerializeFn = fn(&HashMap<String, JsonValue>) -> Result<Vec<u8>, Box<dyn std::error::Error>>;
|
||||
type DeserializeFn = fn(&[u8]) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error>>;
|
||||
type SerializeFn =
|
||||
fn(&HashMap<String, JsonValue>) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>>;
|
||||
type DeserializeFn =
|
||||
fn(&[u8]) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
fn default_serialize(
|
||||
cache: &HashMap<String, JsonValue>,
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
Ok(serde_json::to_vec(&cache)?)
|
||||
}
|
||||
|
||||
fn default_deserialize(
|
||||
bytes: &[u8],
|
||||
) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error>> {
|
||||
) -> Result<HashMap<String, JsonValue>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
serde_json::from_slice(bytes).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Builds a [`Store`]
|
||||
pub struct StoreBuilder {
|
||||
pub struct StoreBuilder<R: Runtime> {
|
||||
app: AppHandle<R>,
|
||||
path: PathBuf,
|
||||
defaults: Option<HashMap<String, JsonValue>>,
|
||||
cache: HashMap<String, JsonValue>,
|
||||
@@ -36,7 +39,7 @@ pub struct StoreBuilder {
|
||||
deserialize: DeserializeFn,
|
||||
}
|
||||
|
||||
impl StoreBuilder {
|
||||
impl<R: Runtime> StoreBuilder<R> {
|
||||
/// Creates a new [`StoreBuilder`].
|
||||
///
|
||||
/// # Examples
|
||||
@@ -49,8 +52,9 @@ impl StoreBuilder {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
pub fn new(app: AppHandle<R>, path: PathBuf) -> Self {
|
||||
Self {
|
||||
app,
|
||||
path,
|
||||
defaults: None,
|
||||
cache: Default::default(),
|
||||
@@ -147,8 +151,9 @@ impl StoreBuilder {
|
||||
///
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
pub fn build(self) -> Store {
|
||||
pub fn build(self) -> Store<R> {
|
||||
Store {
|
||||
app: self.app,
|
||||
path: self.path,
|
||||
defaults: self.defaults,
|
||||
cache: self.cache,
|
||||
@@ -159,15 +164,16 @@ impl StoreBuilder {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Store {
|
||||
pub struct Store<R: Runtime> {
|
||||
app: AppHandle<R>,
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) defaults: Option<HashMap<String, JsonValue>>,
|
||||
pub(crate) cache: HashMap<String, JsonValue>,
|
||||
defaults: Option<HashMap<String, JsonValue>>,
|
||||
cache: HashMap<String, JsonValue>,
|
||||
serialize: SerializeFn,
|
||||
deserialize: DeserializeFn,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
impl<R: Runtime> Store<R> {
|
||||
/// Update the store from the on-disk state
|
||||
pub fn load<R: Runtime>(&mut self, app: &AppHandle<R>) -> Result<(), Error> {
|
||||
let app_dir = app
|
||||
@@ -178,7 +184,8 @@ impl Store {
|
||||
|
||||
let bytes = read(store_path)?;
|
||||
|
||||
self.cache = (self.deserialize)(&bytes).map_err(Error::Deserialize)?;
|
||||
self.cache
|
||||
.extend((self.deserialize)(&bytes).map_err(Error::Deserialize)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -199,9 +206,107 @@ impl Store {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: String, value: JsonValue) -> Result<(), Error> {
|
||||
self.cache.insert(key.clone(), value.clone());
|
||||
self.app.emit_all(
|
||||
"store://change",
|
||||
ChangePayload {
|
||||
path: &self.path,
|
||||
key: &key,
|
||||
value: &value,
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(&self, key: impl AsRef<str>) -> Option<&JsonValue> {
|
||||
self.cache.get(key.as_ref())
|
||||
}
|
||||
|
||||
pub fn has(&self, key: impl AsRef<str>) -> bool {
|
||||
self.cache.contains_key(key.as_ref())
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, key: impl AsRef<str>) -> Result<bool, Error> {
|
||||
let flag = self.cache.remove(key.as_ref()).is_some();
|
||||
if flag {
|
||||
self.app.emit_all(
|
||||
"store://change",
|
||||
ChangePayload {
|
||||
path: &self.path,
|
||||
key: key.as_ref(),
|
||||
value: &JsonValue::Null,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
Ok(flag)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) -> Result<(), Error> {
|
||||
let keys: Vec<String> = self.cache.keys().cloned().collect();
|
||||
self.cache.clear();
|
||||
for key in keys {
|
||||
self.app.emit_all(
|
||||
"store://change",
|
||||
ChangePayload {
|
||||
path: &self.path,
|
||||
key: &key,
|
||||
value: &JsonValue::Null,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) -> Result<(), Error> {
|
||||
let has_defaults = self.defaults.is_some();
|
||||
|
||||
if has_defaults {
|
||||
if let Some(defaults) = &self.defaults {
|
||||
for (key, value) in &self.cache {
|
||||
if defaults.get(key) != Some(value) {
|
||||
let _ = self.app.emit_all(
|
||||
"store://change",
|
||||
ChangePayload {
|
||||
path: &self.path,
|
||||
key,
|
||||
value: defaults.get(key).unwrap_or(&JsonValue::Null),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
self.cache = defaults.clone();
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
self.clear()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> impl Iterator<Item = &String> {
|
||||
self.cache.keys()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &JsonValue> {
|
||||
self.cache.values()
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> impl Iterator<Item = (&String, &JsonValue)> {
|
||||
self.cache.iter()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.cache.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.cache.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Store {
|
||||
impl<R: Runtime> std::fmt::Debug for Store<R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Store")
|
||||
.field("path", &self.path)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-stronghold"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Store secrets and keys using the IOTA Stronghold encrypted database."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
@@ -16,7 +16,7 @@ tauri.workspace = true
|
||||
log.workspace = true
|
||||
thiserror.workspace = true
|
||||
iota_stronghold = "1"
|
||||
iota-crypto = "0.15"
|
||||
iota-crypto = "0.17"
|
||||
hex = "0.4"
|
||||
zeroize = { version = "1", features = ["zeroize_derive"] }
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-upload"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
description = "Upload files from disk to a remote server over HTTP."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -51,6 +51,10 @@ async function upload(
|
||||
});
|
||||
}
|
||||
|
||||
/// Download file from given url.
|
||||
///
|
||||
/// Note that `filePath` currently must include the file name.
|
||||
/// Furthermore the progress events will report a total length of 0 if the server did not sent a `Content-Length` header or if the file is compressed.
|
||||
async function download(
|
||||
url: string,
|
||||
filePath: string,
|
||||
@@ -67,7 +71,7 @@ async function download(
|
||||
|
||||
await listenToEventIfNeeded("download://progress");
|
||||
|
||||
await invoke("plugin:upload|upload", {
|
||||
await invoke("plugin:upload|download", {
|
||||
id,
|
||||
url,
|
||||
filePath,
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -62,9 +62,7 @@ async fn download<R: Runtime>(
|
||||
}
|
||||
|
||||
let response = request.send().await?;
|
||||
let total = response.content_length().ok_or_else(|| {
|
||||
Error::ContentLength(format!("Failed to get content length from '{url}'"))
|
||||
})?;
|
||||
let total = response.content_length().unwrap_or(0);
|
||||
|
||||
let mut file = File::create(file_path).await?;
|
||||
let mut stream = response.bytes_stream();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-websocket"
|
||||
version = "0.1.0"
|
||||
version = "0.0.0"
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^1.0.0",
|
||||
"@sveltejs/kit": "^1.0.0",
|
||||
"svelte": "^3.54.0",
|
||||
"svelte-check": "^2.9.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0"
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.15.5",
|
||||
"@tauri-apps/cli": "^1.2.3",
|
||||
"svelte": "^3.58.0",
|
||||
"svelte-check": "^3.2.0",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/cli": "^2.0.0-alpha.8",
|
||||
|
||||
+61
-136
@@ -49,7 +49,7 @@ dependencies = [
|
||||
"tauri-build",
|
||||
"tauri-plugin-websocket",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.15.0",
|
||||
"tokio-tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -78,9 +78,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
@@ -100,15 +100,6 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.2"
|
||||
@@ -331,15 +322,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
@@ -490,22 +472,13 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.2",
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
@@ -1411,7 +1384,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1568,12 +1541,6 @@ version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.35"
|
||||
@@ -1658,7 +1625,7 @@ dependencies = [
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1780,26 +1747,6 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
@@ -2290,19 +2237,6 @@ dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cpufeatures 0.1.5",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.5"
|
||||
@@ -2310,8 +2244,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.1",
|
||||
"digest 0.10.6",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2321,8 +2255,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.1",
|
||||
"digest 0.10.6",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2345,9 +2279,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.7"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
|
||||
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
@@ -2633,7 +2567,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite 0.18.0",
|
||||
"tokio-tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2808,19 +2742,18 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.19.2"
|
||||
version = "1.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
|
||||
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"winapi",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2833,19 +2766,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"pin-project",
|
||||
"tokio",
|
||||
"tungstenite 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.18.0"
|
||||
@@ -2857,7 +2777,7 @@ dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tungstenite 0.18.0",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2878,25 +2798,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.4",
|
||||
"sha-1",
|
||||
"thiserror",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.18.0"
|
||||
@@ -3238,12 +3139,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc 0.42.0",
|
||||
"windows_i686_gnu 0.42.0",
|
||||
"windows_i686_msvc 0.42.0",
|
||||
"windows_x86_64_gnu 0.42.0",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc 0.42.0",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3260,9 +3185,9 @@ checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@@ -3278,9 +3203,9 @@ checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
@@ -3296,9 +3221,9 @@ checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
@@ -3314,9 +3239,9 @@ checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
@@ -3332,15 +3257,15 @@ checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
@@ -3356,9 +3281,9 @@ checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "winres"
|
||||
|
||||
@@ -10,10 +10,10 @@ edition = "2021"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tauri = { version = "2.0.0-alpha.8", features = [] }
|
||||
tokio = { version = "1.11", features = ["net"] }
|
||||
tokio = { version = "1", features = ["net"] }
|
||||
futures-util = "0.3"
|
||||
tauri-plugin-websocket = { path = "../../../" }
|
||||
tokio-tungstenite = "0.15"
|
||||
tokio-tungstenite = "0.18"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-alpha.4", features = [] }
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "0.1.0"
|
||||
description = "Save window positions and sizse and restore them when the app is reopened."
|
||||
description = "Save window positions and sizes and restore them when the app is reopened."
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
@@ -16,4 +16,4 @@ tauri.workspace = true
|
||||
log.workspace = true
|
||||
thiserror.workspace = true
|
||||
bincode = "1.3"
|
||||
bitflags = "1"
|
||||
bitflags = "2"
|
||||
|
||||
@@ -21,6 +21,18 @@ Install the Core plugin by adding the following to your `Cargo.toml` file:
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
|
||||
```
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-window-state
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-window-state
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-window-state
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
First you need to register the core plugin with Tauri:
|
||||
@@ -38,7 +50,7 @@ fn main() {
|
||||
|
||||
Afterwards all windows will remember their state when the app is being closed and will restore to their previous state on the next launch.
|
||||
|
||||
Optionally you can also tell the plugin to save the state of all open window to disk my using the `save_window_state()` method exposed by the `AppHandleExt` trait:
|
||||
Optionally you can also tell the plugin to save the state of all open window to disk by using the `save_window_state()` method exposed by the `AppHandleExt` trait:
|
||||
|
||||
```rust
|
||||
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
@@ -47,6 +59,14 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
app.save_window_state(StateFlags::all()); // will save the state of all open windows to disk
|
||||
```
|
||||
|
||||
or through Javascript
|
||||
|
||||
```javascript
|
||||
import { saveWindowState, StateFlags } from "tauri-plugin-window-state-api";
|
||||
|
||||
saveWindowState(StateFlags.ALL);
|
||||
```
|
||||
|
||||
To manually restore a windows state from disk you can call the `restore_state()` method exposed by the `WindowExt` trait:
|
||||
|
||||
```rust
|
||||
@@ -56,6 +76,14 @@ use tauri_plugin_window_state::{WindowExt, StateFlags};
|
||||
window.restore_state(StateFlags::all()); // will restore the windows state from disk
|
||||
```
|
||||
|
||||
or through Javascript
|
||||
|
||||
```javascript
|
||||
import { restoreStateCurrent, StateFlags } from "tauri-plugin-window-state-api";
|
||||
|
||||
restoreStateCurrent(StateFlags.ALL);
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
PRs accepted. Please make sure to read the Contributing Guide before making a pull request.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { WindowLabel, getCurrent } from "@tauri-apps/api/window";
|
||||
|
||||
export enum StateFlags {
|
||||
SIZE = 1 << 0,
|
||||
POSITION = 1 << 1,
|
||||
MAXIMIZED = 1 << 2,
|
||||
VISIBLE = 1 << 3,
|
||||
DECORATIONS = 1 << 4,
|
||||
FULLSCREEN = 1 << 5,
|
||||
ALL = SIZE | POSITION | MAXIMIZED | VISIBLE | DECORATIONS | FULLSCREEN,
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the state of all open windows to disk.
|
||||
*/
|
||||
async function saveWindowState(flags: StateFlags) {
|
||||
invoke("plugin:window-state|save_window_state", { flags });
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the state for the specified window from disk.
|
||||
*/
|
||||
async function restoreState(label: WindowLabel, flags: StateFlags) {
|
||||
invoke("plugin:window-state|restore_state", { label, flags });
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the state for the current window from disk.
|
||||
*/
|
||||
async function restoreStateCurrent(flags: StateFlags) {
|
||||
restoreState(getCurrent().label, flags);
|
||||
}
|
||||
|
||||
export { restoreState, restoreStateCurrent, saveWindowState };
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "tauri-plugin-window-state-api",
|
||||
"version": "0.0.0",
|
||||
"description": "Save window positions and sizes and restore them when the app is reopened.",
|
||||
"license": "MIT or APACHE-2.0",
|
||||
"authors": [
|
||||
"Tauri Programme within The Commons Conservancy"
|
||||
],
|
||||
"type": "module",
|
||||
"browser": "dist-js/index.min.js",
|
||||
"module": "dist-js/index.mjs",
|
||||
"types": "dist-js/index.d.ts",
|
||||
"exports": {
|
||||
"import": "./dist-js/index.mjs",
|
||||
"types": "./dist-js/index.d.ts",
|
||||
"browser": "./dist-js/index.min.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c"
|
||||
},
|
||||
"files": [
|
||||
"dist-js",
|
||||
"!dist-js/**/*.map",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
import { createConfig } from "../../shared/rollup.config.mjs";
|
||||
|
||||
export default createConfig({
|
||||
input: "guest-js/index.ts",
|
||||
pkg: JSON.parse(
|
||||
readFileSync(new URL("./package.json", import.meta.url), "utf8")
|
||||
),
|
||||
external: [/^@tauri-apps\/api/],
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
use crate::{AppHandleExt, StateFlags, WindowExt};
|
||||
use tauri::{command, AppHandle, Manager, Runtime};
|
||||
|
||||
#[command]
|
||||
pub async fn save_window_state<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
flags: u32,
|
||||
) -> std::result::Result<(), String> {
|
||||
let flags = StateFlags::from_bits(flags)
|
||||
.ok_or_else(|| format!("Invalid state flags bits: {}", flags))?;
|
||||
app.save_window_state(flags).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn restore_state<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
label: String,
|
||||
flags: u32,
|
||||
) -> std::result::Result<(), String> {
|
||||
let flags = StateFlags::from_bits(flags)
|
||||
.ok_or_else(|| format!("Invalid state flags bits: {}", flags))?;
|
||||
app.get_window(&label)
|
||||
.ok_or_else(|| format!("Couldn't find window with label: {}", label))?
|
||||
.restore_state(flags)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -17,6 +17,8 @@ use std::{
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
mod cmd;
|
||||
|
||||
pub const STATE_FILENAME: &str = ".window-state";
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -34,6 +36,7 @@ pub enum Error {
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct StateFlags: u32 {
|
||||
const SIZE = 1 << 0;
|
||||
const POSITION = 1 << 1;
|
||||
@@ -50,7 +53,7 @@ impl Default for StateFlags {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
#[derive(Debug, Default, Deserialize, Serialize, PartialEq)]
|
||||
struct WindowState {
|
||||
width: f64,
|
||||
height: f64,
|
||||
@@ -64,6 +67,7 @@ struct WindowState {
|
||||
|
||||
struct WindowStateCache(Arc<Mutex<HashMap<String, WindowState>>>);
|
||||
pub trait AppHandleExt {
|
||||
/// Saves all open windows state to disk
|
||||
fn save_window_state(&self, flags: StateFlags) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -93,6 +97,7 @@ impl<R: Runtime> AppHandleExt for tauri::AppHandle<R> {
|
||||
}
|
||||
|
||||
pub trait WindowExt {
|
||||
/// Restores this window state from disk
|
||||
fn restore_state(&self, flags: StateFlags) -> tauri::Result<()>;
|
||||
}
|
||||
|
||||
@@ -100,9 +105,15 @@ impl<R: Runtime> WindowExt for Window<R> {
|
||||
fn restore_state(&self, flags: StateFlags) -> tauri::Result<()> {
|
||||
let cache = self.state::<WindowStateCache>();
|
||||
let mut c = cache.0.lock().unwrap();
|
||||
|
||||
let mut should_show = true;
|
||||
|
||||
if let Some(state) = c.get(self.label()) {
|
||||
// avoid restoring the default zeroed state
|
||||
if *state == WindowState::default() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if flags.contains(StateFlags::DECORATIONS) {
|
||||
self.set_decorations(state.decorated)?;
|
||||
}
|
||||
@@ -225,7 +236,7 @@ impl<R: Runtime> WindowExtInternal for Window<R> {
|
||||
}
|
||||
|
||||
if flags.contains(StateFlags::POSITION) {
|
||||
let position = self.inner_position()?;
|
||||
let position = self.outer_position()?;
|
||||
if let Ok(Some(monitor)) = self.current_monitor() {
|
||||
// save only window positions that are inside the current monitor
|
||||
if monitor.contains(position) && !is_maximized {
|
||||
@@ -269,6 +280,10 @@ impl Builder {
|
||||
pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
|
||||
let flags = self.state_flags;
|
||||
PluginBuilder::new("window-state")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
cmd::save_window_state,
|
||||
cmd::restore_state
|
||||
])
|
||||
.setup(|app, _api| {
|
||||
let cache: Arc<Mutex<HashMap<String, WindowState>>> = if let Ok(app_dir) =
|
||||
app.path().app_config_dir()
|
||||
@@ -304,6 +319,17 @@ impl Builder {
|
||||
let label = window.label().to_string();
|
||||
let window_clone = window.clone();
|
||||
let flags = self.state_flags;
|
||||
|
||||
// insert a default state if this window should be tracked and
|
||||
// the disk cache doesn't have a state for it
|
||||
{
|
||||
cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(label.clone())
|
||||
.or_insert_with(WindowState::default);
|
||||
}
|
||||
|
||||
window.on_window_event(move |e| {
|
||||
if let WindowEvent::CloseRequested { .. } = e {
|
||||
let mut c = cache.lock().unwrap();
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../shared/tsconfig.json
|
||||
Reference in New Issue
Block a user