Electron 开发者的 Tauri 2.0 实战指南:文件系统操作

作为 Electron 开发者,我们习惯了使用 Node.js 的 fs 模块来处理文件操作。在 Tauri 2.0 中,文件系统操作被重新设计,采用了 Rust 的安全特性和权限系统。本文将帮助你理解和重构这部分功能。

文件操作对比

Electron 的文件操作

在 Electron 中,我们通常这样处理文件:

// main.js
const fs = require('fs').promises
const path = require('path')// 读取文件
async function readFile(filePath) {try {const content = await fs.readFile(filePath, 'utf8')return content} catch (error) {console.error('Failed to read file:', error)throw error}
}// 写入文件
async function writeFile(filePath, content) {try {await fs.writeFile(filePath, content, 'utf8')} catch (error) {console.error('Failed to write file:', error)throw error}
}// 列出目录内容
async function listDirectory(dirPath) {try {const files = await fs.readdir(dirPath)return files} catch (error) {console.error('Failed to list directory:', error)throw error}
}

主要特点:

  1. 直接访问文件系统
  2. 无权限限制
  3. 同步/异步操作
  4. 完整的 Node.js API

Tauri 的文件操作

Tauri 采用了更安全的方式:

// main.rs
use std::fs;
use tauri::api::path;
use serde::{Deserialize, Serialize};#[derive(Debug, Serialize)]
struct FileEntry {name: String,path: String,is_file: bool,size: u64,
}#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {fs::read_to_string(path).map_err(|e| e.to_string())
}#[tauri::command]
async fn write_file(path: String, content: String) -> Result<(), String> {fs::write(path, content).map_err(|e| e.to_string())
}#[tauri::command]
async fn list_directory(path: String) -> Result<Vec<FileEntry>, String> {let mut entries = Vec::new();for entry in fs::read_dir(path).map_err(|e| e.to_string())? {let entry = entry.map_err(|e| e.to_string())?;let metadata = entry.metadata().map_err(|e| e.to_string())?;entries.push(FileEntry {name: entry.file_name().to_string_lossy().into_owned(),path: entry.path().to_string_lossy().into_owned(),is_file: metadata.is_file(),size: metadata.len(),});}Ok(entries)
}
// fileSystem.ts
import { invoke } from '@tauri-apps/api/tauri'
import { BaseDirectory, createDir, readDir } from '@tauri-apps/api/fs'interface FileEntry {name: stringpath: stringisFile: booleansize: number
}// 读取文件
export const readFile = async (path: string): Promise<string> => {try {return await invoke('read_file', { path })} catch (error) {console.error('Failed to read file:', error)throw error}
}// 写入文件
export const writeFile = async (path: string, content: string): Promise<void> => {try {await invoke('write_file', { path, content })} catch (error) {console.error('Failed to write file:', error)throw error}
}// 列出目录
export const listDirectory = async (path: string): Promise<FileEntry[]> => {try {return await invoke('list_directory', { path })} catch (error) {console.error('Failed to list directory:', error)throw error}
}

主要特点:

  1. 权限控制
  2. 类型安全
  3. 错误处理
  4. 跨平台兼容

常见文件操作场景

1. 配置文件管理

Electron 实现
// config.js
const fs = require('fs')
const path = require('path')class ConfigManager {constructor() {this.configPath = path.join(app.getPath('userData'), 'config.json')}async load() {try {const data = await fs.promises.readFile(this.configPath, 'utf8')return JSON.parse(data)} catch (error) {return {}}}async save(config) {await fs.promises.writeFile(this.configPath,JSON.stringify(config, null, 2),'utf8')}
}
Tauri 实现
// main.rs
use std::fs;
use tauri::api::path::app_config_dir;
use serde::{Deserialize, Serialize};#[derive(Debug, Serialize, Deserialize)]
struct Config {theme: String,language: String,
}#[tauri::command]
async fn load_config(app: tauri::AppHandle) -> Result<Config, String> {let config_dir = app_config_dir(&app.config()).ok_or("Failed to get config directory")?;let config_path = config_dir.join("config.json");match fs::read_to_string(config_path) {Ok(data) => serde_json::from_str(&data).map_err(|e| e.to_string()),Err(_) => Ok(Config {theme: "light".into(),language: "en".into(),})}
}#[tauri::command]
async fn save_config(app: tauri::AppHandle,config: Config
) -> Result<(), String> {let config_dir = app_config_dir(&app.config()).ok_or("Failed to get config directory")?;fs::create_dir_all(&config_dir).map_err(|e| e.to_string())?;let config_path = config_dir.join("config.json");let data = serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?;fs::write(config_path, data).map_err(|e| e.to_string())
}

2. 文件监听

Electron 实现
const { watch } = require('fs')const watcher = watch('/path/to/watch', (eventType, filename) => {console.log(`File ${filename} changed: ${eventType}`)
})// 清理
watcher.close()
Tauri 实现
use notify::{Watcher, RecursiveMode, Result as NotifyResult};
use std::sync::mpsc::channel;
use std::time::Duration;#[tauri::command]
async fn watch_directory(window: tauri::Window,path: String
) -> Result<(), String> {let (tx, rx) = channel();let mut watcher = notify::recommended_watcher(move |res: NotifyResult<notify::Event>| {if let Ok(event) = res {let _ = tx.send(event);}}).map_err(|e| e.to_string())?;watcher.watch(path.as_ref(),RecursiveMode::Recursive).map_err(|e| e.to_string())?;tauri::async_runtime::spawn(async move {while let Ok(event) = rx.recv() {let _ = window.emit("file-change", event);}});Ok(())
}
// fileWatcher.ts
import { listen } from '@tauri-apps/api/event'export const setupFileWatcher = async (path: string) => {try {await invoke('watch_directory', { path })const unlisten = await listen('file-change', (event) => {console.log('File changed:', event)})return unlisten} catch (error) {console.error('Failed to setup file watcher:', error)throw error}
}

3. 拖放文件处理

Electron 实现
// renderer.js
document.addEventListener('drop', (e) => {e.preventDefault()e.stopPropagation()for (const file of e.dataTransfer.files) {console.log('File path:', file.path)}
})document.addEventListener('dragover', (e) => {e.preventDefault()e.stopPropagation()
})
Tauri 实现
// main.rs
#[tauri::command]
async fn handle_file_drop(paths: Vec<String>
) -> Result<(), String> {for path in paths {println!("Dropped file: {}", path);}Ok(())
}
// App.tsx
import { listen } from '@tauri-apps/api/event'useEffect(() => {const setupDropZone = async () => {await listen('tauri://file-drop', async (event: any) => {const paths = event.payload as string[]await invoke('handle_file_drop', { paths })})}setupDropZone()
}, [])

实战案例:文件加密管理器

让我们通过一个实际的案例来综合运用这些文件操作:

// main.rs
use chacha20poly1305::{aead::{Aead, KeyInit},ChaCha20Poly1305, Nonce
};
use rand::Rng;
use serde::{Deserialize, Serialize};
use std::fs;#[derive(Debug, Serialize, Deserialize)]
struct EncryptedFile {path: String,nonce: Vec<u8>,data: Vec<u8>,
}#[tauri::command]
async fn encrypt_file(path: String,password: String
) -> Result<(), String> {// 读取文件let content = fs::read(&path).map_err(|e| e.to_string())?;// 生成密钥let key = derive_key(password);let cipher = ChaCha20Poly1305::new(&key.into());// 生成随机 noncelet mut rng = rand::thread_rng();let nonce = Nonce::from_slice(&rng.gen::<[u8; 12]>());// 加密数据let encrypted_data = cipher.encrypt(nonce, content.as_ref()).map_err(|e| e.to_string())?;// 保存加密文件let encrypted_file = EncryptedFile {path: path.clone(),nonce: nonce.to_vec(),data: encrypted_data,};let encrypted_path = format!("{}.encrypted", path);let json = serde_json::to_string(&encrypted_file).map_err(|e| e.to_string())?;fs::write(encrypted_path, json).map_err(|e| e.to_string())?;// 删除原文件fs::remove_file(path).map_err(|e| e.to_string())?;Ok(())
}#[tauri::command]
async fn decrypt_file(path: String,password: String
) -> Result<(), String> {// 读取加密文件let json = fs::read_to_string(&path).map_err(|e| e.to_string())?;let encrypted_file: EncryptedFile = serde_json::from_str(&json).map_err(|e| e.to_string())?;// 生成密钥let key = derive_key(password);let cipher = ChaCha20Poly1305::new(&key.into());// 解密数据let nonce = Nonce::from_slice(&encrypted_file.nonce);let decrypted_data = cipher.decrypt(nonce, encrypted_file.data.as_ref()).map_err(|e| e.to_string())?;// 保存解密文件fs::write(&encrypted_file.path, decrypted_data).map_err(|e| e.to_string())?;// 删除加密文件fs::remove_file(path).map_err(|e| e.to_string())?;Ok(())
}fn derive_key(password: String) -> [u8; 32] {use sha2::{Sha256, Digest};let mut hasher = Sha256::new();hasher.update(password.as_bytes());hasher.finalize().into()
}fn main() {tauri::Builder::default().invoke_handler(tauri::generate_handler![encrypt_file,decrypt_file]).run(tauri::generate_context!()).expect("error while running tauri application");
}
// App.tsx
import { useState, useEffect } from 'react'
import { invoke } from '@tauri-apps/api/tauri'
import { listen } from '@tauri-apps/api/event'function App() {const [password, setPassword] = useState('')const [status, setStatus] = useState('')useEffect(() => {const setupDropZone = async () => {await listen('tauri://file-drop', async (event: any) => {const [path] = event.payload as string[]if (path.endsWith('.encrypted')) {await handleDecrypt(path)} else {await handleEncrypt(path)}})}setupDropZone()}, [])const handleEncrypt = async (path: string) => {try {await invoke('encrypt_file', {path,password})setStatus(`Encrypted: ${path}`)} catch (error) {setStatus(`Error: ${error}`)}}const handleDecrypt = async (path: string) => {try {await invoke('decrypt_file', {path,password})setStatus(`Decrypted: ${path}`)} catch (error) {setStatus(`Error: ${error}`)}}return (<div className="container"><h1>File Encryption Manager</h1><div className="password-input"><inputtype="password"placeholder="Enter password"value={password}onChange={(e) => setPassword(e.target.value)}/></div><div className="drop-zone"><p>Drop files here to encrypt/decrypt</p><p className="status">{status}</p></div></div>)
}export default App
/* styles.css */
.container {padding: 20px;max-width: 800px;margin: 0 auto;
}.password-input {margin: 20px 0;
}.password-input input {width: 100%;padding: 10px;border: 1px solid #ddd;border-radius: 4px;
}.drop-zone {border: 2px dashed #ddd;border-radius: 8px;padding: 40px;text-align: center;margin-top: 20px;
}.status {margin-top: 20px;color: #666;
}

性能优化建议

  1. 缓冲区操作

    • 使用缓冲读写
    • 分块处理大文件
    • 实现流式传输
  2. 并发处理

    • 使用异步操作
    • 实现并行处理
    • 避免阻塞主线程
  3. 内存管理

    • 及时释放资源
    • 控制内存使用
    • 实现垃圾回收

安全考虑

  1. 路径验证

    • 检查路径合法性
    • 防止路径遍历
    • 限制访问范围
  2. 权限控制

    • 实现最小权限
    • 验证用户权限
    • 记录操作日志
  3. 数据保护

    • 加密敏感数据
    • 安全删除文件
    • 防止数据泄露

调试技巧

  1. 文件操作日志

    use log::{info, error};#[tauri::command]
    async fn debug_file_operation(path: String) -> Result<(), String> {info!("File operation on: {}", path);Ok(())
    }
  2. 错误处理

    fn handle_file_error(error: std::io::Error) -> String {match error.kind() {std::io::ErrorKind::NotFound => "File not found".into(),std::io::ErrorKind::PermissionDenied => "Permission denied".into(),_ => error.to_string()}
    }
  3. 性能监控

    use std::time::Instant;#[tauri::command]
    async fn measure_file_operation(path: String) -> Result<String, String> {let start = Instant::now();// 执行文件操作let duration = start.elapsed();Ok(format!("Operation took: {:?}", duration))
    }

小结

  1. Tauri 文件操作的优势:

    • 更安全的权限控制
    • 更好的性能表现
    • 更强的类型安全
    • 更现代的 API 设计
  2. 迁移策略:

    • 重构文件操作
    • 实现权限控制
    • 优化性能
    • 加强安全性
  3. 最佳实践:

    • 使用异步操作
    • 实现错误处理
    • 注重安全性
    • 优化性能

下一篇文章,我们将深入探讨 Tauri 2.0 的安全实践,帮助你构建更安全的桌面应用。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/67275.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1️⃣Java中的集合体系学习汇总(List/Map/Set 详解)

目录 01. Java中的集合体系 02. 单列集合体系​ 1. Collection系列集合的遍历方式 &#xff08;1&#xff09;迭代器遍历&#xff08;2&#xff09;增强for遍历​编辑&#xff08;3&#xff09;Lambda表达式遍历 03.List集合详解 04.Set集合详解 05.总结 Collection系列…

事件监听,事件类型

点击按钮实现 盒子关闭 随机点名案例 先给开始按钮添加点击事件 获取显示名字的 div 和 开始按钮的 div给开始按钮添加点击事件&#xff0c;设置定时器&#xff0c;每隔35ms内获取一个数组长度内的随机数&#xff0c;将显示名字的 div内的内容替换为该随机数作为下标的数组的内…

基于PyQt - 6的医疗多模态大模型医疗研究系统中的创新构建与应用(上 .文章部分)

一、引言 1.1 研究背景与意义 在当今数智化时代,医疗行业正经历着深刻的变革,对智能化、高效化的需求日益迫切。传统的医疗模式在面对海量的医疗数据、复杂的诊断流程以及个性化的治疗需求时,逐渐显露出局限性。随着人工智能技术的飞速发展,多模态大模型作为一种前沿技术…

微软震撼发布:Phi-4语言模型登陆Hugging Face

近日&#xff0c;微软公司在Hugging Face平台上正式发布了其最新的语言模型Phi-4&#xff0c;这一发布标志着人工智能技术的又一重要进步。Phi-4模型以其140亿参数的高效配置&#xff0c;在复杂推理任务中表现出色&#xff0c;特别是在数学领域&#xff0c;更是展现出了卓越的能…

深度解析Linux中关于操作系统的知识点

操作系统概述与核心概念 任何计算机系统都包含一个基本的程序集合&#xff0c;成为操作系统OS 操作系统是一款进行软硬件管理的软件 操作系统包括&#xff1a; 内核&#xff08;进程管理&#xff0c;内存管理&#xff0c;驱动管理&#xff09; 其他程序&#xff08;例如数据…

IEC103 转 ModbusTCP 网关

一、产品概述 IEC103 转 ModbusTCP 网关型号 SG-TCP-IEC103 &#xff0c;是三格电子推出的工业级网关&#xff08;以下简 称网关&#xff09;&#xff0c;主要用于 IEC103 数据采集、 DLT645-1997/2007 数据采集&#xff0c; IEC103 支持遥测和遥 信&#xff0c;可接…

如何监控和防范小红书笔记详情API的安全风险?

流量监控与异常检测 请求频率监测&#xff1a; 建立一个系统来记录 API 的请求频率。可以通过在服务器端设置计数器或者使用专业的监控工具来实现。例如&#xff0c;对于每个 API 调用者&#xff08;可以通过 API 密钥或者用户标识来区分&#xff09;&#xff0c;记录它们在单…

程序员独立开发竞品分析:确定网站使用什么建站系统

要确定一个网站使用的建站系统&#xff0c;可以通过以下几种方法尝试分析&#xff1a; 查看页面源代码&#xff1a; 打开网站&#xff0c;右键点击页面并选择“查看页面源代码”。在代码中查找一些常见的建站系统标志&#xff0c;例如&#xff1a; WordPress 的迹象&#xff1a…

迅翼SwiftWing | ROS 固定翼开源仿真平台正式发布!

经过前期内测调试&#xff0c;ROS固定翼开源仿真平台今日正式上线&#xff01;现平台除适配PX4ROS环境外&#xff0c;也已实现APROS环境下的单机飞行控制仿真适配。欢迎大家通过文末链接查看项目地址以及具体使用手册。 1 平台简介 ROS固定翼仿真平台旨在实现固定翼无人机决策…

【计算机网络】深入浅出计算机网络

第一章 计算机网络在信息时代的作用 计算机网络已由一种通信基础设施发展成一种重要的信息服务基础设施 CNNIC 中国互联网网络信息中心 因特网概述 网络、互联网和因特网 网络&#xff08;Network&#xff09;由若干结点&#xff08;Node&#xff09;和连接这些结点的链路…

QT Quick QML 实例之椭圆投影,旋转

文章目录 一、前言二、演示三、部分代码与分析 QML 其它文章请点击这里: QT QUICK QML 学习笔记 国际站点 GitHub: https://github.com/chenchuhan 国内站点 Gitee : https://gitee.com/chuck_chee 一、前言 此 Demo 主要用于无人机吊舱视角的模拟&#xf…

Mysql--架构篇--体系结构(连接层,SQL层,存储引擎层,文件存储层)

MySQL是一种广泛使用的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;其体系结构设计旨在提供高效的数据存储、查询处理和事务管理。MySQL的体系结构可以分为多个层次&#xff0c;每个层次负责不同的功能模块。 MySQL的体系结构主要由以下几个部分组成&#…

【SpringSecurity】SpringSecurity安全框架登录校验流程与登录配置示例

文章目录 SpringSecurity安全框架登录校验流程登录配置示例 SpringSecurity安全框架 Security 是一个能够为基于 Spring 的应用程序提供认证、授权以及保护免受攻击的安全框架。它是 Spring 生态系统的一部分&#xff0c;与 Spring 框架无缝集成。这些框架帮助开发者实现认证&…

dockerfile1.0

docker的数据卷 docker file ------------- 自动自定义镜像 docker的数据卷&#xff1a; 容器与宿主机之间&#xff0c;或者容器和容器之间的数据共享&#xff08;目录&#xff09; 创建容器的时候&#xff0c;通过指定目录&#xff0c;实现容器于宿主机之间&#xff0c;或…

晨辉面试抽签和评分管理系统之九:随机编排考生的分组(以教师资格考试面试为例)

晨辉面试抽签和评分管理系统&#xff08;下载地址:www.chenhuisoft.cn&#xff09;是公务员招录面试、教师资格考试面试、企业招录面试等各类面试通用的考生编排、考生入场抽签、候考室倒计时管理、面试考官抽签、面试评分记录和成绩核算的面试全流程信息化管理软件。提供了考生…

信号与系统初识---信号的分类

文章目录 0.引言1.介绍2.信号的分类3.关于周期大小的求解4.实信号和复信号5.奇信号和偶信号6.能量信号和功率信号 0.引言 学习这个自动控制原理一段时间了&#xff0c;但是只写了一篇博客&#xff0c;其实主要是因为最近在打这个华数杯&#xff0c;其次是因为在补这个数学知识…

解决winodws server iis 下的php mkdir(): Permission denied 问题

这个问题报错原因是权限不够&#xff0c;解决办法如下&#xff1a; 1.在php安装目录下&#xff0c;打开配置文件php.ini 把upload_tmp_dir 前面的分号去掉。 2.给上传的文件夹添加权限 在网站的相应目录&#xff0c;比如目录为tmp&#xff0c;添加IUSR用户&#xff0c;并给所…

如何在本地部署大模型并实现接口访问( Llama3、Qwen、DeepSeek等)

如何在本地部署大模型并实现接口访问&#xff08; Llama3、Qwen、DeepSeek等&#xff09; 如何在本地部署大模型并实现接口访问&#xff08; Llama3、Qwen、DeepSeek等&#xff09;模型地址模型下载模型部署指定显卡运行app.py 运行环境requirements 调用接口代码调用 结语 如何…

数据库增量备份和全量备份

数据库增量备份和全量备份 1.修改配置 首先打开配置文件my.ini 添加以下配置 #log-bin"JSSM-20230617FY-bin" log-bin"mysql-bin"# Server Id. server-id1#指令指定写入二进制日志的事件格式 binlog_formatMIXED添加完之后对MySQL服务进行重启 重启之后…

用 Python 从零开始创建神经网络(十九):真实数据集

真实数据集 引言数据准备数据加载数据预处理数据洗牌批次&#xff08;Batches&#xff09;训练&#xff08;Training&#xff09;到目前为止的全部代码&#xff1a; 引言 在实践中&#xff0c;深度学习通常涉及庞大的数据集&#xff08;通常以TB甚至更多为单位&#xff09;&am…