@xyo-network/image-thumbnail-plugin 2.75.5 → 2.75.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs.json +4946 -4911
- package/dist/node/Diviner/Config.d.cts +23 -0
- package/dist/node/Diviner/Config.d.cts.map +1 -1
- package/dist/node/Diviner/Config.d.mts +23 -0
- package/dist/node/Diviner/Config.d.mts.map +1 -1
- package/dist/node/Diviner/Config.d.ts +23 -0
- package/dist/node/Diviner/Config.d.ts.map +1 -1
- package/dist/node/Diviner/Config.js.map +1 -1
- package/dist/node/Diviner/Config.mjs.map +1 -1
- package/dist/node/Diviner/Diviner.d.cts +38 -20
- package/dist/node/Diviner/Diviner.d.cts.map +1 -1
- package/dist/node/Diviner/Diviner.d.mts +38 -20
- package/dist/node/Diviner/Diviner.d.mts.map +1 -1
- package/dist/node/Diviner/Diviner.d.ts +38 -20
- package/dist/node/Diviner/Diviner.d.ts.map +1 -1
- package/dist/node/Diviner/Diviner.js +169 -139
- package/dist/node/Diviner/Diviner.js.map +1 -1
- package/dist/node/Diviner/Diviner.mjs +168 -138
- package/dist/node/Diviner/Diviner.mjs.map +1 -1
- package/dist/node/Diviner/index.js +169 -139
- package/dist/node/Diviner/index.js.map +1 -1
- package/dist/node/Diviner/index.mjs +168 -138
- package/dist/node/Diviner/index.mjs.map +1 -1
- package/dist/node/Plugin.d.cts +3 -0
- package/dist/node/Plugin.d.cts.map +1 -1
- package/dist/node/Plugin.d.mts +3 -0
- package/dist/node/Plugin.d.mts.map +1 -1
- package/dist/node/Plugin.d.ts +3 -0
- package/dist/node/Plugin.d.ts.map +1 -1
- package/dist/node/Plugin.js +175 -145
- package/dist/node/Plugin.js.map +1 -1
- package/dist/node/Plugin.mjs +172 -142
- package/dist/node/Plugin.mjs.map +1 -1
- package/dist/node/index.js +175 -145
- package/dist/node/index.js.map +1 -1
- package/dist/node/index.mjs +172 -142
- package/dist/node/index.mjs.map +1 -1
- package/package.json +22 -16
- package/src/Diviner/Config.ts +24 -0
- package/src/Diviner/Diviner.ts +233 -158
package/src/Diviner/Config.ts
CHANGED
|
@@ -4,10 +4,34 @@ import { ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugi
|
|
|
4
4
|
export const ImageThumbnailDivinerConfigSchema = `${ImageThumbnailSchema}.diviner.config` as const
|
|
5
5
|
export type ImageThumbnailDivinerConfigSchema = typeof ImageThumbnailDivinerConfigSchema
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Describes an Archivist/Diviner combination
|
|
9
|
+
* that enables searching signed payloads
|
|
10
|
+
*/
|
|
11
|
+
export interface SearchableStorage {
|
|
12
|
+
archivist: string
|
|
13
|
+
boundWitnessDiviner: string
|
|
14
|
+
payloadDiviner: string
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
export type ImageThumbnailDivinerConfig = DivinerConfig<{
|
|
18
|
+
/** @deprecated Use appropriate Storage */
|
|
8
19
|
archivist?: string
|
|
20
|
+
/**
|
|
21
|
+
* Where the diviner should store it's index
|
|
22
|
+
*/
|
|
23
|
+
indexStore?: SearchableStorage
|
|
24
|
+
/** @deprecated Use appropriate Storage */
|
|
9
25
|
payloadDiviner?: string
|
|
10
26
|
payloadDivinerLimit?: number
|
|
11
27
|
pollFrequency?: number
|
|
12
28
|
schema: ImageThumbnailDivinerConfigSchema
|
|
29
|
+
/**
|
|
30
|
+
* Where the diviner should persist its internal state
|
|
31
|
+
*/
|
|
32
|
+
stateStore?: SearchableStorage
|
|
33
|
+
/**
|
|
34
|
+
* Where the diviner should look for stored thumbnails
|
|
35
|
+
*/
|
|
36
|
+
thumbnailStore?: SearchableStorage
|
|
13
37
|
}>
|
package/src/Diviner/Diviner.ts
CHANGED
|
@@ -1,159 +1,259 @@
|
|
|
1
1
|
import { assertEx } from '@xylabs/assert'
|
|
2
|
-
import {
|
|
3
|
-
import { compact } from '@xylabs/lodash'
|
|
2
|
+
import { exists } from '@xylabs/exists'
|
|
4
3
|
import { AbstractDiviner } from '@xyo-network/abstract-diviner'
|
|
5
|
-
import {
|
|
4
|
+
import { asArchivistInstance, withArchivistModule } from '@xyo-network/archivist-model'
|
|
5
|
+
import { ArchivistWrapper } from '@xyo-network/archivist-wrapper'
|
|
6
|
+
import { isBoundWitness } from '@xyo-network/boundwitness-model'
|
|
6
7
|
import { PayloadHasher } from '@xyo-network/core'
|
|
7
|
-
import {
|
|
8
|
+
import { BoundWitnessDivinerQueryPayload, BoundWitnessDivinerQuerySchema } from '@xyo-network/diviner-boundwitness-model'
|
|
9
|
+
import { asDivinerInstance, DivinerConfigSchema } from '@xyo-network/diviner-model'
|
|
8
10
|
import { PayloadDivinerQueryPayload, PayloadDivinerQuerySchema } from '@xyo-network/diviner-payload-model'
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
+
import { DivinerWrapper } from '@xyo-network/diviner-wrapper'
|
|
12
|
+
import { ImageThumbnailSchema, isImageThumbnail } from '@xyo-network/image-thumbnail-payload-plugin'
|
|
13
|
+
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
14
|
+
import { isPayloadOfSchemaType, Payload } from '@xyo-network/payload-model'
|
|
15
|
+
import { isUrlPayload } from '@xyo-network/url-payload-plugin'
|
|
16
|
+
import { isTimestamp, TimestampSchema } from '@xyo-network/witness-timestamp'
|
|
11
17
|
|
|
12
|
-
import { ImageThumbnailDivinerConfigSchema } from './Config'
|
|
18
|
+
import { ImageThumbnailDivinerConfig, ImageThumbnailDivinerConfigSchema } from './Config'
|
|
13
19
|
import { ImageThumbnailDivinerParams } from './Params'
|
|
14
20
|
|
|
21
|
+
/**
|
|
22
|
+
* TODO: Once the shape settles, make a generic payload so that it
|
|
23
|
+
* can be used for other modules
|
|
24
|
+
*/
|
|
25
|
+
interface State<T> {
|
|
26
|
+
state: T
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ImageThumbnailDivinerState {
|
|
30
|
+
offset: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ModuleStateSchema = 'network.xyo.module.state' as const
|
|
34
|
+
type ModuleStateSchema = typeof ModuleStateSchema
|
|
35
|
+
|
|
36
|
+
type ModuleState = Payload<State<ImageThumbnailDivinerState>, ModuleStateSchema>
|
|
37
|
+
|
|
38
|
+
const isModuleState = isPayloadOfSchemaType<ModuleState>(ModuleStateSchema)
|
|
39
|
+
|
|
40
|
+
type ConfigStoreKey = 'indexStore' | 'stateStore' | 'thumbnailStore'
|
|
41
|
+
|
|
42
|
+
type ConfigStore = Extract<keyof ImageThumbnailDivinerConfig, ConfigStoreKey>
|
|
43
|
+
|
|
44
|
+
const ImageThumbnailResultIndexSchema = `${ImageThumbnailSchema}.index` as const
|
|
45
|
+
type ImageThumbnailResultIndexSchema = typeof ImageThumbnailResultIndexSchema
|
|
46
|
+
|
|
47
|
+
interface ImageThumbnailResultInfo {
|
|
48
|
+
sources: string[]
|
|
49
|
+
// TODO: Something richer than HTTP status code that allows for info about failure modes
|
|
50
|
+
status: number
|
|
51
|
+
timestamp: number
|
|
52
|
+
url: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type ImageThumbnailResult = Payload<ImageThumbnailResultInfo, ImageThumbnailResultIndexSchema>
|
|
56
|
+
|
|
57
|
+
const isImageThumbnailResult = isPayloadOfSchemaType<ImageThumbnailResult>(ImageThumbnailResultIndexSchema)
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The fields that will need to be indexed on in the underlying store
|
|
61
|
+
*/
|
|
62
|
+
type QueryableImageThumbnailResultProperties = Extract<keyof ImageThumbnailResult, 'url' | 'timestamp' | 'status'>
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The query that will be used to retrieve the results from the underlying store
|
|
66
|
+
*/
|
|
67
|
+
type ImageThumbnailResultQuery = PayloadDivinerQueryPayload & { schemas: [ImageThumbnailSchema] } & Pick<
|
|
68
|
+
ImageThumbnailResult,
|
|
69
|
+
QueryableImageThumbnailResultProperties
|
|
70
|
+
>
|
|
71
|
+
|
|
72
|
+
const moduleName = 'ImageThumbnailDiviner'
|
|
73
|
+
|
|
15
74
|
export class ImageThumbnailDiviner<TParams extends ImageThumbnailDivinerParams = ImageThumbnailDivinerParams> extends AbstractDiviner<TParams> {
|
|
16
75
|
static override configSchemas = [ImageThumbnailDivinerConfigSchema, DivinerConfigSchema]
|
|
17
76
|
|
|
18
|
-
private _archivistInstance: Promise<ArchivistInstance> | undefined
|
|
19
|
-
private _initializeArchivistConnectionIfNeededPromise: Promise<void> | undefined
|
|
20
|
-
private _map: Record<string, string> | undefined
|
|
21
|
-
private _payloadDivinerInstance: Promise<DivinerInstance> | undefined
|
|
22
77
|
private _pollId?: string | number | NodeJS.Timeout
|
|
23
78
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// }
|
|
27
|
-
|
|
28
|
-
get archivist() {
|
|
29
|
-
return this.config.archivist
|
|
79
|
+
get payloadDivinerLimit() {
|
|
80
|
+
return this.config.payloadDivinerLimit ?? 1_0000
|
|
30
81
|
}
|
|
31
82
|
|
|
32
|
-
get
|
|
33
|
-
return this.config.
|
|
83
|
+
get pollFrequency() {
|
|
84
|
+
return this.config.pollFrequency ?? 10_000
|
|
34
85
|
}
|
|
35
86
|
|
|
36
|
-
|
|
37
|
-
|
|
87
|
+
protected backgroundDivine = async (): Promise<void> => {
|
|
88
|
+
// Load last state
|
|
89
|
+
const lastState = (await this.retrieveState()) ?? { offset: 0 }
|
|
90
|
+
const { offset } = lastState
|
|
91
|
+
// Get next batch of results
|
|
92
|
+
const boundWitnessDiviner = await this.getBoundWitnessDivinerForStore('thumbnailStore')
|
|
93
|
+
const query = new PayloadBuilder<BoundWitnessDivinerQueryPayload>({ schema: BoundWitnessDivinerQuerySchema }).fields({
|
|
94
|
+
limit: this.payloadDivinerLimit,
|
|
95
|
+
offset,
|
|
96
|
+
order: 'asc',
|
|
97
|
+
payload_schemas: [ImageThumbnailSchema, TimestampSchema],
|
|
98
|
+
})
|
|
99
|
+
const batch = await boundWitnessDiviner.divine([query])
|
|
100
|
+
if (batch.length === 0) return
|
|
101
|
+
const imageThumbnailTimestampTuples = batch
|
|
102
|
+
.filter(isBoundWitness)
|
|
103
|
+
.map((bw) => {
|
|
104
|
+
const imageThumbnailIndexes = bw.payload_schemas?.map((schema, index) => (schema === ImageThumbnailSchema ? index : undefined)).filter(exists)
|
|
105
|
+
const timestampIndex = bw.payload_schemas?.findIndex((schema) => schema === TimestampSchema)
|
|
106
|
+
if (!imageThumbnailIndexes.length || timestampIndex === -1) return undefined
|
|
107
|
+
const imageThumbnails = bw.payload_hashes.map((hash, index) => (imageThumbnailIndexes.includes(index) ? hash : undefined)).filter(exists)
|
|
108
|
+
const timestamp = bw.payload_hashes?.[timestampIndex]
|
|
109
|
+
return imageThumbnails.map((imageThumbnail) => [imageThumbnail, timestamp] as const)
|
|
110
|
+
})
|
|
111
|
+
.flat()
|
|
112
|
+
.filter(exists)
|
|
113
|
+
const archivist = await this.getArchivistForStore('thumbnailStore')
|
|
114
|
+
const payloadTuples = (
|
|
115
|
+
await Promise.all(
|
|
116
|
+
imageThumbnailTimestampTuples.map(async ([imageThumbnailHash, timestampHash]) => {
|
|
117
|
+
const results = await archivist.get([imageThumbnailHash, timestampHash])
|
|
118
|
+
const imageThumbnailPayload = results.find(isImageThumbnail)
|
|
119
|
+
const timestampPayload = results.find(isTimestamp)
|
|
120
|
+
if (!imageThumbnailPayload || !timestampPayload) return undefined
|
|
121
|
+
const calculatedImageThumbnailHash = await PayloadHasher.hashAsync(imageThumbnailPayload)
|
|
122
|
+
const calculatedTimestampHash = await PayloadHasher.hashAsync(timestampPayload)
|
|
123
|
+
if (imageThumbnailHash !== calculatedImageThumbnailHash || timestampHash !== calculatedTimestampHash) return undefined
|
|
124
|
+
return [imageThumbnailHash, imageThumbnailPayload, timestampHash, timestampPayload] as const
|
|
125
|
+
}),
|
|
126
|
+
)
|
|
127
|
+
).filter(exists)
|
|
128
|
+
// Build index results
|
|
129
|
+
const indexedResults = payloadTuples.map(([thumbnailHash, thumbnailPayload, timestampHash, timestampPayload]) => {
|
|
130
|
+
const { sourceUrl: url } = thumbnailPayload
|
|
131
|
+
const { timestamp } = timestampPayload
|
|
132
|
+
const status = thumbnailPayload.http?.status ?? -1
|
|
133
|
+
const sources = [thumbnailHash, timestampHash]
|
|
134
|
+
const result = new PayloadBuilder<ImageThumbnailResult>({ schema: ImageThumbnailResultIndexSchema })
|
|
135
|
+
.fields({ sources, status, timestamp, url })
|
|
136
|
+
.build()
|
|
137
|
+
return result
|
|
138
|
+
})
|
|
139
|
+
// Insert index results
|
|
140
|
+
const indexArchivist = await this.getArchivistForStore('indexStore')
|
|
141
|
+
await indexArchivist.insert(indexedResults)
|
|
142
|
+
// Update state
|
|
143
|
+
const nextOffset = offset + batch.length + 1
|
|
144
|
+
const currentState = { ...lastState, offset: nextOffset }
|
|
145
|
+
await this.commitState(currentState)
|
|
38
146
|
}
|
|
39
147
|
|
|
40
|
-
|
|
41
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Commit the internal state of the Diviner process. This is similar
|
|
150
|
+
* to a transaction completion in a database and should only be called
|
|
151
|
+
* when results have been successfully persisted to the appropriate
|
|
152
|
+
* external stores.
|
|
153
|
+
*/
|
|
154
|
+
protected async commitState(state: ImageThumbnailDivinerState) {
|
|
155
|
+
const stateStore = assertEx(this.config.stateStore?.archivist, `${moduleName}: No stateStore configured`)
|
|
156
|
+
const module = assertEx(await this.resolve(stateStore), `${moduleName}: Failed to resolve stateStore`)
|
|
157
|
+
await withArchivistModule(module, async (archivist) => {
|
|
158
|
+
const mod = ArchivistWrapper.wrap(archivist, this.account)
|
|
159
|
+
const payload = new PayloadBuilder<ModuleState>({ schema: ModuleStateSchema }).fields({ state }).build()
|
|
160
|
+
await mod.insert([payload])
|
|
161
|
+
})
|
|
42
162
|
}
|
|
43
163
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
164
|
+
protected override async divineHandler(payloads: Payload[] = []): Promise<ImageThumbnailResult[]> {
|
|
165
|
+
const urls = payloads.filter(isUrlPayload).map((urlPayload) => urlPayload.url)
|
|
166
|
+
const diviner = await this.getPayloadDivinerForStore('indexStore')
|
|
167
|
+
const results = (
|
|
168
|
+
await Promise.all(
|
|
169
|
+
urls.map(async (url) => {
|
|
170
|
+
const query = new PayloadBuilder<ImageThumbnailResultQuery>({ schema: PayloadDivinerQuerySchema })
|
|
171
|
+
// TODO: Expose status, limit (and possibly offset) to caller. Currently only exposing URL
|
|
172
|
+
.fields({ limit: 1, offset: 0, order: 'desc', url })
|
|
173
|
+
.build()
|
|
174
|
+
return await diviner.divine([query])
|
|
175
|
+
}),
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
.flat()
|
|
179
|
+
.filter(isImageThumbnailResult)
|
|
180
|
+
return results
|
|
57
181
|
}
|
|
58
182
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
//if previously checked, but not found, clear promise
|
|
64
|
-
if (this._payloadDivinerInstance && !(await this._payloadDivinerInstance)) {
|
|
65
|
-
this._payloadDivinerInstance = undefined
|
|
66
|
-
}
|
|
67
|
-
this._payloadDivinerInstance =
|
|
68
|
-
this._payloadDivinerInstance ??
|
|
69
|
-
(async () => {
|
|
70
|
-
const module = await this.resolve(payloadDivinerAddress)
|
|
71
|
-
return asDivinerInstance(module, 'Provided payload diviner address did not resolve to a Diviner')
|
|
72
|
-
})()
|
|
73
|
-
|
|
74
|
-
return this._payloadDivinerInstance
|
|
75
|
-
}
|
|
183
|
+
protected async getArchivistForStore(store: ConfigStore, wrap?: boolean) {
|
|
184
|
+
const name = assertEx(this.config?.[store]?.archivist, () => `${moduleName}: Config for ${store}.archivist not specified`)
|
|
185
|
+
const mod = assertEx(await this.resolve(name), () => `${moduleName}: Failed to resolve ${store}.archivist`)
|
|
186
|
+
return wrap ? ArchivistWrapper.wrap(mod, this.account) : asArchivistInstance(mod, () => `${moduleName}: ${store}.archivist is not an Archivist`)
|
|
76
187
|
}
|
|
77
188
|
|
|
78
|
-
protected
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return (await archivist.get(hashes)).filter((payload): payload is ImageThumbnail => payload.schema === ImageThumbnailSchema)
|
|
189
|
+
protected async getBoundWitnessDivinerForStore(store: ConfigStore, wrap?: boolean) {
|
|
190
|
+
const name = assertEx(this.config?.[store]?.boundWitnessDiviner, () => `${moduleName}: Config for ${store}.boundWitnessDiviner not specified`)
|
|
191
|
+
const mod = assertEx(await this.resolve(name), () => `${moduleName}: Failed to resolve ${store}.boundWitnessDiviner`)
|
|
192
|
+
return wrap
|
|
193
|
+
? DivinerWrapper.wrap(mod, this.account)
|
|
194
|
+
: asDivinerInstance(mod, () => `${moduleName}: ${store}.boundWitnessDiviner is not a Diviner`)
|
|
85
195
|
}
|
|
86
196
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
(async () => {
|
|
92
|
-
if (!this._map) {
|
|
93
|
-
await this.attachArchivistEvents()
|
|
94
|
-
console.log('initializeArchivistConnectionIfNeeded: attachArchivistEvents done')
|
|
95
|
-
await this.poll()
|
|
96
|
-
console.log('initializeArchivistConnectionIfNeeded: poll done')
|
|
97
|
-
}
|
|
98
|
-
})()
|
|
99
|
-
return this._initializeArchivistConnectionIfNeededPromise
|
|
197
|
+
protected async getPayloadDivinerForStore(store: ConfigStore, wrap?: boolean) {
|
|
198
|
+
const name = assertEx(this.config?.[store]?.payloadDiviner, () => `${moduleName}: Config for ${store}.payloadDiviner not specified`)
|
|
199
|
+
const mod = assertEx(await this.resolve(name), () => `${moduleName}: Failed to resolve ${store}.payloadDiviner`)
|
|
200
|
+
return wrap ? DivinerWrapper.wrap(mod, this.account) : asDivinerInstance(mod, () => `${moduleName}: ${store}.payloadDiviner is not a Diviner`)
|
|
100
201
|
}
|
|
101
202
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Retrieves the last state of the Diviner process. Used to recover state after
|
|
205
|
+
* preemptions, reboots, etc.
|
|
206
|
+
*/
|
|
207
|
+
protected async retrieveState(): Promise<ImageThumbnailDivinerState | undefined> {
|
|
208
|
+
let hash: string = ''
|
|
209
|
+
const diviner = await this.getBoundWitnessDivinerForStore('stateStore')
|
|
210
|
+
const query = new PayloadBuilder<BoundWitnessDivinerQueryPayload>({ schema: BoundWitnessDivinerQuerySchema }).fields({
|
|
211
|
+
address: this.account.address,
|
|
212
|
+
limit: 1,
|
|
213
|
+
offset: 0,
|
|
214
|
+
order: 'desc',
|
|
215
|
+
payload_schemas: [ModuleStateSchema],
|
|
216
|
+
})
|
|
217
|
+
const boundWitnesses = await diviner.divine([query])
|
|
218
|
+
if (boundWitnesses.length > 0) {
|
|
219
|
+
const boundWitness = boundWitnesses[0]
|
|
220
|
+
if (isBoundWitness(boundWitness)) {
|
|
221
|
+
// Find the index for this address in the BoundWitness that is a ModuleState
|
|
222
|
+
hash = boundWitness.addresses
|
|
223
|
+
.map((address, index) => ({ address, index }))
|
|
224
|
+
.filter(({ address }) => address === this.account.address)
|
|
225
|
+
.reduce(
|
|
226
|
+
(prev, curr) => (boundWitness.payload_schemas?.[curr?.index] === ModuleStateSchema ? boundWitness.payload_hashes[curr?.index] : prev),
|
|
227
|
+
'',
|
|
228
|
+
)
|
|
229
|
+
}
|
|
107
230
|
}
|
|
108
|
-
}
|
|
109
231
|
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
assertEx(archivist
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
232
|
+
// If we able to located the last state
|
|
233
|
+
if (hash) {
|
|
234
|
+
// Get last state
|
|
235
|
+
const stateStoreArchivist = assertEx(this.config.stateStore?.archivist, `${moduleName}: No stateStore archivist configured`)
|
|
236
|
+
await withArchivistModule(
|
|
237
|
+
assertEx(await this.resolve(stateStoreArchivist), `${moduleName}: Failed to resolve stateStore archivist`),
|
|
238
|
+
async (mod) => {
|
|
239
|
+
const archivist = ArchivistWrapper.wrap(mod, this.account)
|
|
240
|
+
const payloads = await archivist.get([hash])
|
|
241
|
+
if (payloads.length > 0) {
|
|
242
|
+
const payload = payloads[0]
|
|
243
|
+
if (isModuleState(payload)) {
|
|
244
|
+
return payload.state
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
119
248
|
)
|
|
120
|
-
this._map = imagePayloadPairs.reduce<Record<string, string>>((prev, [hash, payload]) => {
|
|
121
|
-
prev[payload.sourceUrl] = hash
|
|
122
|
-
return prev
|
|
123
|
-
}, {})
|
|
124
249
|
}
|
|
250
|
+
return undefined
|
|
125
251
|
}
|
|
126
252
|
|
|
127
|
-
protected async
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
let offset: number | undefined = undefined
|
|
132
|
-
let moreAvailable = true
|
|
133
|
-
if (diviner) {
|
|
134
|
-
const newMap: Record<string, string> = {}
|
|
135
|
-
while (moreAvailable) {
|
|
136
|
-
const payloadDivinerQuery: PayloadDivinerQueryPayload = {
|
|
137
|
-
limit: this.payloadDivinerLimit,
|
|
138
|
-
offset,
|
|
139
|
-
schema: PayloadDivinerQuerySchema,
|
|
140
|
-
schemas: [ImageThumbnailSchema],
|
|
141
|
-
}
|
|
142
|
-
const payloads = await diviner.divine([payloadDivinerQuery])
|
|
143
|
-
offset = (offset ?? 0) + payloads.length
|
|
144
|
-
moreAvailable = payloads.length > 0
|
|
145
|
-
console.log(`loadMapWithPayloadDiviner.offset: ${offset}`)
|
|
146
|
-
console.log(`loadMapWithPayloadDiviner.moreAvailable: ${moreAvailable}`)
|
|
147
|
-
const imagePayloadPairs = await Promise.all(
|
|
148
|
-
payloads
|
|
149
|
-
.filter((payload): payload is ImageThumbnail => payload.schema === ImageThumbnailSchema)
|
|
150
|
-
.map<Promise<[string, ImageThumbnail]>>(async (payload) => [await PayloadHasher.hashAsync(payload), payload]),
|
|
151
|
-
)
|
|
152
|
-
imagePayloadPairs.forEach(([hash, payload]) => (newMap[payload.sourceUrl] = hash))
|
|
153
|
-
}
|
|
154
|
-
this._map = newMap
|
|
155
|
-
}
|
|
156
|
-
}
|
|
253
|
+
protected override async startHandler(): Promise<boolean> {
|
|
254
|
+
await super.startHandler()
|
|
255
|
+
this.poll()
|
|
256
|
+
return true
|
|
157
257
|
}
|
|
158
258
|
|
|
159
259
|
protected override async stopHandler(_timeout?: number | undefined): Promise<boolean> {
|
|
@@ -164,42 +264,17 @@ export class ImageThumbnailDiviner<TParams extends ImageThumbnailDivinerParams =
|
|
|
164
264
|
return await super.stopHandler()
|
|
165
265
|
}
|
|
166
266
|
|
|
167
|
-
private
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
private async getSafeMap() {
|
|
178
|
-
let mapRetry = 100 //10 seconds max
|
|
179
|
-
let map = this._map
|
|
180
|
-
while (!map) {
|
|
181
|
-
await delay(100)
|
|
182
|
-
mapRetry = mapRetry - 1
|
|
183
|
-
if (mapRetry === 0) {
|
|
184
|
-
throw Error('Map Not Loaded')
|
|
267
|
+
private poll() {
|
|
268
|
+
this._pollId = setTimeout(async () => {
|
|
269
|
+
try {
|
|
270
|
+
await this.backgroundDivine()
|
|
271
|
+
} catch (e) {
|
|
272
|
+
console.log(e)
|
|
273
|
+
} finally {
|
|
274
|
+
if (this._pollId) clearTimeout(this._pollId)
|
|
275
|
+
this._pollId = undefined
|
|
276
|
+
this.poll()
|
|
185
277
|
}
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
return map
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
private async poll() {
|
|
192
|
-
if (await this.started()) {
|
|
193
|
-
const pollFrequency = this.pollFrequency
|
|
194
|
-
if (pollFrequency) {
|
|
195
|
-
this._pollId = setTimeout(async () => {
|
|
196
|
-
this._pollId = undefined
|
|
197
|
-
await this.loadMap()
|
|
198
|
-
await this.poll()
|
|
199
|
-
}, pollFrequency)
|
|
200
|
-
} else {
|
|
201
|
-
await this.loadMap()
|
|
202
|
-
}
|
|
203
|
-
}
|
|
278
|
+
}, this.pollFrequency)
|
|
204
279
|
}
|
|
205
280
|
}
|