Rust:Tauri和git2碰撞出不一样的火花

如何使用Tauri和git2在Rust中实现Git克隆操作的进度显示与取消

本文将介绍如何使用Rust语言结合Tauri和git2库来实现Git克隆操作的进度显示和取消功能。

核心逻辑

首先,我们使用git2库来实现Git的克隆功能。git2是一个Rust语言的Git库,它提供了丰富的API来与Git仓库进行交互。接着,我们利用Tauri框架提供的命令和事件机制,通过命令执行下载操作,并使用原子布尔值和事件监听器来处理前端发来的取消消息。最后,我们实现了取消逻辑,确保在用户取消操作时,下载任务能够及时停止。

遇到的问题

在使用git2的remoteCallback时,我们发现其执行频率相当高。如果频繁地通过Tauri的Channel向前端发送消息,可能会导致应用程序卡顿。因此,我们需要对消息发送进行节流处理,以避免过多的消息传输影响性能。

代码实现

以下是一个简化的代码示例,展示了如何使用Tauri和git2实现Git克隆操作的进度显示和取消。

git2 实现clone和pull逻辑

rust 复制代码
// 引入标准库中的Path模块,用于处理文件路径。
use std::path::Path;
​
// 引入自定义的错误类型MyError,用于错误处理。
use crate::error::MyError;
// 引入anyhow库,它提供了一个方便的错误处理上下文。
use anyhow::Context;
// 引入derive_builder库,用于生成构建器模式的代码。
use derive_builder::Builder;
// 使用git2库,它提供了与Git仓库交互的接口。
use git2::{build::RepoBuilder, FetchOptions, Progress, RemoteCallbacks, Repository};
// 引入tracing库,用于日志记录。
use tracing::info;
​
// 定义一个常量GITHUB_PROXY,存储GitHub镜像的URL。
pub const GITHUB_PROXY: &str = "https://mirror.ghproxy.com"; 
​
// Git结构体定义,使用Builder宏来生成构建器模式。
#[derive(Debug, Builder, Clone)]
#[builder(setter(into))]
pub struct Git {
    // Git仓库的URL地址。
    url: String,
    // 本地仓库的路径。
    path: String,
    // 是否使用代理,默认为true。
    #[builder(default = "true")]
    proxy: bool,
}
​
// Git结构体的实现。
impl Git {
    // git_clone方法用于克隆Git仓库。
    pub fn git_clone<F>(&self, cb: F) -> Result<(), MyError>
    where
        F: FnMut(Progress) -> bool,
    {
        // 创建RemoteCallbacks对象,用于处理网络传输进度。
        let mut rc = RemoteCallbacks::new();
        rc.transfer_progress(cb);
​
        // 创建FetchOptions对象,用于设置获取操作的选项。
        let mut fo = FetchOptions::new();
        fo.remote_callbacks(rc);
​
        // 根据是否使用代理,构造仓库的URL。
        let url = if self.proxy {
            format!("{GITHUB_PROXY}/{}", self.url)
        } else {
            self.url.clone()
        };
        // 记录克隆操作的URL。
        info!("git clone {}", url);
        // 使用RepoBuilder克隆仓库到指定路径。
        RepoBuilder::new()
            .fetch_options(fo)
            .clone(&url, Path::new(&self.path))?;
        Ok(())
    }
​
    // git_pull方法用于从远程仓库获取更新并合并到本地仓库。
    pub fn git_pull<F>(&self, cb: F) -> Result<usize, MyError>
    where
        F: FnMut(Progress) -> bool,
    {
        // 打开本地仓库。
        let repo = Repository::open(&self.path)?;
        // 获取名为"origin"的远程仓库。
        let remote_name = "origin"; // 远程仓库的默认名称
        // 连接到远程仓库。
        let mut remote = repo.find_remote(remote_name)?;
        remote.connect(git2::Direction::Fetch)?;
​
        // 获取远程仓库的默认分支名称。
        let default_branch = remote.default_branch()?;
        let default_branch_name = default_branch.as_str().context("获取默认分支名称失败")?;
        // 设置回调函数,用于处理获取过程中的进度信息。
        let mut fetch_opts = FetchOptions::new();
        let mut rc = RemoteCallbacks::new();
        rc.transfer_progress(cb);
        fetch_opts.remote_callbacks(rc);
        // 执行获取操作。
        remote.fetch(&[default_branch_name], Some(&mut fetch_opts), None)?;
        // 找到FETCH_HEAD引用。
        let fetch_head = repo.find_reference("FETCH_HEAD")?;
        // 将FETCH_HEAD引用转换为注释提交。
        let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?;
        // 获取合并分析结果。
        let analysis = repo.merge_analysis(&[&fetch_commit])?;
        // 如果本地仓库是最新的,则返回1。
        if analysis.0.is_up_to_date() {
            return Ok(1);
        } else if analysis.0.is_fast_forward() {
            // 如果可以进行快进合并,则执行合并操作。
            let mut refrence = repo.find_reference(default_branch_name)?;
            refrence.set_target(fetch_commit.id(), "Fast forward")?;
            repo.set_head(default_branch_name)?;
            return Ok(repo
                .checkout_head(Some(git2::build::CheckoutBuilder::default().force()))
                .map(|_| 2usize)?);
        } else {
            // 如果不是快进合并,则返回0。
            return Ok(0);
        }
    }
​
    // builder方法用于创建Git结构体的构建器实例。
    pub fn builder() -> GitBuilder {
        GitBuilder::default()
    }
}

tauri定义前端调用接口

rust 复制代码
// 使用Tauri框架的命令宏来定义一个可以被前端调用的函数。
#[tauri::command]
pub async fn download_plugin(
    // app参数是Tauri应用的句柄,用于与前端通信。
    app: AppHandle,
    // config参数是一个包含配置信息的状态对象。
    config: State<'_, MyConfig>,
    // plugin参数是一个包含插件信息的对象。
    plugin: Plugin,
    // on_progress参数是一个通道,用于将下载进度发送到前端。
    on_progress: Channel,
) -> Result<(), MyError> {
    // 从状态对象中克隆配置信息。
    let config = config.inner().clone();
    // 通过通道发送一个下载前的状态消息到前端。
    on_progress
        .send(
            PluginDownloadMessage::builder()
                .status(PluginStatus::Pending)
                .build()
                .unwrap(),
        )
        .context("Send Message Error")?;
​
    // 创建一个原子布尔值,用于控制下载任务的取消。
    let cancel = Arc::new(AtomicBool::new(true));
    // 克隆cancel变量,用于在闭包内部修改其值。
    let cancel2 = cancel.clone();
    // 克隆插件的引用信息,用于后续的取消操作。
    let plugin_reference = plugin.reference.clone();
    // 克隆通道,用于在闭包内部发送消息到前端。
    let on_progress2 = on_progress.clone();
​
    // 使用tokio库的spawn函数创建一个新的异步任务。
    let handler = tokio::spawn(async move {
​
        // 锁定配置信息,以便在异步任务中使用。
        let config = config.lock().await;
        // 记录下载开始的时间。
        let mut start_time = Instant::now();
        // 初始化下载进度。
        let mut progress = 0f64;
​
        // 调用插件的下载方法,并提供进度更新的回调函数。
        match plugin
            .download(&config.comfyui_path, config.is_chinese(), |p| {
                // 检查下载任务是否已被取消。
                let v = cancel2.load(std::sync::atomic::Ordering::SeqCst);
                // 计算当前的下载进度。
                let new_progress = percent(p.received_objects(), p.total_objects());
                // 为了避免发送过多消息导致阻塞,对消息进行节流。
                if start_time.elapsed() > Duration::from_millis(60) && progress != new_progress {
                    start_time = Instant::now();
                    progress = new_progress;
                    // 记录当前的下载进度。
                    info!("Download Progress: {}", new_progress);
                    // 通过通道发送当前的下载进度到前端。
                    on_progress
                        .send(
                            PluginDownloadMessage::builder()
                                .status(PluginStatus::Downloading)
                                .progress(new_progress)
                                .build()
                                .unwrap(),
                        )
                        .unwrap();
                }
                return v;
            })
            .await
        {
            // 如果下载成功,发送100%的进度消息表示下载完成。
            Ok(_) => {
                on_progress.send(100f64).unwrap();
                // 等待500毫秒,确保消息能够被前端接收。
                sleep(Duration::from_millis(500)).await;
                // 发送下载成功的状态消息到前端。
                on_progress
                    .send(
                        PluginDownloadMessage::builder()
                            .status(PluginStatus::Success)
                            .build()
                            .unwrap(),
                    )
                    .unwrap();
            }
            // 如果下载失败,发送错误状态消息到前端。
            Err(e) => {
                if !cancel2.load(std::sync::atomic::Ordering::SeqCst) {
                    // 发送错误状态消息到前端。
                    on_progress
                        .send(
                            PluginDownloadMessage::builder()
                                .status(PluginStatus::Error)
                                .error_message(e.to_string())
                                .build()
                                .unwrap(),
                        )
                        .unwrap();
                }
            }
        }
    });
​
    // 监听来自前端的取消事件。
    app.listen("plugin-cancel", move |event| {
        // 从事件中解析出引用信息。
        let reference = serde_json::from_str::<Value>(event.payload()).unwrap();
        // 如果引用信息与当前插件的引用信息匹配,取消下载任务。
        if reference["reference"] == plugin_reference {
            // 如果成功将cancel变量设置为false,表示取消操作成功。
            if cancel
                .compare_exchange(
                    true,
                    false,
                    std::sync::atomic::Ordering::SeqCst,
                    std::sync::atomic::Ordering::SeqCst,
                )
                .is_ok()
            {
                // 发送已取消的状态消息到前端。
                on_progress2
                    .send(
                        PluginDownloadMessage::builder()
                            .status(PluginStatus::Canceled)
                            .build()
                            .unwrap(),
                    )
                    .context("Send Message Error")
                    .unwrap();
                // 取消异步任务。
                handler.abort();
            }
        }
    });
​
    // 函数执行成功,返回Ok。
    Ok(())
}
相关推荐
GISer_Jing5 分钟前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪1 小时前
CSS复习
前端·css
咖啡の猫3 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲5 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5816 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路6 小时前
GeoTools 读取影像元数据
前端
ssshooter7 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry7 小时前
Jetpack Compose 中的状态
前端
百思可瑞教育8 小时前
Git 对象存储:理解底层原理,实现高效排错与存储优化
大数据·git·elasticsearch·搜索引擎
dae bal8 小时前
关于RSA和AES加密
前端·vue.js