Download Refactor (#483)
# Added - New: Cards when processing a download shows a spinner for the progress of the download # Changed - Changed: Downloads now always take the backend filename and are streamed in a more optimal manner, reducing the javascript processing that was needed previously. ================================== * Started refactor of downloader to be more UX friendly and much faster. * Completed refactor of Volume download to use a new mechanism. Downloads are streamed over and filename used exclusively from header. Backend has additional DB calls to get the Series Name information to make filenames nice. * download service has been updated so all download functions use new event based observable. Duplicates code for downloading, but much cleaner and faster. * Small code cleanup
This commit is contained in:
parent
855f452d14
commit
89b68bc301
20 changed files with 439 additions and 92 deletions
75
UI/Web/src/app/shared/_models/download.ts
Normal file
75
UI/Web/src/app/shared/_models/download.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import {
|
||||
HttpEvent,
|
||||
HttpEventType,
|
||||
HttpHeaders,
|
||||
HttpProgressEvent,
|
||||
HttpResponse
|
||||
} from "@angular/common/http";
|
||||
import { Observable } from "rxjs";
|
||||
import { distinctUntilChanged, scan, map, tap } from "rxjs/operators";
|
||||
|
||||
function isHttpResponse<T>(event: HttpEvent<T>): event is HttpResponse<T> {
|
||||
return event.type === HttpEventType.Response;
|
||||
}
|
||||
|
||||
function isHttpProgressEvent(
|
||||
event: HttpEvent<unknown>
|
||||
): event is HttpProgressEvent {
|
||||
return (
|
||||
event.type === HttpEventType.DownloadProgress ||
|
||||
event.type === HttpEventType.UploadProgress
|
||||
);
|
||||
}
|
||||
|
||||
export interface Download {
|
||||
content: Blob | null;
|
||||
progress: number;
|
||||
state: "PENDING" | "IN_PROGRESS" | "DONE";
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
export function download(saver?: (b: Blob, filename: string) => void): (source: Observable<HttpEvent<Blob>>) => Observable<Download> {
|
||||
return (source: Observable<HttpEvent<Blob>>) =>
|
||||
source.pipe(
|
||||
scan((previous: Download, event: HttpEvent<Blob>): Download => {
|
||||
if (isHttpProgressEvent(event)) {
|
||||
return {
|
||||
progress: event.total
|
||||
? Math.round((100 * event.loaded) / event.total)
|
||||
: previous.progress,
|
||||
state: 'IN_PROGRESS',
|
||||
content: null
|
||||
}
|
||||
}
|
||||
if (isHttpResponse(event)) {
|
||||
if (saver && event.body) {
|
||||
saver(event.body, getFilename(event.headers, ''))
|
||||
}
|
||||
return {
|
||||
progress: 100,
|
||||
state: 'DONE',
|
||||
content: event.body,
|
||||
filename: getFilename(event.headers, '')
|
||||
}
|
||||
}
|
||||
return previous;
|
||||
},
|
||||
{state: 'PENDING', progress: 0, content: null}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function getFilename(headers: HttpHeaders, defaultName: string) {
|
||||
const tokens = (headers.get('content-disposition') || '').split(';');
|
||||
let filename = tokens[1].replace('filename=', '').replace(/"/ig, '').trim();
|
||||
if (filename.startsWith('download_') || filename.startsWith('kavita_download_')) {
|
||||
const ext = filename.substring(filename.lastIndexOf('.'), filename.length);
|
||||
if (defaultName !== '') {
|
||||
return defaultName + ext;
|
||||
}
|
||||
return filename.replace('kavita_', '').replace('download_', '');
|
||||
//return defaultName + ext;
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue