From 42dc01cd1bd9a675ded902daf8f2dcaf5e97e863 Mon Sep 17 00:00:00 2001 From: Lucas Nogueira Date: Mon, 20 Feb 2023 18:10:37 -0300 Subject: [PATCH] feat: add path plugin --- Cargo.lock | 65 +- Cargo.toml | 1 + plugins/path/Cargo.toml | 21 + plugins/path/LICENSE.spdx | 20 + plugins/path/LICENSE_APACHE-2.0 | 177 +++++ plugins/path/LICENSE_MIT | 21 + plugins/path/README.md | 65 ++ plugins/path/android/.gitignore | 2 + plugins/path/android/build.gradle.kts | 45 ++ plugins/path/android/proguard-rules.pro | 21 + plugins/path/android/settings.gradle | 2 + .../java/ExampleInstrumentedTest.kt | 24 + .../path/android/src/main/AndroidManifest.xml | 4 + .../path/android/src/main/java/PathPlugin.kt | 74 +++ .../android/src/test/java/ExampleUnitTest.kt | 17 + plugins/path/build.rs | 15 + plugins/path/guest-js/index.ts | 620 ++++++++++++++++++ plugins/path/package.json | 32 + plugins/path/rollup.config.mjs | 11 + plugins/path/src/desktop.rs | 251 +++++++ plugins/path/src/error.rs | 34 + plugins/path/src/lib.rs | 381 +++++++++++ plugins/path/src/mobile.rs | 112 ++++ plugins/path/tsconfig.json | 1 + pnpm-lock.yaml | 9 + 25 files changed, 1995 insertions(+), 30 deletions(-) create mode 100644 plugins/path/Cargo.toml create mode 100644 plugins/path/LICENSE.spdx create mode 100644 plugins/path/LICENSE_APACHE-2.0 create mode 100644 plugins/path/LICENSE_MIT create mode 100644 plugins/path/README.md create mode 100644 plugins/path/android/.gitignore create mode 100644 plugins/path/android/build.gradle.kts create mode 100644 plugins/path/android/proguard-rules.pro create mode 100644 plugins/path/android/settings.gradle create mode 100644 plugins/path/android/src/androidTest/java/ExampleInstrumentedTest.kt create mode 100644 plugins/path/android/src/main/AndroidManifest.xml create mode 100644 plugins/path/android/src/main/java/PathPlugin.kt create mode 100644 plugins/path/android/src/test/java/ExampleUnitTest.kt create mode 100644 plugins/path/build.rs create mode 100644 plugins/path/guest-js/index.ts create mode 100644 plugins/path/package.json create mode 100644 plugins/path/rollup.config.mjs create mode 100644 plugins/path/src/desktop.rs create mode 100644 plugins/path/src/error.rs create mode 100644 plugins/path/src/lib.rs create mode 100644 plugins/path/src/mobile.rs create mode 120000 plugins/path/tsconfig.json diff --git a/Cargo.lock b/Cargo.lock index b0b561c4..aaa61082 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2707,6 +2707,15 @@ dependencies = [ "syn", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "objc" version = "0.2.7" @@ -3108,7 +3117,7 @@ dependencies = [ "line-wrap", "quick-xml", "serde", - "time 0.3.18", + "time 0.3.15", ] [[package]] @@ -4273,7 +4282,7 @@ dependencies = [ [[package]] name = "tauri" version = "2.0.0-alpha.3" -source = "git+https://github.com/tauri-apps/tauri?branch=next#6aaba83476339fa413fe34d28877a932cb485117" +source = "git+https://github.com/tauri-apps/tauri?branch=next#ed879513d37430dc046a18c52579ff56699e70e7" dependencies = [ "anyhow", "attohttpc", @@ -4323,7 +4332,7 @@ dependencies = [ [[package]] name = "tauri-build" version = "2.0.0-alpha.1" -source = "git+https://github.com/tauri-apps/tauri?branch=next#6aaba83476339fa413fe34d28877a932cb485117" +source = "git+https://github.com/tauri-apps/tauri?branch=next#ed879513d37430dc046a18c52579ff56699e70e7" dependencies = [ "anyhow", "cargo_toml", @@ -4342,7 +4351,7 @@ dependencies = [ [[package]] name = "tauri-codegen" version = "2.0.0-alpha.1" -source = "git+https://github.com/tauri-apps/tauri?branch=next#6aaba83476339fa413fe34d28877a932cb485117" +source = "git+https://github.com/tauri-apps/tauri?branch=next#ed879513d37430dc046a18c52579ff56699e70e7" dependencies = [ "base64 0.21.0", "brotli", @@ -4358,7 +4367,7 @@ dependencies = [ "sha2 0.10.6", "tauri-utils", "thiserror", - "time 0.3.18", + "time 0.3.15", "url", "uuid", "walkdir", @@ -4367,7 +4376,7 @@ dependencies = [ [[package]] name = "tauri-macros" version = "2.0.0-alpha.1" -source = "git+https://github.com/tauri-apps/tauri?branch=next#6aaba83476339fa413fe34d28877a932cb485117" +source = "git+https://github.com/tauri-apps/tauri?branch=next#ed879513d37430dc046a18c52579ff56699e70e7" dependencies = [ "heck", "proc-macro2", @@ -4458,7 +4467,19 @@ dependencies = [ "serde_json", "serde_repr", "tauri", - "time 0.3.18", + "time 0.3.15", +] + +[[package]] +name = "tauri-plugin-path" +version = "0.0.0" +dependencies = [ + "dirs-next", + "serde", + "serde_repr", + "tauri", + "tauri-build", + "thiserror", ] [[package]] @@ -4587,7 +4608,7 @@ dependencies = [ [[package]] name = "tauri-runtime" version = "0.13.0-alpha.1" -source = "git+https://github.com/tauri-apps/tauri?branch=next#6aaba83476339fa413fe34d28877a932cb485117" +source = "git+https://github.com/tauri-apps/tauri?branch=next#ed879513d37430dc046a18c52579ff56699e70e7" dependencies = [ "gtk", "http", @@ -4608,7 +4629,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" version = "0.13.0-alpha.1" -source = "git+https://github.com/tauri-apps/tauri?branch=next#6aaba83476339fa413fe34d28877a932cb485117" +source = "git+https://github.com/tauri-apps/tauri?branch=next#ed879513d37430dc046a18c52579ff56699e70e7" dependencies = [ "cocoa", "gtk", @@ -4628,7 +4649,7 @@ dependencies = [ [[package]] name = "tauri-utils" version = "2.0.0-alpha.1" -source = "git+https://github.com/tauri-apps/tauri?branch=next#6aaba83476339fa413fe34d28877a932cb485117" +source = "git+https://github.com/tauri-apps/tauri?branch=next#ed879513d37430dc046a18c52579ff56699e70e7" dependencies = [ "brotli", "ctor", @@ -4745,29 +4766,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.18" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af0097eaf301d576d0b2aead7a59facab6d53cc636340f0291fab8446a2e8613" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" dependencies = [ "itoa 1.0.5", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - -[[package]] -name = "time-macros" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" -dependencies = [ - "time-core", + "libc", + "num_threads", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 46b0ee6a..a7f742f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ edition = "2021" authors = [ "Tauri Programme within The Commons Conservancy" ] license = "Apache-2.0 OR MIT" rust-version = "1.64" +exclude = ["/examples"] diff --git a/plugins/path/Cargo.toml b/plugins/path/Cargo.toml new file mode 100644 index 00000000..b5d82b14 --- /dev/null +++ b/plugins/path/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "tauri-plugin-path" +version = "0.0.0" +description = "Resolve common file system paths." +authors.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +exclude.workspace = true + +[dependencies] +serde.workspace = true +tauri.workspace = true +thiserror.workspace = true +serde_repr = "0.1" + +[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] +dirs-next = "2" + +[build-dependencies] +tauri-build.workspace = true diff --git a/plugins/path/LICENSE.spdx b/plugins/path/LICENSE.spdx new file mode 100644 index 00000000..cdd0df5a --- /dev/null +++ b/plugins/path/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/plugins/path/LICENSE_APACHE-2.0 b/plugins/path/LICENSE_APACHE-2.0 new file mode 100644 index 00000000..4947287f --- /dev/null +++ b/plugins/path/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/plugins/path/LICENSE_MIT b/plugins/path/LICENSE_MIT new file mode 100644 index 00000000..4d754725 --- /dev/null +++ b/plugins/path/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/plugins/path/README.md b/plugins/path/README.md new file mode 100644 index 00000000..ed5545a2 --- /dev/null +++ b/plugins/path/README.md @@ -0,0 +1,65 @@ +![{{plugin name}}](banner.jpg) + + + +## Install + +_This plugin requires a Rust version of at least **1.64**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] + = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use. + +```sh +pnpm add +# or +npm add +# or +yarn add +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/main.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin() + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript + +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/plugins/path/android/.gitignore b/plugins/path/android/.gitignore new file mode 100644 index 00000000..fb9b198b --- /dev/null +++ b/plugins/path/android/.gitignore @@ -0,0 +1,2 @@ +/build +/tauri-api diff --git a/plugins/path/android/build.gradle.kts b/plugins/path/android/build.gradle.kts new file mode 100644 index 00000000..4c312f66 --- /dev/null +++ b/plugins/path/android/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "" + compileSdk = 32 + + defaultConfig { + minSdk = 24 + targetSdk = 32 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation(project(":tauri-android")) +} diff --git a/plugins/path/android/proguard-rules.pro b/plugins/path/android/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/plugins/path/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/plugins/path/android/settings.gradle b/plugins/path/android/settings.gradle new file mode 100644 index 00000000..32f9aac2 --- /dev/null +++ b/plugins/path/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./tauri-api') diff --git a/plugins/path/android/src/androidTest/java/ExampleInstrumentedTest.kt b/plugins/path/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..c775de2f --- /dev/null +++ b/plugins/path/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("", appContext.packageName) + } +} diff --git a/plugins/path/android/src/main/AndroidManifest.xml b/plugins/path/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..1a9e9c28 --- /dev/null +++ b/plugins/path/android/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/plugins/path/android/src/main/java/PathPlugin.kt b/plugins/path/android/src/main/java/PathPlugin.kt new file mode 100644 index 00000000..066050e5 --- /dev/null +++ b/plugins/path/android/src/main/java/PathPlugin.kt @@ -0,0 +1,74 @@ +package app.tauri.path + +import android.app.Activity +import android.os.Environment +import app.tauri.annotation.PluginMethod +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) + } + + @PluginMethod + fun getAudioDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_MUSIC)?.absolutePath) + } + + @PluginMethod + fun getExternalCacheDir(invoke: Invoke) { + resolvePath(invoke, activity.externalCacheDir?.absolutePath) + } + + @PluginMethod + fun getConfigDir(invoke: Invoke) { + resolvePath(invoke, activity.dataDir.absolutePath) + } + + @PluginMethod + fun getDataDir(invoke: Invoke) { + resolvePath(invoke, activity.dataDir.absolutePath) + } + + @PluginMethod + fun getDocumentDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath) + } + + @PluginMethod + fun getDownloadDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath) + } + + @PluginMethod + fun getPictureDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES)?.absolutePath) + } + + @PluginMethod + fun getPublicDir(invoke: Invoke) { + resolvePath(invoke, activity.getExternalFilesDir(Environment.DIRECTORY_DCIM)?.absolutePath) + } + + @PluginMethod + fun getVideoDir(invoke: Invoke) { + resolvePath(invoke, activity.externalCacheDir?.absolutePath) + } + + @PluginMethod + fun getResourcesDir(invoke: Invoke) { + // TODO + resolvePath(invoke, activity.cacheDir.absolutePath) + } + + @PluginMethod + fun getCacheDir(invoke: Invoke) { + resolvePath(invoke, activity.cacheDir.absolutePath) + } +} diff --git a/plugins/path/android/src/test/java/ExampleUnitTest.kt b/plugins/path/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 00000000..a0b25871 --- /dev/null +++ b/plugins/path/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/plugins/path/build.rs b/plugins/path/build.rs new file mode 100644 index 00000000..55c79c54 --- /dev/null +++ b/plugins/path/build.rs @@ -0,0 +1,15 @@ +// Copyright 2019-2022 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::process::exit; + +fn main() { + if let Err(error) = tauri_build::mobile::PluginBuilder::new() + .android_path("android") + .run() + { + println!("{error:#}"); + exit(1); + } +} diff --git a/plugins/path/guest-js/index.ts b/plugins/path/guest-js/index.ts new file mode 100644 index 00000000..ec273975 --- /dev/null +++ b/plugins/path/guest-js/index.ts @@ -0,0 +1,620 @@ +// Copyright 2019-2022 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from '@tauri-apps/api/tauri' + +function isWindows(): boolean { + return navigator.appVersion.includes('Win') +} + +export enum BaseDirectory { + Audio = 1, + Cache, + Config, + Data, + LocalData, + Document, + Download, + Picture, + Public, + Video, + Resource, + App, + Log, + Temp, + AppConfig, + AppData, + AppLocalData, + AppCache, + AppLog, + + Desktop, + Executable, + Font, + Home, + Runtime, + Template, +} + +/** + * 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`. + * @example + * ```typescript + * import { appConfigDir } from '@tauri-apps/api/path'; + * const appConfigDirPath = await appConfigDir(); + * ``` + */ +async function appConfigDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.AppConfig + }) +} + +/** + * Returns the path to the suggested directory for your app's data files. + * Resolves to `${dataDir}/${bundleIdentifier}`, where `bundleIdentifier` is the value [`tauri.bundle.identifier`](https://tauri.app/v1/api/config/#bundleconfig.identifier) is configured in `tauri.conf.json`. + * @example + * ```typescript + * import { appDataDir } from '@tauri-apps/api/path'; + * const appDataDirPath = await appDataDir(); + * ``` + */ +async function appDataDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.AppData + }) +} + +/** + * Returns the path to the suggested directory for your app's local data files. + * Resolves to `${localDataDir}/${bundleIdentifier}`, where `bundleIdentifier` is the value [`tauri.bundle.identifier`](https://tauri.app/v1/api/config/#bundleconfig.identifier) is configured in `tauri.conf.json`. + * @example + * ```typescript + * import { appLocalDataDir } from '@tauri-apps/api/path'; + * const appLocalDataDirPath = await appLocalDataDir(); + * ``` + */ +async function appLocalDataDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.AppLocalData + }) +} + +/** + * Returns the path to the suggested directory for your app's cache files. + * Resolves to `${cacheDir}/${bundleIdentifier}`, where `bundleIdentifier` is the value [`tauri.bundle.identifier`](https://tauri.app/v1/api/config/#bundleconfig.identifier) is configured in `tauri.conf.json`. + * @example + * ```typescript + * import { appCacheDir } from '@tauri-apps/api/path'; + * const appCacheDirPath = await appCacheDir(); + * ``` + */ +async function appCacheDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.AppCache + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { audioDir } from '@tauri-apps/api/path'; + * const audioDirPath = await audioDir(); + * ``` + */ +async function audioDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Audio + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { cacheDir } from '@tauri-apps/api/path'; + * const cacheDirPath = await cacheDir(); + * ``` + */ +async function cacheDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Cache + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { configDir } from '@tauri-apps/api/path'; + * const configDirPath = await configDir(); + * ``` + */ +async function configDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Config + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { dataDir } from '@tauri-apps/api/path'; + * const dataDirPath = await dataDir(); + * ``` + */ +async function dataDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Data + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { desktopDir } from '@tauri-apps/api/path'; + * const desktopPath = await desktopDir(); + * ``` + */ +async function desktopDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Desktop + }) +} + +/** + * Returns the path to the user's document directory. + * @example + * ```typescript + * import { documentDir } from '@tauri-apps/api/path'; + * const documentDirPath = await documentDir(); + * ``` + * + * #### 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}`. + */ +async function documentDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Document + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { downloadDir } from '@tauri-apps/api/path'; + * const downloadDirPath = await downloadDir(); + * ``` + */ +async function downloadDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Download + }) +} + +/** + * 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. + * @example + * ```typescript + * import { executableDir } from '@tauri-apps/api/path'; + * const executableDirPath = await executableDir(); + * ``` + */ +async function executableDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Executable + }) +} + +/** + * 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. + * @example + * ```typescript + * import { fontDir } from '@tauri-apps/api/path'; + * const fontDirPath = await fontDir(); + * ``` + */ +async function fontDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Font + }) +} + +/** + * Returns the path to the user's home directory. + * + * #### Platform-specific + * + * - **Linux:** Resolves to `$HOME`. + * - **macOS:** Resolves to `$HOME`. + * - **Windows:** Resolves to `{FOLDERID_Profile}`. + * @example + * ```typescript + * import { homeDir } from '@tauri-apps/api/path'; + * const homeDirPath = await homeDir(); + * ``` + */ +async function homeDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Home + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { localDataDir } from '@tauri-apps/api/path'; + * const localDataDirPath = await localDataDir(); + * ``` + */ +async function localDataDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.LocalData + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { pictureDir } from '@tauri-apps/api/path'; + * const pictureDirPath = await pictureDir(); + * ``` + */ +async function pictureDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Picture + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { publicDir } from '@tauri-apps/api/path'; + * const publicDirPath = await publicDir(); + * ``` + */ +async function publicDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Public + }) +} + +/** + * Returns the path to the application's resource directory. + * To resolve a resource path, see the [[resolveResource | `resolveResource API`]]. + * @example + * ```typescript + * import { resourceDir } from '@tauri-apps/api/path'; + * const resourceDirPath = await resourceDir(); + * ``` + */ +async function resourceDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Resource + }) +} + +/** + * Resolve the path to a resource file. + * @example + * ```typescript + * import { resolveResource } from '@tauri-apps/api/path'; + * const resourcePath = await resolveResource('script.sh'); + * ``` + * + * @param resourcePath The path to the resource. + * Must follow the same syntax as defined in `tauri.conf.json > tauri > bundle > resources`, i.e. keeping subfolders and parent dir components (`../`). + * @returns The full path to the resource. + */ +async function resolveResource(resourcePath: string): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Resource, + path: resourcePath + }) +} + +/** + * Returns the path to the user's runtime directory. + * + * #### Platform-specific + * + * - **Linux:** Resolves to `$XDG_RUNTIME_DIR`. + * - **macOS:** Not supported. + * - **Windows:** Not supported. + * @example + * ```typescript + * import { runtimeDir } from '@tauri-apps/api/path'; + * const runtimeDirPath = await runtimeDir(); + * ``` + */ +async function runtimeDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Runtime + }) +} + +/** + * 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}`. + * @example + * ```typescript + * import { templateDir } from '@tauri-apps/api/path'; + * const templateDirPath = await templateDir(); + * ``` + */ +async function templateDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Template + }) +} + +/** + * Returns the path to the user's video directory. + * + * #### 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}`. + * @example + * ```typescript + * import { videoDir } from '@tauri-apps/api/path'; + * const videoDirPath = await videoDir(); + * ``` + */ +async function videoDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.Video + }) +} + +/** + * Returns the path to the suggested directory for your app's log files. + * + * #### Platform-specific + * + * - **Linux:** Resolves to `${configDir}/${bundleIdentifier}/logs`. + * - **macOS:** Resolves to `${homeDir}/Library/Logs/{bundleIdentifier}` + * - **Windows:** Resolves to `${configDir}/${bundleIdentifier}/logs`. + * @example + * ```typescript + * import { appLogDir } from '@tauri-apps/api/path'; + * const appLogDirPath = await appLogDir(); + * ``` + */ +async function appLogDir(): Promise { + return invoke('plugin:path|resolve_directory', { + directory: BaseDirectory.AppLog + }) +} + +/** + * Provides the platform-specific path segment separator: + * - `\` on Windows + * - `/` on POSIX + * + * @since 1.0.0 + */ +const sep = isWindows() ? '\\' : '/' + +/** + * Provides the platform-specific path segment delimiter: + * - `;` on Windows + * - `:` on POSIX + * + * @since 1.0.0 + */ +const delimiter = isWindows() ? ';' : ':' + +/** + * Resolves a sequence of `paths` or `path` segments into an absolute path. + * @example + * ```typescript + * import { resolve, appDataDir } from '@tauri-apps/api/path'; + * const appDataDirPath = await appDataDir(); + * const path = await resolve(appDataDirPath, '..', 'users', 'tauri', 'avatar.png'); + * ``` + */ +async function resolve(...paths: string[]): Promise { + return invoke('plugin:path|resolve', { paths }) +} + +/** + * Normalizes the given `path`, resolving `'..'` and `'.'` segments and resolve symbolic links. + * @example + * ```typescript + * import { normalize, appDataDir } from '@tauri-apps/api/path'; + * const appDataDirPath = await appDataDir(); + * const path = await normalize(appDataDirPath, '..', 'users', 'tauri', 'avatar.png'); + * ``` + */ +async function normalize(path: string): Promise { + return invoke('plugin:path|normalize', { path }) +} + +/** + * Joins all given `path` segments together using the platform-specific separator as a delimiter, then normalizes the resulting path. + * @example + * ```typescript + * import { join, appDataDir } from '@tauri-apps/api/path'; + * const appDataDirPath = await appDataDir(); + * const path = await join(appDataDirPath, 'users', 'tauri', 'avatar.png'); + * ``` + */ +async function join(...paths: string[]): Promise { + return invoke('plugin:path|join', { paths }) +} + +/** + * Returns the directory name of a `path`. Trailing directory separators are ignored. + * @example + * ```typescript + * import { dirname, appDataDir } from '@tauri-apps/api/path'; + * const appDataDirPath = await appDataDir(); + * const dir = await dirname(appDataDirPath); + * ``` + */ +async function dirname(path: string): Promise { + return invoke('plugin:path|dirname', { path }) +} + +/** + * Returns the extension of the `path`. + * @example + * ```typescript + * import { extname, resolveResource } from '@tauri-apps/api/path'; + * const resourcePath = await resolveResource('app.conf'); + * const ext = await extname(resourcePath); + * assert(ext === 'conf'); + * ``` + */ +async function extname(path: string): Promise { + return invoke('plugin:path|extname', { path }) +} + +/** + * Returns the last portion of a `path`. Trailing directory separators are ignored. + * @example + * ```typescript + * import { basename, resolveResource } from '@tauri-apps/api/path'; + * const resourcePath = await resolveResource('app.conf'); + * const base = await basename(resourcePath); + * assert(base === 'app'); + * ``` + * + * @param ext An optional file extension to be removed from the returned path. + */ +async function basename(path: string, ext?: string): Promise { + return invoke('plugin:path|basename', { path, ext }) +} + +/** + * Returns whether the path is absolute or not. + * @example + * ```typescript + * import { isAbsolute } from '@tauri-apps/api/path'; + * assert(await isAbsolute('/home/tauri')); + * ``` + */ +async function isAbsolute(path: string): Promise { + return invoke('plugin:path|isAbsolute', { path }) +} + +export { + appDir, + appConfigDir, + appDataDir, + appLocalDataDir, + appCacheDir, + appLogDir, + audioDir, + cacheDir, + configDir, + dataDir, + desktopDir, + documentDir, + downloadDir, + executableDir, + fontDir, + homeDir, + localDataDir, + pictureDir, + publicDir, + resourceDir, + resolveResource, + runtimeDir, + templateDir, + videoDir, + sep, + delimiter, + resolve, + normalize, + join, + dirname, + extname, + basename, + isAbsolute +} diff --git a/plugins/path/package.json b/plugins/path/package.json new file mode 100644 index 00000000..9c0c3133 --- /dev/null +++ b/plugins/path/package.json @@ -0,0 +1,32 @@ +{ + "name": "tauri-plugin-path-api", + "version": "0.0.0", + "license": "MIT or APACHE-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "type": "module", + "browser": "dist-js/index.min.js", + "module": "dist-js/index.mjs", + "types": "dist-js/index.d.ts", + "exports": { + "import": "./dist-js/index.mjs", + "types": "./dist-js/index.d.ts", + "browser": "./dist-js/index.min.js" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "!dist-js/**/*.map", + "README.md", + "LICENSE" + ], + "devDependencies": { + "tslib": "^2.4.1" + }, + "dependencies": { + "@tauri-apps/api": "^1.2.0" + } +} diff --git a/plugins/path/rollup.config.mjs b/plugins/path/rollup.config.mjs new file mode 100644 index 00000000..6555e98b --- /dev/null +++ b/plugins/path/rollup.config.mjs @@ -0,0 +1,11 @@ +import { readFileSync } from "fs"; + +import { createConfig } from "../../shared/rollup.config.mjs"; + +export default createConfig({ + input: "guest-js/index.ts", + pkg: JSON.parse( + readFileSync(new URL("./package.json", import.meta.url), "utf8") + ), + external: [/^@tauri-apps\/api/], +}); diff --git a/plugins/path/src/desktop.rs b/plugins/path/src/desktop.rs new file mode 100644 index 00000000..b0b6aefb --- /dev/null +++ b/plugins/path/src/desktop.rs @@ -0,0 +1,251 @@ +use crate::{Error, Result}; +use std::path::PathBuf; +use tauri::{AppHandle, Manager, Runtime}; + +/// A helper class to access the mobile camera APIs. +pub struct PathResolver(pub(crate) AppHandle); + +impl PathResolver { + /// 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + dirs_next::video_dir().ok_or(Error::UnknownPath) + } + + /// Returns the path to the resource directory of this app. + pub fn resource_dir(&self) -> Result { + tauri::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 { + 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 { + 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 { + 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 { + 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 { + #[cfg(target_os = "macos")] + let path = dirs_next::home_dir().ok_or(Error::UnknownPath).map(|dir| { + dir.join("Library/Logs") + .join(&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 + } +} diff --git a/plugins/path/src/error.rs b/plugins/path/src/error.rs new file mode 100644 index 00000000..bff0b2dd --- /dev/null +++ b/plugins/path/src/error.rs @@ -0,0 +1,34 @@ +// Copyright 2019-2022 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("path does not have a parent")] + NoParent, + #[error("path does not have an extension")] + NoExtension, + #[error("path does not have a basename")] + NoBasename, + #[error("failed to read current dir: {0}")] + CurrentDir(std::io::Error), + #[cfg(desktop)] + #[error("unknown path")] + UnknownPath, + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/plugins/path/src/lib.rs b/plugins/path/src/lib.rs new file mode 100644 index 00000000..f42fd9c6 --- /dev/null +++ b/plugins/path/src/lib.rs @@ -0,0 +1,381 @@ +// Copyright 2019-2022 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, MAIN_SEPARATOR}, +}; + +use tauri::{ + plugin::{Builder, TauriPlugin}, + AppHandle, Manager, Runtime, State, +}; + +use serde_repr::{Deserialize_repr, Serialize_repr}; + +mod error; +pub use error::*; + +#[cfg(desktop)] +mod desktop; +#[cfg(mobile)] +mod mobile; + +#[cfg(desktop)] +use desktop::PathResolver; +#[cfg(mobile)] +use mobile::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(desktop)] + Desktop, + /// The Executable directory. + #[cfg(desktop)] + Executable, + /// The Font directory. + #[cfg(desktop)] + Font, + /// The Home directory. + #[cfg(desktop)] + Home, + /// The Runtime directory. + #[cfg(desktop)] + Runtime, + /// The Template directory. + #[cfg(desktop)] + Template, +} + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the camera APIs. +pub trait PathResolverExt { + fn path_resolver(&self) -> &PathResolver; +} + +impl> PathResolverExt for T { + fn path_resolver(&self) -> &PathResolver { + self.state::>().inner() + } +} + +/// 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 +} + +#[tauri::command] +fn resolve_directory( + _app: AppHandle, + resolver: State<'_, PathResolver>, + directory: BaseDirectory, + path: Option, +) -> Result { + 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(desktop)] + BaseDirectory::Desktop => resolver.desktop_dir(), + #[cfg(desktop)] + BaseDirectory::Executable => resolver.executable_dir(), + #[cfg(desktop)] + BaseDirectory::Font => resolver.font_dir(), + #[cfg(desktop)] + BaseDirectory::Home => resolver.home_dir(), + #[cfg(desktop)] + BaseDirectory::Runtime => resolver.runtime_dir(), + #[cfg(desktop)] + 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) +} + +#[tauri::command] +fn resolve(paths: Vec) -> Result { + // 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)) +} + +#[tauri::command] +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 + } +} + +#[tauri::command] +fn join(mut paths: Vec) -> 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::(), + ); + + let p = normalize_path_no_absolute(&path) + .to_string_lossy() + .to_string(); + if p.is_empty() { + ".".into() + } else { + p + } +} + +#[tauri::command] +fn dirname(path: String) -> Result { + match Path::new(&path).parent() { + Some(p) => Ok(p.to_path_buf()), + None => Err(Error::NoParent), + } +} + +#[tauri::command] +fn extname(path: String) -> Result { + match Path::new(&path) + .extension() + .and_then(std::ffi::OsStr::to_str) + { + Some(p) => Ok(p.to_string()), + None => Err(Error::NoExtension), + } +} + +#[tauri::command] +fn basename(path: String, ext: Option) -> Result { + 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), + } +} + +#[tauri::command] +fn is_absolute(path: String) -> bool { + Path::new(&path).is_absolute() +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("path") + .invoke_handler(tauri::generate_handler![ + resolve_directory, + resolve, + normalize, + join, + dirname, + extname, + basename, + is_absolute + ]) + .setup(|app, _api| { + #[cfg(mobile)] + { + let handle = + _api.register_android_plugin(mobile::PLUGIN_IDENTIFIER, "PathPlugin")?; + app.manage(PathResolver(handle)); + } + + #[cfg(desktop)] + { + app.manage(PathResolver(app.clone())); + } + + Ok(()) + }) + .build() +} diff --git a/plugins/path/src/mobile.rs b/plugins/path/src/mobile.rs new file mode 100644 index 00000000..2f68cfda --- /dev/null +++ b/plugins/path/src/mobile.rs @@ -0,0 +1,112 @@ +use crate::Result; +use std::path::PathBuf; +use tauri::{plugin::PluginHandle, Runtime}; + +#[cfg(target_os = "android")] +pub const PLUGIN_IDENTIFIER: &str = "app.tauri.path"; + +/// A helper class to access the mobile camera APIs. +pub struct PathResolver(pub(crate) PluginHandle); + +#[derive(serde::Deserialize)] +struct PathResponse { + path: PathBuf, +} + +#[cfg(target_os = "android")] +impl PathResolver { + fn resolve(&self, dir: &str) -> Result { + self.0 + .run_mobile_plugin::(dir, ()) + .map(|r| r.path) + .map_err(Into::into) + } + + /// Returns the path to the user's audio directory. + pub fn audio_dir(&self) -> Result { + self.resolve("getAudioDir") + } + + /// Returns the path to the user's cache directory. + pub fn cache_dir(&self) -> Result { + self.resolve("getExternalCacheDir") + } + + /// Returns the path to the user's config directory. + pub fn config_dir(&self) -> Result { + self.resolve("getConfigDir") + } + + /// Returns the path to the user's data directory. + pub fn data_dir(&self) -> Result { + self.resolve("getDataDir") + } + + /// Returns the path to the user's local data directory. + pub fn local_data_dir(&self) -> Result { + self.resolve("getDataDir") + } + + /// Returns the path to the user's document directory. + pub fn document_dir(&self) -> Result { + self.resolve("getDocumentDir") + } + + /// Returns the path to the user's download directory. + pub fn download_dir(&self) -> Result { + self.resolve("getDownloadDir") + } + + /// Returns the path to the user's picture directory. + pub fn picture_dir(&self) -> Result { + self.resolve("getPictureDir") + } + + /// Returns the path to the user's public directory. + pub fn public_dir(&self) -> Result { + self.resolve("getPublicDir") + } + + /// Returns the path to the user's video dir + pub fn video_dir(&self) -> Result { + self.resolve("getVideoDir") + } + + /// Returns the path to the resource directory of this app. + pub fn resource_dir(&self) -> Result { + self.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 { + self.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 { + self.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 { + self.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 { + self.resolve("getCacheDir") + } + + /// Returns the path to the suggested directory for your app's log files. + pub fn app_log_dir(&self) -> Result { + self.resolve("getConfigDir").map(|dir| dir.join("logs")) + } +} diff --git a/plugins/path/tsconfig.json b/plugins/path/tsconfig.json new file mode 120000 index 00000000..4ec6ff6a --- /dev/null +++ b/plugins/path/tsconfig.json @@ -0,0 +1 @@ +../tsconfig.json \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8fb00f2..ada22561 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,6 +79,15 @@ importers: devDependencies: tslib: 2.4.1 + plugins/path: + specifiers: + '@tauri-apps/api': ^1.2.0 + tslib: ^2.4.1 + dependencies: + '@tauri-apps/api': 1.2.0 + devDependencies: + tslib: 2.4.1 + plugins/positioner: specifiers: '@tauri-apps/api': ^1.2.0