mapcomplete/Logic/FeatureSource/Actors/TileLocalStorage.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

109 lines
3.6 KiB
TypeScript
Raw Normal View History

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