mirror of
https://github.com/markuryy/shark.git
synced 2025-12-13 12:01:01 +00:00
refactor: move download/decryption to backend to fix UI freezing
Now implements streaming download+decryption entirely in Rust: - Added reqwest/tokio/futures-util dependencies - Created StreamingDecryptor for chunk-by-chunk decryption - New download_and_decrypt_track command streams to disk directly - Frontend simplified to single invoke() call
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
* Deezer track downloader with streaming and decryption
|
||||
*/
|
||||
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
import { writeFile, mkdir, remove, rename, exists } from '@tauri-apps/plugin-fs';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { generateTrackPath } from './paths';
|
||||
@@ -56,86 +55,21 @@ export async function downloadTrack(
|
||||
console.log('Temp path:', paths.tempPath);
|
||||
|
||||
try {
|
||||
// Fetch the track with timeout
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout
|
||||
|
||||
const response = await fetch(downloadURL, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'
|
||||
},
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const totalSize = parseInt(response.headers.get('content-length') || '0');
|
||||
const isCrypted = downloadURL.includes('/mobile/') || downloadURL.includes('/media/');
|
||||
|
||||
// Stream the response with progress tracking
|
||||
const reader = response.body!.getReader();
|
||||
const chunks: Uint8Array[] = [];
|
||||
let downloadedBytes = 0;
|
||||
let lastReportedPercentage = 0;
|
||||
// Use the provided decryption track ID (for fallback tracks) or the original track ID
|
||||
const trackIdForDecryption = decryptionTrackId ? decryptionTrackId.toString() : track.id.toString();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
// Download and decrypt in Rust backend (streaming, no memory accumulation)
|
||||
console.log('Downloading and decrypting track in Rust backend...');
|
||||
await invoke('download_and_decrypt_track', {
|
||||
url: downloadURL,
|
||||
trackId: trackIdForDecryption,
|
||||
outputPath: paths.tempPath,
|
||||
isEncrypted: isCrypted
|
||||
});
|
||||
|
||||
chunks.push(value);
|
||||
downloadedBytes += value.length;
|
||||
|
||||
// Call progress callback every 5%
|
||||
if (onProgress && totalSize > 0) {
|
||||
const percentage = (downloadedBytes / totalSize) * 100;
|
||||
const roundedPercentage = Math.floor(percentage / 5) * 5;
|
||||
|
||||
if (roundedPercentage > lastReportedPercentage || percentage === 100) {
|
||||
lastReportedPercentage = roundedPercentage;
|
||||
onProgress({
|
||||
downloaded: downloadedBytes,
|
||||
total: totalSize,
|
||||
percentage
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combine chunks into single Uint8Array
|
||||
const encryptedData = new Uint8Array(downloadedBytes);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
encryptedData.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
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...');
|
||||
// Use the provided decryption track ID (for fallback tracks) or the original track ID
|
||||
const trackIdForDecryption = decryptionTrackId ? decryptionTrackId.toString() : track.id.toString();
|
||||
console.log(`Decrypting with track ID: ${trackIdForDecryption}`);
|
||||
// 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(decryptedArray);
|
||||
} else {
|
||||
decryptedData = encryptedData;
|
||||
}
|
||||
console.log('Download and decryption complete!');
|
||||
|
||||
// Get user settings
|
||||
const appSettings = get(settings);
|
||||
@@ -151,10 +85,7 @@ export async function downloadTrack(
|
||||
}
|
||||
}
|
||||
|
||||
// Write untagged file to temp first
|
||||
console.log('Writing untagged file to temp...');
|
||||
await writeFile(paths.tempPath, decryptedData);
|
||||
|
||||
// File is already written to temp by Rust backend
|
||||
// Move to final location
|
||||
const finalPath = `${paths.filepath}/${paths.filename}`;
|
||||
console.log('Moving to final location:', finalPath);
|
||||
|
||||
Reference in New Issue
Block a user