9 File System
8 分钟阅读
访问文件系统。
如果你想通过 Rust 操作文件/目录,请使用传统 Rust 库(std::fs、tokio::fs 等)。
支持的平台
| 平台 | 支持程度 | 说明 |
|---|---|---|
| Windows | 完整支持 | |
| Linux | 完整支持 | |
| macOS | 完整支持 | |
| Android | 部分支持 | |
| iOS | 部分支持 |
设置
安装 fs 插件即可开始。
安装方式
自动
使用你的项目包管理器添加依赖:
| |
| |
| |
| |
| |
| |
手动
在
src-tauri文件夹中运行以下命令,把插件加入Cargo.toml里的项目依赖:1cargo add tauri-plugin-fs修改
lib.rs初始化插件:1 2 3 4 5 6 7#[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_fs::init()) .run(tauri::generate_context!()) .expect("error while running tauri application"); }用你偏好的 JavaScript 包管理器安装 JavaScript 端绑定:
| |
| |
| |
| |
| |
配置
Android
使用 audio、cache、documents、downloads、picture、public 或 video 目录时,你的应用必须能访问外部存储。
把以下权限加入 gen/android/app/src/main/AndroidManifest.xml 文件中的 manifest 标签:
| |
iOS
Apple 要求应用开发者说明 API 使用的已批准理由,以增强用户隐私。
你必须在 src-tauri/gen/apple 文件夹中创建 PrivacyInfo.xcprivacy 文件,
其中包含所需的 NSPrivacyAccessedAPICategoryFileTimestamp 键以及 C617.1 推荐理由。
| |
用法
fs 插件在 JavaScript 和 Rust 中都可以使用。
虽然该插件在前端提供文件操作 API,但在后端它只提供修改某些资源(文件、目录等)权限的方法。
语言
| |
| |
安全
该模块防止路径穿越,不允许使用父目录访问符 (即不允许 “/usr/path/to/../file” 或 “../path/to/file” 这类路径)。 通过该 API 访问的路径必须相对于某个基础目录,或用路径 API创建。
更多信息请参阅 @tauri-apps/plugin-fs - Security。
路径
文件系统插件提供两种操作路径的方式:基础目录和路径 API。
基础目录
每个 API 都有一个 options 参数,让你定义作为该操作工作目录的 baseDir。
1 2 3 4import { readFile } from '@tauri-apps/plugin-fs'; const contents = await readFile('avatars/tauri.png', { baseDir: BaseDirectory.Home, });在上面的示例中,因为使用了 Home 基础目录,所以读取的是 ~/avatars/tauri.png 文件。
路径 API
或者你也可以使用路径 API 进行路径操作。
1 2 3 4import { readFile } from '@tauri-apps/plugin-fs'; import * as path from '@tauri-apps/api/path'; const home = await path.homeDir(); const contents = await readFile(await path.join(home, 'avatars/tauri.png'));
文件
创建
创建一个文件并返回它的句柄。如果文件已存在,它会被截断。
| |
操作完文件后请始终调用 file.close()。
写入
出于性能考虑,该插件为文本文件和二进制文件分别提供了 API。
文本文件
1 2 3 4 5import { writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'; const contents = JSON.stringify({ notifications: true }); await writeTextFile('config.json', contents, { baseDir: BaseDirectory.AppConfig, });二进制文件
1 2 3 4 5import { writeFile, BaseDirectory } from '@tauri-apps/plugin-fs'; const contents = new Uint8Array(); // 填充一个字节数组 await writeFile('config', contents, { baseDir: BaseDirectory.AppConfig, });
打开
打开一个文件并返回它的句柄。 通过这个 API,你可以更好地控制文件的打开方式 (只读模式、只写模式、追加而非覆盖、仅在文件不存在时创建等)。
操作完文件后请始终调用 file.close()。
只读
这是默认模式。
1 2 3 4 5 6 7 8 9 10 11import { open, BaseDirectory } from '@tauri-apps/plugin-fs'; const file = await open('foo/bar.txt', { read: true, baseDir: BaseDirectory.AppData, }); const stat = await file.stat(); const buf = new Uint8Array(stat.size); await file.read(buf); const textContents = new TextDecoder().decode(buf); await file.close();只写
1 2 3 4 5 6 7import { open, BaseDirectory } from '@tauri-apps/plugin-fs'; const file = await open('foo/bar.txt', { write: true, baseDir: BaseDirectory.AppData, }); await file.write(new TextEncoder().encode('Hello world')); await file.close();默认情况下,任何
file.write()调用都会截断文件。 要了解如何改为追加到已有内容之后,请看下面的示例。追加
1 2 3 4 5 6 7import { open, BaseDirectory } from '@tauri-apps/plugin-fs'; const file = await open('foo/bar.txt', { append: true, baseDir: BaseDirectory.AppData, }); await file.write(new TextEncoder().encode('world')); await file.close();注意
{ append: true }与{ write: true, append: true }效果相同。截断
当设置了
truncate选项且文件已存在时,它会被截断为长度 0。1 2 3 4 5 6 7 8import { open, BaseDirectory } from '@tauri-apps/plugin-fs'; const file = await open('foo/bar.txt', { write: true, truncate: true, baseDir: BaseDirectory.AppData, }); await file.write(new TextEncoder().encode('world')); await file.close();该选项要求
write为true。如果你想通过多次
file.write()调用重写一个已有文件,可以将它与append选项一起使用。create
默认情况下,
openAPI 只打开已存在的文件。要在文件不存在时创建它、存在时打开它, 请把create设为true:1 2 3 4 5 6 7 8import { open, BaseDirectory } from '@tauri-apps/plugin-fs'; const file = await open('foo/bar.txt', { write: true, create: true, baseDir: BaseDirectory.AppData, }); await file.write(new TextEncoder().encode('world')); await file.close();为了让文件被创建,
write或append也必须设为true。要在文件已存在时报错,请参阅
createNew。createNew
createNew与create类似,但如果文件已存在则会失败。1 2 3 4 5 6 7 8import { open, BaseDirectory } from '@tauri-apps/plugin-fs'; const file = await open('foo/bar.txt', { write: true, createNew: true, baseDir: BaseDirectory.AppData, }); await file.write(new TextEncoder().encode('world')); await file.close();为了让文件被创建,
write也必须设为true。
读取
出于性能考虑,该插件为读取文本文件和二进制文件分别提供了 API。
文本文件
1 2 3 4import { readTextFile, BaseDirectory } from '@tauri-apps/plugin-fs'; const configToml = await readTextFile('config.toml', { baseDir: BaseDirectory.AppConfig, });如果文件很大,你可以用
readTextFileLinesAPI 流式读取它的各行:1 2 3 4 5 6 7import { readTextFileLines, BaseDirectory } from '@tauri-apps/plugin-fs'; const lines = await readTextFileLines('app.logs', { baseDir: BaseDirectory.AppLog, }); for await (const line of lines) { console.log(line); }二进制文件
1 2 3 4import { readFile, BaseDirectory } from '@tauri-apps/plugin-fs'; const icon = await readFile('icon.png', { baseDir: BaseDirectory.Resources, });
删除
调用 remove() 删除文件。如果文件不存在,会返回错误。
| |
复制
copyFile 函数接收源路径和目标路径。
注意你必须分别配置各自的基础目录。
| |
在上面的示例中,<app-local-data>/user.db 文件会被复制到 $TMPDIR/user.db.bk。
是否存在
使用 exists() 函数检查文件是否存在:
| |
元数据
文件元数据可以用 stat 和 lstat 函数获取。
stat 会跟随符号链接(如果它指向的真实文件不在作用域允许范围内,则返回错误),
而 lstat 不跟随符号链接,返回符号链接自身的信息。
| |
重命名
rename 函数接收源路径和目标路径。
注意你必须分别配置各自的基础目录。
| |
在上面的示例中,<app-local-data>/user.db.bk 文件会被重命名为 $TMPDIR/user.db。
截断
把指定文件截断或扩展到指定长度(默认 0)。
- 截断到 0 长度
| |
- 截断到指定长度
| |
目录
创建
要创建目录,请调用 mkdir 函数:
| |
读取
readDir 函数递归列出目录的条目:
| |
删除
调用 remove() 删除目录。如果目录不存在,会返回错误。
| |
如果目录非空,必须把 recursive 选项设为 true:
| |
是否存在
使用 exists() 函数检查目录是否存在:
| |
元数据
目录元数据可以用 stat 和 lstat 函数获取。
stat 会跟随符号链接(如果它指向的真实文件不在作用域允许范围内,则返回错误),
而 lstat 不跟随符号链接,返回符号链接自身的信息。
| |
监听变化
要监听目录或文件的变化,请使用 watch 或 watchImmediate 函数。
watch
watch带防抖,因此只会在一定延迟后才发出事件:1 2 3 4 5 6 7 8 9 10 11import { watch, BaseDirectory } from '@tauri-apps/plugin-fs'; await watch( 'app.log', (event) => { console.log('app.log event', event); }, { baseDir: BaseDirectory.AppLog, delayMs: 500, } );watchImmediate
watchImmediate会立即通知监听器:1 2 3 4 5 6 7 8 9 10 11import { watchImmediate, BaseDirectory } from '@tauri-apps/plugin-fs'; await watchImmediate( 'logs', (event) => { console.log('logs directory event', event); }, { baseDir: BaseDirectory.AppLog, recursive: true, } );
默认情况下,对目录的监听操作不是递归的。
把 recursive 选项设为 true,即可递归监听所有子目录的变化。
监听函数需要 watch 特性标志:
| |
权限
默认情况下,所有有潜在危险的插件命令和作用域都被阻止,无法访问。你必须在 capabilities 配置中修改权限才能启用它们。
仅启用 fs:allow-exists 这样的权限并不会允许访问任何路径。大多数 fs 命令还需要一个作用域,明确列出该命令可以访问哪些路径。没有 allow 作用域时,调用会在运行时以 forbidden path 错误失败,即使权限已启用。
| |
| |
allow 和 deny 中可用的全部路径变量见下面的作用域。
| |
作用域
该插件的权限包含用于定义哪些路径被允许或明确拒绝的作用域。 关于作用域的更多信息,请参阅命令作用域。
每个 allow 或 deny 作用域都必须包含一个数组,列出应被允许或拒绝的所有路径。
作用域条目的格式为 { path: string }。
deny 优先于 allow,因此如果某个路径被某个作用域拒绝,即使另一个作用域允许它,运行时也会被阻止。
作用域条目可以使用 $<path> 变量来引用常见系统路径,例如主目录、应用资源目录和配置目录。下表列出了你可以引用的所有常见路径:
| 路径 | 变量 |
|---|---|
| appConfigDir | $APPCONFIG |
| appDataDir | $APPDATA |
| appLocalDataDir | $APPLOCALDATA |
| appcacheDir | $APPCACHE |
| applogDir | $APPLOG |
| audioDir | $AUDIO |
| cacheDir | $CACHE |
| configDir | $CONFIG |
| dataDir | $DATA |
| localDataDir | $LOCALDATA |
| desktopDir | $DESKTOP |
| documentDir | $DOCUMENT |
| downloadDir | $DOWNLOAD |
| executableDir | $EXE |
| fontDir | $FONT |
| homeDir | $HOME |
| pictureDir | $PICTURE |
| publicDir | $PUBLIC |
| runtimeDir | $RUNTIME |
| templateDir | $TEMPLATE |
| videoDir | $VIDEO |
| resourceDir | $RESOURCE |
| tempDir | $TEMP |
示例
- 全局作用域
要把作用域应用到任何 fs 命令,请使用 fs:scope 权限:
| |
要把作用域应用到特定的 fs 命令,
请使用权限的对象形式 { "identifier": string, "allow"?: [], "deny"?: [] }:
| |
在上面的示例中,你可以对 $APPDATA 下的任意子路径(不含子目录)使用 exists API,
以及使用 rename。
如果你想在类 Unix 系统上访问点文件(如 .gitignore)或点目录(如 .ssh),
那么你需要指定完整路径 /home/user/.ssh/example,或在点目录路径组件之后使用 glob /home/user/.ssh/*。
如果在你的用例中这仍然不起作用,你可以配置插件把任意组件都当作合法的路径字面量。
| |
当你使用对象形式(而不是仅数组形式)时,app.security.assetProtocol.scope 也有同样的选项。涉及点目录的真实案例见 tauri#13788。