From 6e1d1167cea4ef20a5b350b2294e1584b31d5791 Mon Sep 17 00:00:00 2001 From: ayang <473033518@qq.com> Date: Mon, 25 Nov 2024 22:45:03 +0800 Subject: [PATCH 1/8] feat(fs): add the `size` method to get the size of a file or directory. --- plugins/fs/build.rs | 1 + plugins/fs/guest-js/index.ts | 25 +++++++++- .../autogenerated/commands/size.toml | 13 +++++ .../fs/permissions/autogenerated/reference.md | 26 ++++++++++ plugins/fs/permissions/read-meta.toml | 2 +- plugins/fs/permissions/schemas/schema.json | 10 ++++ plugins/fs/src/commands.rs | 50 +++++++++++++++++++ 7 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 plugins/fs/permissions/autogenerated/commands/size.toml diff --git a/plugins/fs/build.rs b/plugins/fs/build.rs index e45af528c..3909b1c0a 100644 --- a/plugins/fs/build.rs +++ b/plugins/fs/build.rs @@ -102,6 +102,7 @@ const COMMANDS: &[(&str, &[&str])] = &[ ("exists", &[]), ("watch", &[]), ("unwatch", &[]), + ("size", &[]), ]; fn main() { diff --git a/plugins/fs/guest-js/index.ts b/plugins/fs/guest-js/index.ts index 78a5d5fec..8af720a47 100644 --- a/plugins/fs/guest-js/index.ts +++ b/plugins/fs/guest-js/index.ts @@ -1322,6 +1322,28 @@ async function watchImmediate( } } +/** + * Get the size of a file or directory. + * @example + * ```typescript + * import { size, BaseDirectory } from '@tauri-apps/plugin-fs'; + * // Get the size of the `$APPDATA/tauri` directory. + * const dirSize = await size('tauri', { baseDir: BaseDirectory.AppData }); + * console.log(dirSize); // 1024 + * ``` + * + * @since 2.0.0 + */ +async function size(path: string | URL): Promise { + if (path instanceof URL && path.protocol !== 'file:') { + throw new TypeError('Must be a file URL.') + } + + return await invoke('plugin:fs|size', { + path: path instanceof URL ? path.toString() : path, + }) +} + export type { CreateOptions, OpenOptions, @@ -1369,5 +1391,6 @@ export { writeTextFile, exists, watch, - watchImmediate + watchImmediate, + size } diff --git a/plugins/fs/permissions/autogenerated/commands/size.toml b/plugins/fs/permissions/autogenerated/commands/size.toml new file mode 100644 index 000000000..8a0ea55cc --- /dev/null +++ b/plugins/fs/permissions/autogenerated/commands/size.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-size" +description = "Enables the size command without any pre-configured scope." +commands.allow = ["size"] + +[[permission]] +identifier = "deny-size" +description = "Denies the size command without any pre-configured scope." +commands.deny = ["size"] diff --git a/plugins/fs/permissions/autogenerated/reference.md b/plugins/fs/permissions/autogenerated/reference.md index 05d14475b..3cec32ed8 100644 --- a/plugins/fs/permissions/autogenerated/reference.md +++ b/plugins/fs/permissions/autogenerated/reference.md @@ -3410,6 +3410,32 @@ Denies the seek command without any pre-configured scope. +`fs:allow-size` + + + + +Enables the size command without any pre-configured scope. + + + + + + + +`fs:deny-size` + + + + +Denies the size command without any pre-configured scope. + + + + + + + `fs:allow-stat` diff --git a/plugins/fs/permissions/read-meta.toml b/plugins/fs/permissions/read-meta.toml index 09c731ad6..83024b0c2 100644 --- a/plugins/fs/permissions/read-meta.toml +++ b/plugins/fs/permissions/read-meta.toml @@ -3,4 +3,4 @@ [[permission]] identifier = "read-meta" description = "This enables all index or metadata related commands without any pre-configured accessible paths." -commands.allow = ["read_dir", "stat", "lstat", "fstat", "exists"] +commands.allow = ["read_dir", "stat", "lstat", "fstat", "exists", "size"] diff --git a/plugins/fs/permissions/schemas/schema.json b/plugins/fs/permissions/schemas/schema.json index 275e44d19..2c13d5c61 100644 --- a/plugins/fs/permissions/schemas/schema.json +++ b/plugins/fs/permissions/schemas/schema.json @@ -1589,6 +1589,16 @@ "type": "string", "const": "deny-seek" }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "allow-size" + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "deny-size" + }, { "description": "Enables the stat command without any pre-configured scope.", "type": "string", diff --git a/plugins/fs/src/commands.rs b/plugins/fs/src/commands.rs index 85c866c7d..2088236f8 100644 --- a/plugins/fs/src/commands.rs +++ b/plugins/fs/src/commands.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +use anyhow::Ok; use serde::{Deserialize, Serialize, Serializer}; use serde_repr::{Deserialize_repr, Serialize_repr}; use tauri::{ @@ -897,6 +898,55 @@ pub fn exists( Ok(resolved_path.exists()) } +#[tauri::command] +pub async fn size( + webview: Webview, + global_scope: GlobalScope, + command_scope: CommandScope, + path: SafeFilePath, + options: Option, +) -> CommandResult { + let resolved_path = resolve_path( + &webview, + &global_scope, + &command_scope, + path, + options.as_ref().and_then(|o| o.base_dir), + )?; + + let metadata = resolved_path.metadata()?; + + if metadata.is_file() { + Ok(metadata.len()) + } else { + let size = get_dir_size(resolved_path).map_err(|e| { + format!( + "failed to get size at path: {} with error: {e}", + resolved_path.display() + ) + })?; + + Ok(size) + } +} + +fn get_dir_size(path: PathBuf) -> CommandResult { + let mut size = 0; + + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let metadata = entry.metadata()?; + + if metadata.is_file() { + size += metadata.len(); + } else if metadata.is_dir() { + size += get_dir_size(entry.path())?; + } + } + + Ok(size) +} + #[cfg(not(target_os = "android"))] pub fn resolve_file( webview: &Webview, From a3c049c4e27df5ba2fe117785a1afa0cd8defcc1 Mon Sep 17 00:00:00 2001 From: ayang <473033518@qq.com> Date: Tue, 26 Nov 2024 10:48:32 +0800 Subject: [PATCH 2/8] refactor: some problems are optimized --- plugins/fs/guest-js/index.ts | 6 ++++-- plugins/fs/src/commands.rs | 7 +++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/fs/guest-js/index.ts b/plugins/fs/guest-js/index.ts index 8af720a47..71f71578b 100644 --- a/plugins/fs/guest-js/index.ts +++ b/plugins/fs/guest-js/index.ts @@ -1323,7 +1323,9 @@ async function watchImmediate( } /** - * Get the size of a file or directory. + * Get the size of a file or directory. For files, the `stat` functions can be used as well. + * + * If `path` is a directory, this function will recursively iterate over every file and every directory inside of `path` and therefore will be very time consuming if used on larger directories. * @example * ```typescript * import { size, BaseDirectory } from '@tauri-apps/plugin-fs'; @@ -1332,7 +1334,7 @@ async function watchImmediate( * console.log(dirSize); // 1024 * ``` * - * @since 2.0.0 + * @since 2.1.0 */ async function size(path: string | URL): Promise { if (path instanceof URL && path.protocol !== 'file:') { diff --git a/plugins/fs/src/commands.rs b/plugins/fs/src/commands.rs index 2088236f8..29d1104f1 100644 --- a/plugins/fs/src/commands.rs +++ b/plugins/fs/src/commands.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -use anyhow::Ok; use serde::{Deserialize, Serialize, Serializer}; use serde_repr::{Deserialize_repr, Serialize_repr}; use tauri::{ @@ -919,7 +918,7 @@ pub async fn size( if metadata.is_file() { Ok(metadata.len()) } else { - let size = get_dir_size(resolved_path).map_err(|e| { + let size = get_dir_size(&resolved_path).map_err(|e| { format!( "failed to get size at path: {} with error: {e}", resolved_path.display() @@ -930,7 +929,7 @@ pub async fn size( } } -fn get_dir_size(path: PathBuf) -> CommandResult { +fn get_dir_size(path: &PathBuf) -> CommandResult { let mut size = 0; for entry in std::fs::read_dir(path)? { @@ -940,7 +939,7 @@ fn get_dir_size(path: PathBuf) -> CommandResult { if metadata.is_file() { size += metadata.len(); } else if metadata.is_dir() { - size += get_dir_size(entry.path())?; + size += get_dir_size(&entry.path())?; } } From edabe7da2b4ebc870de63884b0406010037b818e Mon Sep 17 00:00:00 2001 From: ayang <473033518@qq.com> Date: Tue, 26 Nov 2024 10:52:48 +0800 Subject: [PATCH 3/8] chore: add changelog --- .changes/add-fs-size.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/add-fs-size.md diff --git a/.changes/add-fs-size.md b/.changes/add-fs-size.md new file mode 100644 index 000000000..1637e972b --- /dev/null +++ b/.changes/add-fs-size.md @@ -0,0 +1,6 @@ +--- +fs: minor +persisted-scope: minor +--- + +**Breaking Change:** add the `size` method to get the size of a file or directory. From 8cbf5661093591af82bff490a035f1f41c45a727 Mon Sep 17 00:00:00 2001 From: ayang <473033518@qq.com> Date: Tue, 26 Nov 2024 16:24:29 +0800 Subject: [PATCH 4/8] chore: formatting code with prettier --- plugins/fs/guest-js/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/fs/guest-js/index.ts b/plugins/fs/guest-js/index.ts index 71f71578b..897bc7809 100644 --- a/plugins/fs/guest-js/index.ts +++ b/plugins/fs/guest-js/index.ts @@ -1324,7 +1324,7 @@ async function watchImmediate( /** * Get the size of a file or directory. For files, the `stat` functions can be used as well. - * + * * If `path` is a directory, this function will recursively iterate over every file and every directory inside of `path` and therefore will be very time consuming if used on larger directories. * @example * ```typescript @@ -1342,7 +1342,7 @@ async function size(path: string | URL): Promise { } return await invoke('plugin:fs|size', { - path: path instanceof URL ? path.toString() : path, + path: path instanceof URL ? path.toString() : path }) } From b84a823722c9aa6a444a5e490862730aef90d7c4 Mon Sep 17 00:00:00 2001 From: ayang <473033518@qq.com> Date: Tue, 26 Nov 2024 17:08:29 +0800 Subject: [PATCH 5/8] chore: update fs-size changelog --- .changes/add-fs-size.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.changes/add-fs-size.md b/.changes/add-fs-size.md index 1637e972b..adf6f4fab 100644 --- a/.changes/add-fs-size.md +++ b/.changes/add-fs-size.md @@ -1,6 +1,5 @@ --- fs: minor -persisted-scope: minor --- -**Breaking Change:** add the `size` method to get the size of a file or directory. +Add the `size` method to get the size of a file or directory. From c1ccab398b8df7e7f5b8257dd22780e6de25e41d Mon Sep 17 00:00:00 2001 From: Fabian-Lars Date: Tue, 26 Nov 2024 11:52:46 +0100 Subject: [PATCH 6/8] Update plugins/fs/guest-js/index.ts --- plugins/fs/guest-js/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/fs/guest-js/index.ts b/plugins/fs/guest-js/index.ts index 897bc7809..448335c42 100644 --- a/plugins/fs/guest-js/index.ts +++ b/plugins/fs/guest-js/index.ts @@ -1326,6 +1326,7 @@ async function watchImmediate( * Get the size of a file or directory. For files, the `stat` functions can be used as well. * * If `path` is a directory, this function will recursively iterate over every file and every directory inside of `path` and therefore will be very time consuming if used on larger directories. + * * @example * ```typescript * import { size, BaseDirectory } from '@tauri-apps/plugin-fs'; From c07a61fc90f2e541f23ca84ad7a388ad2843f183 Mon Sep 17 00:00:00 2001 From: ayang <473033518@qq.com> Date: Tue, 26 Nov 2024 19:36:54 +0800 Subject: [PATCH 7/8] feat: add `size` to invoke_handler --- plugins/fs/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/fs/src/lib.rs b/plugins/fs/src/lib.rs index 3240ccb3b..731c7040a 100644 --- a/plugins/fs/src/lib.rs +++ b/plugins/fs/src/lib.rs @@ -417,6 +417,7 @@ pub fn init() -> TauriPlugin> { commands::write_file, commands::write_text_file, commands::exists, + commands::size, #[cfg(feature = "watch")] watcher::watch, #[cfg(feature = "watch")] From 7709f2bcec91f4efd2d0fc0d7f671716064262fe Mon Sep 17 00:00:00 2001 From: ayang <473033518@qq.com> Date: Tue, 26 Nov 2024 19:42:34 +0800 Subject: [PATCH 8/8] chore: execute pnpm build --- plugins/fs/api-iife.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/fs/api-iife.js b/plugins/fs/api-iife.js index d1032cd22..c7f53b9ba 100644 --- a/plugins/fs/api-iife.js +++ b/plugins/fs/api-iife.js @@ -1 +1 @@ -if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e?t!==e||!o:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,r,a;"function"==typeof SuppressedError&&SuppressedError;const s="__TAURI_TO_IPC_KEY__";class c{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),o.set(this,0),r.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:a})=>{if(a===e(this,o,"f")){n(this,o,a+1),e(this,i,"f").call(this,t);const s=Object.keys(e(this,r,"f"));if(s.length>0){let t=a+1;for(const n of s.sort()){if(parseInt(n)!==t)break;{const o=e(this,r,"f")[n];delete e(this,r,"f")[n],e(this,i,"f").call(this,o),t+=1}}n(this,o,t)}}else e(this,r,"f")[a.toString()]=t}))}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,s)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[s]()}}async function f(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class l{get rid(){return e(this,a,"f")}constructor(t){a.set(this,void 0),n(this,a,t)}async close(){return f("plugin:resources|close",{rid:this.rid})}}var u,p;function w(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}a=new WeakMap,t.BaseDirectory=void 0,(u=t.BaseDirectory||(t.BaseDirectory={}))[u.Audio=1]="Audio",u[u.Cache=2]="Cache",u[u.Config=3]="Config",u[u.Data=4]="Data",u[u.LocalData=5]="LocalData",u[u.Document=6]="Document",u[u.Download=7]="Download",u[u.Picture=8]="Picture",u[u.Public=9]="Public",u[u.Video=10]="Video",u[u.Resource=11]="Resource",u[u.Temp=12]="Temp",u[u.AppConfig=13]="AppConfig",u[u.AppData=14]="AppData",u[u.AppLocalData=15]="AppLocalData",u[u.AppCache=16]="AppCache",u[u.AppLog=17]="AppLog",u[u.Desktop=18]="Desktop",u[u.Executable=19]="Executable",u[u.Font=20]="Font",u[u.Home=21]="Home",u[u.Runtime=22]="Runtime",u[u.Template=23]="Template",t.SeekMode=void 0,(p=t.SeekMode||(t.SeekMode={}))[p.Start=0]="Start",p[p.Current=1]="Current",p[p.End=2]="End";class d extends l{async read(t){if(0===t.byteLength)return 0;const e=await f("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;tt instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{y(a)}},t.watchImmediate=async function(t,e,n){const i={recursive:!1,...n,delayMs:null},o=Array.isArray(t)?t:[t];for(const t of o)if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const r=new c;r.onmessage=e;const a=await f("plugin:fs|watch",{paths:o.map((t=>t instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{y(a)}},t.writeFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");if(e instanceof ReadableStream){const i=await h(t,n);for await(const t of e)await i.write(t);await i.close()}else await f("plugin:fs|write_file",e,{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t.writeTextFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const i=new TextEncoder;await f("plugin:fs|write_text_file",i.encode(e),{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t}({});Object.defineProperty(window.__TAURI__,"fs",{value:__TAURI_PLUGIN_FS__})} +if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e?t!==e||!o:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,r,a;"function"==typeof SuppressedError&&SuppressedError;const s="__TAURI_TO_IPC_KEY__";class c{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),o.set(this,0),r.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:a})=>{if(a===e(this,o,"f")){n(this,o,a+1),e(this,i,"f").call(this,t);const s=Object.keys(e(this,r,"f"));if(s.length>0){let t=a+1;for(const n of s.sort()){if(parseInt(n)!==t)break;{const o=e(this,r,"f")[n];delete e(this,r,"f")[n],e(this,i,"f").call(this,o),t+=1}}n(this,o,t)}}else e(this,r,"f")[a.toString()]=t}))}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,s)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[s]()}}async function f(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class l{get rid(){return e(this,a,"f")}constructor(t){a.set(this,void 0),n(this,a,t)}async close(){return f("plugin:resources|close",{rid:this.rid})}}var u,p;function w(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}a=new WeakMap,t.BaseDirectory=void 0,(u=t.BaseDirectory||(t.BaseDirectory={}))[u.Audio=1]="Audio",u[u.Cache=2]="Cache",u[u.Config=3]="Config",u[u.Data=4]="Data",u[u.LocalData=5]="LocalData",u[u.Document=6]="Document",u[u.Download=7]="Download",u[u.Picture=8]="Picture",u[u.Public=9]="Public",u[u.Video=10]="Video",u[u.Resource=11]="Resource",u[u.Temp=12]="Temp",u[u.AppConfig=13]="AppConfig",u[u.AppData=14]="AppData",u[u.AppLocalData=15]="AppLocalData",u[u.AppCache=16]="AppCache",u[u.AppLog=17]="AppLog",u[u.Desktop=18]="Desktop",u[u.Executable=19]="Executable",u[u.Font=20]="Font",u[u.Home=21]="Home",u[u.Runtime=22]="Runtime",u[u.Template=23]="Template",t.SeekMode=void 0,(p=t.SeekMode||(t.SeekMode={}))[p.Start=0]="Start",p[p.Current=1]="Current",p[p.End=2]="End";class d extends l{async read(t){if(0===t.byteLength)return 0;const e=await f("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;tt instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{y(a)}},t.watchImmediate=async function(t,e,n){const i={recursive:!1,...n,delayMs:null},o=Array.isArray(t)?t:[t];for(const t of o)if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const r=new c;r.onmessage=e;const a=await f("plugin:fs|watch",{paths:o.map((t=>t instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{y(a)}},t.writeFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");if(e instanceof ReadableStream){const i=await h(t,n);for await(const t of e)await i.write(t);await i.close()}else await f("plugin:fs|write_file",e,{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t.writeTextFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const i=new TextEncoder;await f("plugin:fs|write_text_file",i.encode(e),{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t}({});Object.defineProperty(window.__TAURI__,"fs",{value:__TAURI_PLUGIN_FS__})}