I’m writing a Tauri app, which uses Rust and ReactJS
However, when sending information messages about file downloads, my app has a slight graphical lag
I have the following Tauri Rust code
use serde::Serialize;
use std::path::{Path, PathBuf};
use tauri::{AppHandle, Emitter};
use reqwest::Client;
use tokio::fs as tokio_fs;
use tokio::io::AsyncWriteExt;
use std::time::Instant;
#[derive(Debug, Serialize, Clone)]
struct DownloadMessage {
message: String,
}
#[derive(Debug, Serialize, Clone)]
struct DownloadProgress {
downloaded_bytes: u64,
total_bytes: u64,
}
fn get_exe_directory() -> PathBuf {
std::env::current_exe()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
}
async fn read_patch_file_async(path: &Path) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let content = tokio_fs::read_to_string(path).await?;
let patches: Vec<String> = serde_json::from_str(&content)?;
Ok(patches)
}
async fn download_patch_file(
app_handle: &AppHandle,
client: &Client,
file_name: String,
download_dir: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let url = format!("https://hil-speed.hetzner.com/{}", file_name);
let file_path = download_dir.join(&file_name);
let mut response = client.get(&url).send().await?;
let total_bytes = response.content_length().unwrap_or(0);
let mut file = tokio_fs::OpenOptions::new()
.write(true)
.create(true)
.open(file_path)
.await?;
let mut downloaded_bytes = 0;
let start_time = Instant::now();
while let Some(chunk) = response.chunk().await? {
file.write_all(&chunk).await?;
downloaded_bytes += chunk.len() as u64;
let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64();
let speed = if elapsed_time > 0.0 {
downloaded_bytes as f64 / elapsed_time
} else {
0.0
};
app_handle.emit("download_message", DownloadMessage { message: "Downloading...".to_string() })?;
app_handle.emit("download_progress", DownloadProgress {
downloaded_bytes,
total_bytes,
})?;
app_handle.emit("download_speed", speed)?;
}
app_handle.emit("download_message", DownloadMessage { message: "Download completed".to_string() })?;
Ok(())
}
async fn verify_server_connection(client: &Client, url: &str) -> Result<(), Box<dyn std::error::Error>> {
let response = client.get(url).send().await?;
if response.status().is_success() {
Ok(())
} else {
Err("Failed to connect to the server".into())
}
}
#[tauri::command]
pub async fn start_file_downloads(app_handle: AppHandle) -> Result<(), String> {
let exe_dir = get_exe_directory();
let patch_file_path = exe_dir.join("file.json");
let download_dir = exe_dir.join("files");
if !download_dir.exists() {
tokio_fs::create_dir_all(&download_dir).await.map_err(|e| e.to_string())?;
}
let patches = read_patch_file_async(&patch_file_path).await.map_err(|e| e.to_string())?;
let client = Client::new();
app_handle.emit("download_message", DownloadMessage {
message: "Connecting to server...".to_string(),
}).map_err(|e| e.to_string())?;
let server_url = "https://meysite/";
if let Err(err) = verify_server_connection(&client, server_url).await {
app_handle.emit("download_message", DownloadMessage {
message: format!("Error connecting to server: {}", err),
}).map_err(|e| e.to_string())?;
return Err(format!("Error connecting to server: {}", err));
}
app_handle.emit("download_message", DownloadMessage {
message: "Connection successful. Starting downloads...".to_string(),
}).map_err(|e| e.to_string())?;
for file_name in patches {
if let Err(err) = download_patch_file(&app_handle, &client, file_name, &download_dir).await {
eprintln!("Error downloading file: {}", err);
app_handle.emit("download_message", DownloadMessage {
message: format!("Error downloading file: {}", err),
}).map_err(|e| e.to_string())?;
}
}
Ok(())
}
in my React to show the information I have
import { useEffect, useState } from "react";
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
const DownloadTest = () => {
const [message, setMessage] = useState("");
const [progress, setProgress] = useState<{ downloaded_bytes: number; total_bytes: number }>({
downloaded_bytes: 0,
total_bytes: 0,
});
const [downloadSpeed, setDownloadSpeed] = useState(0);
const [isDownloading, setIsDownloading] = useState(false);
useEffect(() => {
const unlistenMessage = listen<{ message: string }>("download_message", (event) => {
setMessage(event.payload.message);
});
const unlistenProgress = listen<{ downloaded_bytes: number; total_bytes: number }>(
"download_progress",
(event) => {
setProgress(event.payload);
}
);
const unlistenSpeed = listen<number>("download_speed", (event) => {
setDownloadSpeed(event.payload);
});
return () => {
unlistenMessage.then((unlisten) => unlisten());
unlistenProgress.then((unlisten) => unlisten());
unlistenSpeed.then((unlisten) => unlisten());
};
}, []);
const progressPercentage = progress.total_bytes
? ((progress.downloaded_bytes / progress.total_bytes) * 100).toFixed(2)
: 0;
const startDownload = async () => {
try {
setIsDownloading(true);
setMessage("Starting download...");
await invoke("start_file_downloads");
} catch (error) {
console.error("Error starting download:", error);
setMessage("Failed to start download. Check the logs.");
} finally {
setIsDownloading(false);
}
};
return (
<div>
<h1>Download Manager</h1>
<button onClick={startDownload} disabled={isDownloading}>
{isDownloading ? "Downloading..." : "Start Download"}
</button>
<p>{message}</p>
<div>
<h2>Progress</h2>
<p>Downloaded: {progress.downloaded_bytes} bytes</p>
<p>Total: {progress.total_bytes} bytes</p>
<p>Progress: {progressPercentage}%</p>
</div>
<p>Download Speed: {downloadSpeed.toFixed(2)} bytes/sec</p>
</div>
);
};
export default DownloadTest;
What should I do so that the download doesn’t affect the UI performance?