From 785f5853d857a5bb6e93f6441e62ecab42cdace6 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 7 Nov 2021 18:40:05 +0100 Subject: [PATCH] Improve thumbnail handling Signed-off-by: trivernis --- mediarepo-ui/src-tauri/Cargo.lock | 10 ++-- mediarepo-ui/src-tauri/Cargo.toml | 2 +- .../file-gallery-entry.component.ts | 15 ++---- .../file-grid-entry.component.ts | 36 ++++++-------- .../src/app/services/file/file.service.ts | 48 ++++++++++++++++++- 5 files changed, 72 insertions(+), 39 deletions(-) diff --git a/mediarepo-ui/src-tauri/Cargo.lock b/mediarepo-ui/src-tauri/Cargo.lock index 92c9cff..6e88a85 100644 --- a/mediarepo-ui/src-tauri/Cargo.lock +++ b/mediarepo-ui/src-tauri/Cargo.lock @@ -1580,8 +1580,8 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mediarepo-api" -version = "0.4.2" -source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=28b25e94eb2cdb8cec86e3e452081a649b8cd64e#28b25e94eb2cdb8cec86e3e452081a649b8cd64e" +version = "0.5.1" +source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=29131ac96de51d2362562aeb16fbfe4e8b0224ff#29131ac96de51d2362562aeb16fbfe4e8b0224ff" dependencies = [ "async-trait", "chrono", @@ -2507,10 +2507,12 @@ dependencies = [ [[package]] name = "rmp-ipc" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b727984a9179fbacb650930ae09537e06d50ce4fd767b0ce8b5a605f332199" +checksum = "f6e9a9202fb951b3ca3088a4edd351774ef154efabb759d6aac2911cc1ae60c1" dependencies = [ + "async-trait", + "byteorder", "lazy_static", "rmp-serde", "serde", diff --git a/mediarepo-ui/src-tauri/Cargo.toml b/mediarepo-ui/src-tauri/Cargo.toml index 959eeca..4b6f5f8 100644 --- a/mediarepo-ui/src-tauri/Cargo.toml +++ b/mediarepo-ui/src-tauri/Cargo.toml @@ -30,7 +30,7 @@ features = ["env-filter"] [dependencies.mediarepo-api] git = "https://github.com/Trivernis/mediarepo-api.git" -rev = "28b25e94eb2cdb8cec86e3e452081a649b8cd64e" +rev = "29131ac96de51d2362562aeb16fbfe4e8b0224ff" features = ["tauri-plugin"] [features] diff --git a/mediarepo-ui/src/app/components/file-gallery/file-gallery-entry/file-gallery-entry.component.ts b/mediarepo-ui/src/app/components/file-gallery/file-gallery-entry/file-gallery-entry.component.ts index f987f69..11005ae 100644 --- a/mediarepo-ui/src/app/components/file-gallery/file-gallery-entry/file-gallery-entry.component.ts +++ b/mediarepo-ui/src/app/components/file-gallery/file-gallery-entry/file-gallery-entry.component.ts @@ -34,8 +34,6 @@ export class FileGalleryEntryComponent implements OnInit, OnChanges { this.cachedFile = this.file.data; this.contentUrl = undefined; await this.loadImage(); - } else if (!this.contentUrl) { - await this.loadImage(); } } @@ -47,17 +45,10 @@ export class FileGalleryEntryComponent implements OnInit, OnChanges { async loadImage() { try { const hash = this.file.data.hash; - const thumbnails = await this.fileService.getThumbnails(this.file.data); - let thumbnail = thumbnails.find( - t => (t.height > 250 || t.width > 250) && (t.height < 500 && t.width < 500)); - thumbnail = thumbnail ?? thumbnails[0]; + const contentUrl = await this.fileService.getFileThumbnail(this.file.data, 250, 250); - if (!thumbnail) { - console.log("Thumbnail is empty?!", thumbnails); - } else if (this.file.data.hash === hash) { - this.contentUrl = await this.fileService.readThumbnail(thumbnail!!); - } else { - console.warn("Grid file updated while loading thumbnail.") + if (this.file.data.hash === hash) { // avoid issues with changed files + this.contentUrl = contentUrl; } } catch (err) { this.errorBroker.showError(err); diff --git a/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.ts b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.ts index a4ba048..33982ff 100644 --- a/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.ts +++ b/mediarepo-ui/src/app/components/file-grid/file-grid-entry/file-grid-entry.component.ts @@ -1,12 +1,12 @@ import { - Component, - ElementRef, - EventEmitter, - Input, - OnChanges, - OnInit, - Output, - ViewChild + Component, + ElementRef, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, SimpleChanges, + ViewChild } from '@angular/core'; import {File} from "../../../models/File"; import {FileService} from "../../../services/file/file.service"; @@ -37,8 +37,8 @@ export class FileGridEntryComponent implements OnInit, OnChanges { await this.loadImage(); } - async ngOnChanges() { - if (!this.cachedFile || this.gridEntry.file.hash !== this.cachedFile.hash) { + async ngOnChanges(changes: SimpleChanges) { + if (changes["file"] && (!this.cachedFile || this.gridEntry.file.hash !== this.cachedFile.hash)) { this.cachedFile = this.gridEntry.file; await this.loadImage(); } @@ -46,18 +46,12 @@ export class FileGridEntryComponent implements OnInit, OnChanges { async loadImage() { try { - const thumbnails = await this.fileService.getThumbnails( - this.gridEntry.file); - let thumbnail = thumbnails.find( - t => (t.height > 250 || t.width > 250) && (t.height < 500 && t.width < 500)); - thumbnail = thumbnail ?? thumbnails[0]; + const hash = this.gridEntry.file.hash; + const contentUrl = await this.fileService.getFileThumbnail(this.gridEntry.file, 250, 250); - if (!thumbnail) { - console.log("Thumbnail is empty?!", thumbnails); - } else { - this.contentUrl = await this.fileService.readThumbnail( - thumbnail!!); - } + if (this.gridEntry.file.hash === hash) { // avoid issues with changed files + this.contentUrl = contentUrl; + } } catch (err) { this.errorBroker.showError(err); } diff --git a/mediarepo-ui/src/app/services/file/file.service.ts b/mediarepo-ui/src/app/services/file/file.service.ts index f58805f..4dfc033 100644 --- a/mediarepo-ui/src/app/services/file/file.service.ts +++ b/mediarepo-ui/src/app/services/file/file.service.ts @@ -13,6 +13,8 @@ import {SortKey} from "../../models/SortKey"; export class FileService { displayedFiles = new BehaviorSubject([]); + pendingThumbnails: {[key:number]: BehaviorSubject} = {}; + thumbnailCache: {[key: number]: Thumbnail[]} = {}; constructor(@Inject(DomSanitizer) private sanitizer: DomSanitizer) { @@ -35,15 +37,59 @@ export class FileService { return this.sanitizer.bypassSecurityTrustResourceUrl(once_uri); } + /** + * Returns the thumbnail for a file with a specific size (allowing +-10%) + * If none can be found it asks the backend if it has one or generates one of that size + * @param {File} file + * @param {number} width + * @param {number} height + * @returns {Promise} + */ + public async getFileThumbnail(file: File, width: number, height: number): Promise { + let subject = this.pendingThumbnails[file.id]; + if (subject && !await subject.toPromise()) { // avoid calling for the same thumbnail multiple times + await subject.toPromise(); + } + subject = new BehaviorSubject(false); + this.pendingThumbnails[file.id] = subject; + setTimeout(() => subject.next(true), 5000); // allow new request after 5 seconds max + const thumbnails = await this.getThumbnails(file); + const thumbnail = thumbnails.find(t => t.height >= height * 0.7 && t.width >= width * 0.7 && t.height <= height * 1.3 && t.width <= width * 1.3); + let url; + + if (thumbnail) { + url = await this.readThumbnail(thumbnail); + } else { + url = await this.getThumbnailOfSize(file, height * 0.9, width * 0.9, height * 1.1, width * 1.1); + delete this.thumbnailCache[file.id]; + } + this.pendingThumbnails[file.id].next(true); + delete this.pendingThumbnails[file.id]; + + return url; + } + public async readThumbnail(thumbnail: Thumbnail): Promise { let once_uri = await invoke("plugin:mediarepo|read_thumbnail", {hash: thumbnail.hash, mimeType: thumbnail.mime_type}); return this.sanitizer.bypassSecurityTrustResourceUrl(once_uri); } + public async getThumbnailOfSize(file: File, minHeight: number, minWidth: number, maxHeight: number, maxWidth: number): Promise { + let once_uri = await invoke("plugin:mediarepo|get_thumbnail_of_size", {fileId: file.id, minSize: [minHeight, minWidth], maxSize: [maxHeight, maxWidth]}); + return this.sanitizer.bypassSecurityTrustResourceUrl(once_uri); + } + public async getThumbnails(file: File): Promise { - return await invoke("plugin:mediarepo|get_file_thumbnails", + const cachedThumbnails = this.thumbnailCache[file.id]; + if (cachedThumbnails) { + return cachedThumbnails; + } + const thumbnails = await invoke("plugin:mediarepo|get_file_thumbnails", {id: file.id}); + this.thumbnailCache[file.id] = thumbnails; + + return thumbnails; } public async updateFileName(file: File, name: string): Promise {