tenho seguinte código tauri v2 rust



use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use tauri::{AppHandle, Emitter};
use reqwest::Client;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use std::time::{Duration, Instant};

#[derive(Deserialize, Clone)]
struct PatchEntry {
file_name: String,
}

#[derive(Serialize, Clone)]
struct DownloadInfo {
current_file: usize,
total_files: usize,
file_name: String,
downloaded_bytes: u64,
total_bytes: u64,
speed: f64,
}

#[derive(Serialize, Clone)]
struct DownloadStatus {
status: String,
additional_info: Option,
}

fn get_exe_dir() -> PathBuf {
std::env::current_exe().unwrap().parent().unwrap().to_path_buf()
}

async fn dir_exists(path: &Path) -> Result<bool, std::io::Error> {
match fs::metadata(path).await {
Ok(metadata) => Ok(metadata.is_dir()),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(err) => Err(err),
}
}

async fn read_patch_file(path: &Path) -> Result<Vec, String> {
let content = fs::read_to_string(path).await.map_err(|e| e.to_string())?;
let patches: Vec = serde_json::from_str(&content).map_err(|e| e.to_string())?;
Ok(patches)
}

async fn emit_download_status(app_handle: &AppHandle, status: &str, additional_info: Option,) -> Result<(), String> {
app_handle.emit("download_status", DownloadStatus {status: status.to_string(), additional_info,}).map_err(|e| e.to_string())
}

async fn download_file(app_handle: &AppHandle, client: &Client, entry: &PatchEntry, download_dir: &Path, current_file: usize, total_files: usize,) -> Result<(), String> {
let url = format!("https://hil-speed.hetzner.com/{}", entry.file_name);
let file_path = download_dir.join(&entry.file_name);


let mut response = client.get(&url).send().await.map_err(|e| e.to_string())?;
if !response.status().is_success() {
    return Err(format!("Falha ao baixar '{}': HTTP {}", entry.file_name, response.status()));
}

let total_bytes = response.content_length().unwrap_or(0);
let mut file = fs::File::create(&file_path).await.map_err(|e| e.to_string())?;

let mut downloaded_bytes = 0u64;
let start_time = Instant::now();

emit_download_status(app_handle, "Start", None).await?;

let mut last_emit_time = Instant::now();
let mut downloading_message_sent = false;

while let Some(chunk) = response.chunk().await.map_err(|e| e.to_string())? {
    file.write_all(&chunk).await.map_err(|e| e.to_string())?;
    downloaded_bytes += chunk.len() as u64;

    let now = Instant::now();
    if now.duration_since(last_emit_time) >= Duration::from_millis(200) {
        if !downloading_message_sent {
            emit_download_status(app_handle, "Downloading", None).await?;
            downloading_message_sent = true;
        }

        let speed = if start_time.elapsed().is_zero() {
            0.0
        } else {
            downloaded_bytes as f64 / start_time.elapsed().as_secs_f64()
        };

        app_handle.emit("download_info", DownloadInfo {
            current_file,
            total_files,
            file_name: entry.file_name.clone(),
            downloaded_bytes,
            total_bytes,
            speed,
        }).map_err(|e| e.to_string())?;
        last_emit_time = now;
    }
}

Ok(())
}

async fn check_server_connection(client: &Client, url: &str) -> Result<(), String> {
let response = client.get(url).send().await.map_err(|e| e.to_string())?;
if response.status().is_success() {
Ok(())
} else {
Err(format!("Falha ao conectar ao servidor: HTTP {}", response.status()))
}
}

#[tauri::command]
pub async fn start_downloads(app_handle: AppHandle) -> Result<(), String> {
let exe_dir = get_exe_dir();


// Definir caminhos
let patch_file_path = exe_dir.join("patch.json");
let download_dir = exe_dir.join("patchs");

// Verificar se o diretório de download existe, criar se não existir
if !dir_exists(&download_dir).await.map_err(|e| e.to_string())? {
    fs::create_dir_all(&download_dir).await.map_err(|e| e.to_string())?;
}

// Ler os patches do arquivo JSON
let patches = read_patch_file(&patch_file_path).await?;

let client = Client::new();
let total_files = patches.len();

// Emitir status de conexão
emit_download_status(&app_handle, "Connecting", None).await?;

let server_url = "https://hil-speed.hetzner.com/";
if let Err(err) = check_server_connection(&client, server_url).await {
    let error_message = format!("Erro ao conectar ao servidor: {}", err);
    eprintln!("{}", error_message);
    emit_download_status(&app_handle, "Error", Some(error_message.clone())).await?;
    return Err(error_message);
}

emit_download_status(&app_handle, "Connected", None).await?;

for (idx, entry) in patches.iter().enumerate() {
    let current_file = idx + 1;
    if let Err(err) = download_file(&app_handle, &client, entry, &download_dir, current_file, total_files).await {
        let error_message = format!("Erro ao baixar '{}': {}", entry.file_name, err);
        eprintln!("{}", error_message);
        emit_download_status(&app_handle, "Error", Some(error_message.clone())).await?;
        return Err(error_message);
    }
}

// Emitir status de conclusão
emit_download_status(&app_handle, "Completed", None).await?;

Ok(())
}

e o seguinte código react

import { useEffect, useState } from "react";
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";

function formatBytes(bytes: number) {
if (bytes === 0) return "0 B";
const k = 1024;
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
const value = bytes / Math.pow(k, i);
return ${value.toFixed(2)} ${units[i]};
}

enum DownloadStatusType {
Connecting = "Connecting",
Connected = "Connected",
Start = "Start",
Downloading = "Downloading",
Completed = "Completed",
Error = "Error",
}

interface DownloadStatus {
status: DownloadStatusType;
additional_info?: string;
}

interface DownloadInfo {
current_file: number;
total_files: number;
file_name: string;
downloaded_bytes: number;
total_bytes: number;
speed: number;
}

const DownloadManager = () => {
const [message, setMessage] = useState("");
const [currentFile, setCurrentFile] = useState(0);
const [totalFiles, setTotalFiles] = useState(0);
const [fileName, setFileName] = useState("");
const [downloadedBytes, setDownloadedBytes] = useState(0);
const [totalBytes, setTotalBytes] = useState(0);
const [downloadSpeed, setDownloadSpeed] = useState(0);
const [isDownloading, setIsDownloading] = useState(false);

useEffect(() => {
let unlistenStatus: (() => void) | null = null;
let unlistenInfo: (() => void) | null = null;
let unlistenSpeed: (() => void) | null = null;


(async () => {
  unlistenStatus = await listen<DownloadStatus>("download_status", (event) => {
    const { status, additional_info } = event.payload;

    let newMessage = "";

    switch (status) {
      case DownloadStatusType.Connecting:
        newMessage = "Conectando ao servidor...";
        break;
      case DownloadStatusType.Connected:
        newMessage = "Conexão estabelecida com sucesso";
        break;
      case DownloadStatusType.Start:
        newMessage = "Iniciando download...";
        break;
      case DownloadStatusType.Downloading:
        newMessage = "Baixando arquivo";
        break;
      case DownloadStatusType.Completed:
        newMessage = "Atualizações concluídas com sucesso.";
        break;
      case DownloadStatusType.Error:
        newMessage = `Erro: ${additional_info}`;
        break;
      default:
        newMessage = "Status desconhecido";
    }

    setMessage(newMessage);

    if (status === DownloadStatusType.Downloading) {
      setIsDownloading(true);
    } else if (status === DownloadStatusType.Completed || status === DownloadStatusType.Error) {
      setIsDownloading(false);
    }
  });

  unlistenInfo = await listen<DownloadInfo>("download_info", (event) => {
    const {
      current_file,
      total_files,
      file_name,
      downloaded_bytes,
      total_bytes,
      speed,
    } = event.payload;

    setCurrentFile(current_file);
    setTotalFiles(total_files);
    setFileName(file_name);
    setDownloadedBytes(downloaded_bytes);
    setTotalBytes(total_bytes);
    setDownloadSpeed(speed);
  });
})();

return () => {
  if (unlistenStatus) unlistenStatus();
  if (unlistenInfo) unlistenInfo();
  if (unlistenSpeed) unlistenSpeed();
};
}, [currentFile, totalFiles, downloadedBytes, totalBytes, downloadSpeed]);

const startDownload = async () => {
if (isDownloading) return;
setIsDownloading(true);


// Resetar o estado ao iniciar um novo download
setMessage("");
setCurrentFile(0);
setTotalFiles(0);
setFileName("");
setDownloadedBytes(0);
setTotalBytes(0);
setDownloadSpeed(0);

try {
  await invoke("start_downloads");
} catch (error) {
  setMessage(`Erro ao iniciar o download: ${error}`);
  setIsDownloading(false);
}
};

const progressPercent =
downloadedBytes > 0 && totalBytes > 0 ? (downloadedBytes / totalBytes) * 100 : 0;

return (
<>



Iniciar Download





{message}
{isDownloading && totalFiles > 0 && downloadedBytes > 0 && totalBytes > 0 && (
<>

( {currentFile} / {totalFiles} )

: {fileName}

( {formatBytes(downloadedBytes)} / {formatBytes(totalBytes)} )

</>
)}



{isDownloading && totalFiles > 0 && downloadedBytes > 0 && totalBytes > 0 && (


{formatBytes(downloadSpeed)}ps


)}

<div
className="bg-[#1164CA]/[0.95] ml-1 rounded-md h-4"
style={{ width: ${progressPercent}% }}
/>


</>
);
};
export default DownloadManager;

Adicione suporte para continuar o download de onde parou, em caso do app ser fechado ou do app perder conexão,

Se perder a conexão uma mensagem deve ser exibibida, tentando se conectar novamente em 15s e os segundos deve ir reduzindo até chegar a 0, quando chegar a zero, volta o download de onde parou

em caso de fechar o app e abrir novamente verificar os arquivos na pasta e continuar baixando de onde parou