Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/app-directories-override.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": minor:feat
---

Introduce a new config `appDirectoriesOverride` that changes what `app_*_dir` APIs return, useful for putting all your data besides the executable for testing or as portable apps
7 changes: 7 additions & 0 deletions crates/tauri-cli/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@
"description": "If set to true \"identifier\" will be set as GTK app ID (on systems that use GTK).",
"default": false,
"type": "boolean"
},
"appDirectoriesOverride": {
"description": "Override the path returned from `app_*_dir` APIs,\n this is useful for making portable apps that stores all the data inside a single place\n\n Note:\n - Relative paths are resolved based on the app's executable path,\n - The path can start with a variable that resolves to a system base directory.\n The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DOCUMENT`, `$DOWNLOAD`, `$PICTURE`,\n `$PUBLIC`, `$VIDEO`, `$RESOURCE`, `$TEMP`, `$HOME`, `$DESKTOP`, `$EXE`, `$FONT`, `$RUNTIME`, `$TEMPLATE`\n\n ## Example:\n\n To put all the data besides your current executable:\n\n ```json\n {\n \"app\": {\n \"appDirectoriesOverride\": \"./\"\n }\n }\n ```\n\n `app.path().app_local_data_dir()` should now return `${current_exe_dir}/`\n\n ## Platform-specific:\n\n - **Android**: Unsupported.",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
Expand Down
7 changes: 7 additions & 0 deletions crates/tauri-schema-generator/schemas/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@
"description": "If set to true \"identifier\" will be set as GTK app ID (on systems that use GTK).",
"default": false,
"type": "boolean"
},
"appDirectoriesOverride": {
"description": "Override the path returned from `app_*_dir` APIs,\n this is useful for making portable apps that stores all the data inside a single place\n\n Note:\n - Relative paths are resolved based on the app's executable path,\n - The path can start with a variable that resolves to a system base directory.\n The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DOCUMENT`, `$DOWNLOAD`, `$PICTURE`,\n `$PUBLIC`, `$VIDEO`, `$RESOURCE`, `$TEMP`, `$HOME`, `$DESKTOP`, `$EXE`, `$FONT`, `$RUNTIME`, `$TEMPLATE`\n\n ## Example:\n\n To put all the data besides your current executable:\n\n ```json\n {\n \"app\": {\n \"appDirectoriesOverride\": \"./\"\n }\n }\n ```\n\n `app.path().app_local_data_dir()` should now return `${current_exe_dir}/`\n\n ## Platform-specific:\n\n - **Android**: Unsupported.",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
Expand Down
39 changes: 38 additions & 1 deletion crates/tauri-utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2763,6 +2763,34 @@ pub struct AppConfig {
/// If set to true "identifier" will be set as GTK app ID (on systems that use GTK).
#[serde(rename = "enableGTKAppId", alias = "enable-gtk-app-id", default)]
pub enable_gtk_app_id: bool,
/// Override the path returned from `app_*_dir` APIs,
/// this is useful for making portable apps that stores all the data inside a single place
///
/// Note:
/// - Relative paths are resolved based on the app's executable path,
/// - The path can start with a variable that resolves to a system base directory.
/// The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DOCUMENT`, `$DOWNLOAD`, `$PICTURE`,
/// `$PUBLIC`, `$VIDEO`, `$RESOURCE`, `$TEMP`, `$HOME`, `$DESKTOP`, `$EXE`, `$FONT`, `$RUNTIME`, `$TEMPLATE`
///
/// ## Example:
///
/// To put all the data besides your current executable:
///
/// ```json
/// {
/// "app": {
/// "appDirectoriesOverride": "./"
/// }
/// }
/// ```
///
/// `app.path().app_local_data_dir()` should now return `${current_exe_dir}/`
///
/// ## Platform-specific:
///
/// - **Android**: Unsupported.
#[serde(alias = "app-directories-override")]
pub app_directories_override: Option<PathBuf>,
}

impl AppConfig {
Expand Down Expand Up @@ -4029,6 +4057,13 @@ mod build {
let macos_private_api = self.macos_private_api;
let with_global_tauri = self.with_global_tauri;
let enable_gtk_app_id = self.enable_gtk_app_id;
let app_directories_override = opt_lit(
self
.app_directories_override
.as_ref()
.map(path_buf_lit)
.as_ref(),
);

literal_struct!(
tokens,
Expand All @@ -4038,7 +4073,8 @@ mod build {
tray_icon,
macos_private_api,
with_global_tauri,
enable_gtk_app_id
enable_gtk_app_id,
app_directories_override
);
}
}
Expand Down Expand Up @@ -4119,6 +4155,7 @@ mod test {
macos_private_api: false,
with_global_tauri: false,
enable_gtk_app_id: false,
app_directories_override: None,
};

// create a build config
Expand Down
6 changes: 1 addition & 5 deletions crates/tauri/src/manager/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,11 +506,7 @@ impl<R: Runtime> WebviewManager<R> {
// 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 = manager.path().resolve(
&app_manager.config.identifier,
crate::path::BaseDirectory::LocalData,
);
if let Ok(user_data_dir) = local_app_data {
if let Ok(user_data_dir) = manager.path().app_local_data_dir() {
pending.webview_attributes.data_directory = Some(user_data_dir);
}
}
Expand Down
57 changes: 56 additions & 1 deletion crates/tauri/src/path/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT

use super::{Error, Result};
use crate::{AppHandle, Manager, Runtime};
use crate::{path::BaseDirectory, AppHandle, Manager, Runtime};
use std::path::{Path, PathBuf};

/// The path resolver is a helper class for general and application-specific path APIs.
Expand Down Expand Up @@ -236,6 +236,10 @@ impl<R: Runtime> PathResolver<R> {
///
/// Resolves to [`config_dir`](Self::config_dir)`/${bundle_identifier}`.
pub fn app_config_dir(&self) -> Result<PathBuf> {
if let Some(app_directories_override) = self.resolve_app_directories_override() {
return app_directories_override;
}

dirs::config_dir()
.ok_or(Error::UnknownPath)
.map(|dir| dir.join(&self.0.config().identifier))
Expand All @@ -245,6 +249,10 @@ impl<R: Runtime> PathResolver<R> {
///
/// Resolves to [`data_dir`](Self::data_dir)`/${bundle_identifier}`.
pub fn app_data_dir(&self) -> Result<PathBuf> {
if let Some(app_directories_override) = self.resolve_app_directories_override() {
return app_directories_override;
}

dirs::data_dir()
.ok_or(Error::UnknownPath)
.map(|dir| dir.join(&self.0.config().identifier))
Expand All @@ -254,6 +262,10 @@ impl<R: Runtime> PathResolver<R> {
///
/// Resolves to [`local_data_dir`](Self::local_data_dir)`/${bundle_identifier}`.
pub fn app_local_data_dir(&self) -> Result<PathBuf> {
if let Some(app_directories_override) = self.resolve_app_directories_override() {
return app_directories_override;
}

dirs::data_local_dir()
.ok_or(Error::UnknownPath)
.map(|dir| dir.join(&self.0.config().identifier))
Expand All @@ -263,6 +275,10 @@ impl<R: Runtime> PathResolver<R> {
///
/// Resolves to [`cache_dir`](Self::cache_dir)`/${bundle_identifier}`.
pub fn app_cache_dir(&self) -> Result<PathBuf> {
if let Some(app_directories_override) = self.resolve_app_directories_override() {
return Ok(app_directories_override?.join("caches"));
}

dirs::cache_dir()
.ok_or(Error::UnknownPath)
.map(|dir| dir.join(&self.0.config().identifier))
Expand All @@ -276,6 +292,10 @@ impl<R: Runtime> PathResolver<R> {
/// - **macOS:** Resolves to [`home_dir`](Self::home_dir)`/Library/Logs/${bundle_identifier}`
/// - **Windows:** Resolves to [`local_data_dir`](Self::local_data_dir)`/${bundle_identifier}/logs`.
pub fn app_log_dir(&self) -> Result<PathBuf> {
if let Some(app_directories_override) = self.resolve_app_directories_override() {
return Ok(app_directories_override?.join("logs"));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very much open to suggestion on those paths, and we could document them once settled

}

#[cfg(target_os = "macos")]
let path = dirs::home_dir()
.ok_or(Error::UnknownPath)
Expand All @@ -293,4 +313,39 @@ impl<R: Runtime> PathResolver<R> {
pub fn temp_dir(&self) -> Result<PathBuf> {
Ok(std::env::temp_dir())
}

/// Resolves the `app_directories_override` based on `current_exe` if it exists
fn resolve_app_directories_override(&self) -> Option<Result<PathBuf>> {
let app_directories_override = self.0.config().app.app_directories_override.as_ref();
app_directories_override.map(|app_directories_override| {
if let Some(base_directory) = app_directories_override
.components()
.next()
.and_then(|str| BaseDirectory::from_variable(str.as_os_str().to_str()?))
{
return if matches!(
base_directory,
BaseDirectory::AppCache
| BaseDirectory::AppConfig
| BaseDirectory::AppData
| BaseDirectory::AppLocalData
| BaseDirectory::AppLog
) {
// TODO: Maybe add a new variant?
Err(crate::Error::UnknownPath)
} else {
self.parse(app_directories_override)
};
}

Ok(if app_directories_override.is_absolute() {
app_directories_override.clone()
} else {
crate::process::current_binary(&self.0.env())?
.parent()
.ok_or(crate::Error::NoParent)?
.join(app_directories_override)
})
})
}
}
1 change: 1 addition & 0 deletions crates/tauri/src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub fn mock_context<R: Runtime, A: Assets<R>>(assets: A) -> crate::Context<R> {
tray_icon: None,
macos_private_api: false,
enable_gtk_app_id: false,
app_directories_override: None,
},
bundle: Default::default(),
build: Default::default(),
Expand Down
Loading