| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- const STORAGE_KEY = 'cmr-tile-disk-cache-index-v1'
- const ROOT_DIR = `${wx.env.USER_DATA_PATH}/cmr-tile-cache`
- const MAX_PERSISTED_TILES = 600
- const PERSIST_DELAY_MS = 300
- export interface TilePersistentCacheRecord {
- filePath: string
- lastAccessedAt: number
- }
- type TilePersistentCacheIndex = Record<string, TilePersistentCacheRecord>
- let sharedTilePersistentCache: TilePersistentCache | null = null
- function getFileExtension(url: string): string {
- const matched = url.match(/\.(png|jpg|jpeg|webp)(?:$|\?)/i)
- if (!matched) {
- return '.tile'
- }
- return `.${matched[1].toLowerCase()}`
- }
- function hashUrl(url: string): string {
- let hash = 2166136261
- for (let index = 0; index < url.length; index += 1) {
- hash ^= url.charCodeAt(index)
- hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24)
- }
- return (hash >>> 0).toString(36)
- }
- function cloneIndex(rawIndex: any): TilePersistentCacheIndex {
- const nextIndex: TilePersistentCacheIndex = {}
- if (!rawIndex || typeof rawIndex !== 'object') {
- return nextIndex
- }
- Object.keys(rawIndex).forEach((url) => {
- const record = rawIndex[url]
- if (!record || typeof record.filePath !== 'string') {
- return
- }
- nextIndex[url] = {
- filePath: record.filePath,
- lastAccessedAt: typeof record.lastAccessedAt === 'number' ? record.lastAccessedAt : 0,
- }
- })
- return nextIndex
- }
- export class TilePersistentCache {
- fs: WechatMiniprogram.FileSystemManager
- rootDir: string
- index: TilePersistentCacheIndex
- persistTimer: number
- constructor() {
- this.fs = wx.getFileSystemManager()
- this.rootDir = ROOT_DIR
- this.index = {}
- this.persistTimer = 0
- this.ensureRootDir()
- this.loadIndex()
- this.pruneMissingFiles()
- this.pruneIfNeeded()
- }
- ensureRootDir(): void {
- try {
- this.fs.accessSync(this.rootDir)
- } catch {
- this.fs.mkdirSync(this.rootDir, true)
- }
- }
- loadIndex(): void {
- try {
- this.index = cloneIndex(wx.getStorageSync(STORAGE_KEY))
- } catch {
- this.index = {}
- }
- }
- getCount(): number {
- return Object.keys(this.index).length
- }
- schedulePersist(): void {
- if (this.persistTimer) {
- return
- }
- this.persistTimer = setTimeout(() => {
- this.persistTimer = 0
- wx.setStorageSync(STORAGE_KEY, this.index)
- }, PERSIST_DELAY_MS) as unknown as number
- }
- pruneMissingFiles(): void {
- let changed = false
- Object.keys(this.index).forEach((url) => {
- const record = this.index[url]
- try {
- this.fs.accessSync(record.filePath)
- } catch {
- delete this.index[url]
- changed = true
- }
- })
- if (changed) {
- this.schedulePersist()
- }
- }
- getCachedPath(url: string): string | null {
- const record = this.index[url]
- if (!record) {
- return null
- }
- try {
- this.fs.accessSync(record.filePath)
- record.lastAccessedAt = Date.now()
- this.schedulePersist()
- return record.filePath
- } catch {
- delete this.index[url]
- this.schedulePersist()
- return null
- }
- }
- getTargetPath(url: string): string {
- const existingRecord = this.index[url]
- if (existingRecord) {
- existingRecord.lastAccessedAt = Date.now()
- this.schedulePersist()
- return existingRecord.filePath
- }
- const filePath = `${this.rootDir}/${hashUrl(url)}${getFileExtension(url)}`
- this.index[url] = {
- filePath,
- lastAccessedAt: Date.now(),
- }
- this.schedulePersist()
- return filePath
- }
- markReady(url: string, filePath: string): void {
- this.index[url] = {
- filePath,
- lastAccessedAt: Date.now(),
- }
- this.pruneIfNeeded()
- this.schedulePersist()
- }
- remove(url: string): void {
- const record = this.index[url]
- if (!record) {
- return
- }
- try {
- this.fs.unlinkSync(record.filePath)
- } catch {
- // Ignore unlink errors for already-missing files.
- }
- delete this.index[url]
- this.schedulePersist()
- }
- pruneIfNeeded(): void {
- const urls = Object.keys(this.index)
- if (urls.length <= MAX_PERSISTED_TILES) {
- return
- }
- const removableUrls = urls.sort((leftUrl, rightUrl) => {
- return this.index[leftUrl].lastAccessedAt - this.index[rightUrl].lastAccessedAt
- })
- while (removableUrls.length > MAX_PERSISTED_TILES) {
- const nextUrl = removableUrls.shift() as string
- const record = this.index[nextUrl]
- if (record) {
- try {
- this.fs.unlinkSync(record.filePath)
- } catch {
- // Ignore unlink errors for already-missing files.
- }
- }
- delete this.index[nextUrl]
- }
- }
- }
- export function getTilePersistentCache(): TilePersistentCache {
- if (!sharedTilePersistentCache) {
- sharedTilePersistentCache = new TilePersistentCache()
- }
- return sharedTilePersistentCache
- }
|