-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
feat: Add support for AppDocuments, AppLibrary and AppTemp (#12276) #14598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@tauri-apps/api": patch:enhance | ||
| --- | ||
|
|
||
| Add support for AppDocuments, AppLibrary, and AppTemp folders. | ||
| Add documentation for usage of different path methods. | ||
| Add sample page for available paths. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| // Copyright 2019-2024 Tauri Programme within The Commons Conservancy | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| import Foundation | ||
|
|
||
| class PathPlugin: Plugin { | ||
| private let fileManager = FileManager.default | ||
|
|
||
| @objc func getCacheDir(_ invoke: Invoke) { | ||
| if let url = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first { | ||
| resolvePath(invoke, pathValue: url.path) | ||
| } else { | ||
| resolvePath(invoke, pathValue: nil) | ||
| } | ||
| } | ||
|
|
||
| @objc func getDocumentsDir(_ invoke: Invoke) { | ||
| if let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first { | ||
| resolvePath(invoke, pathValue: url.path) | ||
| } else { | ||
| resolvePath(invoke, pathValue: nil) | ||
| } | ||
| } | ||
|
|
||
| @objc func getLibraryDir(_ invoke: Invoke) { | ||
| if let url = fileManager.urls(for: .libraryDirectory, in: .userDomainMask).first { | ||
| resolvePath(invoke, pathValue: url.path) | ||
| } else { | ||
| resolvePath(invoke, pathValue: nil) | ||
| } | ||
| } | ||
|
|
||
| @objc func getTempDir(_ invoke: Invoke) { | ||
| let tempDir = NSTemporaryDirectory() | ||
| resolvePath(invoke, pathValue: tempDir) | ||
| } | ||
|
|
||
| private func resolvePath(_ invoke: Invoke, pathValue: String?) { | ||
| var obj: JSObject = [:] | ||
| if let pathValue = pathValue { | ||
| obj["path"] = pathValue | ||
| } else { | ||
| obj["path"] = NSNull() | ||
| } | ||
| invoke.resolve(obj) | ||
| } | ||
| } | ||
|
|
||
| @_cdecl("init_path_plugin") | ||
| func initPathPlugin() -> Plugin { | ||
| return PathPlugin() | ||
| } | ||
|
|
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -268,6 +268,29 @@ impl<R: Runtime> PathResolver<R> { | |
| .map(|dir| dir.join(&self.0.config().identifier)) | ||
| } | ||
|
|
||
| /// Returns the path to the app's documents directory. | ||
| /// | ||
| /// Not supported on desktop, returns error. | ||
| pub fn app_documents_dir(&self) -> Result<PathBuf> { | ||
| Err(Error::UnknownPath) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't we want these methods to resolve (or fall back) on desktop? Otherwise we'd need to handle desktop/mobile paths separately in the application.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what a sensible fallback would be for these on Desktop implementations would be, since these may not have direct translations to MacOS and Windows apps. |
||
| } | ||
|
|
||
| /// Returns the path to the app's library directory. | ||
| /// | ||
| /// Not supported on desktop, returns error. | ||
| pub fn app_library_dir(&self) -> Result<PathBuf> { | ||
| Err(Error::UnknownPath) | ||
| } | ||
|
|
||
| /// Returns the path to the app's temporary directory. | ||
| /// | ||
| /// Not supported on desktop, returns error. | ||
| pub fn app_temp_dir(&self) -> Result<PathBuf> { | ||
| Err(Error::UnknownPath) | ||
| } | ||
| } | ||
|
|
||
| impl<R: Runtime> PathResolver<R> { | ||
| /// Returns the path to the suggested directory for your app's log files. | ||
| /// | ||
| /// ## Platform-specific | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,257 @@ | ||
| // Copyright 2019-2024 Tauri Programme within The Commons Conservancy | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| use super::{Error, Result}; | ||
| use crate::{plugin::PluginHandle, AppHandle, Runtime}; | ||
| use std::path::{Path, PathBuf}; | ||
|
|
||
| #[cfg(target_os = "ios")] | ||
| use super::desktop; | ||
|
|
||
| /// A helper class to access the mobile path APIs. | ||
| /// | ||
| /// ## Hybrid Approach | ||
| /// | ||
| /// This resolver uses a hybrid approach that combines both desktop and iOS-specific implementations | ||
| /// to maintain backwards compatibility while providing native iOS path resolution for mobile-specific | ||
| /// directories. | ||
| /// | ||
| /// ### Why This Approach is Necessary | ||
| /// | ||
| /// Prior to the introduction of mobile-specific path methods (`appDocumentsDir`, `appLibraryDir`, | ||
| /// `appTempDir`, `appCacheDir`), iOS applications used the desktop path resolution implementation, | ||
| /// which relied on the `dirs` crate and other desktop-oriented path resolution mechanisms. These | ||
| /// paths worked correctly on iOS Simulator but may not have matched the exact native iOS sandbox | ||
| /// paths on real devices. | ||
| /// | ||
| /// To maintain backwards compatibility for existing code that uses methods like `documentDir()`, | ||
| /// `cacheDir()`, `configDir()`, etc., we continue to delegate these methods to the desktop | ||
| /// implementation. This ensures that existing applications continue to work without modification. | ||
| /// | ||
| /// ### How It Works | ||
| /// | ||
| /// The resolver contains both an `AppHandle<R>` (for desktop method delegation) and a | ||
| /// `PluginHandle<R>` (for iOS native plugin calls). The routing logic is as follows: | ||
| /// | ||
| /// - **Most methods** (e.g., `audio_dir()`, `cache_dir()`, `config_dir()`, `document_dir()`, etc.): | ||
| /// Always delegate to the desktop implementation via `desktop::PathResolver`. This maintains | ||
| /// backwards compatibility with existing code. | ||
| /// | ||
| /// - **Four mobile-specific methods** (`app_cache_dir()`, `app_documents_dir()`, `app_library_dir()`, | ||
| /// `app_temp_dir()`): Use the iOS Swift plugin to call native iOS APIs: | ||
| /// - `app_cache_dir()` → `getCacheDir()` → `FileManager.default.urls(for: .cachesDirectory, ...)` | ||
| /// - `app_documents_dir()` → `getDocumentsDir()` → `FileManager.default.urls(for: .documentDirectory, ...)` | ||
| /// - `app_library_dir()` → `getLibraryDir()` → `FileManager.default.urls(for: .libraryDirectory, ...)` | ||
| /// - `app_temp_dir()` → `getTempDir()` → `NSTemporaryDirectory()` | ||
| pub struct PathResolver<R: Runtime> { | ||
| pub(crate) app: AppHandle<R>, | ||
| pub(crate) plugin: PluginHandle<R>, | ||
| } | ||
|
|
||
| impl<R: Runtime> Clone for PathResolver<R> { | ||
| fn clone(&self) -> Self { | ||
| Self { | ||
| app: self.app.clone(), | ||
| plugin: self.plugin.clone(), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(serde::Deserialize)] | ||
| struct PathResponse { | ||
| path: Option<String>, | ||
| } | ||
|
|
||
| impl<R: Runtime> PathResolver<R> { | ||
| /// Returns the final component of the `Path`, if there is one. | ||
| /// | ||
| /// If the path is a normal file, this is the file name. If it's the path of a directory, this | ||
| /// is the directory name. | ||
| /// | ||
| /// Returns [`None`] if the path terminates in `..`. | ||
| pub fn file_name(&self, path: &str) -> Option<String> { | ||
| Path::new(path) | ||
| .file_name() | ||
| .map(|name| name.to_string_lossy().into_owned()) | ||
| } | ||
|
|
||
| /// Helper to get a desktop resolver for delegation | ||
| fn desktop_resolver(&self) -> desktop::PathResolver<R> { | ||
| desktop::PathResolver(self.app.clone()) | ||
| } | ||
|
|
||
| /// Helper to call the iOS plugin | ||
| fn call_resolve(&self, dir: &str) -> Result<PathBuf> { | ||
| let response = self | ||
| .plugin | ||
| .run_mobile_plugin::<PathResponse>(dir, ()) | ||
| .map_err(|_| Error::UnknownPath)?; | ||
|
|
||
| match response.path { | ||
| Some(p) if !p.is_empty() => Ok(PathBuf::from(p)), | ||
| _ => Err(Error::UnknownPath), | ||
| } | ||
| } | ||
|
|
||
| /// Returns the path to the user's audio directory. | ||
| pub fn audio_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().audio_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's cache directory. | ||
| pub fn cache_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().cache_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's config directory. | ||
| pub fn config_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().config_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's data directory. | ||
| pub fn data_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().data_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's local data directory. | ||
| pub fn local_data_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().local_data_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's document directory. | ||
| pub fn document_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().document_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's download directory. | ||
| pub fn download_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().download_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's picture directory. | ||
| pub fn picture_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().picture_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's public directory. | ||
| pub fn public_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().public_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's video dir | ||
| pub fn video_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().video_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the resource directory of this app. | ||
| pub fn resource_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().resource_dir() | ||
| } | ||
|
|
||
| /// 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.desktop_resolver().app_config_dir() | ||
| } | ||
|
|
||
| /// 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.desktop_resolver().app_data_dir() | ||
| } | ||
|
|
||
| /// 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.desktop_resolver().app_local_data_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the suggested directory for your app's cache files. | ||
| /// | ||
| /// Resolves to `FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!` | ||
| pub fn app_cache_dir(&self) -> Result<PathBuf> { | ||
| self.call_resolve("getCacheDir") | ||
| } | ||
|
|
||
| /// Returns the path to the app's documents directory. | ||
| /// | ||
| /// Resolves to `FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!. | ||
| pub fn app_documents_dir(&self) -> Result<PathBuf> { | ||
| self.call_resolve("getDocumentsDir") | ||
| } | ||
|
|
||
| /// Returns the path to the app's library directory. | ||
| /// | ||
| /// Resolves to `FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!. | ||
| pub fn app_library_dir(&self) -> Result<PathBuf> { | ||
| self.call_resolve("getLibraryDir") | ||
| } | ||
|
|
||
| /// Returns the path to the app's temporary directory. | ||
| /// | ||
| /// Resolves to `NSTemporaryDirectory()` | ||
| pub fn app_temp_dir(&self) -> Result<PathBuf> { | ||
| self.call_resolve("getTempDir") | ||
| } | ||
|
|
||
| /// Returns the path to the suggested directory for your app's log files. | ||
| pub fn app_log_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().app_log_dir() | ||
| } | ||
|
|
||
| /// A temporary directory. Resolves to [`std::env::temp_dir`]. | ||
| pub fn temp_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().temp_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}`. | ||
| /// - **iOS**: Cannot be written to directly, use one of the app paths instead. | ||
| pub fn home_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().home_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's desktop directory. | ||
| /// | ||
| /// Not supported on iOS, returns error. | ||
| pub fn desktop_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().desktop_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's executable directory. | ||
| /// | ||
| /// Not supported on iOS, returns error. | ||
| pub fn executable_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().executable_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's font directory. | ||
| /// | ||
| /// Not supported on iOS, returns error. | ||
| pub fn font_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().font_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's runtime directory. | ||
| /// | ||
| /// Not supported on iOS, returns error. | ||
| pub fn runtime_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().runtime_dir() | ||
| } | ||
|
|
||
| /// Returns the path to the user's template directory. | ||
| /// | ||
| /// Not supported on iOS, returns error. | ||
| pub fn template_dir(&self) -> Result<PathBuf> { | ||
| self.desktop_resolver().template_dir() | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.