feat(core): add path plugin, implement mobile APIs (#6339)

This commit is contained in:
Lucas Fernandes Nogueira 2023-04-07 08:48:14 -07:00 committed by GitHub
parent 1eacd51d18
commit be941b9719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1223 additions and 1521 deletions

View File

@ -32,7 +32,7 @@ tauri-winres = "0.1"
semver = "1"
[target."cfg(target_os = \"macos\")".dependencies]
swift-rs = { version = "1.0.3", features = [ "build" ] }
swift-rs = { version = "1.0.4", features = [ "build" ] }
[features]
codegen = [ "tauri-codegen", "quote" ]

View File

@ -4,17 +4,18 @@
use heck::{ToLowerCamelCase, ToSnakeCase};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
parse_macro_input,
spanned::Spanned,
FnArg, Ident, ItemFn, Lit, Meta, Pat, Token, Visibility,
FnArg, ItemFn, Lit, Meta, Pat, Token, Visibility,
};
struct WrapperAttributes {
root: TokenStream2,
execution_context: ExecutionContext,
argument_case: ArgumentCase,
}
@ -22,6 +23,7 @@ struct WrapperAttributes {
impl Parse for WrapperAttributes {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut wrapper_attributes = WrapperAttributes {
root: quote!(::tauri),
execution_context: ExecutionContext::Blocking,
argument_case: ArgumentCase::Camel,
};
@ -43,6 +45,17 @@ impl Parse for WrapperAttributes {
}
};
}
} else if v.path.is_ident("root") {
if let Lit::Str(s) = v.lit {
let lit = s.value();
wrapper_attributes.root = if lit == "crate" {
quote!($crate)
} else {
let ident = Ident::new(&lit, Span::call_site());
quote!(#ident)
};
}
}
}
Ok(Meta::Path(p)) => {
@ -104,21 +117,28 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
};
// body to the command wrapper or a `compile_error!` of an error occurred while parsing it.
let body = syn::parse::<WrapperAttributes>(attributes)
let (body, attributes) = syn::parse::<WrapperAttributes>(attributes)
.map(|mut attrs| {
if function.sig.asyncness.is_some() {
attrs.execution_context = ExecutionContext::Async;
}
attrs
})
.and_then(|attrs| match attrs.execution_context {
ExecutionContext::Async => body_async(&function, &invoke, attrs.argument_case),
ExecutionContext::Blocking => body_blocking(&function, &invoke, attrs.argument_case),
.and_then(|attrs| {
let body = match attrs.execution_context {
ExecutionContext::Async => body_async(&function, &invoke, &attrs),
ExecutionContext::Blocking => body_blocking(&function, &invoke, &attrs),
};
body.map(|b| (b, Some(attrs)))
})
.unwrap_or_else(syn::Error::into_compile_error);
.unwrap_or_else(|e| (syn::Error::into_compile_error(e), None));
let Invoke { message, resolver } = invoke;
let root = attributes
.map(|a| a.root)
.unwrap_or_else(|| quote!(::tauri));
// Rely on rust 2018 edition to allow importing a macro from a path.
quote!(
#function
@ -129,10 +149,10 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
// double braces because the item is expected to be a block expression
($path:path, $invoke:ident) => {{
#[allow(unused_imports)]
use ::tauri::command::private::*;
use #root::command::private::*;
// prevent warnings when the body is a `compile_error!` or if the command has no arguments
#[allow(unused_variables)]
let ::tauri::Invoke { message: #message, resolver: #resolver } = $invoke;
let #root::Invoke { message: #message, resolver: #resolver } = $invoke;
#body
}};
@ -150,9 +170,13 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
/// See the [`tauri::command`] module for all the items and traits that make this possible.
///
/// [`tauri::command`]: https://docs.rs/tauri/*/tauri/runtime/index.html
fn body_async(function: &ItemFn, invoke: &Invoke, case: ArgumentCase) -> syn::Result<TokenStream2> {
fn body_async(
function: &ItemFn,
invoke: &Invoke,
attributes: &WrapperAttributes,
) -> syn::Result<TokenStream2> {
let Invoke { message, resolver } = invoke;
parse_args(function, message, case).map(|args| {
parse_args(function, message, attributes).map(|args| {
quote! {
#resolver.respond_async_serialized(async move {
let result = $path(#(#args?),*);
@ -172,15 +196,15 @@ fn body_async(function: &ItemFn, invoke: &Invoke, case: ArgumentCase) -> syn::Re
fn body_blocking(
function: &ItemFn,
invoke: &Invoke,
case: ArgumentCase,
attributes: &WrapperAttributes,
) -> syn::Result<TokenStream2> {
let Invoke { message, resolver } = invoke;
let args = parse_args(function, message, case)?;
let args = parse_args(function, message, attributes)?;
// the body of a `match` to early return any argument that wasn't successful in parsing.
let match_body = quote!({
Ok(arg) => arg,
Err(err) => {#resolver.invoke_error(err); return true },
Err(err) => { #resolver.invoke_error(err); return true },
});
Ok(quote! {
@ -195,13 +219,13 @@ fn body_blocking(
fn parse_args(
function: &ItemFn,
message: &Ident,
case: ArgumentCase,
attributes: &WrapperAttributes,
) -> syn::Result<Vec<TokenStream2>> {
function
.sig
.inputs
.iter()
.map(|arg| parse_arg(&function.sig.ident, arg, message, case))
.map(|arg| parse_arg(&function.sig.ident, arg, message, attributes))
.collect()
}
@ -210,7 +234,7 @@ fn parse_arg(
command: &Ident,
arg: &FnArg,
message: &Ident,
case: ArgumentCase,
attributes: &WrapperAttributes,
) -> syn::Result<TokenStream2> {
// we have no use for self arguments
let mut arg = match arg {
@ -245,7 +269,7 @@ fn parse_arg(
));
}
match case {
match attributes.argument_case {
ArgumentCase::Camel => {
key = key.to_lower_camel_case();
}
@ -254,8 +278,10 @@ fn parse_arg(
}
}
Ok(quote!(::tauri::command::CommandArg::from_command(
::tauri::command::CommandItem {
let root = &attributes.root;
Ok(quote!(#root::command::CommandArg::from_command(
#root::command::CommandItem {
name: stringify!(#command),
key: #key,
message: &#message,

View File

@ -116,7 +116,7 @@ jni = "0.20"
libc = "0.2"
objc = "0.2"
cocoa = "0.24"
swift-rs = "1.0.3"
swift-rs = "1.0.4"
[build-dependencies]
heck = "0.4"

View File

@ -0,0 +1,78 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
package app.tauri
import android.app.Activity
import android.os.Environment
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Plugin
import app.tauri.plugin.Invoke
import app.tauri.plugin.JSObject
@TauriPlugin
class PathPlugin(private val activity: Activity): Plugin(activity) {
private fun resolvePath(invoke: Invoke, path: String?) {
val obj = JSObject()
obj.put("path", path)
invoke.resolve(obj)
}
@Command
fun getAudioDir(invoke: Invoke) {
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_MUSIC)?.absolutePath)
}
@Command
fun getExternalCacheDir(invoke: Invoke) {
resolvePath(invoke, activity.externalCacheDir?.absolutePath)
}
@Command
fun getConfigDir(invoke: Invoke) {
resolvePath(invoke, activity.dataDir.absolutePath)
}
@Command
fun getDataDir(invoke: Invoke) {
resolvePath(invoke, activity.dataDir.absolutePath)
}
@Command
fun getDocumentDir(invoke: Invoke) {
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath)
}
@Command
fun getDownloadDir(invoke: Invoke) {
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath)
}
@Command
fun getPictureDir(invoke: Invoke) {
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.absolutePath)
}
@Command
fun getPublicDir(invoke: Invoke) {
resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DCIM)?.absolutePath)
}
@Command
fun getVideoDir(invoke: Invoke) {
resolvePath(invoke, activity.externalCacheDir?.absolutePath)
}
@Command
fun getResourcesDir(invoke: Invoke) {
// TODO
resolvePath(invoke, activity.cacheDir.absolutePath)
}
@Command
fun getCacheDir(invoke: Invoke) {
resolvePath(invoke, activity.cacheDir.absolutePath)
}
}

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,6 @@ pub mod file;
#[cfg_attr(doc_cfg, doc(cfg(feature = "http-api")))]
pub mod http;
pub mod ipc;
pub mod path;
pub mod process;
#[cfg(feature = "shell-open-api")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "shell-open-api")))]

View File

@ -1,604 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! Types and functions related to file system path operations.
use std::{
env::temp_dir,
path::{Component, Path, PathBuf},
};
use crate::{Config, Env, PackageInfo};
use serde_repr::{Deserialize_repr, Serialize_repr};
// we have to wrap the BaseDirectory enum in a module for #[allow(deprecated)]
// to work, because the procedural macros on the enum prevent it from working directly
// TODO: remove this workaround in v2 along with deprecated variants
#[allow(deprecated)]
mod base_directory {
use super::*;
/// A base directory to be used in [`resolve_path`].
///
/// The base directory is the optional root of a file system operation.
/// If informed by the API call, all paths will be relative to the path of the given directory.
///
/// For more information, check the [`dirs_next` documentation](https://docs.rs/dirs_next/).
#[derive(Serialize_repr, Deserialize_repr, Clone, Copy, Debug)]
#[repr(u16)]
#[non_exhaustive]
pub enum BaseDirectory {
/// The Audio directory.
Audio = 1,
/// The Cache directory.
Cache,
/// The Config directory.
Config,
/// The Data directory.
Data,
/// The LocalData directory.
LocalData,
/// The Desktop directory.
Desktop,
/// The Document directory.
Document,
/// The Download directory.
Download,
/// The Executable directory.
Executable,
/// The Font directory.
Font,
/// The Home directory.
Home,
/// The Picture directory.
Picture,
/// The Public directory.
Public,
/// The Runtime directory.
Runtime,
/// The Template directory.
Template,
/// The Video directory.
Video,
/// The Resource directory.
Resource,
/// The default app config directory.
/// Resolves to [`BaseDirectory::Config`]`/{bundle_identifier}`.
#[deprecated(
since = "1.2.0",
note = "Will be removed in 2.0.0. Use `BaseDirectory::AppConfig` or BaseDirectory::AppData` instead."
)]
App,
/// The default app log directory.
/// Resolves to [`BaseDirectory::Home`]`/Library/Logs/{bundle_identifier}` on macOS
/// and [`BaseDirectory::Config`]`/{bundle_identifier}/logs` on linux and Windows.
#[deprecated(
since = "1.2.0",
note = "Will be removed in 2.0.0. Use `BaseDirectory::AppLog` instead."
)]
Log,
/// A temporary directory.
/// Resolves to [`temp_dir`].
Temp,
/// The default app config directory.
/// Resolves to [`BaseDirectory::Config`]`/{bundle_identifier}`.
AppConfig,
/// The default app data directory.
/// Resolves to [`BaseDirectory::Data`]`/{bundle_identifier}`.
AppData,
/// The default app local data directory.
/// Resolves to [`BaseDirectory::LocalData`]`/{bundle_identifier}`.
AppLocalData,
/// The default app cache directory.
/// Resolves to [`BaseDirectory::Cache`]`/{bundle_identifier}`.
AppCache,
/// The default app log directory.
/// Resolves to [`BaseDirectory::Home`]`/Library/Logs/{bundle_identifier}` on macOS
/// and [`BaseDirectory::Config`]`/{bundle_identifier}/logs` on linux and Windows.
AppLog,
}
}
pub use base_directory::BaseDirectory;
impl BaseDirectory {
/// Gets the variable that represents this [`BaseDirectory`] for string paths.
pub fn variable(self) -> &'static str {
match self {
Self::Audio => "$AUDIO",
Self::Cache => "$CACHE",
Self::Config => "$CONFIG",
Self::Data => "$DATA",
Self::LocalData => "$LOCALDATA",
Self::Desktop => "$DESKTOP",
Self::Document => "$DOCUMENT",
Self::Download => "$DOWNLOAD",
Self::Executable => "$EXE",
Self::Font => "$FONT",
Self::Home => "$HOME",
Self::Picture => "$PICTURE",
Self::Public => "$PUBLIC",
Self::Runtime => "$RUNTIME",
Self::Template => "$TEMPLATE",
Self::Video => "$VIDEO",
Self::Resource => "$RESOURCE",
#[allow(deprecated)]
Self::App => "$APP",
#[allow(deprecated)]
Self::Log => "$LOG",
Self::Temp => "$TEMP",
Self::AppConfig => "$APPCONFIG",
Self::AppData => "$APPDATA",
Self::AppLocalData => "$APPLOCALDATA",
Self::AppCache => "$APPCACHE",
Self::AppLog => "$APPLOG",
}
}
/// Gets the [`BaseDirectory`] associated with the given variable, or [`None`] if the variable doesn't match any.
pub fn from_variable(variable: &str) -> Option<Self> {
let res = match variable {
"$AUDIO" => Self::Audio,
"$CACHE" => Self::Cache,
"$CONFIG" => Self::Config,
"$DATA" => Self::Data,
"$LOCALDATA" => Self::LocalData,
"$DESKTOP" => Self::Desktop,
"$DOCUMENT" => Self::Document,
"$DOWNLOAD" => Self::Download,
"$EXE" => Self::Executable,
"$FONT" => Self::Font,
"$HOME" => Self::Home,
"$PICTURE" => Self::Picture,
"$PUBLIC" => Self::Public,
"$RUNTIME" => Self::Runtime,
"$TEMPLATE" => Self::Template,
"$VIDEO" => Self::Video,
"$RESOURCE" => Self::Resource,
#[allow(deprecated)]
"$APP" => Self::App,
#[allow(deprecated)]
"$LOG" => Self::Log,
"$TEMP" => Self::Temp,
"$APPCONFIG" => Self::AppConfig,
"$APPDATA" => Self::AppData,
"$APPLOCALDATA" => Self::AppLocalData,
"$APPCACHE" => Self::AppCache,
"$APPLOG" => Self::AppLog,
_ => return None,
};
Some(res)
}
}
/// Parse the given path, resolving a [`BaseDirectory`] variable if the path starts with one.
///
/// # Examples
///
/// ```rust,no_run
/// use tauri::Manager;
/// tauri::Builder::default()
/// .setup(|app| {
/// let path = tauri::api::path::parse(&app.config(), app.package_info(), &app.env(), "$HOME/.bashrc")?;
/// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.bashrc");
/// Ok(())
/// });
/// ```
pub fn parse<P: AsRef<Path>>(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: P,
) -> crate::api::Result<PathBuf> {
let mut p = PathBuf::new();
let mut components = path.as_ref().components();
match components.next() {
Some(Component::Normal(str)) => {
if let Some(base_directory) = BaseDirectory::from_variable(&str.to_string_lossy()) {
p.push(resolve_path(
config,
package_info,
env,
"",
Some(base_directory),
)?);
} else {
p.push(str);
}
}
Some(component) => p.push(component),
None => (),
}
for component in components {
if let Component::ParentDir = component {
continue;
}
p.push(component);
}
Ok(p)
}
/// Resolves the path with the optional base directory.
///
/// This is a low level API. If the application has been built,
/// prefer the [path resolver API](`crate::AppHandle#method.path_resolver`).
///
/// # Examples
///
/// ## Before initializing the application
///
/// ```rust,no_run
/// use tauri::{api::path::{BaseDirectory, resolve_path}, Env};
/// // on an actual app, remove the string argument
/// let context = tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json");
/// let path = resolve_path(
/// context.config(),
/// context.package_info(),
/// &Env::default(),
/// "db/tauri.sqlite",
/// Some(BaseDirectory::AppData))
/// .expect("failed to resolve path");
/// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.config/com.tauri.app/db/tauri.sqlite");
///
/// tauri::Builder::default().run(context).expect("error while running tauri application");
/// ```
///
/// ## With an initialized app
/// ```rust,no_run
/// use tauri::{api::path::{BaseDirectory, resolve_path}, Manager};
/// tauri::Builder::default()
/// .setup(|app| {
/// let path = resolve_path(
/// &app.config(),
/// app.package_info(),
/// &app.env(),
/// "path/to/something",
/// Some(BaseDirectory::Config)
/// )?;
/// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.config/path/to/something");
/// Ok(())
/// });
/// ```
pub fn resolve_path<P: AsRef<Path>>(
config: &Config,
package_info: &PackageInfo,
env: &Env,
path: P,
dir: Option<BaseDirectory>,
) -> crate::api::Result<PathBuf> {
if let Some(base_dir) = dir {
let resolve_resource = matches!(base_dir, BaseDirectory::Resource);
let base_dir_path = match base_dir {
BaseDirectory::Audio => audio_dir(),
BaseDirectory::Cache => cache_dir(),
BaseDirectory::Config => config_dir(),
BaseDirectory::Data => data_dir(),
BaseDirectory::LocalData => local_data_dir(),
BaseDirectory::Desktop => desktop_dir(),
BaseDirectory::Document => document_dir(),
BaseDirectory::Download => download_dir(),
BaseDirectory::Executable => executable_dir(),
BaseDirectory::Font => font_dir(),
BaseDirectory::Home => home_dir(),
BaseDirectory::Picture => picture_dir(),
BaseDirectory::Public => public_dir(),
BaseDirectory::Runtime => runtime_dir(),
BaseDirectory::Template => template_dir(),
BaseDirectory::Video => video_dir(),
BaseDirectory::Resource => resource_dir(package_info, env),
#[allow(deprecated)]
BaseDirectory::App => app_config_dir(config),
#[allow(deprecated)]
BaseDirectory::Log => app_log_dir(config),
BaseDirectory::Temp => Some(temp_dir()),
BaseDirectory::AppConfig => app_config_dir(config),
BaseDirectory::AppData => app_data_dir(config),
BaseDirectory::AppLocalData => app_local_data_dir(config),
BaseDirectory::AppCache => app_cache_dir(config),
BaseDirectory::AppLog => app_log_dir(config),
};
if let Some(mut base_dir_path_value) = base_dir_path {
// use the same path resolution mechanism as the bundler's resource injection algorithm
if resolve_resource {
let mut resource_path = PathBuf::new();
for component in path.as_ref().components() {
match component {
Component::Prefix(_) => {}
Component::RootDir => resource_path.push("_root_"),
Component::CurDir => {}
Component::ParentDir => resource_path.push("_up_"),
Component::Normal(p) => resource_path.push(p),
}
}
base_dir_path_value.push(resource_path);
} else {
base_dir_path_value.push(path);
}
Ok(base_dir_path_value)
} else {
Err(crate::api::Error::Path(
"unable to determine base dir path".to_string(),
))
}
} else {
let mut dir_path = PathBuf::new();
dir_path.push(path);
Ok(dir_path)
}
}
/// Returns the path to the user's audio directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_MUSIC_DIR`.
/// - **macOS:** Resolves to `$HOME/Music`.
/// - **Windows:** Resolves to `{FOLDERID_Music}`.
pub fn audio_dir() -> Option<PathBuf> {
dirs_next::audio_dir()
}
/// Returns the path to the user's cache directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_CACHE_HOME` or `$HOME/.cache`.
/// - **macOS:** Resolves to `$HOME/Library/Caches`.
/// - **Windows:** Resolves to `{FOLDERID_LocalAppData}`.
pub fn cache_dir() -> Option<PathBuf> {
dirs_next::cache_dir()
}
/// Returns the path to the user's config directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_CONFIG_HOME` or `$HOME/.config`.
/// - **macOS:** Resolves to `$HOME/Library/Application Support`.
/// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`.
pub fn config_dir() -> Option<PathBuf> {
dirs_next::config_dir()
}
/// Returns the path to the user's data directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`.
/// - **macOS:** Resolves to `$HOME/Library/Application Support`.
/// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`.
pub fn data_dir() -> Option<PathBuf> {
dirs_next::data_dir()
}
/// Returns the path to the user's local data directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`.
/// - **macOS:** Resolves to `$HOME/Library/Application Support`.
/// - **Windows:** Resolves to `{FOLDERID_LocalAppData}`.
pub fn local_data_dir() -> Option<PathBuf> {
dirs_next::data_local_dir()
}
/// Returns the path to the user's desktop directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DESKTOP_DIR`.
/// - **macOS:** Resolves to `$HOME/Desktop`.
/// - **Windows:** Resolves to `{FOLDERID_Desktop}`.
pub fn desktop_dir() -> Option<PathBuf> {
dirs_next::desktop_dir()
}
/// Returns the path to the user's document directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOCUMENTS_DIR`.
/// - **macOS:** Resolves to `$HOME/Documents`.
/// - **Windows:** Resolves to `{FOLDERID_Documents}`.
pub fn document_dir() -> Option<PathBuf> {
dirs_next::document_dir()
}
/// Returns the path to the user's download directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOWNLOAD_DIR`.
/// - **macOS:** Resolves to `$HOME/Downloads`.
/// - **Windows:** Resolves to `{FOLDERID_Downloads}`.
pub fn download_dir() -> Option<PathBuf> {
dirs_next::download_dir()
}
/// Returns the path to the user's executable directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_BIN_HOME/../bin` or `$XDG_DATA_HOME/../bin` or `$HOME/.local/bin`.
/// - **macOS:** Not supported.
/// - **Windows:** Not supported.
pub fn executable_dir() -> Option<PathBuf> {
dirs_next::executable_dir()
}
/// Returns the path to the user's font directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_DATA_HOME/fonts` or `$HOME/.local/share/fonts`.
/// - **macOS:** Resolves to `$HOME/Library/Fonts`.
/// - **Windows:** Not supported.
pub fn font_dir() -> Option<PathBuf> {
dirs_next::font_dir()
}
/// Returns the path to the user's home directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$HOME`.
/// - **macOS:** Resolves to `$HOME`.
/// - **Windows:** Resolves to `{FOLDERID_Profile}`.
pub fn home_dir() -> Option<PathBuf> {
dirs_next::home_dir()
}
/// Returns the path to the user's picture directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PICTURES_DIR`.
/// - **macOS:** Resolves to `$HOME/Pictures`.
/// - **Windows:** Resolves to `{FOLDERID_Pictures}`.
pub fn picture_dir() -> Option<PathBuf> {
dirs_next::picture_dir()
}
/// Returns the path to the user's public directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PUBLICSHARE_DIR`.
/// - **macOS:** Resolves to `$HOME/Public`.
/// - **Windows:** Resolves to `{FOLDERID_Public}`.
pub fn public_dir() -> Option<PathBuf> {
dirs_next::public_dir()
}
/// Returns the path to the user's runtime directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_RUNTIME_DIR`.
/// - **macOS:** Not supported.
/// - **Windows:** Not supported.
pub fn runtime_dir() -> Option<PathBuf> {
dirs_next::runtime_dir()
}
/// Returns the path to the user's template directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_TEMPLATES_DIR`.
/// - **macOS:** Not supported.
/// - **Windows:** Resolves to `{FOLDERID_Templates}`.
pub fn template_dir() -> Option<PathBuf> {
dirs_next::template_dir()
}
/// Returns the path to the user's video dir
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_VIDEOS_DIR`.
/// - **macOS:** Resolves to `$HOME/Movies`.
/// - **Windows:** Resolves to `{FOLDERID_Videos}`.
pub fn video_dir() -> Option<PathBuf> {
dirs_next::video_dir()
}
/// Returns the path to the resource directory of this app.
///
/// See [`PathResolver::resource_dir`](crate::PathResolver#method.resource_dir) for a more convenient helper function.
pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> Option<PathBuf> {
crate::utils::platform::resource_dir(package_info, env).ok()
}
/// Returns the path to the suggested directory for your app's config files.
///
/// Resolves to [`config_dir`]`/${bundle_identifier}`.
///
/// See [`PathResolver::app_config_dir`](crate::PathResolver#method.app_config_dir) for a more convenient helper function.
pub fn app_config_dir(config: &Config) -> Option<PathBuf> {
dirs_next::config_dir().map(|dir| dir.join(&config.tauri.bundle.identifier))
}
/// Returns the path to the suggested directory for your app's data files.
///
/// Resolves to [`data_dir`]`/${bundle_identifier}`.
///
/// See [`PathResolver::app_data_dir`](crate::PathResolver#method.app_data_dir) for a more convenient helper function.
pub fn app_data_dir(config: &Config) -> Option<PathBuf> {
dirs_next::data_dir().map(|dir| dir.join(&config.tauri.bundle.identifier))
}
/// Returns the path to the suggested directory for your app's local data files.
///
/// Resolves to [`local_data_dir`]`/${bundle_identifier}`.
///
/// See [`PathResolver::app_local_data_dir`](crate::PathResolver#method.app_local_data_dir) for a more convenient helper function.
pub fn app_local_data_dir(config: &Config) -> Option<PathBuf> {
dirs_next::data_local_dir().map(|dir| dir.join(&config.tauri.bundle.identifier))
}
/// Returns the path to the suggested directory for your app's cache files.
///
/// Resolves to [`cache_dir`]`/${bundle_identifier}`.
///
/// See [`PathResolver::app_cache_dir`](crate::PathResolver#method.app_cache_dir) for a more convenient helper function.
pub fn app_cache_dir(config: &Config) -> Option<PathBuf> {
dirs_next::cache_dir().map(|dir| dir.join(&config.tauri.bundle.identifier))
}
/// Returns the path to the suggested directory for your app's log files.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`config_dir`]`/${bundle_identifier}/logs`.
/// - **macOS:** Resolves to [`home_dir`]`/Library/Logs/${bundle_identifier}`
/// - **Windows:** Resolves to [`config_dir`]`/${bundle_identifier}/logs`.
///
/// See [`PathResolver::app_log_dir`](crate::PathResolver#method.app_log_dir) for a more convenient helper function.
pub fn app_log_dir(config: &Config) -> Option<PathBuf> {
#[cfg(target_os = "macos")]
let path = dirs_next::home_dir().map(|dir| {
dir
.join("Library/Logs")
.join(&config.tauri.bundle.identifier)
});
#[cfg(not(target_os = "macos"))]
let path =
dirs_next::config_dir().map(|dir| dir.join(&config.tauri.bundle.identifier).join("logs"));
path
}
/// Returns the path to the suggested directory for your app's config files.
///
/// Resolves to [`config_dir`]`/${bundle_identifier}`.
///
/// See [`PathResolver::app_config_dir`](crate::PathResolver#method.app_config_dir) for a more convenient helper function.
#[deprecated(
since = "1.2.0",
note = "Will be removed in 2.0.0. Use `app_config_dir` or `app_data_dir` instead."
)]
pub fn app_dir(config: &Config) -> Option<PathBuf> {
app_config_dir(config)
}
/// Returns the path to the suggested directory for your app's log files.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`config_dir`]`/${bundle_identifier}`.
/// - **macOS:** Resolves to [`home_dir`]`/Library/Logs/${bundle_identifier}`
/// - **Windows:** Resolves to [`config_dir`]`/${bundle_identifier}`.
///
/// See [`PathResolver::app_log_dir`](crate::PathResolver#method.app_log_dir) for a more convenient helper function.
#[deprecated(
since = "1.2.0",
note = "Will be removed in 2.0.0. Use `app_log_dir` instead."
)]
pub fn log_dir(config: &Config) -> Option<PathBuf> {
app_log_dir(config)
}

View File

@ -22,7 +22,7 @@ use crate::{
scope::FsScope,
sealed::{ManagerBase, RuntimeOrDispatch},
utils::config::Config,
utils::{assets::Assets, resources::resource_relpath, Env},
utils::{assets::Assets, Env},
Context, DeviceEventFilter, EventLoopMessage, Invoke, InvokeError, InvokeResponse, Manager,
Runtime, Scopes, StateManager, Theme, Window,
};
@ -41,7 +41,6 @@ use tauri_utils::PackageInfo;
use std::{
collections::HashMap,
fmt,
path::{Path, PathBuf},
sync::{mpsc::Sender, Arc, Weak},
};
@ -242,111 +241,6 @@ pub(crate) struct UpdaterSettings {
pub(crate) target: Option<String>,
}
/// The path resolver is a helper for the application-specific [`crate::api::path`] APIs.
#[derive(Debug, Clone)]
pub struct PathResolver {
env: Env,
config: Arc<Config>,
package_info: PackageInfo,
}
impl PathResolver {
/// Returns the path to the resource directory of this app.
///
/// Helper function for [`crate::api::path::resource_dir`].
pub fn resource_dir(&self) -> Option<PathBuf> {
crate::api::path::resource_dir(&self.package_info, &self.env)
}
/// Resolves the path of the given resource.
/// Note that the path must be the same as provided in `tauri.conf.json`.
///
/// This function is helpful when your resource path includes a root dir (`/`) or parent component (`..`),
/// because Tauri replaces them with a parent folder, so simply using [`Self::resource_dir`] and joining the path
/// won't work.
///
/// # Examples
///
/// `tauri.conf.json`:
/// ```json
/// {
/// "tauri": {
/// "bundle": {
/// "resources": ["../assets/*"]
/// }
/// }
/// }
/// ```
///
/// ```no_run
/// tauri::Builder::default()
/// .setup(|app| {
/// let resource_path = app.path_resolver()
/// .resolve_resource("../assets/logo.svg")
/// .expect("failed to resolve resource dir");
/// Ok(())
/// });
/// ```
pub fn resolve_resource<P: AsRef<Path>>(&self, path: P) -> Option<PathBuf> {
self
.resource_dir()
.map(|dir| dir.join(resource_relpath(path.as_ref())))
}
/// Returns the path to the suggested directory for your app's config files.
///
/// Helper function for [`crate::api::path::app_config_dir`].
pub fn app_config_dir(&self) -> Option<PathBuf> {
crate::api::path::app_config_dir(&self.config)
}
/// Returns the path to the suggested directory for your app's data files.
///
/// Helper function for [`crate::api::path::app_data_dir`].
pub fn app_data_dir(&self) -> Option<PathBuf> {
crate::api::path::app_data_dir(&self.config)
}
/// Returns the path to the suggested directory for your app's local data files.
///
/// Helper function for [`crate::api::path::app_local_data_dir`].
pub fn app_local_data_dir(&self) -> Option<PathBuf> {
crate::api::path::app_local_data_dir(&self.config)
}
/// Returns the path to the suggested directory for your app's cache files.
///
/// Helper function for [`crate::api::path::app_cache_dir`].
pub fn app_cache_dir(&self) -> Option<PathBuf> {
crate::api::path::app_cache_dir(&self.config)
}
/// Returns the path to the suggested directory for your app's log files.
///
/// Helper function for [`crate::api::path::app_log_dir`].
pub fn app_log_dir(&self) -> Option<PathBuf> {
crate::api::path::app_log_dir(&self.config)
}
/// Returns the path to the suggested directory for your app's config files.
#[deprecated(
since = "1.2.0",
note = "Will be removed in 2.0.0. Use `app_config_dir` or `app_data_dir` instead."
)]
pub fn app_dir(&self) -> Option<PathBuf> {
self.app_config_dir()
}
/// Returns the path to the suggested directory for your app's log files.
#[deprecated(
since = "1.2.0",
note = "Will be removed in 2.0.0. Use `app_log_dir` instead."
)]
pub fn log_dir(&self) -> Option<PathBuf> {
self.app_log_dir()
}
}
/// The asset resolver is a helper to access the [`tauri_utils::assets::Assets`] interface.
#[derive(Debug, Clone)]
pub struct AssetResolver<R: Runtime> {
@ -733,15 +627,6 @@ macro_rules! shared_app_impl {
.get_tray(id)
}
/// The path resolver for the application.
pub fn path_resolver(&self) -> PathResolver {
PathResolver {
env: self.state::<Env>().inner().clone(),
config: self.manager.config(),
package_info: self.manager.package_info().clone(),
}
}
/// Gets a copy of the global shortcut manager instance.
#[cfg(all(desktop, feature = "global-shortcut"))]
#[cfg_attr(doc_cfg, doc(cfg(feature = "global-shortcut")))]
@ -802,6 +687,11 @@ shared_app_impl!(App<R>);
shared_app_impl!(AppHandle<R>);
impl<R: Runtime> App<R> {
fn register_core_plugins(&self) -> crate::Result<()> {
self.handle.plugin(crate::path::init())?;
Ok(())
}
/// Gets a handle to the application instance.
pub fn handle(&self) -> AppHandle<R> {
self.handle.clone()
@ -1663,27 +1553,23 @@ impl<R: Runtime> Builder<R> {
},
};
app.register_core_plugins()?;
let env = Env::default();
app.manage(env);
app.manage(Scopes {
fs: FsScope::for_fs_api(
&app.manager.config(),
app.package_info(),
&env,
&app.config().tauri.allowlist.fs.scope,
)?,
fs: FsScope::for_fs_api(&app, &app.config().tauri.allowlist.fs.scope)?,
#[cfg(protocol_asset)]
asset_protocol: FsScope::for_fs_api(
&app.manager.config(),
app.package_info(),
&env,
&app,
&app.config().tauri.allowlist.protocol.asset_scope,
)?,
#[cfg(http_request)]
http: crate::scope::HttpScope::for_http_api(&app.config().tauri.allowlist.http.scope),
#[cfg(shell_scope)]
shell: ShellScope::new(&app.manager.config(), app.package_info(), &env, shell_scope),
shell: ShellScope::new(&app, shell_scope),
});
app.manage(env);
#[cfg(windows)]
{
@ -1695,7 +1581,8 @@ impl<R: Runtime> Builder<R> {
.windows
.webview_install_mode
{
if let Some(resource_dir) = app.path_resolver().resource_dir() {
use crate::path::PathExt;
if let Ok(resource_dir) = app.path().resource_dir() {
std::env::set_var(
"WEBVIEW2_BROWSER_EXECUTABLE_FOLDER",
resource_dir.join(path),
@ -1874,8 +1761,5 @@ mod tests {
crate::test_utils::assert_send::<super::AssetResolver<crate::Wry>>();
crate::test_utils::assert_sync::<super::AssetResolver<crate::Wry>>();
}
crate::test_utils::assert_send::<super::PathResolver>();
crate::test_utils::assert_sync::<super::PathResolver>();
}
}

View File

@ -29,8 +29,6 @@ mod http;
mod notification;
#[cfg(os_any)]
mod operating_system;
#[cfg(path_any)]
mod path;
#[cfg(process_any)]
mod process;
#[cfg(shell_any)]
@ -78,8 +76,6 @@ enum Module {
Fs(file_system::Cmd),
#[cfg(os_any)]
Os(operating_system::Cmd),
#[cfg(path_any)]
Path(path::Cmd),
Window(Box<window::Cmd>),
#[cfg(shell_any)]
Shell(shell::Cmd),
@ -131,13 +127,6 @@ impl Module {
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(path_any)]
Self::Path(cmd) => resolver.respond_async(async move {
cmd
.run(context)
.and_then(|r| r.json)
.map_err(InvokeError::from_anyhow)
}),
#[cfg(os_any)]
Self::Os(cmd) => resolver.respond_async(async move {
cmd

View File

@ -8,8 +8,8 @@ use crate::{
api::{
dir,
file::{self, SafePathBuf},
path::BaseDirectory,
},
path::{BaseDirectory, PathExt},
scope::Scopes,
Config, Env, Manager, PackageInfo, Runtime, Window,
};
@ -129,13 +129,7 @@ impl Cmd {
path: SafePathBuf,
options: Option<FileOperationOptions>,
) -> super::Result<Vec<u8>> {
let resolved_path = resolve_path(
&context.config,
&context.package_info,
&context.window,
path,
options.and_then(|o| o.dir),
)?;
let resolved_path = resolve_path(&context.window, path, options.and_then(|o| o.dir))?;
file::read_binary(&resolved_path)
.with_context(|| format!("path: {}", resolved_path.display()))
.map_err(Into::into)
@ -147,13 +141,7 @@ impl Cmd {
path: SafePathBuf,
options: Option<FileOperationOptions>,
) -> super::Result<String> {
let resolved_path = resolve_path(
&context.config,
&context.package_info,
&context.window,
path,
options.and_then(|o| o.dir),
)?;
let resolved_path = resolve_path(&context.window, path, options.and_then(|o| o.dir))?;
file::read_string(&resolved_path)
.with_context(|| format!("path: {}", resolved_path.display()))
.map_err(Into::into)
@ -166,13 +154,7 @@ impl Cmd {
contents: Vec<u8>,
options: Option<FileOperationOptions>,
) -> super::Result<()> {
let resolved_path = resolve_path(
&context.config,
&context.package_info,
&context.window,
path,
options.and_then(|o| o.dir),
)?;
let resolved_path = resolve_path(&context.window, path, options.and_then(|o| o.dir))?;
File::create(&resolved_path)
.with_context(|| format!("path: {}", resolved_path.display()))
.map_err(Into::into)
@ -190,13 +172,7 @@ impl Cmd {
} else {
(false, None)
};
let resolved_path = resolve_path(
&context.config,
&context.package_info,
&context.window,
path,
dir,
)?;
let resolved_path = resolve_path(&context.window, path, dir)?;
dir::read_dir_with_options(
&resolved_path,
recursive,
@ -217,20 +193,8 @@ impl Cmd {
) -> super::Result<()> {
let (src, dest) = match options.and_then(|o| o.dir) {
Some(dir) => (
resolve_path(
&context.config,
&context.package_info,
&context.window,
source,
Some(dir),
)?,
resolve_path(
&context.config,
&context.package_info,
&context.window,
destination,
Some(dir),
)?,
resolve_path(&context.window, source, Some(dir))?,
resolve_path(&context.window, destination, Some(dir))?,
),
None => (source, destination),
};
@ -250,13 +214,7 @@ impl Cmd {
} else {
(false, None)
};
let resolved_path = resolve_path(
&context.config,
&context.package_info,
&context.window,
path,
dir,
)?;
let resolved_path = resolve_path(&context.window, path, dir)?;
if recursive {
fs::create_dir_all(&resolved_path)
.with_context(|| format!("path: {}", resolved_path.display()))?;
@ -279,13 +237,7 @@ impl Cmd {
} else {
(false, None)
};
let resolved_path = resolve_path(
&context.config,
&context.package_info,
&context.window,
path,
dir,
)?;
let resolved_path = resolve_path(&context.window, path, dir)?;
if recursive {
fs::remove_dir_all(&resolved_path)
.with_context(|| format!("path: {}", resolved_path.display()))?;
@ -303,13 +255,7 @@ impl Cmd {
path: SafePathBuf,
options: Option<FileOperationOptions>,
) -> super::Result<()> {
let resolved_path = resolve_path(
&context.config,
&context.package_info,
&context.window,
path,
options.and_then(|o| o.dir),
)?;
let resolved_path = resolve_path(&context.window, path, options.and_then(|o| o.dir))?;
fs::remove_file(&resolved_path)
.with_context(|| format!("path: {}", resolved_path.display()))?;
Ok(())
@ -324,20 +270,8 @@ impl Cmd {
) -> super::Result<()> {
let (old, new) = match options.and_then(|o| o.dir) {
Some(dir) => (
resolve_path(
&context.config,
&context.package_info,
&context.window,
old_path,
Some(dir),
)?,
resolve_path(
&context.config,
&context.package_info,
&context.window,
new_path,
Some(dir),
)?,
resolve_path(&context.window, old_path, Some(dir))?,
resolve_path(&context.window, new_path, Some(dir))?,
),
None => (old_path, new_path),
};
@ -352,27 +286,22 @@ impl Cmd {
path: SafePathBuf,
options: Option<FileOperationOptions>,
) -> super::Result<bool> {
let resolved_path = resolve_path(
&context.config,
&context.package_info,
&context.window,
path,
options.and_then(|o| o.dir),
)?;
let resolved_path = resolve_path(&context.window, path, options.and_then(|o| o.dir))?;
Ok(resolved_path.as_ref().exists())
}
}
#[allow(dead_code)]
fn resolve_path<R: Runtime>(
config: &Config,
package_info: &PackageInfo,
window: &Window<R>,
path: SafePathBuf,
dir: Option<BaseDirectory>,
) -> super::Result<SafePathBuf> {
let env = window.state::<Env>().inner();
match crate::api::path::resolve_path(config, package_info, env, &path, dir) {
match if let Some(dir) = dir {
window.path().resolve(&path, dir)
} else {
Ok(path.as_ref().to_path_buf())
} {
Ok(path) => {
if window.state::<Scopes>().fs.is_allowed(&path) {
Ok(

View File

@ -1,283 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![allow(unused_imports)]
use crate::{api::path::BaseDirectory, Runtime};
#[cfg(path_all)]
use crate::{Env, Manager};
use std::path::PathBuf;
#[cfg(path_all)]
use std::path::{Component, Path, MAIN_SEPARATOR};
use super::InvokeContext;
use serde::Deserialize;
use tauri_macros::{command_enum, module_command_handler, CommandModule};
/// The API descriptor.
#[command_enum]
#[derive(Deserialize, CommandModule)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
#[cmd(path_all, "path > all")]
ResolvePath {
path: String,
directory: Option<BaseDirectory>,
},
#[cmd(path_all, "path > all")]
Resolve { paths: Vec<String> },
#[cmd(path_all, "path > all")]
Normalize { path: String },
#[cmd(path_all, "path > all")]
Join { paths: Vec<String> },
#[cmd(path_all, "path > all")]
Dirname { path: String },
#[cmd(path_all, "path > all")]
Extname { path: String },
#[cmd(path_all, "path > all")]
Basename { path: String, ext: Option<String> },
#[cmd(path_all, "path > all")]
IsAbsolute { path: String },
}
impl Cmd {
#[module_command_handler(path_all)]
fn resolve_path<R: Runtime>(
context: InvokeContext<R>,
path: String,
directory: Option<BaseDirectory>,
) -> super::Result<PathBuf> {
crate::api::path::resolve_path(
&context.config,
&context.package_info,
context.window.state::<Env>().inner(),
path,
directory,
)
.map_err(Into::into)
}
#[module_command_handler(path_all)]
fn resolve<R: Runtime>(_context: InvokeContext<R>, paths: Vec<String>) -> super::Result<PathBuf> {
// Start with current directory then start adding paths from the vector one by one using `PathBuf.push()` which
// will ensure that if an absolute path is encountered in the iteration, it will be used as the current full path.
//
// examples:
// 1. `vec!["."]` or `vec![]` will be equal to `std::env::current_dir()`
// 2. `vec!["/foo/bar", "/tmp/file", "baz"]` will be equal to `PathBuf::from("/tmp/file/baz")`
let mut path = std::env::current_dir()?;
for p in paths {
path.push(p);
}
Ok(normalize_path(&path))
}
#[module_command_handler(path_all)]
fn normalize<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<String> {
let mut p = normalize_path_no_absolute(Path::new(&path))
.to_string_lossy()
.to_string();
Ok(
// Node.js behavior is to return `".."` for `normalize("..")`
// and `"."` for `normalize("")` or `normalize(".")`
if p.is_empty() && path == ".." {
"..".into()
} else if p.is_empty() && path == "." {
".".into()
} else {
// Add a trailing separator if the path passed to this functions had a trailing separator. That's how Node.js behaves.
if (path.ends_with('/') || path.ends_with('\\'))
&& (!p.ends_with('/') || !p.ends_with('\\'))
{
p.push(MAIN_SEPARATOR);
}
p
},
)
}
#[module_command_handler(path_all)]
fn join<R: Runtime>(_context: InvokeContext<R>, mut paths: Vec<String>) -> super::Result<String> {
let path = PathBuf::from(
paths
.iter_mut()
.map(|p| {
// Add a `MAIN_SEPARATOR` if it doesn't already have one.
// Doing this to ensure that the vector elements are separated in
// the resulting string so path.components() can work correctly when called
// in `normalize_path_no_absolute()` later on.
if !p.ends_with('/') && !p.ends_with('\\') {
p.push(MAIN_SEPARATOR);
}
p.to_string()
})
.collect::<String>(),
);
let p = normalize_path_no_absolute(&path)
.to_string_lossy()
.to_string();
Ok(if p.is_empty() { ".".into() } else { p })
}
#[module_command_handler(path_all)]
fn dirname<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<PathBuf> {
match Path::new(&path).parent() {
Some(p) => Ok(p.to_path_buf()),
None => Err(crate::error::into_anyhow(crate::api::Error::Path(
"Couldn't get the parent directory".into(),
))),
}
}
#[module_command_handler(path_all)]
fn extname<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<String> {
match Path::new(&path)
.extension()
.and_then(std::ffi::OsStr::to_str)
{
Some(p) => Ok(p.to_string()),
None => Err(crate::error::into_anyhow(crate::api::Error::Path(
"Couldn't get the extension of the file".into(),
))),
}
}
#[module_command_handler(path_all)]
fn basename<R: Runtime>(
_context: InvokeContext<R>,
path: String,
ext: Option<String>,
) -> super::Result<String> {
match Path::new(&path)
.file_name()
.and_then(std::ffi::OsStr::to_str)
{
Some(p) => Ok(if let Some(ext) = ext {
p.replace(ext.as_str(), "")
} else {
p.to_string()
}),
None => Err(crate::error::into_anyhow(crate::api::Error::Path(
"Couldn't get the basename".into(),
))),
}
}
#[module_command_handler(path_all)]
fn is_absolute<R: Runtime>(_context: InvokeContext<R>, path: String) -> super::Result<bool> {
Ok(Path::new(&path).is_absolute())
}
}
/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util.
/// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106
#[cfg(path_all)]
fn normalize_path(path: &Path) -> PathBuf {
let mut components = path.components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
components.next();
PathBuf::from(c.as_os_str())
} else {
PathBuf::new()
};
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => {
ret.push(component.as_os_str());
}
Component::CurDir => {}
Component::ParentDir => {
ret.pop();
}
Component::Normal(c) => {
ret.push(c);
}
}
}
ret
}
/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util but
/// slightly modified to not resolve absolute paths.
/// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106
#[cfg(path_all)]
fn normalize_path_no_absolute(path: &Path) -> PathBuf {
let mut components = path.components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
components.next();
PathBuf::from(c.as_os_str())
} else {
PathBuf::new()
};
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => {
ret.push(component.as_os_str());
}
Component::CurDir => {}
Component::ParentDir => {
ret.pop();
}
Component::Normal(c) => {
// Using PathBuf::push here will replace the whole path if an absolute path is encountered
// which is not the intended behavior, so instead of that, convert the current resolved path
// to a string and do simple string concatenation with the current component then convert it
// back to a PathBuf
let mut p = ret.to_string_lossy().to_string();
// Only add a separator if it doesn't have one already or if current normalized path is empty,
// this ensures it won't have an unwanted leading separator
if !p.is_empty() && !p.ends_with('/') && !p.ends_with('\\') {
p.push(MAIN_SEPARATOR);
}
if let Some(c) = c.to_str() {
p.push_str(c);
}
ret = PathBuf::from(p);
}
}
}
ret
}
#[cfg(test)]
mod tests {
use crate::api::path::BaseDirectory;
#[tauri_macros::module_command_test(path_all, "path > all")]
#[quickcheck_macros::quickcheck]
fn resolve_path(_path: String, _directory: Option<BaseDirectory>) {}
#[tauri_macros::module_command_test(path_all, "path > all")]
#[quickcheck_macros::quickcheck]
fn resolve(_paths: Vec<String>) {}
#[tauri_macros::module_command_test(path_all, "path > all")]
#[quickcheck_macros::quickcheck]
fn normalize(_path: String) {}
#[tauri_macros::module_command_test(path_all, "path > all")]
#[quickcheck_macros::quickcheck]
fn join(_paths: Vec<String>) {}
#[tauri_macros::module_command_test(path_all, "path > all")]
#[quickcheck_macros::quickcheck]
fn dirname(_path: String) {}
#[tauri_macros::module_command_test(path_all, "path > all")]
#[quickcheck_macros::quickcheck]
fn extname(_path: String) {}
#[tauri_macros::module_command_test(path_all, "path > all")]
#[quickcheck_macros::quickcheck]
fn basename(_path: String, _ext: Option<String>) {}
#[tauri_macros::module_command_test(path_all, "path > all")]
#[quickcheck_macros::quickcheck]
fn is_absolute(_path: String) {}
}

View File

@ -205,6 +205,8 @@ use tauri_runtime as runtime;
mod ios;
#[cfg(target_os = "android")]
mod jni_helpers;
/// Path APIs.
pub mod path;
/// The allowlist scopes.
pub mod scope;
mod state;
@ -286,8 +288,8 @@ pub use {
};
pub use {
self::app::{
App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, PathResolver,
RunEvent, WindowEvent,
App, AppHandle, AssetResolver, Builder, CloseRequestApi, GlobalWindowEvent, RunEvent,
WindowEvent,
},
self::hooks::{
Invoke, InvokeError, InvokeHandler, InvokeMessage, InvokePayload, InvokeResolver,
@ -690,6 +692,11 @@ pub trait Manager<R: Runtime>: sealed::ManagerBase<R> {
self.manager().config()
}
/// The [`PackageInfo`] the manager was created with.
fn package_info(&self) -> &PackageInfo {
self.manager().package_info()
}
/// Emits a event to all windows.
fn emit_all<S: Serialize + Clone>(&self, event: &str, payload: S) -> Result<()> {
self.manager().emit_filter(event, None, payload, |_| true)

View File

@ -56,7 +56,7 @@ use crate::{
};
#[cfg(any(target_os = "linux", target_os = "windows"))]
use crate::api::path::{resolve_path, BaseDirectory};
use crate::path::{BaseDirectory, PathExt};
use crate::{runtime::menu::Menu, MenuEvent};
@ -1321,28 +1321,13 @@ impl<R: Runtime> WindowManager<R> {
});
}
if is_local {
let label = pending.label.clone();
pending = self.prepare_pending_window(
pending,
&label,
window_labels,
app_handle.clone(),
web_resource_request_handler,
)?;
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle));
}
// in `Windows`, we need to force a data_directory
// but we do respect user-specification
#[cfg(any(target_os = "linux", target_os = "windows"))]
if pending.webview_attributes.data_directory.is_none() {
let local_app_data = resolve_path(
&self.inner.config,
&self.inner.package_info,
self.inner.state.get::<crate::Env>().inner(),
let local_app_data = app_handle.path().resolve(
&self.inner.config.tauri.bundle.identifier,
Some(BaseDirectory::LocalData),
BaseDirectory::LocalData,
);
if let Ok(user_data_dir) = local_app_data {
pending.webview_attributes.data_directory = Some(user_data_dir);
@ -1356,6 +1341,18 @@ impl<R: Runtime> WindowManager<R> {
}
}
if is_local {
let label = pending.label.clone();
pending = self.prepare_pending_window(
pending,
&label,
window_labels,
app_handle.clone(),
web_resource_request_handler,
)?;
pending.ipc_handler = Some(self.prepare_ipc_handler(app_handle));
}
Ok(pending)
}

View File

@ -0,0 +1,115 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::Result;
use crate::{plugin::PluginHandle, Runtime};
use std::path::PathBuf;
/// A helper class to access the mobile path APIs.
pub struct PathResolver<R: Runtime>(pub(crate) PluginHandle<R>);
#[derive(serde::Deserialize)]
struct PathResponse {
path: PathBuf,
}
impl<R: Runtime> PathResolver<R> {
fn call_resolve(&self, dir: &str) -> Result<PathBuf> {
self
.0
.run_mobile_plugin::<PathResponse>(dir, ())
.map(|r| r.path)
.map_err(Into::into)
}
/// Returns the path to the user's audio directory.
pub fn audio_dir(&self) -> Result<PathBuf> {
self.call_resolve("getAudioDir")
}
/// Returns the path to the user's cache directory.
pub fn cache_dir(&self) -> Result<PathBuf> {
self.call_resolve("getExternalCacheDir")
}
/// Returns the path to the user's config directory.
pub fn config_dir(&self) -> Result<PathBuf> {
self.call_resolve("getConfigDir")
}
/// Returns the path to the user's data directory.
pub fn data_dir(&self) -> Result<PathBuf> {
self.call_resolve("getDataDir")
}
/// Returns the path to the user's local data directory.
pub fn local_data_dir(&self) -> Result<PathBuf> {
self.call_resolve("getDataDir")
}
/// Returns the path to the user's document directory.
pub fn document_dir(&self) -> Result<PathBuf> {
self.call_resolve("getDocumentDir")
}
/// Returns the path to the user's download directory.
pub fn download_dir(&self) -> Result<PathBuf> {
self.call_resolve("getDownloadDir")
}
/// Returns the path to the user's picture directory.
pub fn picture_dir(&self) -> Result<PathBuf> {
self.call_resolve("getPictureDir")
}
/// Returns the path to the user's public directory.
pub fn public_dir(&self) -> Result<PathBuf> {
self.call_resolve("getPublicDir")
}
/// Returns the path to the user's video dir
pub fn video_dir(&self) -> Result<PathBuf> {
self.call_resolve("getVideoDir")
}
/// Returns the path to the resource directory of this app.
pub fn resource_dir(&self) -> Result<PathBuf> {
self.call_resolve("getResourcesDir")
}
/// Returns the path to the suggested directory for your app's config files.
///
/// Resolves to [`config_dir`]`/${bundle_identifier}`.
pub fn app_config_dir(&self) -> Result<PathBuf> {
self.call_resolve("getConfigDir")
}
/// Returns the path to the suggested directory for your app's data files.
///
/// Resolves to [`data_dir`]`/${bundle_identifier}`.
pub fn app_data_dir(&self) -> Result<PathBuf> {
self.call_resolve("getDataDir")
}
/// Returns the path to the suggested directory for your app's local data files.
///
/// Resolves to [`local_data_dir`]`/${bundle_identifier}`.
pub fn app_local_data_dir(&self) -> Result<PathBuf> {
self.call_resolve("getDataDir")
}
/// Returns the path to the suggested directory for your app's cache files.
///
/// Resolves to [`cache_dir`]`/${bundle_identifier}`.
pub fn app_cache_dir(&self) -> Result<PathBuf> {
self.call_resolve("getCacheDir")
}
/// Returns the path to the suggested directory for your app's log files.
pub fn app_log_dir(&self) -> Result<PathBuf> {
self
.call_resolve("getConfigDir")
.map(|dir| dir.join("logs"))
}
}

View File

@ -0,0 +1,193 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR};
use super::{BaseDirectory, Error, PathResolver, Result};
use crate::{command, AppHandle, Runtime, State};
/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util.
/// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106
fn normalize_path(path: &Path) -> PathBuf {
let mut components = path.components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
components.next();
PathBuf::from(c.as_os_str())
} else {
PathBuf::new()
};
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => {
ret.push(component.as_os_str());
}
Component::CurDir => {}
Component::ParentDir => {
ret.pop();
}
Component::Normal(c) => {
ret.push(c);
}
}
}
ret
}
/// Normalize a path, removing things like `.` and `..`, this snippet is taken from cargo's paths util but
/// slightly modified to not resolve absolute paths.
/// https://github.com/rust-lang/cargo/blob/46fa867ff7043e3a0545bf3def7be904e1497afd/crates/cargo-util/src/paths.rs#L73-L106
fn normalize_path_no_absolute(path: &Path) -> PathBuf {
let mut components = path.components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
components.next();
PathBuf::from(c.as_os_str())
} else {
PathBuf::new()
};
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => {
ret.push(component.as_os_str());
}
Component::CurDir => {}
Component::ParentDir => {
ret.pop();
}
Component::Normal(c) => {
// Using PathBuf::push here will replace the whole path if an absolute path is encountered
// which is not the intended behavior, so instead of that, convert the current resolved path
// to a string and do simple string concatenation with the current component then convert it
// back to a PathBuf
let mut p = ret.to_string_lossy().to_string();
// Only add a separator if it doesn't have one already or if current normalized path is empty,
// this ensures it won't have an unwanted leading separator
if !p.is_empty() && !p.ends_with('/') && !p.ends_with('\\') {
p.push(MAIN_SEPARATOR);
}
if let Some(c) = c.to_str() {
p.push_str(c);
}
ret = PathBuf::from(p);
}
}
}
ret
}
#[command(root = "crate")]
pub fn resolve_directory<R: Runtime>(
_app: AppHandle<R>,
resolver: State<'_, PathResolver<R>>,
directory: BaseDirectory,
path: Option<PathBuf>,
) -> Result<PathBuf> {
super::resolve_path(&resolver, directory, path)
}
#[command(root = "crate")]
pub fn resolve(paths: Vec<String>) -> Result<PathBuf> {
// Start with current directory then start adding paths from the vector one by one using `PathBuf.push()` which
// will ensure that if an absolute path is encountered in the iteration, it will be used as the current full path.
//
// examples:
// 1. `vec!["."]` or `vec![]` will be equal to `std::env::current_dir()`
// 2. `vec!["/foo/bar", "/tmp/file", "baz"]` will be equal to `PathBuf::from("/tmp/file/baz")`
let mut path = std::env::current_dir().map_err(Error::CurrentDir)?;
for p in paths {
path.push(p);
}
Ok(normalize_path(&path))
}
#[command(root = "crate")]
pub fn normalize(path: String) -> String {
let mut p = normalize_path_no_absolute(Path::new(&path))
.to_string_lossy()
.to_string();
// Node.js behavior is to return `".."` for `normalize("..")`
// and `"."` for `normalize("")` or `normalize(".")`
if p.is_empty() && path == ".." {
"..".into()
} else if p.is_empty() && path == "." {
".".into()
} else {
// Add a trailing separator if the path passed to this functions had a trailing separator. That's how Node.js behaves.
if (path.ends_with('/') || path.ends_with('\\')) && (!p.ends_with('/') || !p.ends_with('\\')) {
p.push(MAIN_SEPARATOR);
}
p
}
}
#[command(root = "crate")]
pub fn join(mut paths: Vec<String>) -> String {
let path = PathBuf::from(
paths
.iter_mut()
.map(|p| {
// Add a `MAIN_SEPARATOR` if it doesn't already have one.
// Doing this to ensure that the vector elements are separated in
// the resulting string so path.components() can work correctly when called
// in `normalize_path_no_absolute()` later on.
if !p.ends_with('/') && !p.ends_with('\\') {
p.push(MAIN_SEPARATOR);
}
p.to_string()
})
.collect::<String>(),
);
let p = normalize_path_no_absolute(&path)
.to_string_lossy()
.to_string();
if p.is_empty() {
".".into()
} else {
p
}
}
#[command(root = "crate")]
pub fn dirname(path: String) -> Result<PathBuf> {
match Path::new(&path).parent() {
Some(p) => Ok(p.to_path_buf()),
None => Err(Error::NoParent),
}
}
#[command(root = "crate")]
pub fn extname(path: String) -> Result<String> {
match Path::new(&path)
.extension()
.and_then(std::ffi::OsStr::to_str)
{
Some(p) => Ok(p.to_string()),
None => Err(Error::NoExtension),
}
}
#[command(root = "crate")]
pub fn basename(path: String, ext: Option<String>) -> Result<String> {
match Path::new(&path)
.file_name()
.and_then(std::ffi::OsStr::to_str)
{
Some(p) => Ok(if let Some(ext) = ext {
p.replace(ext.as_str(), "")
} else {
p.to_string()
}),
None => Err(Error::NoBasename),
}
}
#[command(root = "crate")]
pub fn is_absolute(path: String) -> bool {
Path::new(&path).is_absolute()
}

View File

@ -0,0 +1,257 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use super::{Error, Result};
use crate::{AppHandle, Manager, Runtime};
use std::path::PathBuf;
/// A helper class to access the mobile camera APIs.
pub struct PathResolver<R: Runtime>(pub(crate) AppHandle<R>);
impl<R: Runtime> PathResolver<R> {
/// Returns the path to the user's audio directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_MUSIC_DIR`.
/// - **macOS:** Resolves to `$HOME/Music`.
/// - **Windows:** Resolves to `{FOLDERID_Music}`.
pub fn audio_dir(&self) -> Result<PathBuf> {
dirs_next::audio_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's cache directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_CACHE_HOME` or `$HOME/.cache`.
/// - **macOS:** Resolves to `$HOME/Library/Caches`.
/// - **Windows:** Resolves to `{FOLDERID_LocalAppData}`.
pub fn cache_dir(&self) -> Result<PathBuf> {
dirs_next::cache_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's config directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_CONFIG_HOME` or `$HOME/.config`.
/// - **macOS:** Resolves to `$HOME/Library/Application Support`.
/// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`.
pub fn config_dir(&self) -> Result<PathBuf> {
dirs_next::config_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's data directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`.
/// - **macOS:** Resolves to `$HOME/Library/Application Support`.
/// - **Windows:** Resolves to `{FOLDERID_RoamingAppData}`.
pub fn data_dir(&self) -> Result<PathBuf> {
dirs_next::data_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's local data directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_DATA_HOME` or `$HOME/.local/share`.
/// - **macOS:** Resolves to `$HOME/Library/Application Support`.
/// - **Windows:** Resolves to `{FOLDERID_LocalAppData}`.
pub fn local_data_dir(&self) -> Result<PathBuf> {
dirs_next::data_local_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's desktop directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DESKTOP_DIR`.
/// - **macOS:** Resolves to `$HOME/Desktop`.
/// - **Windows:** Resolves to `{FOLDERID_Desktop}`.
pub fn desktop_dir(&self) -> Result<PathBuf> {
dirs_next::desktop_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's document directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOCUMENTS_DIR`.
/// - **macOS:** Resolves to `$HOME/Documents`.
/// - **Windows:** Resolves to `{FOLDERID_Documents}`.
pub fn document_dir(&self) -> Result<PathBuf> {
dirs_next::document_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's download directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_DOWNLOAD_DIR`.
/// - **macOS:** Resolves to `$HOME/Downloads`.
/// - **Windows:** Resolves to `{FOLDERID_Downloads}`.
pub fn download_dir(&self) -> Result<PathBuf> {
dirs_next::download_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's executable directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_BIN_HOME/../bin` or `$XDG_DATA_HOME/../bin` or `$HOME/.local/bin`.
/// - **macOS:** Not supported.
/// - **Windows:** Not supported.
pub fn executable_dir(&self) -> Result<PathBuf> {
dirs_next::executable_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's font directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_DATA_HOME/fonts` or `$HOME/.local/share/fonts`.
/// - **macOS:** Resolves to `$HOME/Library/Fonts`.
/// - **Windows:** Not supported.
pub fn font_dir(&self) -> Result<PathBuf> {
dirs_next::font_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's home directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$HOME`.
/// - **macOS:** Resolves to `$HOME`.
/// - **Windows:** Resolves to `{FOLDERID_Profile}`.
pub fn home_dir(&self) -> Result<PathBuf> {
dirs_next::home_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's picture directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PICTURES_DIR`.
/// - **macOS:** Resolves to `$HOME/Pictures`.
/// - **Windows:** Resolves to `{FOLDERID_Pictures}`.
pub fn picture_dir(&self) -> Result<PathBuf> {
dirs_next::picture_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's public directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_PUBLICSHARE_DIR`.
/// - **macOS:** Resolves to `$HOME/Public`.
/// - **Windows:** Resolves to `{FOLDERID_Public}`.
pub fn public_dir(&self) -> Result<PathBuf> {
dirs_next::public_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's runtime directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to `$XDG_RUNTIME_DIR`.
/// - **macOS:** Not supported.
/// - **Windows:** Not supported.
pub fn runtime_dir(&self) -> Result<PathBuf> {
dirs_next::runtime_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's template directory.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_TEMPLATES_DIR`.
/// - **macOS:** Not supported.
/// - **Windows:** Resolves to `{FOLDERID_Templates}`.
pub fn template_dir(&self) -> Result<PathBuf> {
dirs_next::template_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the user's video dir
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`xdg-user-dirs`](https://www.freedesktop.org/wiki/Software/xdg-user-dirs/)' `XDG_VIDEOS_DIR`.
/// - **macOS:** Resolves to `$HOME/Movies`.
/// - **Windows:** Resolves to `{FOLDERID_Videos}`.
pub fn video_dir(&self) -> Result<PathBuf> {
dirs_next::video_dir().ok_or(Error::UnknownPath)
}
/// Returns the path to the resource directory of this app.
pub fn resource_dir(&self) -> Result<PathBuf> {
crate::utils::platform::resource_dir(self.0.package_info(), &self.0.env())
.map_err(|_| Error::UnknownPath)
}
/// Returns the path to the suggested directory for your app's config files.
///
/// Resolves to [`config_dir`]`/${bundle_identifier}`.
pub fn app_config_dir(&self) -> Result<PathBuf> {
dirs_next::config_dir()
.ok_or(Error::UnknownPath)
.map(|dir| dir.join(&self.0.config().tauri.bundle.identifier))
}
/// Returns the path to the suggested directory for your app's data files.
///
/// Resolves to [`data_dir`]`/${bundle_identifier}`.
pub fn app_data_dir(&self) -> Result<PathBuf> {
dirs_next::data_dir()
.ok_or(Error::UnknownPath)
.map(|dir| dir.join(&self.0.config().tauri.bundle.identifier))
}
/// Returns the path to the suggested directory for your app's local data files.
///
/// Resolves to [`local_data_dir`]`/${bundle_identifier}`.
pub fn app_local_data_dir(&self) -> Result<PathBuf> {
dirs_next::data_local_dir()
.ok_or(Error::UnknownPath)
.map(|dir| dir.join(&self.0.config().tauri.bundle.identifier))
}
/// Returns the path to the suggested directory for your app's cache files.
///
/// Resolves to [`cache_dir`]`/${bundle_identifier}`.
pub fn app_cache_dir(&self) -> Result<PathBuf> {
dirs_next::cache_dir()
.ok_or(Error::UnknownPath)
.map(|dir| dir.join(&self.0.config().tauri.bundle.identifier))
}
/// Returns the path to the suggested directory for your app's log files.
///
/// ## Platform-specific
///
/// - **Linux:** Resolves to [`config_dir`]`/${bundle_identifier}/logs`.
/// - **macOS:** Resolves to [`home_dir`]`/Library/Logs/${bundle_identifier}`
/// - **Windows:** Resolves to [`config_dir`]`/${bundle_identifier}/logs`.
pub fn app_log_dir(&self) -> Result<PathBuf> {
#[cfg(target_os = "macos")]
let path = dirs_next::home_dir().ok_or(Error::UnknownPath).map(|dir| {
dir
.join("Library/Logs")
.join(&self.0.config().tauri.bundle.identifier)
});
#[cfg(not(target_os = "macos"))]
let path = dirs_next::config_dir()
.ok_or(Error::UnknownPath)
.map(|dir| {
dir
.join(&self.0.config().tauri.bundle.identifier)
.join("logs")
});
path
}
}

View File

@ -0,0 +1,42 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use serde::{ser::Serializer, Serialize};
/// Path result.
pub type Result<T> = std::result::Result<T, Error>;
/// Path error.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Path does not have a parent.
#[error("path does not have a parent")]
NoParent,
/// Path does not have an extension.
#[error("path does not have an extension")]
NoExtension,
/// Path does not have a basename.
#[error("path does not have a basename")]
NoBasename,
/// Cannot resolve current directory.
#[error("failed to read current dir: {0}")]
CurrentDir(std::io::Error),
/// Unknown path.
#[cfg(not(target_os = "android"))]
#[error("unknown path")]
UnknownPath,
/// Failed to invoke mobile plugin.
#[cfg(target_os = "android")]
#[error(transparent)]
PluginInvoke(#[from] crate::plugin::mobile::PluginInvokeError),
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}

346
core/tauri/src/path/mod.rs Normal file
View File

@ -0,0 +1,346 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
env::temp_dir,
path::{Component, Path, PathBuf},
};
use crate::{
plugin::{Builder, TauriPlugin},
Manager, Runtime,
};
use serde_repr::{Deserialize_repr, Serialize_repr};
#[cfg(path_all)]
mod commands;
mod error;
pub use error::*;
#[cfg(target_os = "android")]
mod android;
#[cfg(not(target_os = "android"))]
mod desktop;
#[cfg(target_os = "android")]
use android::PathResolver;
#[cfg(not(target_os = "android"))]
use desktop::PathResolver;
/// A base directory to be used in [`resolve_directory`].
///
/// The base directory is the optional root of a file system operation.
/// If informed by the API call, all paths will be relative to the path of the given directory.
///
/// For more information, check the [`dirs_next` documentation](https://docs.rs/dirs_next/).
#[derive(Serialize_repr, Deserialize_repr, Clone, Copy, Debug)]
#[repr(u16)]
#[non_exhaustive]
pub enum BaseDirectory {
/// The Audio directory.
Audio = 1,
/// The Cache directory.
Cache,
/// The Config directory.
Config,
/// The Data directory.
Data,
/// The LocalData directory.
LocalData,
/// The Document directory.
Document,
/// The Download directory.
Download,
/// The Picture directory.
Picture,
/// The Public directory.
Public,
/// The Video directory.
Video,
/// The Resource directory.
Resource,
/// A temporary directory.
/// Resolves to [`temp_dir`].
Temp,
/// The default app config directory.
/// Resolves to [`BaseDirectory::Config`]`/{bundle_identifier}`.
AppConfig,
/// The default app data directory.
/// Resolves to [`BaseDirectory::Data`]`/{bundle_identifier}`.
AppData,
/// The default app local data directory.
/// Resolves to [`BaseDirectory::LocalData`]`/{bundle_identifier}`.
AppLocalData,
/// The default app cache directory.
/// Resolves to [`BaseDirectory::Cache`]`/{bundle_identifier}`.
AppCache,
/// The default app log directory.
/// Resolves to [`BaseDirectory::Home`]`/Library/Logs/{bundle_identifier}` on macOS
/// and [`BaseDirectory::Config`]`/{bundle_identifier}/logs` on linux and Windows.
AppLog,
/// The Desktop directory.
#[cfg(not(target_os = "android"))]
Desktop,
/// The Executable directory.
#[cfg(not(target_os = "android"))]
Executable,
/// The Font directory.
#[cfg(not(target_os = "android"))]
Font,
/// The Home directory.
#[cfg(not(target_os = "android"))]
Home,
/// The Runtime directory.
#[cfg(not(target_os = "android"))]
Runtime,
/// The Template directory.
#[cfg(not(target_os = "android"))]
Template,
}
impl BaseDirectory {
/// Gets the variable that represents this [`BaseDirectory`] for string paths.
pub fn variable(self) -> &'static str {
match self {
Self::Audio => "$AUDIO",
Self::Cache => "$CACHE",
Self::Config => "$CONFIG",
Self::Data => "$DATA",
Self::LocalData => "$LOCALDATA",
Self::Document => "$DOCUMENT",
Self::Download => "$DOWNLOAD",
Self::Picture => "$PICTURE",
Self::Public => "$PUBLIC",
Self::Video => "$VIDEO",
Self::Resource => "$RESOURCE",
Self::Temp => "$TEMP",
Self::AppConfig => "$APPCONFIG",
Self::AppData => "$APPDATA",
Self::AppLocalData => "$APPLOCALDATA",
Self::AppCache => "$APPCACHE",
Self::AppLog => "$APPLOG",
#[cfg(not(target_os = "android"))]
Self::Desktop => "$DESKTOP",
#[cfg(not(target_os = "android"))]
Self::Executable => "$EXE",
#[cfg(not(target_os = "android"))]
Self::Font => "$FONT",
#[cfg(not(target_os = "android"))]
Self::Home => "$HOME",
#[cfg(not(target_os = "android"))]
Self::Runtime => "$RUNTIME",
#[cfg(not(target_os = "android"))]
Self::Template => "$TEMPLATE",
}
}
/// Gets the [`BaseDirectory`] associated with the given variable, or [`None`] if the variable doesn't match any.
pub fn from_variable(variable: &str) -> Option<Self> {
let res = match variable {
"$AUDIO" => Self::Audio,
"$CACHE" => Self::Cache,
"$CONFIG" => Self::Config,
"$DATA" => Self::Data,
"$LOCALDATA" => Self::LocalData,
"$DOCUMENT" => Self::Document,
"$DOWNLOAD" => Self::Download,
"$PICTURE" => Self::Picture,
"$PUBLIC" => Self::Public,
"$VIDEO" => Self::Video,
"$RESOURCE" => Self::Resource,
"$TEMP" => Self::Temp,
"$APPCONFIG" => Self::AppConfig,
"$APPDATA" => Self::AppData,
"$APPLOCALDATA" => Self::AppLocalData,
"$APPCACHE" => Self::AppCache,
"$APPLOG" => Self::AppLog,
#[cfg(not(target_os = "android"))]
"$DESKTOP" => Self::Desktop,
#[cfg(not(target_os = "android"))]
"$EXE" => Self::Executable,
#[cfg(not(target_os = "android"))]
"$FONT" => Self::Font,
#[cfg(not(target_os = "android"))]
"$HOME" => Self::Home,
#[cfg(not(target_os = "android"))]
"$RUNTIME" => Self::Runtime,
#[cfg(not(target_os = "android"))]
"$TEMPLATE" => Self::Template,
_ => return None,
};
Some(res)
}
}
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the path APIs.
pub trait PathExt<R: Runtime> {
/// The path resolver.
fn path(&self) -> &PathResolver<R>;
}
impl<R: Runtime, T: Manager<R>> PathExt<R> for T {
fn path(&self) -> &PathResolver<R> {
self.state::<PathResolver<R>>().inner()
}
}
impl<R: Runtime> PathResolver<R> {
/// Resolves the path with the base directory.
///
/// # Examples
///
/// ```rust,no_run
/// use tauri::{path::{BaseDirectory, PathExt}, Manager};
/// tauri::Builder::default()
/// .setup(|app| {
/// let path = app.path().resolve("path/to/something", BaseDirectory::Config)?;
/// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.config/path/to/something");
/// Ok(())
/// });
/// ```
pub fn resolve<P: AsRef<Path>>(&self, path: P, base_directory: BaseDirectory) -> Result<PathBuf> {
resolve_path::<R>(self, base_directory, Some(path.as_ref().to_path_buf()))
}
/// Parse the given path, resolving a [`BaseDirectory`] variable if the path starts with one.
///
/// # Examples
///
/// ```rust,no_run
/// use tauri::{Manager, path::PathExt};
/// tauri::Builder::default()
/// .setup(|app| {
/// let path = app.path().parse("$HOME/.bashrc")?;
/// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.bashrc");
/// Ok(())
/// });
/// ```
pub fn parse<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
let mut p = PathBuf::new();
let mut components = path.as_ref().components();
match components.next() {
Some(Component::Normal(str)) => {
if let Some(base_directory) = BaseDirectory::from_variable(&str.to_string_lossy()) {
p.push(resolve_path::<R>(self, base_directory, None)?);
} else {
p.push(str);
}
}
Some(component) => p.push(component),
None => (),
}
for component in components {
if let Component::ParentDir = component {
continue;
}
p.push(component);
}
Ok(p)
}
}
fn resolve_path<R: Runtime>(
resolver: &PathResolver<R>,
directory: BaseDirectory,
path: Option<PathBuf>,
) -> Result<PathBuf> {
let resolve_resource = matches!(directory, BaseDirectory::Resource);
let mut base_dir_path = match directory {
BaseDirectory::Audio => resolver.audio_dir(),
BaseDirectory::Cache => resolver.cache_dir(),
BaseDirectory::Config => resolver.config_dir(),
BaseDirectory::Data => resolver.data_dir(),
BaseDirectory::LocalData => resolver.local_data_dir(),
BaseDirectory::Document => resolver.document_dir(),
BaseDirectory::Download => resolver.download_dir(),
BaseDirectory::Picture => resolver.picture_dir(),
BaseDirectory::Public => resolver.public_dir(),
BaseDirectory::Video => resolver.video_dir(),
BaseDirectory::Resource => resolver.resource_dir(),
BaseDirectory::Temp => Ok(temp_dir()),
BaseDirectory::AppConfig => resolver.app_config_dir(),
BaseDirectory::AppData => resolver.app_data_dir(),
BaseDirectory::AppLocalData => resolver.app_local_data_dir(),
BaseDirectory::AppCache => resolver.app_cache_dir(),
BaseDirectory::AppLog => resolver.app_log_dir(),
#[cfg(not(target_os = "android"))]
BaseDirectory::Desktop => resolver.desktop_dir(),
#[cfg(not(target_os = "android"))]
BaseDirectory::Executable => resolver.executable_dir(),
#[cfg(not(target_os = "android"))]
BaseDirectory::Font => resolver.font_dir(),
#[cfg(not(target_os = "android"))]
BaseDirectory::Home => resolver.home_dir(),
#[cfg(not(target_os = "android"))]
BaseDirectory::Runtime => resolver.runtime_dir(),
#[cfg(not(target_os = "android"))]
BaseDirectory::Template => resolver.template_dir(),
}?;
if let Some(path) = path {
// use the same path resolution mechanism as the bundler's resource injection algorithm
if resolve_resource {
let mut resource_path = PathBuf::new();
for component in path.components() {
match component {
Component::Prefix(_) => {}
Component::RootDir => resource_path.push("_root_"),
Component::CurDir => {}
Component::ParentDir => resource_path.push("_up_"),
Component::Normal(p) => resource_path.push(p),
}
}
base_dir_path.push(resource_path);
} else {
base_dir_path.push(path);
}
}
Ok(base_dir_path)
}
/// Initializes the plugin.
pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
#[allow(unused_mut)]
let mut builder = Builder::new("path");
#[cfg(path_all)]
{
builder = builder.invoke_handler(crate::generate_handler![
commands::resolve_directory,
commands::resolve,
commands::normalize,
commands::join,
commands::dirname,
commands::extname,
commands::basename,
commands::is_absolute
]);
}
builder
.setup(|app, _api| {
#[cfg(target_os = "android")]
{
let handle = _api.register_android_plugin("app.tauri", "PathPlugin")?;
app.manage(PathResolver(handle));
}
#[cfg(not(target_os = "android"))]
{
app.manage(PathResolver(app.clone()));
}
Ok(())
})
.build()
}

View File

@ -10,13 +10,10 @@ use std::{
};
pub use glob::Pattern;
use tauri_utils::{
config::{Config, FsAllowlistScope},
Env, PackageInfo,
};
use tauri_utils::config::FsAllowlistScope;
use uuid::Uuid;
use crate::api::path::parse as parse_path;
use crate::{path::PathExt, Manager, Runtime};
/// Scope change event.
#[derive(Debug, Clone)]
@ -84,15 +81,13 @@ fn push_pattern<P: AsRef<Path>, F: Fn(&str) -> Result<Pattern, glob::PatternErro
impl Scope {
/// Creates a new scope from a `FsAllowlistScope` configuration.
pub(crate) fn for_fs_api(
config: &Config,
package_info: &PackageInfo,
env: &Env,
pub(crate) fn for_fs_api<R: Runtime, M: Manager<R>>(
manager: &M,
scope: &FsAllowlistScope,
) -> crate::Result<Self> {
let mut allowed_patterns = HashSet::new();
for path in scope.allowed_paths() {
if let Ok(path) = parse_path(config, package_info, env, path) {
if let Ok(path) = manager.path().parse(path) {
push_pattern(&mut allowed_patterns, path, Pattern::new)?;
}
}
@ -100,7 +95,7 @@ impl Scope {
let mut forbidden_patterns = HashSet::new();
if let Some(forbidden_paths) = scope.forbidden_paths() {
for path in forbidden_paths {
if let Ok(path) = parse_path(config, package_info, env, path) {
if let Ok(path) = manager.path().parse(path) {
push_pattern(&mut forbidden_patterns, path, Pattern::new)?;
}
}

View File

@ -6,9 +6,9 @@
use crate::api::process::Command;
#[cfg(feature = "shell-open-api")]
use crate::api::shell::Program;
use crate::{path::PathExt, Manager, Runtime};
use regex::Regex;
use tauri_utils::{config::Config, Env, PackageInfo};
use std::collections::HashMap;
@ -194,14 +194,9 @@ pub enum ScopeError {
impl Scope {
/// Creates a new shell scope.
pub(crate) fn new(
config: &Config,
package_info: &PackageInfo,
env: &Env,
mut scope: ScopeConfig,
) -> Self {
pub(crate) fn new<R: Runtime, M: Manager<R>>(manager: &M, mut scope: ScopeConfig) -> Self {
for cmd in scope.scopes.values_mut() {
if let Ok(path) = crate::api::path::parse(config, package_info, env, &cmd.command) {
if let Ok(path) = manager.path().parse(&cmd.command) {
cmd.command = path;
}
}

View File

@ -67,24 +67,6 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "android_log-sys"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e"
[[package]]
name = "android_logger"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8619b80c242aa7bd638b5c7ddd952addeecb71f69c75e33f1d47b2804f8f883a"
dependencies = [
"android_log-sys",
"env_logger",
"log",
"once_cell",
]
[[package]]
name = "anyhow"
version = "1.0.69"
@ -100,7 +82,6 @@ dependencies = [
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-log",
"tauri-plugin-sample",
"tiny_http",
"window-shadows",
@ -347,16 +328,6 @@ version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "byte-unit"
version = "4.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3348673602e04848647fffaa8e9a861e7b5d5cae6570727b41bde0f722514484"
dependencies = [
"serde",
"utf8-width",
]
[[package]]
name = "bytemuck"
version = "1.13.0"
@ -861,16 +832,6 @@ dependencies = [
"syn",
]
[[package]]
name = "env_logger"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"log",
"regex",
]
[[package]]
name = "errno"
version = "0.3.0"
@ -907,15 +868,6 @@ dependencies = [
"instant",
]
[[package]]
name = "fern"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a"
dependencies = [
"log",
]
[[package]]
name = "field-offset"
version = "0.3.4"
@ -1833,7 +1785,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
"value-bag",
]
[[package]]
@ -3170,9 +3121,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "swift-rs"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eab55f44d02ced20ffa22fd5b4bd083ab59c940c7637d37fec4426b1a01d769"
checksum = "8fa67d647176dfa7bdc5775430a1cb339e0ea48fe24707424023a4b17eb9688e"
dependencies = [
"base64 0.21.0",
"serde",
@ -3397,24 +3348,6 @@ dependencies = [
"tauri-utils",
]
[[package]]
name = "tauri-plugin-log"
version = "0.1.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=next#39b144df42e6f3e485beef4d0d31101f13d4c24e"
dependencies = [
"android_logger",
"byte-unit",
"fern",
"log",
"serde",
"serde_json",
"serde_repr",
"swift-rs",
"tauri",
"tauri-build",
"time",
]
[[package]]
name = "tauri-plugin-sample"
version = "0.1.0"
@ -3871,12 +3804,6 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8-width"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1"
[[package]]
name = "uuid"
version = "1.3.0"
@ -3892,16 +3819,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "value-bag"
version = "1.0.0-alpha.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55"
dependencies = [
"ctor",
"version_check",
]
[[package]]
name = "version-compare"
version = "0.1.1"

View File

@ -17,7 +17,7 @@ serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
tiny_http = "0.11"
log = "0.4"
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "next" }
#tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "next" }
tauri-plugin-sample = { path = "./tauri-plugin-sample/" }
[patch.crates-io]

View File

@ -27,11 +27,11 @@ pub type OnEvent = Box<dyn FnMut(&AppHandle, RunEvent)>;
pub fn run() {
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.plugin(
tauri_plugin_log::Builder::default()
/*.plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)
)*/
.plugin(tauri_plugin_sample::init())
.setup(move |app| {
#[cfg(desktop)]

File diff suppressed because one or more lines are too long

View File

@ -62,8 +62,8 @@
* {@link path.localDataDir | `$LOCALDATA`}, {@link path.desktopDir | `$DESKTOP`}, {@link path.documentDir | `$DOCUMENT`},
* {@link path.downloadDir | `$DOWNLOAD`}, {@link path.executableDir | `$EXE`}, {@link path.fontDir | `$FONT`}, {@link path.homeDir | `$HOME`},
* {@link path.pictureDir | `$PICTURE`}, {@link path.publicDir | `$PUBLIC`}, {@link path.runtimeDir | `$RUNTIME`},
* {@link path.templateDir | `$TEMPLATE`}, {@link path.videoDir | `$VIDEO`}, {@link path.resourceDir | `$RESOURCE`}, {@link path.appDir | `$APP`},
* {@link path.logDir | `$LOG`}, {@link os.tempdir | `$TEMP`}.
* {@link path.templateDir | `$TEMPLATE`}, {@link path.videoDir | `$VIDEO`}, {@link path.resourceDir | `$RESOURCE`},
* {@link os.tempdir | `$TEMP`}.
*
* Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access.
*
@ -83,26 +83,25 @@ export enum BaseDirectory {
Config,
Data,
LocalData,
Desktop,
Document,
Download,
Executable,
Font,
Home,
Picture,
Public,
Runtime,
Template,
Video,
Resource,
App,
Log,
Temp,
AppConfig,
AppData,
AppLocalData,
AppCache,
AppLog
AppLog,
Desktop,
Executable,
Font,
Home,
Runtime,
Template
}
/**

View File

@ -23,20 +23,10 @@
* @module
*/
import { invokeTauriCommand } from './helpers/tauri'
import { invoke } from './tauri'
import { BaseDirectory } from './fs'
import { isWindows } from './helpers/os-check'
/**
* Returns the path to the suggested directory for your app config files.
*
* @deprecated since 1.2.0: Will be removed in 2.0.0. Use {@link appConfigDir} or {@link appDataDir} instead.
* @since 1.0.0
*/
async function appDir(): Promise<string> {
return appConfigDir()
}
/**
* Returns the path to the suggested directory for your app's config files.
* Resolves to `${configDir}/${bundleIdentifier}`, where `bundleIdentifier` is the value [`tauri.bundle.identifier`](https://tauri.app/v1/api/config/#bundleconfig.identifier) is configured in `tauri.conf.json`.
@ -49,13 +39,8 @@ async function appDir(): Promise<string> {
* @since 1.2.0
*/
async function appConfigDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.AppConfig
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.AppConfig
})
}
@ -71,13 +56,8 @@ async function appConfigDir(): Promise<string> {
* @since 1.2.0
*/
async function appDataDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.AppData
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.AppData
})
}
@ -93,13 +73,8 @@ async function appDataDir(): Promise<string> {
* @since 1.2.0
*/
async function appLocalDataDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.AppLocalData
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.AppLocalData
})
}
@ -115,13 +90,8 @@ async function appLocalDataDir(): Promise<string> {
* @since 1.2.0
*/
async function appCacheDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.AppCache
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.AppCache
})
}
@ -142,13 +112,8 @@ async function appCacheDir(): Promise<string> {
* @since 1.0.0
*/
async function audioDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Audio
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Audio
})
}
@ -169,13 +134,8 @@ async function audioDir(): Promise<string> {
* @since 1.0.0
*/
async function cacheDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Cache
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Cache
})
}
@ -196,13 +156,8 @@ async function cacheDir(): Promise<string> {
* @since 1.0.0
*/
async function configDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Config
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Config
})
}
@ -223,13 +178,8 @@ async function configDir(): Promise<string> {
* @since 1.0.0
*/
async function dataDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Data
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Data
})
}
@ -250,13 +200,8 @@ async function dataDir(): Promise<string> {
* @since 1.0.0
*/
async function desktopDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Desktop
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Desktop
})
}
@ -277,13 +222,8 @@ async function desktopDir(): Promise<string> {
* @since 1.0.0
*/
async function documentDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Document
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Document
})
}
@ -304,13 +244,8 @@ async function documentDir(): Promise<string> {
* @since 1.0.0
*/
async function downloadDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Download
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Download
})
}
@ -331,13 +266,8 @@ async function downloadDir(): Promise<string> {
* @since 1.0.0
*/
async function executableDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Executable
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Executable
})
}
@ -358,13 +288,8 @@ async function executableDir(): Promise<string> {
* @since 1.0.0
*/
async function fontDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Font
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Font
})
}
@ -385,13 +310,8 @@ async function fontDir(): Promise<string> {
* @since 1.0.0
*/
async function homeDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Home
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Home
})
}
@ -412,13 +332,8 @@ async function homeDir(): Promise<string> {
* @since 1.0.0
*/
async function localDataDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.LocalData
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.LocalData
})
}
@ -439,13 +354,8 @@ async function localDataDir(): Promise<string> {
* @since 1.0.0
*/
async function pictureDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Picture
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Picture
})
}
@ -466,13 +376,8 @@ async function pictureDir(): Promise<string> {
* @since 1.0.0
*/
async function publicDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Public
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Public
})
}
@ -488,13 +393,8 @@ async function publicDir(): Promise<string> {
* @since 1.0.0
*/
async function resourceDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Resource
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Resource
})
}
@ -513,13 +413,9 @@ async function resourceDir(): Promise<string> {
* @since 1.0.0
*/
async function resolveResource(resourcePath: string): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: resourcePath,
directory: BaseDirectory.Resource
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Resource,
path: resourcePath
})
}
@ -540,13 +436,8 @@ async function resolveResource(resourcePath: string): Promise<string> {
* @since 1.0.0
*/
async function runtimeDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Runtime
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Runtime
})
}
@ -567,13 +458,8 @@ async function runtimeDir(): Promise<string> {
* @since 1.0.0
*/
async function templateDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Template
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Template
})
}
@ -594,26 +480,11 @@ async function templateDir(): Promise<string> {
* @since 1.0.0
*/
async function videoDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.Video
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Video
})
}
/**
* Returns the path to the suggested log directory.
*
* @deprecated since 1.2.0: Will be removed in 2.0.0. Use {@link appLogDir} instead.
* @since 1.0.0
*/
async function logDir(): Promise<string> {
return appLogDir()
}
/**
* Returns the path to the suggested directory for your app's log files.
*
@ -631,13 +502,8 @@ async function logDir(): Promise<string> {
* @since 1.2.0
*/
async function appLogDir(): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolvePath',
path: '',
directory: BaseDirectory.AppLog
}
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.AppLog
})
}
@ -671,13 +537,7 @@ const delimiter = isWindows() ? ';' : ':'
* @since 1.0.0
*/
async function resolve(...paths: string[]): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'resolve',
paths
}
})
return invoke('plugin:path|resolve', { paths })
}
/**
@ -692,13 +552,7 @@ async function resolve(...paths: string[]): Promise<string> {
* @since 1.0.0
*/
async function normalize(path: string): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'normalize',
path
}
})
return invoke('plugin:path|normalize', { path })
}
/**
@ -713,13 +567,7 @@ async function normalize(path: string): Promise<string> {
* @since 1.0.0
*/
async function join(...paths: string[]): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'join',
paths
}
})
return invoke('plugin:path|join', { paths })
}
/**
@ -734,13 +582,7 @@ async function join(...paths: string[]): Promise<string> {
* @since 1.0.0
*/
async function dirname(path: string): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'dirname',
path
}
})
return invoke('plugin:path|dirname', { path })
}
/**
@ -756,13 +598,7 @@ async function dirname(path: string): Promise<string> {
* @since 1.0.0
*/
async function extname(path: string): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'extname',
path
}
})
return invoke('plugin:path|extname', { path })
}
/**
@ -780,14 +616,7 @@ async function extname(path: string): Promise<string> {
* @since 1.0.0
*/
async function basename(path: string, ext?: string): Promise<string> {
return invokeTauriCommand<string>({
__tauriModule: 'Path',
message: {
cmd: 'basename',
path,
ext
}
})
return invoke('plugin:path|basename', { path, ext })
}
/**
@ -801,17 +630,11 @@ async function basename(path: string, ext?: string): Promise<string> {
* @since 1.0.0
*/
async function isAbsolute(path: string): Promise<boolean> {
return invokeTauriCommand<boolean>({
__tauriModule: 'Path',
message: {
cmd: 'isAbsolute',
path
}
})
return invoke('plugin:path|isAbsolute', { path })
}
export {
appDir,
BaseDirectory,
appConfigDir,
appDataDir,
appLocalDataDir,
@ -835,8 +658,6 @@ export {
runtimeDir,
templateDir,
videoDir,
logDir,
BaseDirectory,
sep,
delimiter,
resolve,