From f53c0f681ff7c5f940b58518bd997296ed05dd2e Mon Sep 17 00:00:00 2001 From: Patrik Braun Date: Sun, 30 Jul 2017 09:06:12 +0200 Subject: [PATCH] implementing search caching --- backend/model/sql/SearchManager.ts | 2 +- common/config/public/ConfigClass.ts | 6 +- frontend/app/gallery/cache.gallery.service.ts | 75 ++++++++++++++++--- frontend/app/gallery/gallery.service.ts | 61 +++++++++++---- .../gallery/share/share.gallery.component.ts | 2 +- frontend/app/settings/settings.service.ts | 2 + 6 files changed, 120 insertions(+), 28 deletions(-) diff --git a/backend/model/sql/SearchManager.ts b/backend/model/sql/SearchManager.ts index dae956bb..7759d795 100644 --- a/backend/model/sql/SearchManager.ts +++ b/backend/model/sql/SearchManager.ts @@ -159,7 +159,7 @@ export class SearchManager implements ISearchManager { result.photos = photos; } - let directories = await connection + const directories = await connection .getRepository(DirectoryEntity) .createQueryBuilder("dir") .where('dir.name LIKE :text COLLATE utf8_general_ci', {text: "%" + text + "%"}) diff --git a/common/config/public/ConfigClass.ts b/common/config/public/ConfigClass.ts index a37906da..24af6c3a 100644 --- a/common/config/public/ConfigClass.ts +++ b/common/config/public/ConfigClass.ts @@ -5,6 +5,8 @@ export module ClientConfig { autocompleteEnabled: boolean InstantSearchTimeout: number; autocompleteCacheTimeout: number; + instantSearchCacheTimeout: number; + searchCacheTimeout: number; } export interface SharingConfig { @@ -54,7 +56,9 @@ export class PublicConfigClass { instantSearchEnabled: true, autocompleteEnabled: true, InstantSearchTimeout: 3000, - autocompleteCacheTimeout: 1000 * 60 * 60 + autocompleteCacheTimeout: 1000 * 60 * 60, + searchCacheTimeout: 1000 * 60 * 60, + instantSearchCacheTimeout: 1000 * 60 * 60 }, Sharing: { enabled: true, diff --git a/frontend/app/gallery/cache.gallery.service.ts b/frontend/app/gallery/cache.gallery.service.ts index 54126280..e2dc5591 100644 --- a/frontend/app/gallery/cache.gallery.service.ts +++ b/frontend/app/gallery/cache.gallery.service.ts @@ -3,42 +3,99 @@ import {PhotoDTO} from "../../../common/entities/PhotoDTO"; import {DirectoryDTO} from "../../../common/entities/DirectoryDTO"; import {Utils} from "../../../common/Utils"; import {Config} from "../../../common/config/public/Config"; -import {AutoCompleteItem} from "../../../common/entities/AutoCompleteItem"; +import {AutoCompleteItem, SearchTypes} from "../../../common/entities/AutoCompleteItem"; +import {SearchResultDTO} from "../../../common/entities/SearchResultDTO"; -interface AutoCompleteCacheItem { +interface CacheItem { timestamp: number; - items: Array; + item: T; } @Injectable() export class GalleryCacheService { private static CONTENT_PREFIX = "content:"; - private static AUTO_COMPLETE_PREFIX = "content:"; + private static AUTO_COMPLETE_PREFIX = "autocomplete:"; + private static INSTANT_SEARCH_PREFIX = "instant_search:"; + private static SEARCH_PREFIX = "search:"; + private static SEARCH_TYPE_PREFIX = ":type:"; public getAutoComplete(text: string): Array { const key = GalleryCacheService.AUTO_COMPLETE_PREFIX + text; const tmp = localStorage.getItem(key); if (tmp != null) { - const value: AutoCompleteCacheItem = JSON.parse(tmp); + const value: CacheItem> = JSON.parse(tmp); if (value.timestamp < Date.now() - Config.Client.Search.autocompleteCacheTimeout) { localStorage.removeItem(key); return null; } - return value.items; + return value.item; } return null; } - public setAutoComplete(text, items: Array): void { - const tmp: AutoCompleteCacheItem = { + public setAutoComplete(text: string, items: Array): void { + const tmp: CacheItem> = { timestamp: Date.now(), - items: items + item: items }; localStorage.setItem(GalleryCacheService.AUTO_COMPLETE_PREFIX + text, JSON.stringify(tmp)); } + public getInstantSearch(text: string): SearchResultDTO { + const key = GalleryCacheService.INSTANT_SEARCH_PREFIX + text; + const tmp = localStorage.getItem(key); + if (tmp != null) { + const value: CacheItem = JSON.parse(tmp); + if (value.timestamp < Date.now() - Config.Client.Search.instantSearchCacheTimeout) { + localStorage.removeItem(key); + return null; + } + return value.item; + } + return null; + } + + public setInstantSearch(text: string, searchResult: SearchResultDTO): void { + const tmp: CacheItem = { + timestamp: Date.now(), + item: searchResult + }; + localStorage.setItem(GalleryCacheService.INSTANT_SEARCH_PREFIX + text, JSON.stringify(tmp)); + } + + + public getSearch(text: string, type?: SearchTypes): SearchResultDTO { + let key = GalleryCacheService.SEARCH_PREFIX + text; + if (typeof type != "undefined") { + key += GalleryCacheService.SEARCH_TYPE_PREFIX + type; + } + const tmp = localStorage.getItem(key); + if (tmp != null) { + const value: CacheItem = JSON.parse(tmp); + if (value.timestamp < Date.now() - Config.Client.Search.searchCacheTimeout) { + localStorage.removeItem(key); + return null; + } + return value.item; + } + return null; + } + + public setSearch(text: string, type: SearchTypes, searchResult: SearchResultDTO): void { + const tmp: CacheItem = { + timestamp: Date.now(), + item: searchResult + }; + let key = GalleryCacheService.SEARCH_PREFIX + text; + if (typeof type != "undefined") { + key += GalleryCacheService.SEARCH_TYPE_PREFIX + type; + } + localStorage.setItem(key, JSON.stringify(tmp)); + } + + public getDirectory(directoryName: string): DirectoryDTO { if (Config.Client.enableCache == false) { return null; diff --git a/frontend/app/gallery/gallery.service.ts b/frontend/app/gallery/gallery.service.ts index ad8cf3de..0bc63a1f 100644 --- a/frontend/app/gallery/gallery.service.ts +++ b/frontend/app/gallery/gallery.service.ts @@ -74,43 +74,72 @@ export class GalleryService { } - //TODO: cache public async search(text: string, type?: SearchTypes): Promise { - clearTimeout(this.searchId); + if (this.searchId != null) { + clearTimeout(this.searchId); + } if (text === null || text === '' || text.trim() == ".") { return null } this.content.next(new ContentWrapper()); - const cw: ContentWrapper = await this.networkService.getJson("/search/" + text, {type: type}); - console.log("photos", cw.searchResult.photos.length); - console.log("direcotries", cw.searchResult.directories.length); + const cw = new ContentWrapper(); + cw.searchResult = this.galleryCacheService.getSearch(text, type); + if (cw.searchResult == null) { + const params = {}; + if (typeof type != "undefined") { + params['type'] = type; + } + cw.searchResult = (await this.networkService.getJson("/search/" + text, params)).searchResult; + this.galleryCacheService.setSearch(text, type, cw.searchResult); + } this.content.next(cw); return cw; } - //TODO: cache (together with normal search) public async instantSearch(text: string): Promise { if (text === null || text === '' || text.trim() == ".") { - const content = new ContentWrapper(); - content.directory = this.lastDirectory; - content.searchResult = null; + const content = new ContentWrapper(this.lastDirectory); this.content.next(content); - clearTimeout(this.searchId); + if (this.searchId != null) { + clearTimeout(this.searchId); + } + if (!this.lastDirectory) { + this.getDirectory("/"); + } return null } if (this.searchId != null) { clearTimeout(this.searchId); - } - this.searchId = setTimeout(() => { - this.search(text); - this.searchId = null; - }, Config.Client.Search.InstantSearchTimeout); - const cw = await this.networkService.getJson("/instant-search/" + text); + + const cw = new ContentWrapper(); + cw.directory = null; + cw.searchResult = this.galleryCacheService.getSearch(text); + if (cw.searchResult == null) { + //If result is not search cache, try to load load more + this.searchId = setTimeout(() => { + this.search(text); + this.searchId = null; + }, Config.Client.Search.InstantSearchTimeout); + + cw.searchResult = this.galleryCacheService.getInstantSearch(text); + + if (cw.searchResult == null) { + cw.searchResult = (await this.networkService.getJson("/instant-search/" + text)).searchResult; + this.galleryCacheService.setInstantSearch(text, cw.searchResult); + } + } this.content.next(cw); + + //if instant search do not have a result, do not do a search + if (cw.searchResult.photos.length == 0 && cw.searchResult.directories.length == 0) { + if (this.searchId != null) { + clearTimeout(this.searchId); + } + } return cw; } diff --git a/frontend/app/gallery/share/share.gallery.component.ts b/frontend/app/gallery/share/share.gallery.component.ts index 883ace8d..6914af83 100644 --- a/frontend/app/gallery/share/share.gallery.component.ts +++ b/frontend/app/gallery/share/share.gallery.component.ts @@ -100,7 +100,7 @@ export class GalleryShareComponent implements OnInit, OnDestroy { this._notification.success("Url has been copied to clipboard"); } - hodiModel() { + public hodeModal() { this.childModal.hide(); this.sharing = null; } diff --git a/frontend/app/settings/settings.service.ts b/frontend/app/settings/settings.service.ts index 0a11b698..67686b36 100644 --- a/frontend/app/settings/settings.service.ts +++ b/frontend/app/settings/settings.service.ts @@ -20,6 +20,8 @@ export class SettingsService { autocompleteEnabled: true, instantSearchEnabled: true, InstantSearchTimeout: 0, + searchCacheTimeout: 1000 * 60 * 60, + instantSearchCacheTimeout: 1000 * 60 * 60, autocompleteCacheTimeout: 1000 * 60 * 60 }, concurrentThumbnailGenerations: null,