forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			108 lines
		
	
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			108 lines
		
	
	
	
		
			3.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { IdbLocalStorage } from "../../Web/IdbLocalStorage"
 | |
| import { UIEventSource } from "../../UIEventSource"
 | |
| 
 | |
| /**
 | |
|  * A class which allows to read/write a tile to local storage.
 | |
|  *
 | |
|  * Does the heavy lifting for LocalStorageFeatureSource and SaveFeatureToLocalStorage.
 | |
|  *
 | |
|  * Note: OSM-features with a negative id are ignored
 | |
|  */
 | |
| export default class TileLocalStorage<T> {
 | |
|     private static perLayer: Record<string, TileLocalStorage<any>> = {}
 | |
|     private static readonly useIndexedDb = typeof indexedDB !== "undefined"
 | |
|     private readonly _layername: string
 | |
|     private readonly inUse = new UIEventSource(false)
 | |
|     private readonly cachedSources: Record<number, UIEventSource<T> & { flush: () => void }> = {}
 | |
|     private readonly _maxAgeSeconds: number
 | |
| 
 | |
|     private constructor(layername: string, maxAgeSeconds: number) {
 | |
|         this._layername = layername
 | |
|         this._maxAgeSeconds = maxAgeSeconds
 | |
|     }
 | |
| 
 | |
|     public static construct<T>(
 | |
|         backend: string,
 | |
|         layername: string,
 | |
|         maxAgeS: number
 | |
|     ): TileLocalStorage<T> {
 | |
|         const key = backend + "_" + layername
 | |
|         const cached = TileLocalStorage.perLayer[key]
 | |
|         if (cached) {
 | |
|             return cached
 | |
|         }
 | |
| 
 | |
|         const tls = new TileLocalStorage<T>(key, maxAgeS)
 | |
|         TileLocalStorage.perLayer[key] = tls
 | |
|         return tls
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Constructs a UIEventSource element which is synced with localStorage.
 | |
|      * Supports 'flush'
 | |
|      */
 | |
|     public getTileSource(tileIndex: number): UIEventSource<T> & { flush: () => void } {
 | |
|         const cached = this.cachedSources[tileIndex]
 | |
|         if (cached) {
 | |
|             return cached
 | |
|         }
 | |
|         const src = <UIEventSource<T> & { flush: () => void }>(
 | |
|             UIEventSource.FromPromise(this.GetIdb(tileIndex))
 | |
|         )
 | |
|         src.flush = () => this.SetIdb(tileIndex, src.data)
 | |
|         src.addCallbackD((data) => this.SetIdb(tileIndex, data))
 | |
|         this.cachedSources[tileIndex] = src
 | |
|         return src
 | |
|     }
 | |
| 
 | |
|     private async SetIdb(tileIndex: number, data: any): Promise<void> {
 | |
|         if (!TileLocalStorage.useIndexedDb) {
 | |
|             return
 | |
|         }
 | |
|         try {
 | |
|             await this.inUse.AsPromise((inUse) => !inUse)
 | |
|             this.inUse.setData(true)
 | |
|             await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data)
 | |
|             await IdbLocalStorage.SetDirectly(
 | |
|                 this._layername + "_" + tileIndex + "_date",
 | |
|                 Date.now()
 | |
|             )
 | |
| 
 | |
|             this.inUse.setData(false)
 | |
|         } catch (e) {
 | |
|             console.error(
 | |
|                 "Could not save tile to indexed-db: ",
 | |
|                 e,
 | |
|                 "tileIndex is:",
 | |
|                 tileIndex,
 | |
|                 "for layer",
 | |
|                 this._layername,
 | |
|                 "data is",
 | |
|                 data
 | |
|             )
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private async GetIdb(tileIndex: number): Promise<any> {
 | |
|         if (!TileLocalStorage.useIndexedDb) {
 | |
|             return undefined
 | |
|         }
 | |
|         const date = <any>(
 | |
|             await IdbLocalStorage.GetDirectly(this._layername + "_" + tileIndex + "_date")
 | |
|         )
 | |
|         const maxAge = this._maxAgeSeconds
 | |
|         const timeDiff = Date.now() - date
 | |
|         if (timeDiff >= maxAge) {
 | |
|             console.debug("Dropping cache for", this._layername, tileIndex, "out of date")
 | |
|             await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, undefined)
 | |
| 
 | |
|             return undefined
 | |
|         }
 | |
|         const data = await IdbLocalStorage.GetDirectly(this._layername + "_" + tileIndex)
 | |
|         return <any>data
 | |
|     }
 | |
| 
 | |
|     invalidate(zoomlevel: number, tileIndex) {
 | |
|         this.getTileSource(tileIndex).setData(undefined)
 | |
|     }
 | |
| }
 |