forked from akai/readest
This commit is contained in:
@@ -110,7 +110,7 @@ pub async fn download_file(
|
||||
single_threaded: Option<bool>,
|
||||
skip_ssl_verification: Option<bool>,
|
||||
on_progress: Channel<ProgressPayload>,
|
||||
) -> Result<()> {
|
||||
) -> Result<HashMap<String, String>> {
|
||||
use futures::stream::{self, StreamExt};
|
||||
use std::cmp::min;
|
||||
use tokio::io::AsyncSeekExt;
|
||||
@@ -130,7 +130,7 @@ pub async fn download_file(
|
||||
headers: &HashMap<String, String>,
|
||||
body: &Option<String>,
|
||||
on_progress: Channel<ProgressPayload>,
|
||||
) -> Result<()> {
|
||||
) -> Result<HashMap<String, String>> {
|
||||
let mut request = if let Some(body) = body {
|
||||
client.post(url).body(body.clone())
|
||||
} else {
|
||||
@@ -149,6 +149,13 @@ pub async fn download_file(
|
||||
));
|
||||
}
|
||||
|
||||
let mut resp_headers = HashMap::new();
|
||||
for (key, value) in response.headers().iter() {
|
||||
if let Ok(val_str) = value.to_str() {
|
||||
resp_headers.insert(key.to_string(), val_str.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let total = response.content_length().unwrap_or(0);
|
||||
let mut file = BufWriter::new(File::create(file_path).await?);
|
||||
let mut stream = response.bytes_stream();
|
||||
@@ -165,7 +172,7 @@ pub async fn download_file(
|
||||
}
|
||||
file.flush().await?;
|
||||
|
||||
Ok(())
|
||||
Ok(resp_headers)
|
||||
}
|
||||
|
||||
if force_single {
|
||||
@@ -193,6 +200,13 @@ pub async fn download_file(
|
||||
.and_then(|s| s.parse::<u64>().ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut resp_headers = HashMap::new();
|
||||
for (key, value) in range_resp.headers().iter() {
|
||||
if let Ok(val_str) = value.to_str() {
|
||||
resp_headers.insert(key.to_string(), val_str.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if !accept_ranges || total == 0 {
|
||||
return single_threaded_download(&client, url, file_path, &headers, &body, on_progress)
|
||||
.await;
|
||||
@@ -260,7 +274,7 @@ pub async fn download_file(
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
Ok(resp_headers)
|
||||
}
|
||||
|
||||
#[command]
|
||||
|
||||
@@ -120,6 +120,7 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
return new NextResponse(response.body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
...Object.fromEntries(response.headers.entries()),
|
||||
'Content-Type': contentType,
|
||||
'X-Content-Length': contentLength || '',
|
||||
'Cache-Control': 'public, max-age=300',
|
||||
@@ -137,6 +138,7 @@ async function handleRequest(request: NextRequest, method: 'GET' | 'HEAD') {
|
||||
return new NextResponse(buf, {
|
||||
status: 200,
|
||||
headers: {
|
||||
...Object.fromEntries(response.headers.entries()),
|
||||
'Content-Type': contentType,
|
||||
'Content-Length': length.toString(),
|
||||
'Cache-Control': 'public, max-age=300',
|
||||
|
||||
@@ -436,14 +436,14 @@ export default function BrowserPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const probedFilename = await probeFilename(url, useProxy, headers);
|
||||
const pathname = decodeURIComponent(new URL(url).pathname);
|
||||
const ext = getFileExtFromMimeType(parsed?.mediaType) || getFileExtFromPath(pathname);
|
||||
const basename = pathname.replaceAll('/', '_');
|
||||
const filename = probedFilename ? probedFilename : ext ? `${basename}.${ext}` : basename;
|
||||
const dstFilePath = await appService?.resolveFilePath(filename, 'Cache');
|
||||
console.log('Downloading to:', dstFilePath);
|
||||
await downloadFile({
|
||||
const filename = ext ? `${basename}.${ext}` : basename;
|
||||
let dstFilePath = await appService?.resolveFilePath(filename, 'Cache');
|
||||
console.log('Downloading to:', url, dstFilePath);
|
||||
|
||||
const responseHeaders = await downloadFile({
|
||||
appService,
|
||||
dst: dstFilePath,
|
||||
cfp: '',
|
||||
@@ -453,6 +453,15 @@ export default function BrowserPage() {
|
||||
skipSslVerification: true,
|
||||
onProgress,
|
||||
});
|
||||
const probedFilename = await probeFilename(responseHeaders);
|
||||
if (probedFilename) {
|
||||
const newFilePath = await appService?.resolveFilePath(probedFilename, 'Cache');
|
||||
await appService?.copyFile(dstFilePath, newFilePath, 'None');
|
||||
await appService?.deleteFile(dstFilePath, 'None');
|
||||
console.log('Renamed downloaded file to:', newFilePath);
|
||||
dstFilePath = newFilePath;
|
||||
}
|
||||
|
||||
const { library, setLibrary } = useLibraryStore.getState();
|
||||
const book = await appService.importBook(dstFilePath, library);
|
||||
if (user && book && !book.uploadedAt && settings.autoUpload) {
|
||||
|
||||
@@ -245,40 +245,19 @@ export const probeAuth = async (
|
||||
return createBasicAuth(finalUsername, finalPassword);
|
||||
};
|
||||
|
||||
export const probeFilename = async (
|
||||
url: string,
|
||||
useProxy = false,
|
||||
headers?: Record<string, string>,
|
||||
) => {
|
||||
const { url: cleanUrl } = extractCredentialsFromURL(url);
|
||||
export const probeFilename = async (headers: Record<string, string>) => {
|
||||
const contentDisposition = headers['content-disposition'];
|
||||
if (contentDisposition) {
|
||||
const extendedMatch = contentDisposition.match(
|
||||
/filename\*\s*=\s*(?:utf-8|UTF-8)'[^']*'([^;\s]+)/i,
|
||||
);
|
||||
if (extendedMatch?.[1]) {
|
||||
return decodeURIComponent(extendedMatch[1]);
|
||||
}
|
||||
|
||||
const fetchURL = useProxy ? getProxiedURL(cleanUrl) : cleanUrl;
|
||||
const baseHeaders: Record<string, string> = {
|
||||
'User-Agent': READEST_OPDS_USER_AGENT,
|
||||
Accept: 'application/atom+xml, application/xml, text/xml, */*',
|
||||
};
|
||||
|
||||
const fetch = isTauriAppPlatform() ? tauriFetch : window.fetch;
|
||||
const res = await fetch(fetchURL, {
|
||||
method: 'HEAD',
|
||||
headers: { ...baseHeaders, ...(headers || {}) },
|
||||
danger: { acceptInvalidCerts: true, acceptInvalidHostnames: true },
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const contentDisposition = res.headers.get('content-disposition');
|
||||
if (contentDisposition) {
|
||||
const extendedMatch = contentDisposition.match(
|
||||
/filename\*\s*=\s*(?:utf-8|UTF-8)'[^']*'([^;\s]+)/i,
|
||||
);
|
||||
if (extendedMatch?.[1]) {
|
||||
return decodeURIComponent(extendedMatch[1]);
|
||||
}
|
||||
|
||||
const plainMatch = contentDisposition.match(/filename\s*=\s*["']?([^"';\s]+)["']?/i);
|
||||
if (plainMatch?.[1]) {
|
||||
return decodeURIComponent(plainMatch[1]);
|
||||
}
|
||||
const plainMatch = contentDisposition.match(/filename\s*=\s*["']?([^"';\s]+)["']?/i);
|
||||
if (plainMatch?.[1]) {
|
||||
return decodeURIComponent(plainMatch[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,10 +149,15 @@ export const downloadFile = async ({
|
||||
}
|
||||
|
||||
if (isWebAppPlatform()) {
|
||||
const file = await webDownload(downloadUrl, onProgress, headers);
|
||||
await appService.writeFile(dst, 'None', await file.arrayBuffer());
|
||||
const { headers: responseHeaders, blob } = await webDownload(
|
||||
downloadUrl,
|
||||
onProgress,
|
||||
headers,
|
||||
);
|
||||
await appService.writeFile(dst, 'None', await blob.arrayBuffer());
|
||||
return responseHeaders;
|
||||
} else {
|
||||
await tauriDownload(
|
||||
return await tauriDownload(
|
||||
downloadUrl,
|
||||
dst,
|
||||
onProgress,
|
||||
|
||||
@@ -61,6 +61,7 @@ export const webDownload = async (
|
||||
throw new Error(UploadFileError.DownloadFailed);
|
||||
}
|
||||
|
||||
const responseHeaders = Object.fromEntries(response.headers.entries());
|
||||
const contentLength =
|
||||
response.headers.get('Content-Length') || response.headers.get('X-Content-Length');
|
||||
if (!contentLength) throw new Error('Cannot track progress: Content-Length missing');
|
||||
@@ -87,7 +88,7 @@ export const webDownload = async (
|
||||
}
|
||||
}
|
||||
|
||||
return new Blob(chunks as BlobPart[]);
|
||||
return { headers: responseHeaders, blob: new Blob(chunks as BlobPart[]) };
|
||||
};
|
||||
|
||||
export const tauriUpload = async (
|
||||
@@ -124,7 +125,7 @@ export const tauriDownload = async (
|
||||
body?: string,
|
||||
singleThreaded?: boolean,
|
||||
skipSslVerification?: boolean,
|
||||
): Promise<void> => {
|
||||
): Promise<Record<string, string>> => {
|
||||
const ids = new Uint32Array(1);
|
||||
window.crypto.getRandomValues(ids);
|
||||
const id = ids[0];
|
||||
@@ -134,7 +135,7 @@ export const tauriDownload = async (
|
||||
onProgress.onmessage = progressHandler;
|
||||
}
|
||||
|
||||
await invoke('download_file', {
|
||||
const responseHeaders = await invoke<Record<string, string>>('download_file', {
|
||||
id,
|
||||
url,
|
||||
filePath,
|
||||
@@ -144,4 +145,5 @@ export const tauriDownload = async (
|
||||
singleThreaded,
|
||||
skipSslVerification,
|
||||
});
|
||||
return responseHeaders;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user