feat: dialog add stop_accessing_path command

pull/2548/head
nashaofu 2 days ago committed by nashaofu
parent 715e28d922
commit 1367c60fc9

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
const COMMANDS: &[&str] = &["open", "save", "message", "ask", "confirm"]; const COMMANDS: &[&str] = &["open", "save", "stop_accessing_path", "message", "ask", "confirm"];
fn main() { fn main() {
let result = tauri_plugin::Builder::new(COMMANDS) let result = tauri_plugin::Builder::new(COMMANDS)

@ -4,6 +4,31 @@
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
class Path {
public path: string
constructor(path: string) {
this.path = path
}
destroy() {
return invoke('plugin:dialog|stop-accessing-path', { path: this.path })
}
toPath() {
return this.path
}
toString() {
return this.toPath()
}
toJSON() {
return {
path: this.path
}
}
}
/** /**
* Extension filters for the file dialog. * Extension filters for the file dialog.
* *
@ -100,13 +125,7 @@ interface ConfirmDialogOptions {
cancelLabel?: string cancelLabel?: string
} }
type OpenDialogReturn<T extends OpenDialogOptions> = T['directory'] extends true type OpenDialogReturn<T extends OpenDialogOptions> = T['multiple'] extends true ? Path[] | null : Path | null
? T['multiple'] extends true
? string[] | null
: string | null
: T['multiple'] extends true
? string[] | null
: string | null
/** /**
* Open a file/directory selection dialog. * Open a file/directory selection dialog.
@ -171,7 +190,17 @@ async function open<T extends OpenDialogOptions>(
Object.freeze(options) Object.freeze(options)
} }
return await invoke('plugin:dialog|open', { options }) const path = await invoke<string[] | string | null>('plugin:dialog|open', { options })
if (Array.isArray(path)) {
return path.map((p) => new Path(p))
}
if (!path) {
return null
}
return new Path(path)
} }
/** /**
@ -202,12 +231,18 @@ async function open<T extends OpenDialogOptions>(
* *
* @since 2.0.0 * @since 2.0.0
*/ */
async function save(options: SaveDialogOptions = {}): Promise<string | null> { async function save(options: SaveDialogOptions = {}): Promise<Path | null> {
if (typeof options === 'object') { if (typeof options === 'object') {
Object.freeze(options) Object.freeze(options)
} }
return await invoke('plugin:dialog|save', { options }) const path = await invoke<string | null>('plugin:dialog|save', { options })
if (!path) {
return null
}
return new Path(path)
} }
/** /**

@ -38,6 +38,10 @@ struct SaveFileDialogOptions: Decodable {
var defaultPath: String? var defaultPath: String?
} }
struct StopAccessingPathOptions: Decodable {
var path: URL
}
class DialogPlugin: Plugin { class DialogPlugin: Plugin {
var filePickerController: FilePickerController! var filePickerController: FilePickerController!
@ -74,14 +78,8 @@ class DialogPlugin: Plugin {
onFilePickerResult = { (event: FilePickerEvent) -> Void in onFilePickerResult = { (event: FilePickerEvent) -> Void in
switch event { switch event {
case .selected(let urls): case .selected(let urls):
do { urls.forEach { $0.startAccessingSecurityScopedResource() }
let temporaryUrls = try urls.map { try self.saveTemporaryFile($0) } invoke.resolve(["files": urls])
invoke.resolve(["files": temporaryUrls])
} catch {
let message = "Failed to create a temporary copy of the file: \(error)"
Logger.error("\(message)")
invoke.reject(message)
}
case .cancelled: case .cancelled:
invoke.resolve(["files": nil]) invoke.resolve(["files": nil])
case .error(let error): case .error(let error):
@ -179,6 +177,12 @@ class DialogPlugin: Plugin {
} }
} }
@objc public func stopAccessingPath(_ invoke: Invoke) throws {
let args = try invoke.parseArgs(StopAccessingPathOptions.self)
args.path.stopAccessingSecurityScopedResource()
invoke.resolve()
}
private func presentViewController(_ viewControllerToPresent: UIViewController) { private func presentViewController(_ viewControllerToPresent: UIViewController) {
self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil) self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
} }
@ -203,27 +207,6 @@ class DialogPlugin: Plugin {
self.onFilePickerResult?(event) self.onFilePickerResult?(event)
} }
private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL {
var directory = URL(fileURLWithPath: NSTemporaryDirectory())
if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
.first
{
directory = cachesDirectory
}
let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent)
do {
try deleteFile(targetUrl)
}
try FileManager.default.copyItem(at: sourceUrl, to: targetUrl)
return targetUrl
}
private func deleteFile(_ url: URL) throws {
if FileManager.default.fileExists(atPath: url.path) {
try FileManager.default.removeItem(atPath: url.path)
}
}
@objc public func showMessageDialog(_ invoke: Invoke) throws { @objc public func showMessageDialog(_ invoke: Invoke) throws {
let manager = self.manager let manager = self.manager
let args = try invoke.parseArgs(MessageDialogOptions.self) let args = try invoke.parseArgs(MessageDialogOptions.self)

@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-stop-accessing-path"
description = "Enables the stop_accessing_path command without any pre-configured scope."
commands.allow = ["stop_accessing_path"]
[[permission]]
identifier = "deny-stop-accessing-path"
description = "Denies the stop_accessing_path command without any pre-configured scope."
commands.deny = ["stop_accessing_path"]

@ -14,6 +14,7 @@ All dialog types are enabled.
- `allow-message` - `allow-message`
- `allow-save` - `allow-save`
- `allow-open` - `allow-open`
- `allow-stop-accessing-path`
## Permission Table ## Permission Table
@ -151,6 +152,32 @@ Enables the save command without any pre-configured scope.
Denies the save command without any pre-configured scope. Denies the save command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`dialog:allow-stop-accessing-path`
</td>
<td>
Enables the stop_accessing_path command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`dialog:deny-stop-accessing-path`
</td>
<td>
Denies the stop_accessing_path command without any pre-configured scope.
</td> </td>
</tr> </tr>
</table> </table>

@ -17,4 +17,5 @@ permissions = [
"allow-message", "allow-message",
"allow-save", "allow-save",
"allow-open", "allow-open",
"allow-stop-accessing-path"
] ]

@ -355,10 +355,22 @@
"markdownDescription": "Denies the save command without any pre-configured scope." "markdownDescription": "Denies the save command without any pre-configured scope."
}, },
{ {
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`", "description": "Enables the stop_accessing_path command without any pre-configured scope.",
"type": "string",
"const": "allow-stop-accessing-path",
"markdownDescription": "Enables the stop_accessing_path command without any pre-configured scope."
},
{
"description": "Denies the stop_accessing_path command without any pre-configured scope.",
"type": "string",
"const": "deny-stop-accessing-path",
"markdownDescription": "Denies the stop_accessing_path command without any pre-configured scope."
},
{
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`\n- `allow-stop-accessing-path`",
"type": "string", "type": "string",
"const": "default", "const": "default",
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`\n- `allow-stop-accessing-path`"
} }
] ]
} }

@ -9,6 +9,7 @@ use tauri::{command, Manager, Runtime, State, Window};
use tauri_plugin_fs::FsExt; use tauri_plugin_fs::FsExt;
use crate::{ use crate::{
StopAccessingPath,
Dialog, FileDialogBuilder, FilePath, MessageDialogButtons, MessageDialogKind, Result, CANCEL, Dialog, FileDialogBuilder, FilePath, MessageDialogButtons, MessageDialogKind, Result, CANCEL,
NO, OK, YES, NO, OK, YES,
}; };
@ -241,6 +242,11 @@ pub(crate) async fn save<R: Runtime>(
Ok(path.map(|p| p.simplified())) Ok(path.map(|p| p.simplified()))
} }
#[command]
pub fn stop_accessing_path(_p: StopAccessingPath) -> bool {
true
}
fn message_dialog<R: Runtime>( fn message_dialog<R: Runtime>(
#[allow(unused_variables)] window: Window<R>, #[allow(unused_variables)] window: Window<R>,
dialog: State<'_, Dialog<R>>, dialog: State<'_, Dialog<R>>,

@ -9,7 +9,7 @@
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
)] )]
use serde::Serialize; use serde::{Deserialize, Serialize};
use tauri::{ use tauri::{
plugin::{Builder, TauriPlugin}, plugin::{Builder, TauriPlugin},
Manager, Runtime, Manager, Runtime,
@ -180,6 +180,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
commands::open, commands::open,
commands::save, commands::save,
commands::stop_accessing_path,
commands::message, commands::message,
commands::ask, commands::ask,
commands::confirm commands::confirm
@ -337,6 +338,11 @@ pub(crate) struct FileDialogPayload<'a> {
multiple: bool, multiple: bool,
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct StopAccessingPath {
path: FilePath,
}
// raw window handle :( // raw window handle :(
unsafe impl<R: Runtime> Send for FileDialogBuilder<R> {} unsafe impl<R: Runtime> Send for FileDialogBuilder<R> {}
@ -566,6 +572,10 @@ impl<R: Runtime> FileDialogBuilder<R> {
pub fn save_file<F: FnOnce(Option<FilePath>) + Send + 'static>(self, f: F) { pub fn save_file<F: FnOnce(Option<FilePath>) + Send + 'static>(self, f: F) {
save_file(self, f) save_file(self, f)
} }
pub fn stop_accessing_path(self, p: StopAccessingPath) -> bool {
stop_accessing_path(self, p)
}
} }
/// Blocking APIs. /// Blocking APIs.
@ -678,4 +688,8 @@ impl<R: Runtime> FileDialogBuilder<R> {
pub fn blocking_save_file(self) -> Option<FilePath> { pub fn blocking_save_file(self) -> Option<FilePath> {
blocking_fn!(self, save_file) blocking_fn!(self, save_file)
} }
pub fn blocking_stop_accessing_path(self, p: StopAccessingPath) -> bool {
self.stop_accessing_path(p)
}
} }

@ -8,7 +8,7 @@ use tauri::{
AppHandle, Runtime, AppHandle, Runtime,
}; };
use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder}; use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder, StopAccessingPath};
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog"; const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog";
@ -105,6 +105,23 @@ pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
}); });
} }
#[allow(unused_variables)]
pub fn stop_accessing_path<R: Runtime>(dialog: FileDialogBuilder<R>, p: StopAccessingPath) -> bool {
#[cfg(target_os = "ios")]
{
let res = dialog
.dialog
.0
.run_mobile_plugin::<()>("stopAccessingPath", p);
if let Err(_) = res {
return false
}
}
true
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct ShowMessageDialogResponse { struct ShowMessageDialogResponse {
#[allow(dead_code)] #[allow(dead_code)]

Loading…
Cancel
Save