Rust 开发压缩解压工具

476 阅读4分钟

4.gif

Rust 开发压缩解压工具的优点
  1. 性能优异:Rust 提供了接近 C/C++ 的性能,这对于压缩和解压缩工作来说非常重要,因为这些操作通常是计算密集型的。Rust 的编译器在优化代码方面表现出色,可以生成高效的机器代码。
  2. 内存安全:Rust 通过其所有权模型消除了空指针解引用、缓冲区溢出等常见的内存安全问题。这意味着开发的压缩工具不太可能因内存错误而崩溃或产生安全漏洞。
  3. 并发编程:Rust 的并发编程模型旨在提供无数据竞争的线程安全性。这使得它非常适合开发需要处理大量数据或多线程执行的应用程序,如压缩工具,以实现更高的性能和效率。
  4. 跨平台支持:Rust 支持多种操作系统,包括 Windows、Linux 和 macOS。这使得开发的压缩工具可以轻松地部署到不同的平台上,无需修改大量代码。
  5. 现代语言特性:Rust 拥有现代语言的特性,如类型推断、模式匹配、错误处理等,这些特性可以帮助开发者写出更安全、更易读的代码。
  6. 生态系统和工具链:Rust 拥有活跃的开发社区和丰富的生态系统,提供了大量的库和工具,如Cargo(Rust 的包管理器和构建工具),这可以帮助开发者提高开发效率。
  7. 错误处理:Rust 强制进行错误处理,这有助于开发更可靠的应用程序。在压缩或解压缩过程中,任何可能的错误都必须被显式处理,减少了运行时错误的可能性。
详情内容
  1. 创建Tauri 项目(具体的Tauri配置方式就不提供了,需要的请看官网tauri.app/。)


npm create tauri-app@latest

// 执行命令后会出现以下内容

Need to install the following packages:
create-tauri-app@3.14.0
Ok to proceed? (y)   
Project name (tauri-app) ›   //项目名称
Choose which language to use for your frontend › // 选择语言
    ❯ TypeScript / JavaScript  (pnpm, yarn, npm, bun)
      Rust
      .NET
? Choose your package manager › //包管理器
    ❯ npm
      pnpm
      yarn
      bun
Choose your UI template ›    // UI 框架
      Vanilla
    ❯ Vue  (https://vuejs.org/)
      Svelte
      React
      Solid
      Angular
      Preact
? Choose your UI flavor ›    // 选择语言风格
    ❯ TypeScript
      JavaScript

// 完成安装后
├─.vscode
├─public  
├─src      // vue项目资源
│  ├─assets
│  └─components
└─src-tauri
    ├─icons
    └─src
├─index.html
├─.gitignore
├─package.json
├─README.md
└─vite.config.js

// 执行命令
  cd tauri-app
  npm install
  npm run tauri dev

  1. 配置cargo.toml

    使用Zip 和 Tar 针对主流的zip包和tar包进行操作,如果有其他的压缩包可以去create.io 搜索相关的库,目前rust的模块库内容还是挺丰富的,你可以找到很多你需要的内容,根据其中的dosc编写你的功能。


[dependencies]
tauri = { version = "1", features = [ "api-all"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
zip='1.2.2'
encoding_rs = "0.8.28"

tar = "0.4.38"
flate2 = "1.0"

  1. 开发解压功能
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use flate2::read::GzDecoder;
use std::io::{Read, Seek, SeekFrom, Write};
use std::{
    fs::File,
    path::{Path, PathBuf},
};

use tar::Archive;
use tauri::{App, Manager};
use zip::read::ZipArchive;
use zip::write::FileOptions;
use zip::ZipWriter;

use encoding_rs::*;

fn unzip_file(source: &str, destination: &str) -> zip::result::ZipResult<()> {
    let file = File::open(source).unwrap();
    let mut archive = ZipArchive::new(file)?;

    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;

        let file_name_bytes = file.name_raw();
        let (cow, _encoding_used, had_errors) = GBK.decode(file_name_bytes);

        let outpath = Path::new(destination).join(cow.as_ref());
        if (*file.name()).ends_with("/") {
            std::fs::create_dir_all(&outpath)?;
        } else {
            if let Some(p) = outpath.parent() {
                if !p.exists() {
                    std::fs::create_dir_all(&p)?;
                }
            }
            println!("outpath:{:?}", outpath);
            let mut outfile = File::create(&outpath)?;
            std::io::copy(&mut file, &mut outfile)?;
        }
    }
    Ok(())
}

fn extract_tgz(tgz_path: &str, out_path: &str) -> Result<(), String> {
    // 检查文件是否存在并打开
    let tar_gz = File::open(tgz_path)
        .map_err(|e| {
            eprintln!("Failed to open file '{}': {}", tgz_path, e);
            e
        })
        .map_err(|err| format!("failed not find file{:?}", err))?;

    // 创建 gzip 解码器
    let tar = GzDecoder::new(tar_gz);

    // 创建 tar 归档
    let mut archive = Archive::new(tar);

    // 获取 .tgz 文件的名称,用作新目录的名称
    let path = Path::new(tgz_path);
    let dir_name = path
        .file_stem()
        .ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::NotFound,
                "Cannot extract directory name from file path",
            )
        })
        .map_err(|err| format!("Cannot extract directory name from file path {:?}", err))?;

    let mut dir_path = PathBuf::from(out_path);
    dir_path.push(dir_name);

    // 创建目录
    std::fs::create_dir_all(&dir_path).map_err(|e| {
        eprintln!("Failed to create directory '{}': {}", dir_path.display(), e);
        format!("Failed to create directory '{}': {}", dir_path.display(), e)
    })?;

    // 解包到新创建的目录
    archive.unpack(&dir_path).map_err(|e| {
        eprintln!(
            "Failed to unpack archive to '{}': {}",
            dir_path.display(),
            e
        );
        format!(
            "Failed to unpack archive to '{}': {}",
            dir_path.display(),
            e
        )
    })?;

    Ok(())
}

fn main() {
    tauri::Builder::default()
        .setup(|app: &mut App| {
            let app_win = app.get_window("main").unwrap();
            let listener_win = app_win.clone();
            listener_win.listen("open_file", move |event| {
                let emit_win = app_win.clone();
                println!("event {:?}", event.payload());
                if let Some(payload) = event.clone().payload() {
                    // 去除Option封装,并处理JSON字符串
                    if let Ok(file_path) = serde_json::from_str::<String>(&payload) {
                        println!("处理后的路径: {}", file_path);
                        let path = Path::new(&file_path);
                        match path.parent() {
                            Some(parent_path) => {
                                let res = extract_tgz(&file_path, parent_path.to_str().unwrap());

                                match res {
                                    Ok(_) => {
                                        let _ = emit_win.emit("gunzip_result", "ok");
                                    },
                                    Err(err) => {
                                        let _ = emit_win.emit("gunzip_result", err);
                                    }
                                }
                            }
                            None => println!("No parent directory."),
                        }
                    } else {
                        println!("数据解析错误");
                    };
                } else {
                    println!("Event data is empty");
                }
            });

            Ok(())
        })
        .invoke_handler(tauri::generate_handler![])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import Header from './components/Header.vue'
import { open } from '@tauri-apps/api/dialog';
import { emit, listen } from '@tauri-apps/api/event';
import { message } from 'ant-design-vue';
let path = ref('')
let gunzipLoading = ref(false)

const OpenFile = () => {
  open().then(res => {
    if (res == null) return false;
    path.value = res as string;
    gunzipLoading.value = true;
    emit("open_file", path.value);
  })
}
const GetGunzip = () => {
  listen("gunzip_result", (event) => {
    console.log(event.payload);
    if (event.payload == "ok") {
      message.success('解压成功')
    } else {
      message.error(`解压失败,错误信息:${event.payload}`)
    }
    gunzipLoading.value = false;
  });
}
onMounted(() => {
  GetGunzip();
})
</script>

<template>
  <Header />
  <div class="container">
    <div>
      <a-input-group compact style="text-align: center;">
        <a-input v-model:value="path" style="width: calc(100% - 120px)" />
        <a-button type="primary" @click='OpenFile' :loading="gunzipLoading">选择文件</a-button>
      </a-input-group>
    </div>
  </div>
</template>

<style scoped></style>