refactor(api): make audio tagging and decryption async to avoid UI blocking

This commit is contained in:
2025-10-03 11:09:06 -04:00
parent 14a5b50942
commit 0ef56c3bed
4 changed files with 44 additions and 22 deletions

View File

@@ -12,13 +12,22 @@ fn greet(name: &str) -> String {
/// Tag an audio file with metadata, cover art, and lyrics
#[tauri::command]
fn tag_audio_file(
async fn tag_audio_file(
path: String,
metadata: tagger::TaggingMetadata,
cover_data: Option<Vec<u8>>,
embed_lyrics: bool,
) -> Result<(), String> {
tagger::tag_audio_file(&path, &metadata, cover_data.as_deref(), embed_lyrics)
// Run tagging on a background thread to avoid blocking the UI
tauri::async_runtime::spawn_blocking(move || {
tagger::tag_audio_file(&path, &metadata, cover_data.as_deref(), embed_lyrics)
})
.await
.map_err(|e| format!("Tagging task failed: {}", e))?
// Flatten the inner Result
?;
Ok(())
}
/// Read metadata from an audio file (MP3 or FLAC)
@@ -29,8 +38,15 @@ fn read_audio_metadata(path: String) -> Result<metadata::AudioMetadata, String>
/// Decrypt Deezer track data
#[tauri::command]
fn decrypt_deezer_track(data: Vec<u8>, track_id: String) -> Result<Vec<u8>, String> {
Ok(deezer_crypto::decrypt_track(&data, &track_id))
async fn decrypt_deezer_track(data: Vec<u8>, track_id: String) -> Result<Vec<u8>, String> {
// Run decryption on a background thread to avoid blocking the UI
let result = tauri::async_runtime::spawn_blocking(move || {
deezer_crypto::decrypt_track(&data, &track_id)
})
.await
.map_err(|e| format!("Decryption task failed: {}", e))?;
Ok(result)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]

View File

@@ -142,7 +142,7 @@ async function findActualFilePath(basePath: string): Promise<string | null> {
}
/**
* Read metadata from audio file using Rust backend
* Read metadata from audio file on backend
*/
async function readAudioMetadata(filePath: string, format: AudioFormat): Promise<TrackMetadata> {
try {

View File

@@ -81,6 +81,7 @@ export async function downloadTrack(
const reader = response.body!.getReader();
const chunks: Uint8Array[] = [];
let downloadedBytes = 0;
let lastReportedPercentage = 0;
while (true) {
const { done, value } = await reader.read();
@@ -89,15 +90,19 @@ export async function downloadTrack(
chunks.push(value);
downloadedBytes += value.length;
// Call progress callback
// Call progress callback every 5%
if (onProgress && totalSize > 0) {
const percentage = (downloadedBytes / totalSize) * 100;
console.log(`[Download Progress] ${downloadedBytes}/${totalSize} bytes (${percentage.toFixed(1)}%)`);
onProgress({
downloaded: downloadedBytes,
total: totalSize,
percentage
});
const roundedPercentage = Math.floor(percentage / 5) * 5;
if (roundedPercentage > lastReportedPercentage || percentage === 100) {
lastReportedPercentage = roundedPercentage;
onProgress({
downloaded: downloadedBytes,
total: totalSize,
percentage
});
}
}
}
@@ -111,20 +116,23 @@ export async function downloadTrack(
console.log(`Downloaded ${encryptedData.length} bytes, encrypted: ${isCrypted}`);
// Yield to the browser to keep UI responsive
await new Promise(resolve => setTimeout(resolve, 0));
// Decrypt if needed
let decryptedData: Uint8Array;
if (isCrypted) {
console.log('Decrypting track using Rust...');
console.log('Decrypting track...');
// Use the provided decryption track ID (for fallback tracks) or the original track ID
const trackIdForDecryption = decryptionTrackId || track.id.toString();
const trackIdForDecryption = decryptionTrackId ? decryptionTrackId.toString() : track.id.toString();
console.log(`Decrypting with track ID: ${trackIdForDecryption}`);
// Call Rust decryption function
const decrypted = await invoke<number[]>('decrypt_deezer_track', {
data: Array.from(encryptedData),
// Call Rust decryption function - Tauri returns Vec<u8> as number[]
const decryptedArray = await invoke<number[]>('decrypt_deezer_track', {
data: encryptedData,
trackId: trackIdForDecryption
});
decryptedData = new Uint8Array(decrypted);
decryptedData = new Uint8Array(decryptedArray);
} else {
decryptedData = encryptedData;
}

View File

@@ -90,14 +90,12 @@ export async function tagAudioFile(
): Promise<void> {
const metadata = convertToTaggingMetadata(track);
// Convert Uint8Array to regular array for JSON serialization
const coverArray = coverData ? Array.from(coverData) : undefined;
// Tauri handles Uint8Array -> Vec<u8> conversion automatically
try {
await invoke('tag_audio_file', {
path: filePath,
metadata,
coverData: coverArray,
coverData,
embedLyrics,
});
} catch (error) {