@milaboratories/pl-drivers 1.10.15 → 1.10.17
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/drivers/download_blob/download_blob.cjs +16 -2
- package/dist/drivers/download_blob/download_blob.cjs.map +1 -1
- package/dist/drivers/download_blob/download_blob.d.ts.map +1 -1
- package/dist/drivers/download_blob/download_blob.js +16 -2
- package/dist/drivers/download_blob/download_blob.js.map +1 -1
- package/dist/helpers/download.cjs +54 -4
- package/dist/helpers/download.cjs.map +1 -1
- package/dist/helpers/download.d.ts +10 -0
- package/dist/helpers/download.d.ts.map +1 -1
- package/dist/helpers/download.js +53 -5
- package/dist/helpers/download.js.map +1 -1
- package/package.json +5 -5
- package/src/drivers/download_blob/download_blob.ts +16 -2
- package/src/helpers/download.ts +54 -4
|
@@ -23,6 +23,7 @@ var blob_key = require('./blob_key.cjs');
|
|
|
23
23
|
var download_blob_task = require('./download_blob_task.cjs');
|
|
24
24
|
var files_cache = require('../helpers/files_cache.cjs');
|
|
25
25
|
var cache = require('./sparse_cache/cache.cjs');
|
|
26
|
+
var download = require('../../helpers/download.cjs');
|
|
26
27
|
|
|
27
28
|
function _interopNamespaceDefault(e) {
|
|
28
29
|
var n = Object.create(null);
|
|
@@ -178,7 +179,7 @@ class DownloadDriver {
|
|
|
178
179
|
options = { range };
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
|
-
|
|
182
|
+
const request = () => this.withContent(handle, {
|
|
182
183
|
...options,
|
|
183
184
|
handler: async (content) => {
|
|
184
185
|
const chunks = [];
|
|
@@ -189,6 +190,15 @@ class DownloadDriver {
|
|
|
189
190
|
return Buffer.concat(chunks);
|
|
190
191
|
}
|
|
191
192
|
});
|
|
193
|
+
try {
|
|
194
|
+
return await request();
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (download.isOffByOneError(error)) {
|
|
198
|
+
return await request();
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
192
202
|
}
|
|
193
203
|
/** Gets a content stream of a blob by a handle and calls handler with it. */
|
|
194
204
|
async withContent(handle, options) {
|
|
@@ -207,7 +217,11 @@ class DownloadDriver {
|
|
|
207
217
|
const [handlerStream, cacheStream] = content.tee();
|
|
208
218
|
const handlerPromise = handler(handlerStream, size);
|
|
209
219
|
consumers.buffer(cacheStream)
|
|
210
|
-
.then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data))
|
|
220
|
+
.then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data))
|
|
221
|
+
.catch(() => {
|
|
222
|
+
// Ignore cache errors - they shouldn't affect the main handler result
|
|
223
|
+
// This prevents unhandled promise rejections when the stream fails
|
|
224
|
+
});
|
|
211
225
|
return await handlerPromise;
|
|
212
226
|
});
|
|
213
227
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download_blob.cjs","sources":["../../../src/drivers/download_blob/download_blob.ts"],"sourcesContent":["import type {\n ComputableCtx,\n ComputableStableDefined,\n Watcher,\n} from '@milaboratories/computable';\nimport {\n ChangeSource,\n Computable,\n} from '@milaboratories/computable';\nimport type { ResourceId, ResourceType } from '@milaboratories/pl-client';\nimport { resourceIdToString, stringifyWithResourceId } from '@milaboratories/pl-client';\nimport type {\n AnyLogHandle,\n BlobDriver,\n ContentHandler,\n GetContentOptions,\n LocalBlobHandle,\n LocalBlobHandleAndSize,\n ReadyLogHandle,\n RemoteBlobHandle,\n RemoteBlobHandleAndSize,\n StreamingApiResponse,\n} from '@milaboratories/pl-model-common';\nimport { type RangeBytes, validateRangeBytes } from '@milaboratories/pl-model-common';\nimport type {\n PlTreeEntry,\n ResourceInfo,\n ResourceSnapshot\n} from '@milaboratories/pl-tree';\nimport {\n isPlTreeEntry,\n makeResourceSnapshot,\n treeEntryToResourceInfo,\n} from '@milaboratories/pl-tree';\nimport type { MiLogger, Signer } from '@milaboratories/ts-helpers';\nimport { CallersCounter, mapGet, TaskProcessor } from '@milaboratories/ts-helpers';\nimport Denque from 'denque';\nimport * as fs from 'fs';\nimport { randomUUID } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport * as readline from 'node:readline/promises';\nimport { buffer } from 'node:stream/consumers';\nimport { Readable } from 'node:stream';\nimport type { ClientDownload } from '../../clients/download';\nimport type { ClientLogs } from '../../clients/logs';\nimport { withFileContent } from '../helpers/read_file';\nimport {\n isLocalBlobHandle,\n newLocalHandle,\n parseLocalHandle,\n} from '../helpers/download_local_handle';\nimport {\n isRemoteBlobHandle,\n newRemoteHandle,\n parseRemoteHandle,\n} from '../helpers/download_remote_handle';\nimport { Updater, WrongResourceTypeError } from '../helpers/helpers';\nimport { getResourceInfoFromLogHandle, newLogHandle } from '../helpers/logs_handle';\nimport { getSize, OnDemandBlobResourceSnapshot } from '../types';\nimport { blobKey, pathToKey } from './blob_key';\nimport { DownloadBlobTask, nonRecoverableError } from './download_blob_task';\nimport { FilesCache } from '../helpers/files_cache';\nimport { SparseCache, SparseCacheFsFile, SparseCacheFsRanges } from './sparse_cache/cache';\n\nexport type DownloadDriverOps = {\n /**\n * A soft limit of the amount of blob storage, in bytes.\n * Once exceeded, the download driver will start deleting blobs one by one\n * when they become unneeded.\n * */\n cacheSoftSizeBytes: number;\n\n /**\n * A hard limit of the amount of sparse cache, in bytes.\n * Once exceeded, the download driver will start deleting blobs one by one.\n *\n * The sparse cache is used to store ranges of blobs.\n * */\n rangesCacheMaxSizeBytes: number;\n\n /**\n * Max number of concurrent downloads while calculating computable states\n * derived from this driver\n * */\n nConcurrentDownloads: number;\n};\n\n/** DownloadDriver holds a queue of downloading tasks,\n * and notifies every watcher when a file were downloaded. */\nexport class DownloadDriver implements BlobDriver {\n /** Represents a unique key to the path of a blob as a map. */\n private keyToDownload: Map<string, DownloadBlobTask> = new Map();\n\n /** Writes and removes files to a hard drive and holds a counter for every\n * file that should be kept. */\n private cache: FilesCache<DownloadBlobTask>;\n private rangesCache: SparseCache;\n\n /** Downloads files and writes them to the local dir. */\n private downloadQueue: TaskProcessor;\n\n private keyToOnDemand: Map<string, OnDemandBlobHolder> = new Map();\n\n private idToLastLines: Map<string, LastLinesGetter> = new Map();\n private idToProgressLog: Map<string, LastLinesGetter> = new Map();\n\n private readonly saveDir: string;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly clientDownload: ClientDownload,\n private readonly clientLogs: ClientLogs,\n saveDir: string,\n private readonly rangesCacheDir: string,\n private readonly signer: Signer,\n private readonly ops: DownloadDriverOps,\n ) {\n this.cache = new FilesCache(this.ops.cacheSoftSizeBytes);\n\n const fsRanges = new SparseCacheFsRanges(this.logger, this.rangesCacheDir);\n const fsStorage = new SparseCacheFsFile(this.logger, this.rangesCacheDir);\n this.rangesCache = new SparseCache(this.logger, this.ops.rangesCacheMaxSizeBytes, fsRanges, fsStorage);\n\n this.downloadQueue = new TaskProcessor(this.logger, ops.nConcurrentDownloads);\n\n this.saveDir = path.resolve(saveDir);\n }\n\n static async init(\n logger: MiLogger,\n clientDownload: ClientDownload,\n clientLogs: ClientLogs,\n saveDir: string,\n rangesCacheDir: string,\n signer: Signer,\n ops: DownloadDriverOps,\n ): Promise<DownloadDriver> {\n const driver = new DownloadDriver(logger, clientDownload, clientLogs, saveDir, rangesCacheDir, signer, ops);\n await driver.rangesCache.reset();\n\n return driver;\n }\n\n /** Gets a blob or part of the blob by its resource id or downloads a blob and sets it in a cache. */\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ctx: ComputableCtx,\n ): LocalBlobHandleAndSize | undefined;\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ): ComputableStableDefined<LocalBlobHandleAndSize>;\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<LocalBlobHandleAndSize | undefined> | LocalBlobHandleAndSize | undefined {\n if (ctx === undefined) {\n return Computable.make((ctx) => this.getDownloadedBlob(res, ctx));\n }\n\n const rInfo = treeEntryToResourceInfo(res, ctx);\n\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(rInfo, callerId));\n\n const result = this.getDownloadedBlobNoCtx(ctx.watcher, rInfo as ResourceSnapshot, callerId);\n if (result == undefined) {\n ctx.markUnstable('download blob is still undefined');\n }\n\n return result;\n }\n\n private getDownloadedBlobNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n callerId: string,\n ): LocalBlobHandleAndSize | undefined {\n validateDownloadableResourceType('getDownloadedBlob', rInfo.type);\n\n // We don't need to request files with wider limits,\n // PFrame's engine does it disk-optimally by itself.\n\n const task = this.getOrSetNewTask(rInfo, callerId);\n task.attach(w, callerId);\n\n const result = task.getBlob();\n if (!result.done) {\n return undefined;\n }\n if (result.result.ok) {\n return result.result.value;\n }\n throw result.result.error;\n }\n\n private getOrSetNewTask(\n rInfo: ResourceSnapshot,\n callerId: string,\n ): DownloadBlobTask {\n const key = blobKey(rInfo.id);\n\n const inMemoryTask = this.keyToDownload.get(key);\n if (inMemoryTask) {\n return inMemoryTask;\n }\n\n // schedule the blob downloading, then it'll be added to the cache.\n const fPath = path.resolve(this.saveDir, key);\n\n const newTask = new DownloadBlobTask(\n this.logger,\n this.clientDownload,\n rInfo,\n newLocalHandle(fPath, this.signer),\n fPath,\n );\n this.keyToDownload.set(key, newTask);\n\n this.downloadQueue.push({\n fn: () => this.downloadBlob(newTask, callerId),\n recoverableErrorPredicate: (e) => !nonRecoverableError(e),\n });\n\n return newTask;\n }\n\n private async downloadBlob(task: DownloadBlobTask, callerId: string) {\n await task.download();\n const blob = task.getBlob();\n if (blob.done && blob.result.ok) {\n this.cache.addCache(task, callerId);\n }\n }\n\n /** Gets on demand blob. */\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ): Computable<RemoteBlobHandleAndSize>;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx?: undefined,\n fromBytes?: number,\n toBytes?: number,\n ): Computable<RemoteBlobHandleAndSize>;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx: ComputableCtx,\n fromBytes?: number,\n toBytes?: number,\n ): RemoteBlobHandleAndSize;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx?: ComputableCtx,\n ): ComputableStableDefined<RemoteBlobHandleAndSize> | RemoteBlobHandleAndSize | undefined {\n if (ctx === undefined) return Computable.make((ctx) => this.getOnDemandBlob(res, ctx));\n\n const rInfo: OnDemandBlobResourceSnapshot = isPlTreeEntry(res)\n ? makeResourceSnapshot(res, OnDemandBlobResourceSnapshot, ctx)\n : res;\n\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseOnDemandBlob(rInfo.id, callerId));\n\n // note that the watcher is not needed,\n // the handler never changes.\n const result = this.getOnDemandBlobNoCtx(rInfo, callerId);\n\n return result;\n }\n\n private getOnDemandBlobNoCtx(\n info: OnDemandBlobResourceSnapshot,\n callerId: string,\n ): RemoteBlobHandleAndSize {\n validateDownloadableResourceType('getOnDemandBlob', info.type);\n\n let blob = this.keyToOnDemand.get(blobKey(info.id));\n\n if (blob === undefined) {\n blob = new OnDemandBlobHolder(getSize(info), newRemoteHandle(info, this.signer));\n this.keyToOnDemand.set(blobKey(info.id), blob);\n }\n\n blob.attach(callerId);\n\n return blob.getHandle();\n }\n\n /** Gets a path from a handle. */\n public getLocalPath(handle: LocalBlobHandle): string {\n const { path } = parseLocalHandle(handle, this.signer);\n return path;\n }\n\n /** Gets a content of a blob by a handle. */\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n options?: GetContentOptions,\n ): Promise<Uint8Array>;\n /** @deprecated Use {@link getContent} with {@link GetContentOptions} instead */\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n range?: RangeBytes,\n ): Promise<Uint8Array>;\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n optionsOrRange?: GetContentOptions | RangeBytes,\n ): Promise<Uint8Array> {\n let options: GetContentOptions = {};\n if (typeof optionsOrRange === 'object' && optionsOrRange !== null) {\n if ('range' in optionsOrRange) {\n options = optionsOrRange;\n } else {\n const range = optionsOrRange as RangeBytes;\n validateRangeBytes(range, `getContent`);\n options = { range };\n }\n }\n\n return await this.withContent(handle, {\n ...options,\n handler: async (content) => {\n const chunks: Uint8Array[] = [];\n for await (const chunk of content) {\n options.signal?.throwIfAborted();\n chunks.push(chunk);\n }\n return Buffer.concat(chunks);\n }\n });\n }\n\n /** Gets a content stream of a blob by a handle and calls handler with it. */\n public async withContent<T>(\n handle: LocalBlobHandle | RemoteBlobHandle,\n options: GetContentOptions & {\n handler: ContentHandler<T>;\n },\n ): Promise<T> {\n const { range, signal, handler } = options;\n\n if (isLocalBlobHandle(handle)) {\n return await withFileContent({ path: this.getLocalPath(handle), range, signal, handler });\n }\n\n if (isRemoteBlobHandle(handle)) {\n const result = parseRemoteHandle(handle, this.signer);\n\n const key = blobKey(result.info.id);\n const filePath = await this.rangesCache.get(key, range ?? { from: 0, to: result.size });\n signal?.throwIfAborted();\n\n if (filePath) return await withFileContent({ path: filePath, range, signal, handler });\n\n return await this.clientDownload.withBlobContent(\n result.info,\n { signal },\n options,\n async (content, size) => {\n const [handlerStream, cacheStream] = content.tee();\n \n const handlerPromise = handler(handlerStream, size);\n const _cachePromise = buffer(cacheStream)\n .then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data));\n\n return await handlerPromise;\n }\n );\n }\n\n throw new Error('Malformed remote handle');\n }\n\n /**\n * Creates computable that will return blob content once it is downloaded.\n * Uses downloaded blob handle under the hood, so stores corresponding blob in file system.\n */\n public getComputableContent(\n res: ResourceInfo | PlTreeEntry,\n range?: RangeBytes,\n ): ComputableStableDefined<Uint8Array> {\n if (range) {\n validateRangeBytes(range, `getComputableContent`);\n }\n\n return Computable.make((ctx) =>\n this.getDownloadedBlob(res, ctx), {\n postprocessValue: (v) => v ? this.getContent(v.handle, { range }) : undefined\n }\n ).withStableType()\n }\n\n /** Returns all logs and schedules a job that reads remain logs.\n * Notifies when a new portion of the log appeared. */\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number\n ): Computable<string | undefined>;\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number,\n ctx: ComputableCtx\n ): Computable<string | undefined>;\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number,\n ctx?: ComputableCtx,\n ): Computable<string | undefined> | string | undefined {\n if (ctx == undefined) return Computable.make((ctx) => this.getLastLogs(res, lines, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(r, callerId));\n\n const result = this.getLastLogsNoCtx(ctx.watcher, r as ResourceSnapshot, lines, callerId);\n if (result == undefined)\n ctx.markUnstable('either a file was not downloaded or logs was not read');\n\n return result;\n }\n\n private getLastLogsNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n lines: number,\n callerId: string,\n ): string | undefined {\n validateDownloadableResourceType('getLastLogs', rInfo.type);\n const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);\n if (blob == undefined) return undefined;\n\n const { path } = parseLocalHandle(blob.handle, this.signer);\n\n let logGetter = this.idToLastLines.get(blobKey(rInfo.id));\n\n if (logGetter == undefined) {\n const newLogGetter = new LastLinesGetter(path, lines);\n this.idToLastLines.set(blobKey(rInfo.id), newLogGetter);\n logGetter = newLogGetter;\n }\n\n const result = logGetter.getOrSchedule(w);\n if (result.error) throw result.error;\n\n return result.log;\n }\n\n /** Returns a last line that has patternToSearch.\n * Notifies when a new line appeared or EOF reached. */\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string\n ): Computable<string | undefined>;\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string,\n ctx: ComputableCtx\n ): string | undefined;\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string,\n ctx?: ComputableCtx,\n ): Computable<string | undefined> | string | undefined {\n if (ctx == undefined)\n return Computable.make((ctx) => this.getProgressLog(res, patternToSearch, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(r, callerId));\n\n const result = this.getProgressLogNoCtx(\n ctx.watcher,\n r as ResourceSnapshot,\n patternToSearch,\n callerId,\n );\n if (result === undefined)\n ctx.markUnstable('either a file was not downloaded or a progress log was not read');\n\n return result;\n }\n\n private getProgressLogNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n patternToSearch: string,\n callerId: string,\n ): string | undefined {\n validateDownloadableResourceType('getProgressLog', rInfo.type);\n\n const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);\n if (blob == undefined) return undefined;\n const { path } = parseLocalHandle(blob.handle, this.signer);\n\n let logGetter = this.idToProgressLog.get(blobKey(rInfo.id));\n\n if (logGetter == undefined) {\n const newLogGetter = new LastLinesGetter(path, 1, patternToSearch);\n this.idToProgressLog.set(blobKey(rInfo.id), newLogGetter);\n\n logGetter = newLogGetter;\n }\n\n const result = logGetter.getOrSchedule(w);\n if (result.error) throw result.error;\n\n return result.log;\n }\n\n /** Returns an Id of a smart object, that can read logs directly from\n * the platform. */\n public getLogHandle(res: ResourceInfo | PlTreeEntry): Computable<AnyLogHandle>;\n public getLogHandle(res: ResourceInfo | PlTreeEntry, ctx: ComputableCtx): AnyLogHandle;\n public getLogHandle(\n res: ResourceInfo | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<AnyLogHandle> | AnyLogHandle {\n if (ctx == undefined) return Computable.make((ctx) => this.getLogHandle(res, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n\n return this.getLogHandleNoCtx(r as ResourceSnapshot);\n }\n\n private getLogHandleNoCtx(rInfo: ResourceSnapshot): AnyLogHandle {\n validateDownloadableResourceType('getLogHandle', rInfo.type);\n return newLogHandle(false, rInfo);\n }\n\n public async lastLines(\n handle: ReadyLogHandle,\n lineCount: number,\n offsetBytes?: number, // if 0n, then start from the end.\n searchStr?: string,\n ): Promise<StreamingApiResponse> {\n const resp = await this.clientLogs.lastLines(\n getResourceInfoFromLogHandle(handle),\n lineCount,\n BigInt(offsetBytes ?? 0),\n searchStr,\n );\n\n return {\n live: false,\n shouldUpdateHandle: false,\n data: resp.data,\n size: Number(resp.size),\n newOffset: Number(resp.newOffset),\n };\n }\n\n public async readText(\n handle: ReadyLogHandle,\n lineCount: number,\n offsetBytes?: number,\n searchStr?: string,\n ): Promise<StreamingApiResponse> {\n const resp = await this.clientLogs.readText(\n getResourceInfoFromLogHandle(handle),\n lineCount,\n BigInt(offsetBytes ?? 0),\n searchStr,\n );\n\n return {\n live: false,\n shouldUpdateHandle: false,\n data: resp.data,\n size: Number(resp.size),\n newOffset: Number(resp.newOffset),\n };\n }\n\n private async releaseBlob(rInfo: ResourceInfo, callerId: string) {\n const task = this.keyToDownload.get(blobKey(rInfo.id));\n if (task == undefined) {\n return;\n }\n\n if (this.cache.existsFile(blobKey(rInfo.id))) {\n const toDelete = this.cache.removeFile(blobKey(rInfo.id), callerId);\n\n await Promise.all(\n toDelete.map(async (cachedFile) => {\n await fsp.rm(cachedFile.path);\n\n this.cache.removeCache(cachedFile);\n\n this.removeTask(\n mapGet(this.keyToDownload, pathToKey(cachedFile.path)),\n `the task ${stringifyWithResourceId(cachedFile)} was removed`\n + `from cache along with ${stringifyWithResourceId(toDelete.map((d) => d.path))}`,\n );\n }),\n );\n } else {\n // The task is still in a downloading queue.\n const deleted = task.counter.dec(callerId);\n if (deleted) {\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was removed from cache`,\n );\n }\n }\n }\n\n private removeTask(task: DownloadBlobTask, reason: string) {\n task.abort(reason);\n task.change.markChanged(`download task for ${task.path} removed: ${reason}`);\n this.keyToDownload.delete(pathToKey(task.path));\n this.idToLastLines.delete(blobKey(task.rInfo.id));\n this.idToProgressLog.delete(blobKey(task.rInfo.id));\n }\n\n private async releaseOnDemandBlob(blobId: ResourceId, callerId: string) {\n const deleted = this.keyToOnDemand.get(blobKey(blobId))?.release(callerId) ?? false;\n if (deleted) this.keyToOnDemand.delete(blobKey(blobId));\n }\n\n /** Removes all files from a hard drive. */\n async releaseAll() {\n this.downloadQueue.stop();\n\n this.keyToDownload.forEach((task, key) => {\n this.keyToDownload.delete(key);\n task.change.markChanged(`task ${resourceIdToString(task.rInfo.id)} released`);\n });\n }\n}\n\n/** Keeps a counter to the on demand handle. */\nclass OnDemandBlobHolder {\n private readonly counter = new CallersCounter();\n\n constructor(\n private readonly size: number,\n private readonly handle: RemoteBlobHandle,\n ) {}\n\n public getHandle(): RemoteBlobHandleAndSize {\n return { handle: this.handle, size: this.size };\n }\n\n public attach(callerId: string) {\n this.counter.inc(callerId);\n }\n\n public release(callerId: string): boolean {\n return this.counter.dec(callerId);\n }\n}\n\nclass LastLinesGetter {\n private updater: Updater;\n private log: string | undefined;\n private readonly change: ChangeSource = new ChangeSource();\n private error: any | undefined = undefined;\n\n constructor(\n private readonly path: string,\n private readonly lines: number,\n private readonly patternToSearch?: string,\n ) {\n this.updater = new Updater(async () => this.update());\n }\n\n getOrSchedule(w: Watcher): {\n log: string | undefined;\n error?: any | undefined;\n } {\n this.change.attachWatcher(w);\n\n this.updater.schedule();\n\n return {\n log: this.log,\n error: this.error,\n };\n }\n\n async update(): Promise<void> {\n try {\n const newLogs = await getLastLines(this.path, this.lines, this.patternToSearch);\n\n if (this.log != newLogs) this.change.markChanged(`logs for ${this.path} updated`);\n this.log = newLogs;\n } catch (e: any) {\n if (e.name == 'RpcError' && e.code == 'NOT_FOUND') {\n // No resource\n this.log = '';\n this.error = e;\n this.change.markChanged(`log update for ${this.path} failed, resource not found`);\n return;\n }\n\n throw e;\n }\n }\n}\n\n/** Gets last lines from a file by reading the file from the top and keeping\n * last N lines in a window queue. */\nasync function getLastLines(fPath: string, nLines: number, patternToSearch?: string): Promise<string> {\n let inStream: fs.ReadStream | undefined;\n let rl: readline.Interface | undefined;\n\n try {\n inStream = fs.createReadStream(fPath);\n rl = readline.createInterface({ input: inStream, crlfDelay: Infinity });\n\n const lines = new Denque();\n\n for await (const line of rl) {\n if (patternToSearch != undefined && !line.includes(patternToSearch)) continue;\n\n lines.push(line);\n if (lines.length > nLines) {\n lines.shift();\n }\n }\n\n // last EOL is for keeping backward compat with platforma implementation.\n return lines.toArray().join(os.EOL) + os.EOL;\n } finally {\n // Cleanup resources in finally block to ensure they're always cleaned up\n try {\n if (rl) {\n rl.close();\n }\n } catch (cleanupError) {\n console.error('Error closing readline interface:', cleanupError);\n }\n\n try {\n if (inStream && !inStream.destroyed) {\n inStream.destroy();\n }\n } catch (cleanupError) {\n console.error('Error destroying read stream:', cleanupError);\n }\n }\n}\n\nfunction validateDownloadableResourceType(methodName: string, rType: ResourceType) {\n if (!rType.name.startsWith('Blob/')) {\n let message = `${methodName}: wrong resource type: ${rType.name}, expected: a resource of type that starts with 'Blob/'.`;\n if (rType.name == 'Blob')\n message += ` If it's called from workflow, should a file be exported with 'file.exportFile' function?`;\n\n throw new WrongResourceTypeError(message);\n }\n}\n"],"names":["FilesCache","SparseCacheFsRanges","SparseCacheFsFile","SparseCache","TaskProcessor","path","Computable","treeEntryToResourceInfo","randomUUID","blobKey","DownloadBlobTask","newLocalHandle","nonRecoverableError","isPlTreeEntry","makeResourceSnapshot","OnDemandBlobResourceSnapshot","getSize","newRemoteHandle","parseLocalHandle","validateRangeBytes","isLocalBlobHandle","withFileContent","isRemoteBlobHandle","parseRemoteHandle","buffer","newLogHandle","getResourceInfoFromLogHandle","fsp","mapGet","pathToKey","stringifyWithResourceId","resourceIdToString","CallersCounter","ChangeSource","Updater","fs","readline","os","WrongResourceTypeError"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFA;AAC6D;MAChD,cAAc,CAAA;AAoBN,IAAA,MAAA;AACA,IAAA,cAAA;AACA,IAAA,UAAA;AAEA,IAAA,cAAA;AACA,IAAA,MAAA;AACA,IAAA,GAAA;;AAxBX,IAAA,aAAa,GAAkC,IAAI,GAAG,EAAE;AAEhE;AAC+B;AACvB,IAAA,KAAK;AACL,IAAA,WAAW;;AAGX,IAAA,aAAa;AAEb,IAAA,aAAa,GAAoC,IAAI,GAAG,EAAE;AAE1D,IAAA,aAAa,GAAiC,IAAI,GAAG,EAAE;AACvD,IAAA,eAAe,GAAiC,IAAI,GAAG,EAAE;AAEhD,IAAA,OAAO;AAExB,IAAA,WAAA,CACmB,MAAgB,EAChB,cAA8B,EAC9B,UAAsB,EACvC,OAAe,EACE,cAAsB,EACtB,MAAc,EACd,GAAsB,EAAA;QANtB,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,cAAc,GAAd,cAAc;QACd,IAAA,CAAA,UAAU,GAAV,UAAU;QAEV,IAAA,CAAA,cAAc,GAAd,cAAc;QACd,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,GAAG,GAAH,GAAG;AAEpB,QAAA,IAAI,CAAC,KAAK,GAAG,IAAIA,sBAAU,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAExD,QAAA,MAAM,QAAQ,GAAG,IAAIC,yBAAmB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,CAAC;AAC1E,QAAA,MAAM,SAAS,GAAG,IAAIC,uBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,CAAC;QACzE,IAAI,CAAC,WAAW,GAAG,IAAIC,iBAAW,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,uBAAuB,EAAE,QAAQ,EAAE,SAAS,CAAC;AAEtG,QAAA,IAAI,CAAC,aAAa,GAAG,IAAIC,uBAAa,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,oBAAoB,CAAC;QAE7E,IAAI,CAAC,OAAO,GAAGC,eAAI,CAAC,OAAO,CAAC,OAAO,CAAC;IACtC;AAEA,IAAA,aAAa,IAAI,CACf,MAAgB,EAChB,cAA8B,EAC9B,UAAsB,EACtB,OAAe,EACf,cAAsB,EACtB,MAAc,EACd,GAAsB,EAAA;AAEtB,QAAA,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,CAAC;AAC3G,QAAA,MAAM,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE;AAEhC,QAAA,OAAO,MAAM;IACf;IAUO,iBAAiB,CACtB,GAA+B,EAC/B,GAAmB,EAAA;AAEnB,QAAA,IAAI,GAAG,KAAK,SAAS,EAAE;AACrB,YAAA,OAAOC,qBAAU,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACnE;QAEA,MAAM,KAAK,GAAGC,8BAAuB,CAAC,GAAG,EAAE,GAAG,CAAC;AAE/C,QAAA,MAAM,QAAQ,GAAGC,sBAAU,EAAE;AAC7B,QAAA,GAAG,CAAC,YAAY,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAEzD,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,OAAO,EAAE,KAAyB,EAAE,QAAQ,CAAC;AAC5F,QAAA,IAAI,MAAM,IAAI,SAAS,EAAE;AACvB,YAAA,GAAG,CAAC,YAAY,CAAC,kCAAkC,CAAC;QACtD;AAEA,QAAA,OAAO,MAAM;IACf;AAEQ,IAAA,sBAAsB,CAC5B,CAAU,EACV,KAAuB,EACvB,QAAgB,EAAA;AAEhB,QAAA,gCAAgC,CAAC,mBAAmB,EAAE,KAAK,CAAC,IAAI,CAAC;;;QAKjE,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC;AAClD,QAAA,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC;AAExB,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE;AAC7B,QAAA,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;AAChB,YAAA,OAAO,SAAS;QAClB;AACA,QAAA,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE;AACpB,YAAA,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK;QAC5B;AACA,QAAA,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK;IAC3B;IAEQ,eAAe,CACrB,KAAuB,EACvB,QAAgB,EAAA;QAEhB,MAAM,GAAG,GAAGC,gBAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAE7B,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC;QAChD,IAAI,YAAY,EAAE;AAChB,YAAA,OAAO,YAAY;QACrB;;AAGA,QAAA,MAAM,KAAK,GAAGJ,eAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC;QAE7C,MAAM,OAAO,GAAG,IAAIK,mCAAgB,CAClC,IAAI,CAAC,MAAM,EACX,IAAI,CAAC,cAAc,EACnB,KAAK,EACLC,oCAAc,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAClC,KAAK,CACN;QACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC;AAEpC,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC;YACtB,EAAE,EAAE,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC;YAC9C,yBAAyB,EAAE,CAAC,CAAC,KAAK,CAACC,sCAAmB,CAAC,CAAC,CAAC;AAC1D,SAAA,CAAC;AAEF,QAAA,OAAO,OAAO;IAChB;AAEQ,IAAA,MAAM,YAAY,CAAC,IAAsB,EAAE,QAAgB,EAAA;AACjE,QAAA,MAAM,IAAI,CAAC,QAAQ,EAAE;AACrB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE;QAC3B,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE;YAC/B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;QACrC;IACF;IAkBO,eAAe,CACpB,GAA+C,EAC/C,GAAmB,EAAA;QAEnB,IAAI,GAAG,KAAK,SAAS;AAAE,YAAA,OAAON,qBAAU,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAEtF,QAAA,MAAM,KAAK,GAAiCO,oBAAa,CAAC,GAAG;cACzDC,2BAAoB,CAAC,GAAG,EAAEC,kCAA4B,EAAE,GAAG;cAC3D,GAAG;AAEP,QAAA,MAAM,QAAQ,GAAGP,sBAAU,EAAE;AAC7B,QAAA,GAAG,CAAC,YAAY,CAAC,MAAM,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;;;QAIpE,MAAM,MAAM,GAAG,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC;AAEzD,QAAA,OAAO,MAAM;IACf;IAEQ,oBAAoB,CAC1B,IAAkC,EAClC,QAAgB,EAAA;AAEhB,QAAA,gCAAgC,CAAC,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC;AAE9D,QAAA,IAAI,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAACC,gBAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAEnD,QAAA,IAAI,IAAI,KAAK,SAAS,EAAE;AACtB,YAAA,IAAI,GAAG,IAAI,kBAAkB,CAACO,aAAO,CAAC,IAAI,CAAC,EAAEC,sCAAe,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;AAChF,YAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAACR,gBAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC;QAChD;AAEA,QAAA,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;AAErB,QAAA,OAAO,IAAI,CAAC,SAAS,EAAE;IACzB;;AAGO,IAAA,YAAY,CAAC,MAAuB,EAAA;AACzC,QAAA,MAAM,EAAE,IAAI,EAAE,GAAGS,sCAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;AACtD,QAAA,OAAO,IAAI;IACb;AAYO,IAAA,MAAM,UAAU,CACrB,MAA0C,EAC1C,cAA+C,EAAA;QAE/C,IAAI,OAAO,GAAsB,EAAE;QACnC,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,IAAI,EAAE;AACjE,YAAA,IAAI,OAAO,IAAI,cAAc,EAAE;gBAC7B,OAAO,GAAG,cAAc;YAC1B;iBAAO;gBACL,MAAM,KAAK,GAAG,cAA4B;AAC1C,gBAAAC,gCAAkB,CAAC,KAAK,EAAE,CAAA,UAAA,CAAY,CAAC;AACvC,gBAAA,OAAO,GAAG,EAAE,KAAK,EAAE;YACrB;QACF;AAEA,QAAA,OAAO,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE;AACpC,YAAA,GAAG,OAAO;AACV,YAAA,OAAO,EAAE,OAAO,OAAO,KAAI;gBACzB,MAAM,MAAM,GAAiB,EAAE;AAC/B,gBAAA,WAAW,MAAM,KAAK,IAAI,OAAO,EAAE;AACjC,oBAAA,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE;AAChC,oBAAA,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;gBACpB;AACA,gBAAA,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YAC9B;AACD,SAAA,CAAC;IACJ;;AAGO,IAAA,MAAM,WAAW,CACtB,MAA0C,EAC1C,OAEC,EAAA;QAED,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO;AAE1C,QAAA,IAAIC,uCAAiB,CAAC,MAAM,CAAC,EAAE;YAC7B,OAAO,MAAMC,yBAAe,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAC3F;AAEA,QAAA,IAAIC,yCAAkB,CAAC,MAAM,CAAC,EAAE;YAC9B,MAAM,MAAM,GAAGC,wCAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;YAErD,MAAM,GAAG,GAAGd,gBAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;YACvF,MAAM,EAAE,cAAc,EAAE;AAExB,YAAA,IAAI,QAAQ;AAAE,gBAAA,OAAO,MAAMY,yBAAe,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAEtF,OAAO,MAAM,IAAI,CAAC,cAAc,CAAC,eAAe,CAC9C,MAAM,CAAC,IAAI,EACX,EAAE,MAAM,EAAE,EACV,OAAO,EACP,OAAO,OAAO,EAAE,IAAI,KAAI;gBACtB,MAAM,CAAC,aAAa,EAAE,WAAW,CAAC,GAAG,OAAO,CAAC,GAAG,EAAE;gBAElD,MAAM,cAAc,GAAG,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC;AACnD,gBAAsBG,gBAAM,CAAC,WAAW;AACrC,qBAAA,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC;gBAExF,OAAO,MAAM,cAAc;AAC7B,YAAA,CAAC,CACF;QACH;AAEA,QAAA,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC;IAC5C;AAEA;;;AAGG;IACI,oBAAoB,CACzB,GAA+B,EAC/B,KAAkB,EAAA;QAElB,IAAI,KAAK,EAAE;AACT,YAAAL,gCAAkB,CAAC,KAAK,EAAE,CAAA,oBAAA,CAAsB,CAAC;QACnD;AAEA,QAAA,OAAOb,qBAAU,CAAC,IAAI,CAAC,CAAC,GAAG,KACzB,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;YAClC,gBAAgB,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,GAAG;SACrE,CACA,CAAC,cAAc,EAAE;IACpB;AAaO,IAAA,WAAW,CAChB,GAA+B,EAC/B,KAAa,EACb,GAAmB,EAAA;QAEnB,IAAI,GAAG,IAAI,SAAS;YAAE,OAAOA,qBAAU,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAExF,MAAM,CAAC,GAAGC,8BAAuB,CAAC,GAAG,EAAE,GAAG,CAAC;AAC3C,QAAA,MAAM,QAAQ,GAAGC,sBAAU,EAAE;AAC7B,QAAA,GAAG,CAAC,YAAY,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAErD,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,EAAE,CAAqB,EAAE,KAAK,EAAE,QAAQ,CAAC;QACzF,IAAI,MAAM,IAAI,SAAS;AACrB,YAAA,GAAG,CAAC,YAAY,CAAC,uDAAuD,CAAC;AAE3E,QAAA,OAAO,MAAM;IACf;AAEQ,IAAA,gBAAgB,CACtB,CAAU,EACV,KAAuB,EACvB,KAAa,EACb,QAAgB,EAAA;AAEhB,QAAA,gCAAgC,CAAC,aAAa,EAAE,KAAK,CAAC,IAAI,CAAC;AAC3D,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC;QAC5D,IAAI,IAAI,IAAI,SAAS;AAAE,YAAA,OAAO,SAAS;AAEvC,QAAA,MAAM,EAAE,IAAI,EAAE,GAAGU,sCAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;AAE3D,QAAA,IAAI,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAACT,gBAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAEzD,QAAA,IAAI,SAAS,IAAI,SAAS,EAAE;YAC1B,MAAM,YAAY,GAAG,IAAI,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC;AACrD,YAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAACA,gBAAO,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC;YACvD,SAAS,GAAG,YAAY;QAC1B;QAEA,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;QACzC,IAAI,MAAM,CAAC,KAAK;YAAE,MAAM,MAAM,CAAC,KAAK;QAEpC,OAAO,MAAM,CAAC,GAAG;IACnB;AAaO,IAAA,cAAc,CACnB,GAA+B,EAC/B,eAAuB,EACvB,GAAmB,EAAA;QAEnB,IAAI,GAAG,IAAI,SAAS;YAClB,OAAOH,qBAAU,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;QAEjF,MAAM,CAAC,GAAGC,8BAAuB,CAAC,GAAG,EAAE,GAAG,CAAC;AAC3C,QAAA,MAAM,QAAQ,GAAGC,sBAAU,EAAE;AAC7B,QAAA,GAAG,CAAC,YAAY,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAErD,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,CACrC,GAAG,CAAC,OAAO,EACX,CAAqB,EACrB,eAAe,EACf,QAAQ,CACT;QACD,IAAI,MAAM,KAAK,SAAS;AACtB,YAAA,GAAG,CAAC,YAAY,CAAC,iEAAiE,CAAC;AAErF,QAAA,OAAO,MAAM;IACf;AAEQ,IAAA,mBAAmB,CACzB,CAAU,EACV,KAAuB,EACvB,eAAuB,EACvB,QAAgB,EAAA;AAEhB,QAAA,gCAAgC,CAAC,gBAAgB,EAAE,KAAK,CAAC,IAAI,CAAC;AAE9D,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC;QAC5D,IAAI,IAAI,IAAI,SAAS;AAAE,YAAA,OAAO,SAAS;AACvC,QAAA,MAAM,EAAE,IAAI,EAAE,GAAGU,sCAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;AAE3D,QAAA,IAAI,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAACT,gBAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAE3D,QAAA,IAAI,SAAS,IAAI,SAAS,EAAE;YAC1B,MAAM,YAAY,GAAG,IAAI,eAAe,CAAC,IAAI,EAAE,CAAC,EAAE,eAAe,CAAC;AAClE,YAAA,IAAI,CAAC,eAAe,CAAC,GAAG,CAACA,gBAAO,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,YAAY,CAAC;YAEzD,SAAS,GAAG,YAAY;QAC1B;QAEA,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC;QACzC,IAAI,MAAM,CAAC,KAAK;YAAE,MAAM,MAAM,CAAC,KAAK;QAEpC,OAAO,MAAM,CAAC,GAAG;IACnB;IAMO,YAAY,CACjB,GAA+B,EAC/B,GAAmB,EAAA;QAEnB,IAAI,GAAG,IAAI,SAAS;AAAE,YAAA,OAAOH,qBAAU,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAElF,MAAM,CAAC,GAAGC,8BAAuB,CAAC,GAAG,EAAE,GAAG,CAAC;AAE3C,QAAA,OAAO,IAAI,CAAC,iBAAiB,CAAC,CAAqB,CAAC;IACtD;AAEQ,IAAA,iBAAiB,CAAC,KAAuB,EAAA;AAC/C,QAAA,gCAAgC,CAAC,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC;AAC5D,QAAA,OAAOkB,wBAAY,CAAC,KAAK,EAAE,KAAK,CAAC;IACnC;IAEO,MAAM,SAAS,CACpB,MAAsB,EACtB,SAAiB,EACjB,WAAoB;IACpB,SAAkB,EAAA;QAElB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAC1CC,wCAA4B,CAAC,MAAM,CAAC,EACpC,SAAS,EACT,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC,EACxB,SAAS,CACV;QAED,OAAO;AACL,YAAA,IAAI,EAAE,KAAK;AACX,YAAA,kBAAkB,EAAE,KAAK;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;AACf,YAAA,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;AACvB,YAAA,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;SAClC;IACH;IAEO,MAAM,QAAQ,CACnB,MAAsB,EACtB,SAAiB,EACjB,WAAoB,EACpB,SAAkB,EAAA;QAElB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,CACzCA,wCAA4B,CAAC,MAAM,CAAC,EACpC,SAAS,EACT,MAAM,CAAC,WAAW,IAAI,CAAC,CAAC,EACxB,SAAS,CACV;QAED,OAAO;AACL,YAAA,IAAI,EAAE,KAAK;AACX,YAAA,kBAAkB,EAAE,KAAK;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;AACf,YAAA,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;AACvB,YAAA,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;SAClC;IACH;AAEQ,IAAA,MAAM,WAAW,CAAC,KAAmB,EAAE,QAAgB,EAAA;AAC7D,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAACjB,gBAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACtD,QAAA,IAAI,IAAI,IAAI,SAAS,EAAE;YACrB;QACF;AAEA,QAAA,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAACA,gBAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE;AAC5C,YAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAACA,gBAAO,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC;AAEnE,YAAA,MAAM,OAAO,CAAC,GAAG,CACf,QAAQ,CAAC,GAAG,CAAC,OAAO,UAAU,KAAI;gBAChC,MAAMkB,cAAG,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;AAE7B,gBAAA,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,CAAC;gBAElC,IAAI,CAAC,UAAU,CACbC,gBAAM,CAAC,IAAI,CAAC,aAAa,EAAEC,kBAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EACtD,YAAYC,gCAAuB,CAAC,UAAU,CAAC,CAAA,YAAA;AAC7C,sBAAA,CAAA,sBAAA,EAAyBA,gCAAuB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA,CAAE,CAClF;YACH,CAAC,CAAC,CACH;QACH;aAAO;;YAEL,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC1C,IAAI,OAAO,EAAE;AACX,gBAAA,IAAI,CAAC,UAAU,CACb,IAAI,EACJ,CAAA,SAAA,EAAYA,gCAAuB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA,uBAAA,CAAyB,CAC1E;YACH;QACF;IACF;IAEQ,UAAU,CAAC,IAAsB,EAAE,MAAc,EAAA;AACvD,QAAA,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;AAClB,QAAA,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA,kBAAA,EAAqB,IAAI,CAAC,IAAI,CAAA,UAAA,EAAa,MAAM,CAAA,CAAE,CAAC;AAC5E,QAAA,IAAI,CAAC,aAAa,CAAC,MAAM,CAACD,kBAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/C,QAAA,IAAI,CAAC,aAAa,CAAC,MAAM,CAACpB,gBAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACjD,QAAA,IAAI,CAAC,eAAe,CAAC,MAAM,CAACA,gBAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACrD;AAEQ,IAAA,MAAM,mBAAmB,CAAC,MAAkB,EAAE,QAAgB,EAAA;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAACA,gBAAO,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK;AACnF,QAAA,IAAI,OAAO;YAAE,IAAI,CAAC,aAAa,CAAC,MAAM,CAACA,gBAAO,CAAC,MAAM,CAAC,CAAC;IACzD;;AAGA,IAAA,MAAM,UAAU,GAAA;AACd,QAAA,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE;QAEzB,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,KAAI;AACvC,YAAA,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC;AAC9B,YAAA,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,QAAQsB,2BAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA,SAAA,CAAW,CAAC;AAC/E,QAAA,CAAC,CAAC;IACJ;AACD;AAED;AACA,MAAM,kBAAkB,CAAA;AAIH,IAAA,IAAA;AACA,IAAA,MAAA;AAJF,IAAA,OAAO,GAAG,IAAIC,wBAAc,EAAE;IAE/C,WAAA,CACmB,IAAY,EACZ,MAAwB,EAAA;QADxB,IAAA,CAAA,IAAI,GAAJ,IAAI;QACJ,IAAA,CAAA,MAAM,GAAN,MAAM;IACtB;IAEI,SAAS,GAAA;AACd,QAAA,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;IACjD;AAEO,IAAA,MAAM,CAAC,QAAgB,EAAA;AAC5B,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC5B;AAEO,IAAA,OAAO,CAAC,QAAgB,EAAA;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnC;AACD;AAED,MAAM,eAAe,CAAA;AAOA,IAAA,IAAA;AACA,IAAA,KAAA;AACA,IAAA,eAAA;AARX,IAAA,OAAO;AACP,IAAA,GAAG;AACM,IAAA,MAAM,GAAiB,IAAIC,uBAAY,EAAE;IAClD,KAAK,GAAoB,SAAS;AAE1C,IAAA,WAAA,CACmB,IAAY,EACZ,KAAa,EACb,eAAwB,EAAA;QAFxB,IAAA,CAAA,IAAI,GAAJ,IAAI;QACJ,IAAA,CAAA,KAAK,GAAL,KAAK;QACL,IAAA,CAAA,eAAe,GAAf,eAAe;AAEhC,QAAA,IAAI,CAAC,OAAO,GAAG,IAAIC,eAAO,CAAC,YAAY,IAAI,CAAC,MAAM,EAAE,CAAC;IACvD;AAEA,IAAA,aAAa,CAAC,CAAU,EAAA;AAItB,QAAA,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;AAE5B,QAAA,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;QAEvB,OAAO;YACL,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB;IACH;AAEA,IAAA,MAAM,MAAM,GAAA;AACV,QAAA,IAAI;AACF,YAAA,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC;AAE/E,YAAA,IAAI,IAAI,CAAC,GAAG,IAAI,OAAO;gBAAE,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA,SAAA,EAAY,IAAI,CAAC,IAAI,CAAA,QAAA,CAAU,CAAC;AACjF,YAAA,IAAI,CAAC,GAAG,GAAG,OAAO;QACpB;QAAE,OAAO,CAAM,EAAE;AACf,YAAA,IAAI,CAAC,CAAC,IAAI,IAAI,UAAU,IAAI,CAAC,CAAC,IAAI,IAAI,WAAW,EAAE;;AAEjD,gBAAA,IAAI,CAAC,GAAG,GAAG,EAAE;AACb,gBAAA,IAAI,CAAC,KAAK,GAAG,CAAC;gBACd,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA,eAAA,EAAkB,IAAI,CAAC,IAAI,CAAA,2BAAA,CAA6B,CAAC;gBACjF;YACF;AAEA,YAAA,MAAM,CAAC;QACT;IACF;AACD;AAED;AACqC;AACrC,eAAe,YAAY,CAAC,KAAa,EAAE,MAAc,EAAE,eAAwB,EAAA;AACjF,IAAA,IAAI,QAAmC;AACvC,IAAA,IAAI,EAAkC;AAEtC,IAAA,IAAI;AACF,QAAA,QAAQ,GAAGC,aAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC;AACrC,QAAA,EAAE,GAAGC,mBAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AAEvE,QAAA,MAAM,KAAK,GAAG,IAAI,MAAM,EAAE;AAE1B,QAAA,WAAW,MAAM,IAAI,IAAI,EAAE,EAAE;YAC3B,IAAI,eAAe,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC;gBAAE;AAErE,YAAA,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;AAChB,YAAA,IAAI,KAAK,CAAC,MAAM,GAAG,MAAM,EAAE;gBACzB,KAAK,CAAC,KAAK,EAAE;YACf;QACF;;AAGA,QAAA,OAAO,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,CAACC,aAAE,CAAC,GAAG,CAAC,GAAGA,aAAE,CAAC,GAAG;IAC9C;YAAU;;AAER,QAAA,IAAI;YACF,IAAI,EAAE,EAAE;gBACN,EAAE,CAAC,KAAK,EAAE;YACZ;QACF;QAAE,OAAO,YAAY,EAAE;AACrB,YAAA,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,YAAY,CAAC;QAClE;AAEA,QAAA,IAAI;AACF,YAAA,IAAI,QAAQ,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE;gBACnC,QAAQ,CAAC,OAAO,EAAE;YACpB;QACF;QAAE,OAAO,YAAY,EAAE;AACrB,YAAA,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,YAAY,CAAC;QAC9D;IACF;AACF;AAEA,SAAS,gCAAgC,CAAC,UAAkB,EAAE,KAAmB,EAAA;IAC/E,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE;QACnC,IAAI,OAAO,GAAG,CAAA,EAAG,UAAU,0BAA0B,KAAK,CAAC,IAAI,CAAA,wDAAA,CAA0D;AACzH,QAAA,IAAI,KAAK,CAAC,IAAI,IAAI,MAAM;YACtB,OAAO,IAAI,2FAA2F;AAExG,QAAA,MAAM,IAAIC,8BAAsB,CAAC,OAAO,CAAC;IAC3C;AACF;;;;"}
|
|
1
|
+
{"version":3,"file":"download_blob.cjs","sources":["../../../src/drivers/download_blob/download_blob.ts"],"sourcesContent":["import type {\n ComputableCtx,\n ComputableStableDefined,\n Watcher,\n} from '@milaboratories/computable';\nimport {\n ChangeSource,\n Computable,\n} from '@milaboratories/computable';\nimport type { ResourceId, ResourceType } from '@milaboratories/pl-client';\nimport { resourceIdToString, stringifyWithResourceId } from '@milaboratories/pl-client';\nimport type {\n AnyLogHandle,\n BlobDriver,\n ContentHandler,\n GetContentOptions,\n LocalBlobHandle,\n LocalBlobHandleAndSize,\n ReadyLogHandle,\n RemoteBlobHandle,\n RemoteBlobHandleAndSize,\n StreamingApiResponse,\n} from '@milaboratories/pl-model-common';\nimport { type RangeBytes, validateRangeBytes } from '@milaboratories/pl-model-common';\nimport type {\n PlTreeEntry,\n ResourceInfo,\n ResourceSnapshot\n} from '@milaboratories/pl-tree';\nimport {\n isPlTreeEntry,\n makeResourceSnapshot,\n treeEntryToResourceInfo,\n} from '@milaboratories/pl-tree';\nimport type { MiLogger, Signer } from '@milaboratories/ts-helpers';\nimport { CallersCounter, mapGet, TaskProcessor } from '@milaboratories/ts-helpers';\nimport Denque from 'denque';\nimport * as fs from 'fs';\nimport { randomUUID } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport * as readline from 'node:readline/promises';\nimport { buffer } from 'node:stream/consumers';\nimport { Readable } from 'node:stream';\nimport type { ClientDownload } from '../../clients/download';\nimport type { ClientLogs } from '../../clients/logs';\nimport { withFileContent } from '../helpers/read_file';\nimport {\n isLocalBlobHandle,\n newLocalHandle,\n parseLocalHandle,\n} from '../helpers/download_local_handle';\nimport {\n isRemoteBlobHandle,\n newRemoteHandle,\n parseRemoteHandle,\n} from '../helpers/download_remote_handle';\nimport { Updater, WrongResourceTypeError } from '../helpers/helpers';\nimport { getResourceInfoFromLogHandle, newLogHandle } from '../helpers/logs_handle';\nimport { getSize, OnDemandBlobResourceSnapshot } from '../types';\nimport { blobKey, pathToKey } from './blob_key';\nimport { DownloadBlobTask, nonRecoverableError } from './download_blob_task';\nimport { FilesCache } from '../helpers/files_cache';\nimport { SparseCache, SparseCacheFsFile, SparseCacheFsRanges } from './sparse_cache/cache';\nimport { isOffByOneError } from '../../helpers/download';\n\nexport type DownloadDriverOps = {\n /**\n * A soft limit of the amount of blob storage, in bytes.\n * Once exceeded, the download driver will start deleting blobs one by one\n * when they become unneeded.\n * */\n cacheSoftSizeBytes: number;\n\n /**\n * A hard limit of the amount of sparse cache, in bytes.\n * Once exceeded, the download driver will start deleting blobs one by one.\n *\n * The sparse cache is used to store ranges of blobs.\n * */\n rangesCacheMaxSizeBytes: number;\n\n /**\n * Max number of concurrent downloads while calculating computable states\n * derived from this driver\n * */\n nConcurrentDownloads: number;\n};\n\n/** DownloadDriver holds a queue of downloading tasks,\n * and notifies every watcher when a file were downloaded. */\nexport class DownloadDriver implements BlobDriver {\n /** Represents a unique key to the path of a blob as a map. */\n private keyToDownload: Map<string, DownloadBlobTask> = new Map();\n\n /** Writes and removes files to a hard drive and holds a counter for every\n * file that should be kept. */\n private cache: FilesCache<DownloadBlobTask>;\n private rangesCache: SparseCache;\n\n /** Downloads files and writes them to the local dir. */\n private downloadQueue: TaskProcessor;\n\n private keyToOnDemand: Map<string, OnDemandBlobHolder> = new Map();\n\n private idToLastLines: Map<string, LastLinesGetter> = new Map();\n private idToProgressLog: Map<string, LastLinesGetter> = new Map();\n\n private readonly saveDir: string;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly clientDownload: ClientDownload,\n private readonly clientLogs: ClientLogs,\n saveDir: string,\n private readonly rangesCacheDir: string,\n private readonly signer: Signer,\n private readonly ops: DownloadDriverOps,\n ) {\n this.cache = new FilesCache(this.ops.cacheSoftSizeBytes);\n\n const fsRanges = new SparseCacheFsRanges(this.logger, this.rangesCacheDir);\n const fsStorage = new SparseCacheFsFile(this.logger, this.rangesCacheDir);\n this.rangesCache = new SparseCache(this.logger, this.ops.rangesCacheMaxSizeBytes, fsRanges, fsStorage);\n\n this.downloadQueue = new TaskProcessor(this.logger, ops.nConcurrentDownloads);\n\n this.saveDir = path.resolve(saveDir);\n }\n\n static async init(\n logger: MiLogger,\n clientDownload: ClientDownload,\n clientLogs: ClientLogs,\n saveDir: string,\n rangesCacheDir: string,\n signer: Signer,\n ops: DownloadDriverOps,\n ): Promise<DownloadDriver> {\n const driver = new DownloadDriver(logger, clientDownload, clientLogs, saveDir, rangesCacheDir, signer, ops);\n await driver.rangesCache.reset();\n\n return driver;\n }\n\n /** Gets a blob or part of the blob by its resource id or downloads a blob and sets it in a cache. */\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ctx: ComputableCtx,\n ): LocalBlobHandleAndSize | undefined;\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ): ComputableStableDefined<LocalBlobHandleAndSize>;\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<LocalBlobHandleAndSize | undefined> | LocalBlobHandleAndSize | undefined {\n if (ctx === undefined) {\n return Computable.make((ctx) => this.getDownloadedBlob(res, ctx));\n }\n\n const rInfo = treeEntryToResourceInfo(res, ctx);\n\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(rInfo, callerId));\n\n const result = this.getDownloadedBlobNoCtx(ctx.watcher, rInfo as ResourceSnapshot, callerId);\n if (result == undefined) {\n ctx.markUnstable('download blob is still undefined');\n }\n\n return result;\n }\n\n private getDownloadedBlobNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n callerId: string,\n ): LocalBlobHandleAndSize | undefined {\n validateDownloadableResourceType('getDownloadedBlob', rInfo.type);\n\n // We don't need to request files with wider limits,\n // PFrame's engine does it disk-optimally by itself.\n\n const task = this.getOrSetNewTask(rInfo, callerId);\n task.attach(w, callerId);\n\n const result = task.getBlob();\n if (!result.done) {\n return undefined;\n }\n if (result.result.ok) {\n return result.result.value;\n }\n throw result.result.error;\n }\n\n private getOrSetNewTask(\n rInfo: ResourceSnapshot,\n callerId: string,\n ): DownloadBlobTask {\n const key = blobKey(rInfo.id);\n\n const inMemoryTask = this.keyToDownload.get(key);\n if (inMemoryTask) {\n return inMemoryTask;\n }\n\n // schedule the blob downloading, then it'll be added to the cache.\n const fPath = path.resolve(this.saveDir, key);\n\n const newTask = new DownloadBlobTask(\n this.logger,\n this.clientDownload,\n rInfo,\n newLocalHandle(fPath, this.signer),\n fPath,\n );\n this.keyToDownload.set(key, newTask);\n\n this.downloadQueue.push({\n fn: () => this.downloadBlob(newTask, callerId),\n recoverableErrorPredicate: (e) => !nonRecoverableError(e),\n });\n\n return newTask;\n }\n\n private async downloadBlob(task: DownloadBlobTask, callerId: string) {\n await task.download();\n const blob = task.getBlob();\n if (blob.done && blob.result.ok) {\n this.cache.addCache(task, callerId);\n }\n }\n\n /** Gets on demand blob. */\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ): Computable<RemoteBlobHandleAndSize>;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx?: undefined,\n fromBytes?: number,\n toBytes?: number,\n ): Computable<RemoteBlobHandleAndSize>;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx: ComputableCtx,\n fromBytes?: number,\n toBytes?: number,\n ): RemoteBlobHandleAndSize;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx?: ComputableCtx,\n ): ComputableStableDefined<RemoteBlobHandleAndSize> | RemoteBlobHandleAndSize | undefined {\n if (ctx === undefined) return Computable.make((ctx) => this.getOnDemandBlob(res, ctx));\n\n const rInfo: OnDemandBlobResourceSnapshot = isPlTreeEntry(res)\n ? makeResourceSnapshot(res, OnDemandBlobResourceSnapshot, ctx)\n : res;\n\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseOnDemandBlob(rInfo.id, callerId));\n\n // note that the watcher is not needed,\n // the handler never changes.\n const result = this.getOnDemandBlobNoCtx(rInfo, callerId);\n\n return result;\n }\n\n private getOnDemandBlobNoCtx(\n info: OnDemandBlobResourceSnapshot,\n callerId: string,\n ): RemoteBlobHandleAndSize {\n validateDownloadableResourceType('getOnDemandBlob', info.type);\n\n let blob = this.keyToOnDemand.get(blobKey(info.id));\n\n if (blob === undefined) {\n blob = new OnDemandBlobHolder(getSize(info), newRemoteHandle(info, this.signer));\n this.keyToOnDemand.set(blobKey(info.id), blob);\n }\n\n blob.attach(callerId);\n\n return blob.getHandle();\n }\n\n /** Gets a path from a handle. */\n public getLocalPath(handle: LocalBlobHandle): string {\n const { path } = parseLocalHandle(handle, this.signer);\n return path;\n }\n\n /** Gets a content of a blob by a handle. */\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n options?: GetContentOptions,\n ): Promise<Uint8Array>;\n /** @deprecated Use {@link getContent} with {@link GetContentOptions} instead */\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n range?: RangeBytes,\n ): Promise<Uint8Array>;\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n optionsOrRange?: GetContentOptions | RangeBytes,\n ): Promise<Uint8Array> {\n let options: GetContentOptions = {};\n if (typeof optionsOrRange === 'object' && optionsOrRange !== null) {\n if ('range' in optionsOrRange) {\n options = optionsOrRange;\n } else {\n const range = optionsOrRange as RangeBytes;\n validateRangeBytes(range, `getContent`);\n options = { range };\n }\n }\n\n const request = () => this.withContent(handle, {\n ...options,\n handler: async (content) => {\n const chunks: Uint8Array[] = [];\n for await (const chunk of content) {\n options.signal?.throwIfAborted();\n chunks.push(chunk);\n }\n return Buffer.concat(chunks);\n }\n });\n\n try {\n return await request();\n } catch (error) {\n if (isOffByOneError(error)) {\n return await request();\n }\n throw error;\n }\n }\n\n /** Gets a content stream of a blob by a handle and calls handler with it. */\n public async withContent<T>(\n handle: LocalBlobHandle | RemoteBlobHandle,\n options: GetContentOptions & {\n handler: ContentHandler<T>;\n },\n ): Promise<T> {\n const { range, signal, handler } = options;\n\n if (isLocalBlobHandle(handle)) {\n return await withFileContent({ path: this.getLocalPath(handle), range, signal, handler });\n }\n\n if (isRemoteBlobHandle(handle)) {\n const result = parseRemoteHandle(handle, this.signer);\n\n const key = blobKey(result.info.id);\n const filePath = await this.rangesCache.get(key, range ?? { from: 0, to: result.size });\n signal?.throwIfAborted();\n\n if (filePath) return await withFileContent({ path: filePath, range, signal, handler });\n\n return await this.clientDownload.withBlobContent(\n result.info,\n { signal },\n options,\n async (content, size) => {\n const [handlerStream, cacheStream] = content.tee();\n \n const handlerPromise = handler(handlerStream, size);\n const _cachePromise = buffer(cacheStream)\n .then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data))\n .catch(() => {\n // Ignore cache errors - they shouldn't affect the main handler result\n // This prevents unhandled promise rejections when the stream fails\n });\n\n return await handlerPromise;\n }\n );\n }\n\n throw new Error('Malformed remote handle');\n }\n\n /**\n * Creates computable that will return blob content once it is downloaded.\n * Uses downloaded blob handle under the hood, so stores corresponding blob in file system.\n */\n public getComputableContent(\n res: ResourceInfo | PlTreeEntry,\n range?: RangeBytes,\n ): ComputableStableDefined<Uint8Array> {\n if (range) {\n validateRangeBytes(range, `getComputableContent`);\n }\n\n return Computable.make((ctx) =>\n this.getDownloadedBlob(res, ctx), {\n postprocessValue: (v) => v ? this.getContent(v.handle, { range }) : undefined\n }\n ).withStableType()\n }\n\n /** Returns all logs and schedules a job that reads remain logs.\n * Notifies when a new portion of the log appeared. */\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number\n ): Computable<string | undefined>;\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number,\n ctx: ComputableCtx\n ): Computable<string | undefined>;\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number,\n ctx?: ComputableCtx,\n ): Computable<string | undefined> | string | undefined {\n if (ctx == undefined) return Computable.make((ctx) => this.getLastLogs(res, lines, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(r, callerId));\n\n const result = this.getLastLogsNoCtx(ctx.watcher, r as ResourceSnapshot, lines, callerId);\n if (result == undefined)\n ctx.markUnstable('either a file was not downloaded or logs was not read');\n\n return result;\n }\n\n private getLastLogsNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n lines: number,\n callerId: string,\n ): string | undefined {\n validateDownloadableResourceType('getLastLogs', rInfo.type);\n const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);\n if (blob == undefined) return undefined;\n\n const { path } = parseLocalHandle(blob.handle, this.signer);\n\n let logGetter = this.idToLastLines.get(blobKey(rInfo.id));\n\n if (logGetter == undefined) {\n const newLogGetter = new LastLinesGetter(path, lines);\n this.idToLastLines.set(blobKey(rInfo.id), newLogGetter);\n logGetter = newLogGetter;\n }\n\n const result = logGetter.getOrSchedule(w);\n if (result.error) throw result.error;\n\n return result.log;\n }\n\n /** Returns a last line that has patternToSearch.\n * Notifies when a new line appeared or EOF reached. */\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string\n ): Computable<string | undefined>;\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string,\n ctx: ComputableCtx\n ): string | undefined;\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string,\n ctx?: ComputableCtx,\n ): Computable<string | undefined> | string | undefined {\n if (ctx == undefined)\n return Computable.make((ctx) => this.getProgressLog(res, patternToSearch, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(r, callerId));\n\n const result = this.getProgressLogNoCtx(\n ctx.watcher,\n r as ResourceSnapshot,\n patternToSearch,\n callerId,\n );\n if (result === undefined)\n ctx.markUnstable('either a file was not downloaded or a progress log was not read');\n\n return result;\n }\n\n private getProgressLogNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n patternToSearch: string,\n callerId: string,\n ): string | undefined {\n validateDownloadableResourceType('getProgressLog', rInfo.type);\n\n const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);\n if (blob == undefined) return undefined;\n const { path } = parseLocalHandle(blob.handle, this.signer);\n\n let logGetter = this.idToProgressLog.get(blobKey(rInfo.id));\n\n if (logGetter == undefined) {\n const newLogGetter = new LastLinesGetter(path, 1, patternToSearch);\n this.idToProgressLog.set(blobKey(rInfo.id), newLogGetter);\n\n logGetter = newLogGetter;\n }\n\n const result = logGetter.getOrSchedule(w);\n if (result.error) throw result.error;\n\n return result.log;\n }\n\n /** Returns an Id of a smart object, that can read logs directly from\n * the platform. */\n public getLogHandle(res: ResourceInfo | PlTreeEntry): Computable<AnyLogHandle>;\n public getLogHandle(res: ResourceInfo | PlTreeEntry, ctx: ComputableCtx): AnyLogHandle;\n public getLogHandle(\n res: ResourceInfo | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<AnyLogHandle> | AnyLogHandle {\n if (ctx == undefined) return Computable.make((ctx) => this.getLogHandle(res, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n\n return this.getLogHandleNoCtx(r as ResourceSnapshot);\n }\n\n private getLogHandleNoCtx(rInfo: ResourceSnapshot): AnyLogHandle {\n validateDownloadableResourceType('getLogHandle', rInfo.type);\n return newLogHandle(false, rInfo);\n }\n\n public async lastLines(\n handle: ReadyLogHandle,\n lineCount: number,\n offsetBytes?: number, // if 0n, then start from the end.\n searchStr?: string,\n ): Promise<StreamingApiResponse> {\n const resp = await this.clientLogs.lastLines(\n getResourceInfoFromLogHandle(handle),\n lineCount,\n BigInt(offsetBytes ?? 0),\n searchStr,\n );\n\n return {\n live: false,\n shouldUpdateHandle: false,\n data: resp.data,\n size: Number(resp.size),\n newOffset: Number(resp.newOffset),\n };\n }\n\n public async readText(\n handle: ReadyLogHandle,\n lineCount: number,\n offsetBytes?: number,\n searchStr?: string,\n ): Promise<StreamingApiResponse> {\n const resp = await this.clientLogs.readText(\n getResourceInfoFromLogHandle(handle),\n lineCount,\n BigInt(offsetBytes ?? 0),\n searchStr,\n );\n\n return {\n live: false,\n shouldUpdateHandle: false,\n data: resp.data,\n size: Number(resp.size),\n newOffset: Number(resp.newOffset),\n };\n }\n\n private async releaseBlob(rInfo: ResourceInfo, callerId: string) {\n const task = this.keyToDownload.get(blobKey(rInfo.id));\n if (task == undefined) {\n return;\n }\n\n if (this.cache.existsFile(blobKey(rInfo.id))) {\n const toDelete = this.cache.removeFile(blobKey(rInfo.id), callerId);\n\n await Promise.all(\n toDelete.map(async (cachedFile) => {\n await fsp.rm(cachedFile.path);\n\n this.cache.removeCache(cachedFile);\n\n this.removeTask(\n mapGet(this.keyToDownload, pathToKey(cachedFile.path)),\n `the task ${stringifyWithResourceId(cachedFile)} was removed`\n + `from cache along with ${stringifyWithResourceId(toDelete.map((d) => d.path))}`,\n );\n }),\n );\n } else {\n // The task is still in a downloading queue.\n const deleted = task.counter.dec(callerId);\n if (deleted) {\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was removed from cache`,\n );\n }\n }\n }\n\n private removeTask(task: DownloadBlobTask, reason: string) {\n task.abort(reason);\n task.change.markChanged(`download task for ${task.path} removed: ${reason}`);\n this.keyToDownload.delete(pathToKey(task.path));\n this.idToLastLines.delete(blobKey(task.rInfo.id));\n this.idToProgressLog.delete(blobKey(task.rInfo.id));\n }\n\n private async releaseOnDemandBlob(blobId: ResourceId, callerId: string) {\n const deleted = this.keyToOnDemand.get(blobKey(blobId))?.release(callerId) ?? false;\n if (deleted) this.keyToOnDemand.delete(blobKey(blobId));\n }\n\n /** Removes all files from a hard drive. */\n async releaseAll() {\n this.downloadQueue.stop();\n\n this.keyToDownload.forEach((task, key) => {\n this.keyToDownload.delete(key);\n task.change.markChanged(`task ${resourceIdToString(task.rInfo.id)} released`);\n });\n }\n}\n\n/** Keeps a counter to the on demand handle. */\nclass OnDemandBlobHolder {\n private readonly counter = new CallersCounter();\n\n constructor(\n private readonly size: number,\n private readonly handle: RemoteBlobHandle,\n ) {}\n\n public getHandle(): RemoteBlobHandleAndSize {\n return { handle: this.handle, size: this.size };\n }\n\n public attach(callerId: string) {\n this.counter.inc(callerId);\n }\n\n public release(callerId: string): boolean {\n return this.counter.dec(callerId);\n }\n}\n\nclass LastLinesGetter {\n private updater: Updater;\n private log: string | undefined;\n private readonly change: ChangeSource = new ChangeSource();\n private error: any | undefined = undefined;\n\n constructor(\n private readonly path: string,\n private readonly lines: number,\n private readonly patternToSearch?: string,\n ) {\n this.updater = new Updater(async () => this.update());\n }\n\n getOrSchedule(w: Watcher): {\n log: string | undefined;\n error?: any | undefined;\n } {\n this.change.attachWatcher(w);\n\n this.updater.schedule();\n\n return {\n log: this.log,\n error: this.error,\n };\n }\n\n async update(): Promise<void> {\n try {\n const newLogs = await getLastLines(this.path, this.lines, this.patternToSearch);\n\n if (this.log != newLogs) this.change.markChanged(`logs for ${this.path} updated`);\n this.log = newLogs;\n } catch (e: any) {\n if (e.name == 'RpcError' && e.code == 'NOT_FOUND') {\n // No resource\n this.log = '';\n this.error = e;\n this.change.markChanged(`log update for ${this.path} failed, resource not found`);\n return;\n }\n\n throw e;\n }\n }\n}\n\n/** Gets last lines from a file by reading the file from the top and keeping\n * last N lines in a window queue. */\nasync function getLastLines(fPath: string, nLines: number, patternToSearch?: string): Promise<string> {\n let inStream: fs.ReadStream | undefined;\n let rl: readline.Interface | undefined;\n\n try {\n inStream = fs.createReadStream(fPath);\n rl = readline.createInterface({ input: inStream, crlfDelay: Infinity });\n\n const lines = new Denque();\n\n for await (const line of rl) {\n if (patternToSearch != undefined && !line.includes(patternToSearch)) continue;\n\n lines.push(line);\n if (lines.length > nLines) {\n lines.shift();\n }\n }\n\n // last EOL is for keeping backward compat with platforma implementation.\n return lines.toArray().join(os.EOL) + os.EOL;\n } finally {\n // Cleanup resources in finally block to ensure they're always cleaned up\n try {\n if (rl) {\n rl.close();\n }\n } catch (cleanupError) {\n console.error('Error closing readline interface:', cleanupError);\n }\n\n try {\n if (inStream && !inStream.destroyed) {\n inStream.destroy();\n }\n } catch (cleanupError) {\n console.error('Error destroying read stream:', cleanupError);\n }\n }\n}\n\nfunction validateDownloadableResourceType(methodName: string, rType: ResourceType) {\n if (!rType.name.startsWith('Blob/')) {\n let message = `${methodName}: wrong resource type: ${rType.name}, expected: a resource of type that starts with 'Blob/'.`;\n if (rType.name == 'Blob')\n message += ` If it's called from workflow, should a file be exported with 'file.exportFile' function?`;\n\n throw new WrongResourceTypeError(message);\n }\n}\n"],"names":["FilesCache","SparseCacheFsRanges","SparseCacheFsFile","SparseCache","TaskProcessor","path","Computable","treeEntryToResourceInfo","randomUUID","blobKey","DownloadBlobTask","newLocalHandle","nonRecoverableError","isPlTreeEntry","makeResourceSnapshot","OnDemandBlobResourceSnapshot","getSize","newRemoteHandle","parseLocalHandle","validateRangeBytes","isOffByOneError","isLocalBlobHandle","withFileContent","isRemoteBlobHandle","parseRemoteHandle","buffer","newLogHandle","getResourceInfoFromLogHandle","fsp","mapGet","pathToKey","stringifyWithResourceId","resourceIdToString","CallersCounter","ChangeSource","Updater","fs","readline","os","WrongResourceTypeError"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download_blob.d.ts","sourceRoot":"","sources":["../../../src/drivers/download_blob/download_blob.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,uBAAuB,EAExB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAEL,UAAU,EACX,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,KAAK,UAAU,EAAsB,MAAM,iCAAiC,CAAC;AACtF,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EAEb,MAAM,yBAAyB,CAAC;AAMjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAWnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAcrD,OAAO,EAAW,4BAA4B,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"download_blob.d.ts","sourceRoot":"","sources":["../../../src/drivers/download_blob/download_blob.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,6 +21,7 @@ import { blobKey, pathToKey } from './blob_key.js';
|
|
|
21
21
|
import { DownloadBlobTask, nonRecoverableError } from './download_blob_task.js';
|
|
22
22
|
import { FilesCache } from '../helpers/files_cache.js';
|
|
23
23
|
import { SparseCacheFsRanges, SparseCacheFsFile, SparseCache } from './sparse_cache/cache.js';
|
|
24
|
+
import { isOffByOneError } from '../../helpers/download.js';
|
|
24
25
|
|
|
25
26
|
/** DownloadDriver holds a queue of downloading tasks,
|
|
26
27
|
* and notifies every watcher when a file were downloaded. */
|
|
@@ -153,7 +154,7 @@ class DownloadDriver {
|
|
|
153
154
|
options = { range };
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
|
-
|
|
157
|
+
const request = () => this.withContent(handle, {
|
|
157
158
|
...options,
|
|
158
159
|
handler: async (content) => {
|
|
159
160
|
const chunks = [];
|
|
@@ -164,6 +165,15 @@ class DownloadDriver {
|
|
|
164
165
|
return Buffer.concat(chunks);
|
|
165
166
|
}
|
|
166
167
|
});
|
|
168
|
+
try {
|
|
169
|
+
return await request();
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (isOffByOneError(error)) {
|
|
173
|
+
return await request();
|
|
174
|
+
}
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
167
177
|
}
|
|
168
178
|
/** Gets a content stream of a blob by a handle and calls handler with it. */
|
|
169
179
|
async withContent(handle, options) {
|
|
@@ -182,7 +192,11 @@ class DownloadDriver {
|
|
|
182
192
|
const [handlerStream, cacheStream] = content.tee();
|
|
183
193
|
const handlerPromise = handler(handlerStream, size);
|
|
184
194
|
buffer(cacheStream)
|
|
185
|
-
.then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data))
|
|
195
|
+
.then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data))
|
|
196
|
+
.catch(() => {
|
|
197
|
+
// Ignore cache errors - they shouldn't affect the main handler result
|
|
198
|
+
// This prevents unhandled promise rejections when the stream fails
|
|
199
|
+
});
|
|
186
200
|
return await handlerPromise;
|
|
187
201
|
});
|
|
188
202
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download_blob.js","sources":["../../../src/drivers/download_blob/download_blob.ts"],"sourcesContent":["import type {\n ComputableCtx,\n ComputableStableDefined,\n Watcher,\n} from '@milaboratories/computable';\nimport {\n ChangeSource,\n Computable,\n} from '@milaboratories/computable';\nimport type { ResourceId, ResourceType } from '@milaboratories/pl-client';\nimport { resourceIdToString, stringifyWithResourceId } from '@milaboratories/pl-client';\nimport type {\n AnyLogHandle,\n BlobDriver,\n ContentHandler,\n GetContentOptions,\n LocalBlobHandle,\n LocalBlobHandleAndSize,\n ReadyLogHandle,\n RemoteBlobHandle,\n RemoteBlobHandleAndSize,\n StreamingApiResponse,\n} from '@milaboratories/pl-model-common';\nimport { type RangeBytes, validateRangeBytes } from '@milaboratories/pl-model-common';\nimport type {\n PlTreeEntry,\n ResourceInfo,\n ResourceSnapshot\n} from '@milaboratories/pl-tree';\nimport {\n isPlTreeEntry,\n makeResourceSnapshot,\n treeEntryToResourceInfo,\n} from '@milaboratories/pl-tree';\nimport type { MiLogger, Signer } from '@milaboratories/ts-helpers';\nimport { CallersCounter, mapGet, TaskProcessor } from '@milaboratories/ts-helpers';\nimport Denque from 'denque';\nimport * as fs from 'fs';\nimport { randomUUID } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport * as readline from 'node:readline/promises';\nimport { buffer } from 'node:stream/consumers';\nimport { Readable } from 'node:stream';\nimport type { ClientDownload } from '../../clients/download';\nimport type { ClientLogs } from '../../clients/logs';\nimport { withFileContent } from '../helpers/read_file';\nimport {\n isLocalBlobHandle,\n newLocalHandle,\n parseLocalHandle,\n} from '../helpers/download_local_handle';\nimport {\n isRemoteBlobHandle,\n newRemoteHandle,\n parseRemoteHandle,\n} from '../helpers/download_remote_handle';\nimport { Updater, WrongResourceTypeError } from '../helpers/helpers';\nimport { getResourceInfoFromLogHandle, newLogHandle } from '../helpers/logs_handle';\nimport { getSize, OnDemandBlobResourceSnapshot } from '../types';\nimport { blobKey, pathToKey } from './blob_key';\nimport { DownloadBlobTask, nonRecoverableError } from './download_blob_task';\nimport { FilesCache } from '../helpers/files_cache';\nimport { SparseCache, SparseCacheFsFile, SparseCacheFsRanges } from './sparse_cache/cache';\n\nexport type DownloadDriverOps = {\n /**\n * A soft limit of the amount of blob storage, in bytes.\n * Once exceeded, the download driver will start deleting blobs one by one\n * when they become unneeded.\n * */\n cacheSoftSizeBytes: number;\n\n /**\n * A hard limit of the amount of sparse cache, in bytes.\n * Once exceeded, the download driver will start deleting blobs one by one.\n *\n * The sparse cache is used to store ranges of blobs.\n * */\n rangesCacheMaxSizeBytes: number;\n\n /**\n * Max number of concurrent downloads while calculating computable states\n * derived from this driver\n * */\n nConcurrentDownloads: number;\n};\n\n/** DownloadDriver holds a queue of downloading tasks,\n * and notifies every watcher when a file were downloaded. */\nexport class DownloadDriver implements BlobDriver {\n /** Represents a unique key to the path of a blob as a map. */\n private keyToDownload: Map<string, DownloadBlobTask> = new Map();\n\n /** Writes and removes files to a hard drive and holds a counter for every\n * file that should be kept. */\n private cache: FilesCache<DownloadBlobTask>;\n private rangesCache: SparseCache;\n\n /** Downloads files and writes them to the local dir. */\n private downloadQueue: TaskProcessor;\n\n private keyToOnDemand: Map<string, OnDemandBlobHolder> = new Map();\n\n private idToLastLines: Map<string, LastLinesGetter> = new Map();\n private idToProgressLog: Map<string, LastLinesGetter> = new Map();\n\n private readonly saveDir: string;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly clientDownload: ClientDownload,\n private readonly clientLogs: ClientLogs,\n saveDir: string,\n private readonly rangesCacheDir: string,\n private readonly signer: Signer,\n private readonly ops: DownloadDriverOps,\n ) {\n this.cache = new FilesCache(this.ops.cacheSoftSizeBytes);\n\n const fsRanges = new SparseCacheFsRanges(this.logger, this.rangesCacheDir);\n const fsStorage = new SparseCacheFsFile(this.logger, this.rangesCacheDir);\n this.rangesCache = new SparseCache(this.logger, this.ops.rangesCacheMaxSizeBytes, fsRanges, fsStorage);\n\n this.downloadQueue = new TaskProcessor(this.logger, ops.nConcurrentDownloads);\n\n this.saveDir = path.resolve(saveDir);\n }\n\n static async init(\n logger: MiLogger,\n clientDownload: ClientDownload,\n clientLogs: ClientLogs,\n saveDir: string,\n rangesCacheDir: string,\n signer: Signer,\n ops: DownloadDriverOps,\n ): Promise<DownloadDriver> {\n const driver = new DownloadDriver(logger, clientDownload, clientLogs, saveDir, rangesCacheDir, signer, ops);\n await driver.rangesCache.reset();\n\n return driver;\n }\n\n /** Gets a blob or part of the blob by its resource id or downloads a blob and sets it in a cache. */\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ctx: ComputableCtx,\n ): LocalBlobHandleAndSize | undefined;\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ): ComputableStableDefined<LocalBlobHandleAndSize>;\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<LocalBlobHandleAndSize | undefined> | LocalBlobHandleAndSize | undefined {\n if (ctx === undefined) {\n return Computable.make((ctx) => this.getDownloadedBlob(res, ctx));\n }\n\n const rInfo = treeEntryToResourceInfo(res, ctx);\n\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(rInfo, callerId));\n\n const result = this.getDownloadedBlobNoCtx(ctx.watcher, rInfo as ResourceSnapshot, callerId);\n if (result == undefined) {\n ctx.markUnstable('download blob is still undefined');\n }\n\n return result;\n }\n\n private getDownloadedBlobNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n callerId: string,\n ): LocalBlobHandleAndSize | undefined {\n validateDownloadableResourceType('getDownloadedBlob', rInfo.type);\n\n // We don't need to request files with wider limits,\n // PFrame's engine does it disk-optimally by itself.\n\n const task = this.getOrSetNewTask(rInfo, callerId);\n task.attach(w, callerId);\n\n const result = task.getBlob();\n if (!result.done) {\n return undefined;\n }\n if (result.result.ok) {\n return result.result.value;\n }\n throw result.result.error;\n }\n\n private getOrSetNewTask(\n rInfo: ResourceSnapshot,\n callerId: string,\n ): DownloadBlobTask {\n const key = blobKey(rInfo.id);\n\n const inMemoryTask = this.keyToDownload.get(key);\n if (inMemoryTask) {\n return inMemoryTask;\n }\n\n // schedule the blob downloading, then it'll be added to the cache.\n const fPath = path.resolve(this.saveDir, key);\n\n const newTask = new DownloadBlobTask(\n this.logger,\n this.clientDownload,\n rInfo,\n newLocalHandle(fPath, this.signer),\n fPath,\n );\n this.keyToDownload.set(key, newTask);\n\n this.downloadQueue.push({\n fn: () => this.downloadBlob(newTask, callerId),\n recoverableErrorPredicate: (e) => !nonRecoverableError(e),\n });\n\n return newTask;\n }\n\n private async downloadBlob(task: DownloadBlobTask, callerId: string) {\n await task.download();\n const blob = task.getBlob();\n if (blob.done && blob.result.ok) {\n this.cache.addCache(task, callerId);\n }\n }\n\n /** Gets on demand blob. */\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ): Computable<RemoteBlobHandleAndSize>;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx?: undefined,\n fromBytes?: number,\n toBytes?: number,\n ): Computable<RemoteBlobHandleAndSize>;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx: ComputableCtx,\n fromBytes?: number,\n toBytes?: number,\n ): RemoteBlobHandleAndSize;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx?: ComputableCtx,\n ): ComputableStableDefined<RemoteBlobHandleAndSize> | RemoteBlobHandleAndSize | undefined {\n if (ctx === undefined) return Computable.make((ctx) => this.getOnDemandBlob(res, ctx));\n\n const rInfo: OnDemandBlobResourceSnapshot = isPlTreeEntry(res)\n ? makeResourceSnapshot(res, OnDemandBlobResourceSnapshot, ctx)\n : res;\n\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseOnDemandBlob(rInfo.id, callerId));\n\n // note that the watcher is not needed,\n // the handler never changes.\n const result = this.getOnDemandBlobNoCtx(rInfo, callerId);\n\n return result;\n }\n\n private getOnDemandBlobNoCtx(\n info: OnDemandBlobResourceSnapshot,\n callerId: string,\n ): RemoteBlobHandleAndSize {\n validateDownloadableResourceType('getOnDemandBlob', info.type);\n\n let blob = this.keyToOnDemand.get(blobKey(info.id));\n\n if (blob === undefined) {\n blob = new OnDemandBlobHolder(getSize(info), newRemoteHandle(info, this.signer));\n this.keyToOnDemand.set(blobKey(info.id), blob);\n }\n\n blob.attach(callerId);\n\n return blob.getHandle();\n }\n\n /** Gets a path from a handle. */\n public getLocalPath(handle: LocalBlobHandle): string {\n const { path } = parseLocalHandle(handle, this.signer);\n return path;\n }\n\n /** Gets a content of a blob by a handle. */\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n options?: GetContentOptions,\n ): Promise<Uint8Array>;\n /** @deprecated Use {@link getContent} with {@link GetContentOptions} instead */\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n range?: RangeBytes,\n ): Promise<Uint8Array>;\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n optionsOrRange?: GetContentOptions | RangeBytes,\n ): Promise<Uint8Array> {\n let options: GetContentOptions = {};\n if (typeof optionsOrRange === 'object' && optionsOrRange !== null) {\n if ('range' in optionsOrRange) {\n options = optionsOrRange;\n } else {\n const range = optionsOrRange as RangeBytes;\n validateRangeBytes(range, `getContent`);\n options = { range };\n }\n }\n\n return await this.withContent(handle, {\n ...options,\n handler: async (content) => {\n const chunks: Uint8Array[] = [];\n for await (const chunk of content) {\n options.signal?.throwIfAborted();\n chunks.push(chunk);\n }\n return Buffer.concat(chunks);\n }\n });\n }\n\n /** Gets a content stream of a blob by a handle and calls handler with it. */\n public async withContent<T>(\n handle: LocalBlobHandle | RemoteBlobHandle,\n options: GetContentOptions & {\n handler: ContentHandler<T>;\n },\n ): Promise<T> {\n const { range, signal, handler } = options;\n\n if (isLocalBlobHandle(handle)) {\n return await withFileContent({ path: this.getLocalPath(handle), range, signal, handler });\n }\n\n if (isRemoteBlobHandle(handle)) {\n const result = parseRemoteHandle(handle, this.signer);\n\n const key = blobKey(result.info.id);\n const filePath = await this.rangesCache.get(key, range ?? { from: 0, to: result.size });\n signal?.throwIfAborted();\n\n if (filePath) return await withFileContent({ path: filePath, range, signal, handler });\n\n return await this.clientDownload.withBlobContent(\n result.info,\n { signal },\n options,\n async (content, size) => {\n const [handlerStream, cacheStream] = content.tee();\n \n const handlerPromise = handler(handlerStream, size);\n const _cachePromise = buffer(cacheStream)\n .then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data));\n\n return await handlerPromise;\n }\n );\n }\n\n throw new Error('Malformed remote handle');\n }\n\n /**\n * Creates computable that will return blob content once it is downloaded.\n * Uses downloaded blob handle under the hood, so stores corresponding blob in file system.\n */\n public getComputableContent(\n res: ResourceInfo | PlTreeEntry,\n range?: RangeBytes,\n ): ComputableStableDefined<Uint8Array> {\n if (range) {\n validateRangeBytes(range, `getComputableContent`);\n }\n\n return Computable.make((ctx) =>\n this.getDownloadedBlob(res, ctx), {\n postprocessValue: (v) => v ? this.getContent(v.handle, { range }) : undefined\n }\n ).withStableType()\n }\n\n /** Returns all logs and schedules a job that reads remain logs.\n * Notifies when a new portion of the log appeared. */\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number\n ): Computable<string | undefined>;\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number,\n ctx: ComputableCtx\n ): Computable<string | undefined>;\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number,\n ctx?: ComputableCtx,\n ): Computable<string | undefined> | string | undefined {\n if (ctx == undefined) return Computable.make((ctx) => this.getLastLogs(res, lines, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(r, callerId));\n\n const result = this.getLastLogsNoCtx(ctx.watcher, r as ResourceSnapshot, lines, callerId);\n if (result == undefined)\n ctx.markUnstable('either a file was not downloaded or logs was not read');\n\n return result;\n }\n\n private getLastLogsNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n lines: number,\n callerId: string,\n ): string | undefined {\n validateDownloadableResourceType('getLastLogs', rInfo.type);\n const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);\n if (blob == undefined) return undefined;\n\n const { path } = parseLocalHandle(blob.handle, this.signer);\n\n let logGetter = this.idToLastLines.get(blobKey(rInfo.id));\n\n if (logGetter == undefined) {\n const newLogGetter = new LastLinesGetter(path, lines);\n this.idToLastLines.set(blobKey(rInfo.id), newLogGetter);\n logGetter = newLogGetter;\n }\n\n const result = logGetter.getOrSchedule(w);\n if (result.error) throw result.error;\n\n return result.log;\n }\n\n /** Returns a last line that has patternToSearch.\n * Notifies when a new line appeared or EOF reached. */\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string\n ): Computable<string | undefined>;\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string,\n ctx: ComputableCtx\n ): string | undefined;\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string,\n ctx?: ComputableCtx,\n ): Computable<string | undefined> | string | undefined {\n if (ctx == undefined)\n return Computable.make((ctx) => this.getProgressLog(res, patternToSearch, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(r, callerId));\n\n const result = this.getProgressLogNoCtx(\n ctx.watcher,\n r as ResourceSnapshot,\n patternToSearch,\n callerId,\n );\n if (result === undefined)\n ctx.markUnstable('either a file was not downloaded or a progress log was not read');\n\n return result;\n }\n\n private getProgressLogNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n patternToSearch: string,\n callerId: string,\n ): string | undefined {\n validateDownloadableResourceType('getProgressLog', rInfo.type);\n\n const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);\n if (blob == undefined) return undefined;\n const { path } = parseLocalHandle(blob.handle, this.signer);\n\n let logGetter = this.idToProgressLog.get(blobKey(rInfo.id));\n\n if (logGetter == undefined) {\n const newLogGetter = new LastLinesGetter(path, 1, patternToSearch);\n this.idToProgressLog.set(blobKey(rInfo.id), newLogGetter);\n\n logGetter = newLogGetter;\n }\n\n const result = logGetter.getOrSchedule(w);\n if (result.error) throw result.error;\n\n return result.log;\n }\n\n /** Returns an Id of a smart object, that can read logs directly from\n * the platform. */\n public getLogHandle(res: ResourceInfo | PlTreeEntry): Computable<AnyLogHandle>;\n public getLogHandle(res: ResourceInfo | PlTreeEntry, ctx: ComputableCtx): AnyLogHandle;\n public getLogHandle(\n res: ResourceInfo | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<AnyLogHandle> | AnyLogHandle {\n if (ctx == undefined) return Computable.make((ctx) => this.getLogHandle(res, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n\n return this.getLogHandleNoCtx(r as ResourceSnapshot);\n }\n\n private getLogHandleNoCtx(rInfo: ResourceSnapshot): AnyLogHandle {\n validateDownloadableResourceType('getLogHandle', rInfo.type);\n return newLogHandle(false, rInfo);\n }\n\n public async lastLines(\n handle: ReadyLogHandle,\n lineCount: number,\n offsetBytes?: number, // if 0n, then start from the end.\n searchStr?: string,\n ): Promise<StreamingApiResponse> {\n const resp = await this.clientLogs.lastLines(\n getResourceInfoFromLogHandle(handle),\n lineCount,\n BigInt(offsetBytes ?? 0),\n searchStr,\n );\n\n return {\n live: false,\n shouldUpdateHandle: false,\n data: resp.data,\n size: Number(resp.size),\n newOffset: Number(resp.newOffset),\n };\n }\n\n public async readText(\n handle: ReadyLogHandle,\n lineCount: number,\n offsetBytes?: number,\n searchStr?: string,\n ): Promise<StreamingApiResponse> {\n const resp = await this.clientLogs.readText(\n getResourceInfoFromLogHandle(handle),\n lineCount,\n BigInt(offsetBytes ?? 0),\n searchStr,\n );\n\n return {\n live: false,\n shouldUpdateHandle: false,\n data: resp.data,\n size: Number(resp.size),\n newOffset: Number(resp.newOffset),\n };\n }\n\n private async releaseBlob(rInfo: ResourceInfo, callerId: string) {\n const task = this.keyToDownload.get(blobKey(rInfo.id));\n if (task == undefined) {\n return;\n }\n\n if (this.cache.existsFile(blobKey(rInfo.id))) {\n const toDelete = this.cache.removeFile(blobKey(rInfo.id), callerId);\n\n await Promise.all(\n toDelete.map(async (cachedFile) => {\n await fsp.rm(cachedFile.path);\n\n this.cache.removeCache(cachedFile);\n\n this.removeTask(\n mapGet(this.keyToDownload, pathToKey(cachedFile.path)),\n `the task ${stringifyWithResourceId(cachedFile)} was removed`\n + `from cache along with ${stringifyWithResourceId(toDelete.map((d) => d.path))}`,\n );\n }),\n );\n } else {\n // The task is still in a downloading queue.\n const deleted = task.counter.dec(callerId);\n if (deleted) {\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was removed from cache`,\n );\n }\n }\n }\n\n private removeTask(task: DownloadBlobTask, reason: string) {\n task.abort(reason);\n task.change.markChanged(`download task for ${task.path} removed: ${reason}`);\n this.keyToDownload.delete(pathToKey(task.path));\n this.idToLastLines.delete(blobKey(task.rInfo.id));\n this.idToProgressLog.delete(blobKey(task.rInfo.id));\n }\n\n private async releaseOnDemandBlob(blobId: ResourceId, callerId: string) {\n const deleted = this.keyToOnDemand.get(blobKey(blobId))?.release(callerId) ?? false;\n if (deleted) this.keyToOnDemand.delete(blobKey(blobId));\n }\n\n /** Removes all files from a hard drive. */\n async releaseAll() {\n this.downloadQueue.stop();\n\n this.keyToDownload.forEach((task, key) => {\n this.keyToDownload.delete(key);\n task.change.markChanged(`task ${resourceIdToString(task.rInfo.id)} released`);\n });\n }\n}\n\n/** Keeps a counter to the on demand handle. */\nclass OnDemandBlobHolder {\n private readonly counter = new CallersCounter();\n\n constructor(\n private readonly size: number,\n private readonly handle: RemoteBlobHandle,\n ) {}\n\n public getHandle(): RemoteBlobHandleAndSize {\n return { handle: this.handle, size: this.size };\n }\n\n public attach(callerId: string) {\n this.counter.inc(callerId);\n }\n\n public release(callerId: string): boolean {\n return this.counter.dec(callerId);\n }\n}\n\nclass LastLinesGetter {\n private updater: Updater;\n private log: string | undefined;\n private readonly change: ChangeSource = new ChangeSource();\n private error: any | undefined = undefined;\n\n constructor(\n private readonly path: string,\n private readonly lines: number,\n private readonly patternToSearch?: string,\n ) {\n this.updater = new Updater(async () => this.update());\n }\n\n getOrSchedule(w: Watcher): {\n log: string | undefined;\n error?: any | undefined;\n } {\n this.change.attachWatcher(w);\n\n this.updater.schedule();\n\n return {\n log: this.log,\n error: this.error,\n };\n }\n\n async update(): Promise<void> {\n try {\n const newLogs = await getLastLines(this.path, this.lines, this.patternToSearch);\n\n if (this.log != newLogs) this.change.markChanged(`logs for ${this.path} updated`);\n this.log = newLogs;\n } catch (e: any) {\n if (e.name == 'RpcError' && e.code == 'NOT_FOUND') {\n // No resource\n this.log = '';\n this.error = e;\n this.change.markChanged(`log update for ${this.path} failed, resource not found`);\n return;\n }\n\n throw e;\n }\n }\n}\n\n/** Gets last lines from a file by reading the file from the top and keeping\n * last N lines in a window queue. */\nasync function getLastLines(fPath: string, nLines: number, patternToSearch?: string): Promise<string> {\n let inStream: fs.ReadStream | undefined;\n let rl: readline.Interface | undefined;\n\n try {\n inStream = fs.createReadStream(fPath);\n rl = readline.createInterface({ input: inStream, crlfDelay: Infinity });\n\n const lines = new Denque();\n\n for await (const line of rl) {\n if (patternToSearch != undefined && !line.includes(patternToSearch)) continue;\n\n lines.push(line);\n if (lines.length > nLines) {\n lines.shift();\n }\n }\n\n // last EOL is for keeping backward compat with platforma implementation.\n return lines.toArray().join(os.EOL) + os.EOL;\n } finally {\n // Cleanup resources in finally block to ensure they're always cleaned up\n try {\n if (rl) {\n rl.close();\n }\n } catch (cleanupError) {\n console.error('Error closing readline interface:', cleanupError);\n }\n\n try {\n if (inStream && !inStream.destroyed) {\n inStream.destroy();\n }\n } catch (cleanupError) {\n console.error('Error destroying read stream:', cleanupError);\n }\n }\n}\n\nfunction validateDownloadableResourceType(methodName: string, rType: ResourceType) {\n if (!rType.name.startsWith('Blob/')) {\n let message = `${methodName}: wrong resource type: ${rType.name}, expected: a resource of type that starts with 'Blob/'.`;\n if (rType.name == 'Blob')\n message += ` If it's called from workflow, should a file be exported with 'file.exportFile' function?`;\n\n throw new WrongResourceTypeError(message);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"download_blob.js","sources":["../../../src/drivers/download_blob/download_blob.ts"],"sourcesContent":["import type {\n ComputableCtx,\n ComputableStableDefined,\n Watcher,\n} from '@milaboratories/computable';\nimport {\n ChangeSource,\n Computable,\n} from '@milaboratories/computable';\nimport type { ResourceId, ResourceType } from '@milaboratories/pl-client';\nimport { resourceIdToString, stringifyWithResourceId } from '@milaboratories/pl-client';\nimport type {\n AnyLogHandle,\n BlobDriver,\n ContentHandler,\n GetContentOptions,\n LocalBlobHandle,\n LocalBlobHandleAndSize,\n ReadyLogHandle,\n RemoteBlobHandle,\n RemoteBlobHandleAndSize,\n StreamingApiResponse,\n} from '@milaboratories/pl-model-common';\nimport { type RangeBytes, validateRangeBytes } from '@milaboratories/pl-model-common';\nimport type {\n PlTreeEntry,\n ResourceInfo,\n ResourceSnapshot\n} from '@milaboratories/pl-tree';\nimport {\n isPlTreeEntry,\n makeResourceSnapshot,\n treeEntryToResourceInfo,\n} from '@milaboratories/pl-tree';\nimport type { MiLogger, Signer } from '@milaboratories/ts-helpers';\nimport { CallersCounter, mapGet, TaskProcessor } from '@milaboratories/ts-helpers';\nimport Denque from 'denque';\nimport * as fs from 'fs';\nimport { randomUUID } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport * as readline from 'node:readline/promises';\nimport { buffer } from 'node:stream/consumers';\nimport { Readable } from 'node:stream';\nimport type { ClientDownload } from '../../clients/download';\nimport type { ClientLogs } from '../../clients/logs';\nimport { withFileContent } from '../helpers/read_file';\nimport {\n isLocalBlobHandle,\n newLocalHandle,\n parseLocalHandle,\n} from '../helpers/download_local_handle';\nimport {\n isRemoteBlobHandle,\n newRemoteHandle,\n parseRemoteHandle,\n} from '../helpers/download_remote_handle';\nimport { Updater, WrongResourceTypeError } from '../helpers/helpers';\nimport { getResourceInfoFromLogHandle, newLogHandle } from '../helpers/logs_handle';\nimport { getSize, OnDemandBlobResourceSnapshot } from '../types';\nimport { blobKey, pathToKey } from './blob_key';\nimport { DownloadBlobTask, nonRecoverableError } from './download_blob_task';\nimport { FilesCache } from '../helpers/files_cache';\nimport { SparseCache, SparseCacheFsFile, SparseCacheFsRanges } from './sparse_cache/cache';\nimport { isOffByOneError } from '../../helpers/download';\n\nexport type DownloadDriverOps = {\n /**\n * A soft limit of the amount of blob storage, in bytes.\n * Once exceeded, the download driver will start deleting blobs one by one\n * when they become unneeded.\n * */\n cacheSoftSizeBytes: number;\n\n /**\n * A hard limit of the amount of sparse cache, in bytes.\n * Once exceeded, the download driver will start deleting blobs one by one.\n *\n * The sparse cache is used to store ranges of blobs.\n * */\n rangesCacheMaxSizeBytes: number;\n\n /**\n * Max number of concurrent downloads while calculating computable states\n * derived from this driver\n * */\n nConcurrentDownloads: number;\n};\n\n/** DownloadDriver holds a queue of downloading tasks,\n * and notifies every watcher when a file were downloaded. */\nexport class DownloadDriver implements BlobDriver {\n /** Represents a unique key to the path of a blob as a map. */\n private keyToDownload: Map<string, DownloadBlobTask> = new Map();\n\n /** Writes and removes files to a hard drive and holds a counter for every\n * file that should be kept. */\n private cache: FilesCache<DownloadBlobTask>;\n private rangesCache: SparseCache;\n\n /** Downloads files and writes them to the local dir. */\n private downloadQueue: TaskProcessor;\n\n private keyToOnDemand: Map<string, OnDemandBlobHolder> = new Map();\n\n private idToLastLines: Map<string, LastLinesGetter> = new Map();\n private idToProgressLog: Map<string, LastLinesGetter> = new Map();\n\n private readonly saveDir: string;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly clientDownload: ClientDownload,\n private readonly clientLogs: ClientLogs,\n saveDir: string,\n private readonly rangesCacheDir: string,\n private readonly signer: Signer,\n private readonly ops: DownloadDriverOps,\n ) {\n this.cache = new FilesCache(this.ops.cacheSoftSizeBytes);\n\n const fsRanges = new SparseCacheFsRanges(this.logger, this.rangesCacheDir);\n const fsStorage = new SparseCacheFsFile(this.logger, this.rangesCacheDir);\n this.rangesCache = new SparseCache(this.logger, this.ops.rangesCacheMaxSizeBytes, fsRanges, fsStorage);\n\n this.downloadQueue = new TaskProcessor(this.logger, ops.nConcurrentDownloads);\n\n this.saveDir = path.resolve(saveDir);\n }\n\n static async init(\n logger: MiLogger,\n clientDownload: ClientDownload,\n clientLogs: ClientLogs,\n saveDir: string,\n rangesCacheDir: string,\n signer: Signer,\n ops: DownloadDriverOps,\n ): Promise<DownloadDriver> {\n const driver = new DownloadDriver(logger, clientDownload, clientLogs, saveDir, rangesCacheDir, signer, ops);\n await driver.rangesCache.reset();\n\n return driver;\n }\n\n /** Gets a blob or part of the blob by its resource id or downloads a blob and sets it in a cache. */\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ctx: ComputableCtx,\n ): LocalBlobHandleAndSize | undefined;\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ): ComputableStableDefined<LocalBlobHandleAndSize>;\n public getDownloadedBlob(\n res: ResourceInfo | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<LocalBlobHandleAndSize | undefined> | LocalBlobHandleAndSize | undefined {\n if (ctx === undefined) {\n return Computable.make((ctx) => this.getDownloadedBlob(res, ctx));\n }\n\n const rInfo = treeEntryToResourceInfo(res, ctx);\n\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(rInfo, callerId));\n\n const result = this.getDownloadedBlobNoCtx(ctx.watcher, rInfo as ResourceSnapshot, callerId);\n if (result == undefined) {\n ctx.markUnstable('download blob is still undefined');\n }\n\n return result;\n }\n\n private getDownloadedBlobNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n callerId: string,\n ): LocalBlobHandleAndSize | undefined {\n validateDownloadableResourceType('getDownloadedBlob', rInfo.type);\n\n // We don't need to request files with wider limits,\n // PFrame's engine does it disk-optimally by itself.\n\n const task = this.getOrSetNewTask(rInfo, callerId);\n task.attach(w, callerId);\n\n const result = task.getBlob();\n if (!result.done) {\n return undefined;\n }\n if (result.result.ok) {\n return result.result.value;\n }\n throw result.result.error;\n }\n\n private getOrSetNewTask(\n rInfo: ResourceSnapshot,\n callerId: string,\n ): DownloadBlobTask {\n const key = blobKey(rInfo.id);\n\n const inMemoryTask = this.keyToDownload.get(key);\n if (inMemoryTask) {\n return inMemoryTask;\n }\n\n // schedule the blob downloading, then it'll be added to the cache.\n const fPath = path.resolve(this.saveDir, key);\n\n const newTask = new DownloadBlobTask(\n this.logger,\n this.clientDownload,\n rInfo,\n newLocalHandle(fPath, this.signer),\n fPath,\n );\n this.keyToDownload.set(key, newTask);\n\n this.downloadQueue.push({\n fn: () => this.downloadBlob(newTask, callerId),\n recoverableErrorPredicate: (e) => !nonRecoverableError(e),\n });\n\n return newTask;\n }\n\n private async downloadBlob(task: DownloadBlobTask, callerId: string) {\n await task.download();\n const blob = task.getBlob();\n if (blob.done && blob.result.ok) {\n this.cache.addCache(task, callerId);\n }\n }\n\n /** Gets on demand blob. */\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ): Computable<RemoteBlobHandleAndSize>;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx?: undefined,\n fromBytes?: number,\n toBytes?: number,\n ): Computable<RemoteBlobHandleAndSize>;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx: ComputableCtx,\n fromBytes?: number,\n toBytes?: number,\n ): RemoteBlobHandleAndSize;\n public getOnDemandBlob(\n res: OnDemandBlobResourceSnapshot | PlTreeEntry,\n ctx?: ComputableCtx,\n ): ComputableStableDefined<RemoteBlobHandleAndSize> | RemoteBlobHandleAndSize | undefined {\n if (ctx === undefined) return Computable.make((ctx) => this.getOnDemandBlob(res, ctx));\n\n const rInfo: OnDemandBlobResourceSnapshot = isPlTreeEntry(res)\n ? makeResourceSnapshot(res, OnDemandBlobResourceSnapshot, ctx)\n : res;\n\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseOnDemandBlob(rInfo.id, callerId));\n\n // note that the watcher is not needed,\n // the handler never changes.\n const result = this.getOnDemandBlobNoCtx(rInfo, callerId);\n\n return result;\n }\n\n private getOnDemandBlobNoCtx(\n info: OnDemandBlobResourceSnapshot,\n callerId: string,\n ): RemoteBlobHandleAndSize {\n validateDownloadableResourceType('getOnDemandBlob', info.type);\n\n let blob = this.keyToOnDemand.get(blobKey(info.id));\n\n if (blob === undefined) {\n blob = new OnDemandBlobHolder(getSize(info), newRemoteHandle(info, this.signer));\n this.keyToOnDemand.set(blobKey(info.id), blob);\n }\n\n blob.attach(callerId);\n\n return blob.getHandle();\n }\n\n /** Gets a path from a handle. */\n public getLocalPath(handle: LocalBlobHandle): string {\n const { path } = parseLocalHandle(handle, this.signer);\n return path;\n }\n\n /** Gets a content of a blob by a handle. */\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n options?: GetContentOptions,\n ): Promise<Uint8Array>;\n /** @deprecated Use {@link getContent} with {@link GetContentOptions} instead */\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n range?: RangeBytes,\n ): Promise<Uint8Array>;\n public async getContent(\n handle: LocalBlobHandle | RemoteBlobHandle,\n optionsOrRange?: GetContentOptions | RangeBytes,\n ): Promise<Uint8Array> {\n let options: GetContentOptions = {};\n if (typeof optionsOrRange === 'object' && optionsOrRange !== null) {\n if ('range' in optionsOrRange) {\n options = optionsOrRange;\n } else {\n const range = optionsOrRange as RangeBytes;\n validateRangeBytes(range, `getContent`);\n options = { range };\n }\n }\n\n const request = () => this.withContent(handle, {\n ...options,\n handler: async (content) => {\n const chunks: Uint8Array[] = [];\n for await (const chunk of content) {\n options.signal?.throwIfAborted();\n chunks.push(chunk);\n }\n return Buffer.concat(chunks);\n }\n });\n\n try {\n return await request();\n } catch (error) {\n if (isOffByOneError(error)) {\n return await request();\n }\n throw error;\n }\n }\n\n /** Gets a content stream of a blob by a handle and calls handler with it. */\n public async withContent<T>(\n handle: LocalBlobHandle | RemoteBlobHandle,\n options: GetContentOptions & {\n handler: ContentHandler<T>;\n },\n ): Promise<T> {\n const { range, signal, handler } = options;\n\n if (isLocalBlobHandle(handle)) {\n return await withFileContent({ path: this.getLocalPath(handle), range, signal, handler });\n }\n\n if (isRemoteBlobHandle(handle)) {\n const result = parseRemoteHandle(handle, this.signer);\n\n const key = blobKey(result.info.id);\n const filePath = await this.rangesCache.get(key, range ?? { from: 0, to: result.size });\n signal?.throwIfAborted();\n\n if (filePath) return await withFileContent({ path: filePath, range, signal, handler });\n\n return await this.clientDownload.withBlobContent(\n result.info,\n { signal },\n options,\n async (content, size) => {\n const [handlerStream, cacheStream] = content.tee();\n \n const handlerPromise = handler(handlerStream, size);\n const _cachePromise = buffer(cacheStream)\n .then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data))\n .catch(() => {\n // Ignore cache errors - they shouldn't affect the main handler result\n // This prevents unhandled promise rejections when the stream fails\n });\n\n return await handlerPromise;\n }\n );\n }\n\n throw new Error('Malformed remote handle');\n }\n\n /**\n * Creates computable that will return blob content once it is downloaded.\n * Uses downloaded blob handle under the hood, so stores corresponding blob in file system.\n */\n public getComputableContent(\n res: ResourceInfo | PlTreeEntry,\n range?: RangeBytes,\n ): ComputableStableDefined<Uint8Array> {\n if (range) {\n validateRangeBytes(range, `getComputableContent`);\n }\n\n return Computable.make((ctx) =>\n this.getDownloadedBlob(res, ctx), {\n postprocessValue: (v) => v ? this.getContent(v.handle, { range }) : undefined\n }\n ).withStableType()\n }\n\n /** Returns all logs and schedules a job that reads remain logs.\n * Notifies when a new portion of the log appeared. */\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number\n ): Computable<string | undefined>;\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number,\n ctx: ComputableCtx\n ): Computable<string | undefined>;\n public getLastLogs(\n res: ResourceInfo | PlTreeEntry,\n lines: number,\n ctx?: ComputableCtx,\n ): Computable<string | undefined> | string | undefined {\n if (ctx == undefined) return Computable.make((ctx) => this.getLastLogs(res, lines, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(r, callerId));\n\n const result = this.getLastLogsNoCtx(ctx.watcher, r as ResourceSnapshot, lines, callerId);\n if (result == undefined)\n ctx.markUnstable('either a file was not downloaded or logs was not read');\n\n return result;\n }\n\n private getLastLogsNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n lines: number,\n callerId: string,\n ): string | undefined {\n validateDownloadableResourceType('getLastLogs', rInfo.type);\n const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);\n if (blob == undefined) return undefined;\n\n const { path } = parseLocalHandle(blob.handle, this.signer);\n\n let logGetter = this.idToLastLines.get(blobKey(rInfo.id));\n\n if (logGetter == undefined) {\n const newLogGetter = new LastLinesGetter(path, lines);\n this.idToLastLines.set(blobKey(rInfo.id), newLogGetter);\n logGetter = newLogGetter;\n }\n\n const result = logGetter.getOrSchedule(w);\n if (result.error) throw result.error;\n\n return result.log;\n }\n\n /** Returns a last line that has patternToSearch.\n * Notifies when a new line appeared or EOF reached. */\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string\n ): Computable<string | undefined>;\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string,\n ctx: ComputableCtx\n ): string | undefined;\n public getProgressLog(\n res: ResourceInfo | PlTreeEntry,\n patternToSearch: string,\n ctx?: ComputableCtx,\n ): Computable<string | undefined> | string | undefined {\n if (ctx == undefined)\n return Computable.make((ctx) => this.getProgressLog(res, patternToSearch, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n const callerId = randomUUID();\n ctx.addOnDestroy(() => this.releaseBlob(r, callerId));\n\n const result = this.getProgressLogNoCtx(\n ctx.watcher,\n r as ResourceSnapshot,\n patternToSearch,\n callerId,\n );\n if (result === undefined)\n ctx.markUnstable('either a file was not downloaded or a progress log was not read');\n\n return result;\n }\n\n private getProgressLogNoCtx(\n w: Watcher,\n rInfo: ResourceSnapshot,\n patternToSearch: string,\n callerId: string,\n ): string | undefined {\n validateDownloadableResourceType('getProgressLog', rInfo.type);\n\n const blob = this.getDownloadedBlobNoCtx(w, rInfo, callerId);\n if (blob == undefined) return undefined;\n const { path } = parseLocalHandle(blob.handle, this.signer);\n\n let logGetter = this.idToProgressLog.get(blobKey(rInfo.id));\n\n if (logGetter == undefined) {\n const newLogGetter = new LastLinesGetter(path, 1, patternToSearch);\n this.idToProgressLog.set(blobKey(rInfo.id), newLogGetter);\n\n logGetter = newLogGetter;\n }\n\n const result = logGetter.getOrSchedule(w);\n if (result.error) throw result.error;\n\n return result.log;\n }\n\n /** Returns an Id of a smart object, that can read logs directly from\n * the platform. */\n public getLogHandle(res: ResourceInfo | PlTreeEntry): Computable<AnyLogHandle>;\n public getLogHandle(res: ResourceInfo | PlTreeEntry, ctx: ComputableCtx): AnyLogHandle;\n public getLogHandle(\n res: ResourceInfo | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<AnyLogHandle> | AnyLogHandle {\n if (ctx == undefined) return Computable.make((ctx) => this.getLogHandle(res, ctx));\n\n const r = treeEntryToResourceInfo(res, ctx);\n\n return this.getLogHandleNoCtx(r as ResourceSnapshot);\n }\n\n private getLogHandleNoCtx(rInfo: ResourceSnapshot): AnyLogHandle {\n validateDownloadableResourceType('getLogHandle', rInfo.type);\n return newLogHandle(false, rInfo);\n }\n\n public async lastLines(\n handle: ReadyLogHandle,\n lineCount: number,\n offsetBytes?: number, // if 0n, then start from the end.\n searchStr?: string,\n ): Promise<StreamingApiResponse> {\n const resp = await this.clientLogs.lastLines(\n getResourceInfoFromLogHandle(handle),\n lineCount,\n BigInt(offsetBytes ?? 0),\n searchStr,\n );\n\n return {\n live: false,\n shouldUpdateHandle: false,\n data: resp.data,\n size: Number(resp.size),\n newOffset: Number(resp.newOffset),\n };\n }\n\n public async readText(\n handle: ReadyLogHandle,\n lineCount: number,\n offsetBytes?: number,\n searchStr?: string,\n ): Promise<StreamingApiResponse> {\n const resp = await this.clientLogs.readText(\n getResourceInfoFromLogHandle(handle),\n lineCount,\n BigInt(offsetBytes ?? 0),\n searchStr,\n );\n\n return {\n live: false,\n shouldUpdateHandle: false,\n data: resp.data,\n size: Number(resp.size),\n newOffset: Number(resp.newOffset),\n };\n }\n\n private async releaseBlob(rInfo: ResourceInfo, callerId: string) {\n const task = this.keyToDownload.get(blobKey(rInfo.id));\n if (task == undefined) {\n return;\n }\n\n if (this.cache.existsFile(blobKey(rInfo.id))) {\n const toDelete = this.cache.removeFile(blobKey(rInfo.id), callerId);\n\n await Promise.all(\n toDelete.map(async (cachedFile) => {\n await fsp.rm(cachedFile.path);\n\n this.cache.removeCache(cachedFile);\n\n this.removeTask(\n mapGet(this.keyToDownload, pathToKey(cachedFile.path)),\n `the task ${stringifyWithResourceId(cachedFile)} was removed`\n + `from cache along with ${stringifyWithResourceId(toDelete.map((d) => d.path))}`,\n );\n }),\n );\n } else {\n // The task is still in a downloading queue.\n const deleted = task.counter.dec(callerId);\n if (deleted) {\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was removed from cache`,\n );\n }\n }\n }\n\n private removeTask(task: DownloadBlobTask, reason: string) {\n task.abort(reason);\n task.change.markChanged(`download task for ${task.path} removed: ${reason}`);\n this.keyToDownload.delete(pathToKey(task.path));\n this.idToLastLines.delete(blobKey(task.rInfo.id));\n this.idToProgressLog.delete(blobKey(task.rInfo.id));\n }\n\n private async releaseOnDemandBlob(blobId: ResourceId, callerId: string) {\n const deleted = this.keyToOnDemand.get(blobKey(blobId))?.release(callerId) ?? false;\n if (deleted) this.keyToOnDemand.delete(blobKey(blobId));\n }\n\n /** Removes all files from a hard drive. */\n async releaseAll() {\n this.downloadQueue.stop();\n\n this.keyToDownload.forEach((task, key) => {\n this.keyToDownload.delete(key);\n task.change.markChanged(`task ${resourceIdToString(task.rInfo.id)} released`);\n });\n }\n}\n\n/** Keeps a counter to the on demand handle. */\nclass OnDemandBlobHolder {\n private readonly counter = new CallersCounter();\n\n constructor(\n private readonly size: number,\n private readonly handle: RemoteBlobHandle,\n ) {}\n\n public getHandle(): RemoteBlobHandleAndSize {\n return { handle: this.handle, size: this.size };\n }\n\n public attach(callerId: string) {\n this.counter.inc(callerId);\n }\n\n public release(callerId: string): boolean {\n return this.counter.dec(callerId);\n }\n}\n\nclass LastLinesGetter {\n private updater: Updater;\n private log: string | undefined;\n private readonly change: ChangeSource = new ChangeSource();\n private error: any | undefined = undefined;\n\n constructor(\n private readonly path: string,\n private readonly lines: number,\n private readonly patternToSearch?: string,\n ) {\n this.updater = new Updater(async () => this.update());\n }\n\n getOrSchedule(w: Watcher): {\n log: string | undefined;\n error?: any | undefined;\n } {\n this.change.attachWatcher(w);\n\n this.updater.schedule();\n\n return {\n log: this.log,\n error: this.error,\n };\n }\n\n async update(): Promise<void> {\n try {\n const newLogs = await getLastLines(this.path, this.lines, this.patternToSearch);\n\n if (this.log != newLogs) this.change.markChanged(`logs for ${this.path} updated`);\n this.log = newLogs;\n } catch (e: any) {\n if (e.name == 'RpcError' && e.code == 'NOT_FOUND') {\n // No resource\n this.log = '';\n this.error = e;\n this.change.markChanged(`log update for ${this.path} failed, resource not found`);\n return;\n }\n\n throw e;\n }\n }\n}\n\n/** Gets last lines from a file by reading the file from the top and keeping\n * last N lines in a window queue. */\nasync function getLastLines(fPath: string, nLines: number, patternToSearch?: string): Promise<string> {\n let inStream: fs.ReadStream | undefined;\n let rl: readline.Interface | undefined;\n\n try {\n inStream = fs.createReadStream(fPath);\n rl = readline.createInterface({ input: inStream, crlfDelay: Infinity });\n\n const lines = new Denque();\n\n for await (const line of rl) {\n if (patternToSearch != undefined && !line.includes(patternToSearch)) continue;\n\n lines.push(line);\n if (lines.length > nLines) {\n lines.shift();\n }\n }\n\n // last EOL is for keeping backward compat with platforma implementation.\n return lines.toArray().join(os.EOL) + os.EOL;\n } finally {\n // Cleanup resources in finally block to ensure they're always cleaned up\n try {\n if (rl) {\n rl.close();\n }\n } catch (cleanupError) {\n console.error('Error closing readline interface:', cleanupError);\n }\n\n try {\n if (inStream && !inStream.destroyed) {\n inStream.destroy();\n }\n } catch (cleanupError) {\n console.error('Error destroying read stream:', cleanupError);\n }\n }\n}\n\nfunction validateDownloadableResourceType(methodName: string, rType: ResourceType) {\n if (!rType.name.startsWith('Blob/')) {\n let message = `${methodName}: wrong resource type: ${rType.name}, expected: a resource of type that starts with 'Blob/'.`;\n if (rType.name == 'Blob')\n message += ` If it's called from workflow, should a file be exported with 'file.exportFile' function?`;\n\n throw new WrongResourceTypeError(message);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,22 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
var undici = require('undici');
|
|
4
4
|
var node_stream = require('node:stream');
|
|
5
|
+
var web = require('node:stream/web');
|
|
5
6
|
var consumers = require('node:stream/consumers');
|
|
6
7
|
|
|
7
8
|
/** Throws when a status code of the downloading URL was in range [400, 500). */
|
|
8
9
|
class NetworkError400 extends Error {
|
|
9
10
|
name = 'NetworkError400';
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* There are backend versions that return 1 less byte than requested in range.
|
|
14
|
+
* For such cases, this error will be thrown, so client can retry the request.
|
|
15
|
+
* Dowloader will retry the request with one more byte in range.
|
|
16
|
+
*/
|
|
17
|
+
class OffByOneError extends Error {
|
|
18
|
+
name = 'OffByOneError';
|
|
19
|
+
}
|
|
20
|
+
function isOffByOneError(error) {
|
|
21
|
+
return error instanceof Error && error.name === 'OffByOneError';
|
|
22
|
+
}
|
|
11
23
|
class RemoteFileDownloader {
|
|
12
24
|
httpClient;
|
|
25
|
+
offByOneServers = [];
|
|
13
26
|
constructor(httpClient) {
|
|
14
27
|
this.httpClient = httpClient;
|
|
15
28
|
}
|
|
16
29
|
async withContent(url, reqHeaders, ops, handler) {
|
|
17
30
|
const headers = { ...reqHeaders };
|
|
31
|
+
const urlOrigin = new URL(url).origin;
|
|
18
32
|
// Add range header if specified
|
|
19
33
|
if (ops.range) {
|
|
20
|
-
|
|
34
|
+
const offByOne = this.offByOneServers.includes(urlOrigin);
|
|
35
|
+
headers['Range'] = `bytes=${ops.range.from}-${ops.range.to - (offByOne ? 0 : 1)}`;
|
|
21
36
|
}
|
|
22
37
|
const { statusCode, body, headers: responseHeaders } = await undici.request(url, {
|
|
23
38
|
dispatcher: this.httpClient,
|
|
@@ -30,9 +45,42 @@ class RemoteFileDownloader {
|
|
|
30
45
|
try {
|
|
31
46
|
await checkStatusCodeOk(statusCode, webBody, url);
|
|
32
47
|
ops.signal?.throwIfAborted();
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
48
|
+
let result = undefined;
|
|
49
|
+
const contentLength = Number(responseHeaders['content-length']);
|
|
50
|
+
if (Number.isNaN(contentLength) || contentLength === 0) {
|
|
51
|
+
// Some backend versions have a bug that they are not returning content-length header.
|
|
52
|
+
// In this case `content-length` header is returned as 0.
|
|
53
|
+
// We should not clip the result stream to 0 bytes in such case.
|
|
54
|
+
result = await handler(webBody, 0);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Some backend versions have a bug where they return more data than requested in range.
|
|
58
|
+
// So we have to manually normalize the stream to the expected size.
|
|
59
|
+
const size = ops.range ? ops.range.to - ops.range.from : contentLength;
|
|
60
|
+
const normalizedStream = webBody.pipeThrough(new (class extends web.TransformStream {
|
|
61
|
+
constructor(sizeBytes, recordOffByOne) {
|
|
62
|
+
super({
|
|
63
|
+
transform(chunk, controller) {
|
|
64
|
+
const truncatedChunk = chunk.slice(0, sizeBytes);
|
|
65
|
+
controller.enqueue(truncatedChunk);
|
|
66
|
+
sizeBytes -= truncatedChunk.length;
|
|
67
|
+
if (!sizeBytes)
|
|
68
|
+
controller.terminate();
|
|
69
|
+
},
|
|
70
|
+
flush(controller) {
|
|
71
|
+
// Some backend versions have a bug where they return 1 less byte than requested in range.
|
|
72
|
+
// We cannot always request one more byte because if this end byte is the last byte of the file,
|
|
73
|
+
// the backend will return 416 (Range Not Satisfiable). So error is thrown to force client to retry the request.
|
|
74
|
+
if (sizeBytes === 1) {
|
|
75
|
+
recordOffByOne();
|
|
76
|
+
controller.error(new OffByOneError());
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
})(size, () => this.offByOneServers.push(urlOrigin)));
|
|
82
|
+
result = await handler(normalizedStream, size);
|
|
83
|
+
}
|
|
36
84
|
handlerSuccess = true;
|
|
37
85
|
return result;
|
|
38
86
|
}
|
|
@@ -62,5 +110,7 @@ async function checkStatusCodeOk(statusCode, webBody, url) {
|
|
|
62
110
|
}
|
|
63
111
|
|
|
64
112
|
exports.NetworkError400 = NetworkError400;
|
|
113
|
+
exports.OffByOneError = OffByOneError;
|
|
65
114
|
exports.RemoteFileDownloader = RemoteFileDownloader;
|
|
115
|
+
exports.isOffByOneError = isOffByOneError;
|
|
66
116
|
//# sourceMappingURL=download.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.cjs","sources":["../../src/helpers/download.ts"],"sourcesContent":["// @TODO Gleb Zakharov\n/* eslint-disable n/no-unsupported-features/node-builtins */\nimport type { Dispatcher } from 'undici';\nimport { request } from 'undici';\nimport { Readable } from 'node:stream';\nimport type { ReadableStream } from 'node:stream/web';\nimport { text } from 'node:stream/consumers';\nimport type { GetContentOptions } from '@milaboratories/pl-model-common';\n\nexport type ContentHandler<T> = (content: ReadableStream, size: number) => Promise<T>;\n\n/** Throws when a status code of the downloading URL was in range [400, 500). */\nexport class NetworkError400 extends Error {\n name = 'NetworkError400';\n}\n\nexport class RemoteFileDownloader {\n constructor(public readonly httpClient: Dispatcher) {}\n\n async withContent<T>(\n url: string,\n reqHeaders: Record<string, string>,\n ops: GetContentOptions,\n handler: ContentHandler<T>,\n ): Promise<T> {\n const headers = { ...reqHeaders };\n\n // Add range header if specified\n if (ops.range) {\n headers['Range'] = `bytes=${ops.range.from}-${ops.range.to - 1}`;\n }\n\n const { statusCode, body, headers: responseHeaders } = await request(url, {\n dispatcher: this.httpClient,\n headers,\n signal: ops.signal,\n });\n ops.signal?.throwIfAborted();\n\n const webBody = Readable.toWeb(body);\n let handlerSuccess = false;\n\n try {\n await checkStatusCodeOk(statusCode, webBody, url);\n ops.signal?.throwIfAborted();\n\n const
|
|
1
|
+
{"version":3,"file":"download.cjs","sources":["../../src/helpers/download.ts"],"sourcesContent":["// @TODO Gleb Zakharov\n/* eslint-disable n/no-unsupported-features/node-builtins */\nimport type { Dispatcher } from 'undici';\nimport { request } from 'undici';\nimport { Readable } from 'node:stream';\nimport type { ReadableStream } from 'node:stream/web';\nimport { TransformStream } from 'node:stream/web';\nimport { text } from 'node:stream/consumers';\nimport type { GetContentOptions } from '@milaboratories/pl-model-common';\n\nexport type ContentHandler<T> = (content: ReadableStream, size: number) => Promise<T>;\n\n/** Throws when a status code of the downloading URL was in range [400, 500). */\nexport class NetworkError400 extends Error {\n name = 'NetworkError400';\n}\n\n/**\n * There are backend versions that return 1 less byte than requested in range.\n * For such cases, this error will be thrown, so client can retry the request.\n * Dowloader will retry the request with one more byte in range.\n */\nexport class OffByOneError extends Error {\n name = 'OffByOneError';\n}\n\nexport function isOffByOneError(error: unknown): error is OffByOneError {\n return error instanceof Error && error.name === 'OffByOneError';\n}\n\nexport class RemoteFileDownloader {\n private readonly offByOneServers: string[] = [];\n\n constructor(public readonly httpClient: Dispatcher) {}\n\n async withContent<T>(\n url: string,\n reqHeaders: Record<string, string>,\n ops: GetContentOptions,\n handler: ContentHandler<T>,\n ): Promise<T> {\n const headers = { ...reqHeaders };\n const urlOrigin = new URL(url).origin;\n\n // Add range header if specified\n if (ops.range) {\n const offByOne = this.offByOneServers.includes(urlOrigin);\n headers['Range'] = `bytes=${ops.range.from}-${ops.range.to - (offByOne ? 0 : 1)}`;\n }\n\n const { statusCode, body, headers: responseHeaders } = await request(url, {\n dispatcher: this.httpClient,\n headers,\n signal: ops.signal,\n });\n ops.signal?.throwIfAborted();\n\n const webBody = Readable.toWeb(body);\n let handlerSuccess = false;\n\n try {\n await checkStatusCodeOk(statusCode, webBody, url);\n ops.signal?.throwIfAborted();\n\n let result: T | undefined = undefined;\n\n const contentLength = Number(responseHeaders['content-length']);\n if (Number.isNaN(contentLength) || contentLength === 0) {\n // Some backend versions have a bug that they are not returning content-length header.\n // In this case `content-length` header is returned as 0.\n // We should not clip the result stream to 0 bytes in such case.\n result = await handler(webBody, 0);\n } else {\n // Some backend versions have a bug where they return more data than requested in range.\n // So we have to manually normalize the stream to the expected size.\n const size = ops.range ? ops.range.to - ops.range.from : contentLength;\n const normalizedStream = webBody.pipeThrough(new (class extends TransformStream {\n constructor(sizeBytes: number, recordOffByOne: () => void) {\n super({\n transform(chunk: Uint8Array, controller) {\n const truncatedChunk = chunk.slice(0, sizeBytes);\n controller.enqueue(truncatedChunk);\n sizeBytes -= truncatedChunk.length;\n if (!sizeBytes) controller.terminate();\n },\n flush(controller) {\n // Some backend versions have a bug where they return 1 less byte than requested in range.\n // We cannot always request one more byte because if this end byte is the last byte of the file,\n // the backend will return 416 (Range Not Satisfiable). So error is thrown to force client to retry the request.\n if (sizeBytes === 1) {\n recordOffByOne();\n controller.error(new OffByOneError());\n }\n },\n });\n }\n })(size, () => this.offByOneServers.push(urlOrigin)));\n result = await handler(normalizedStream, size);\n }\n\n handlerSuccess = true;\n return result;\n } catch (error) {\n // Cleanup on error (including handler errors)\n if (!handlerSuccess && !webBody.locked) {\n try {\n await webBody.cancel();\n } catch {\n // Ignore cleanup errors\n }\n }\n throw error;\n }\n }\n}\n\nasync function checkStatusCodeOk(statusCode: number, webBody: ReadableStream, url: string) {\n if (statusCode != 200 && statusCode != 206 /* partial content from range request */) {\n const beginning = (await text(webBody)).substring(0, 1000);\n\n if (400 <= statusCode && statusCode < 500) {\n throw new NetworkError400(\n `Http error: statusCode: ${statusCode} `\n + `url: ${url.toString()}, beginning of body: ${beginning}`);\n }\n\n throw new Error(`Http error: statusCode: ${statusCode} url: ${url.toString()}`);\n }\n}\n"],"names":["request","Readable","TransformStream","text"],"mappings":"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"}
|
|
@@ -6,8 +6,18 @@ export type ContentHandler<T> = (content: ReadableStream, size: number) => Promi
|
|
|
6
6
|
export declare class NetworkError400 extends Error {
|
|
7
7
|
name: string;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* There are backend versions that return 1 less byte than requested in range.
|
|
11
|
+
* For such cases, this error will be thrown, so client can retry the request.
|
|
12
|
+
* Dowloader will retry the request with one more byte in range.
|
|
13
|
+
*/
|
|
14
|
+
export declare class OffByOneError extends Error {
|
|
15
|
+
name: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function isOffByOneError(error: unknown): error is OffByOneError;
|
|
9
18
|
export declare class RemoteFileDownloader {
|
|
10
19
|
readonly httpClient: Dispatcher;
|
|
20
|
+
private readonly offByOneServers;
|
|
11
21
|
constructor(httpClient: Dispatcher);
|
|
12
22
|
withContent<T>(url: string, reqHeaders: Record<string, string>, ops: GetContentOptions, handler: ContentHandler<T>): Promise<T>;
|
|
13
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/helpers/download.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/helpers/download.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtF,gFAAgF;AAChF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,IAAI,SAAqB;CAC1B;AAED;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,KAAK;IACtC,IAAI,SAAmB;CACxB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAEtE;AAED,qBAAa,oBAAoB;aAGH,UAAU,EAAE,UAAU;IAFlD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgB;gBAEpB,UAAU,EAAE,UAAU;IAE5C,WAAW,CAAC,CAAC,EACjB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,GAAG,EAAE,iBAAiB,EACtB,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GACzB,OAAO,CAAC,CAAC,CAAC;CA0Ed"}
|
package/dist/helpers/download.js
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
import { request } from 'undici';
|
|
2
2
|
import { Readable } from 'node:stream';
|
|
3
|
+
import { TransformStream } from 'node:stream/web';
|
|
3
4
|
import { text } from 'node:stream/consumers';
|
|
4
5
|
|
|
5
6
|
/** Throws when a status code of the downloading URL was in range [400, 500). */
|
|
6
7
|
class NetworkError400 extends Error {
|
|
7
8
|
name = 'NetworkError400';
|
|
8
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* There are backend versions that return 1 less byte than requested in range.
|
|
12
|
+
* For such cases, this error will be thrown, so client can retry the request.
|
|
13
|
+
* Dowloader will retry the request with one more byte in range.
|
|
14
|
+
*/
|
|
15
|
+
class OffByOneError extends Error {
|
|
16
|
+
name = 'OffByOneError';
|
|
17
|
+
}
|
|
18
|
+
function isOffByOneError(error) {
|
|
19
|
+
return error instanceof Error && error.name === 'OffByOneError';
|
|
20
|
+
}
|
|
9
21
|
class RemoteFileDownloader {
|
|
10
22
|
httpClient;
|
|
23
|
+
offByOneServers = [];
|
|
11
24
|
constructor(httpClient) {
|
|
12
25
|
this.httpClient = httpClient;
|
|
13
26
|
}
|
|
14
27
|
async withContent(url, reqHeaders, ops, handler) {
|
|
15
28
|
const headers = { ...reqHeaders };
|
|
29
|
+
const urlOrigin = new URL(url).origin;
|
|
16
30
|
// Add range header if specified
|
|
17
31
|
if (ops.range) {
|
|
18
|
-
|
|
32
|
+
const offByOne = this.offByOneServers.includes(urlOrigin);
|
|
33
|
+
headers['Range'] = `bytes=${ops.range.from}-${ops.range.to - (offByOne ? 0 : 1)}`;
|
|
19
34
|
}
|
|
20
35
|
const { statusCode, body, headers: responseHeaders } = await request(url, {
|
|
21
36
|
dispatcher: this.httpClient,
|
|
@@ -28,9 +43,42 @@ class RemoteFileDownloader {
|
|
|
28
43
|
try {
|
|
29
44
|
await checkStatusCodeOk(statusCode, webBody, url);
|
|
30
45
|
ops.signal?.throwIfAborted();
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
46
|
+
let result = undefined;
|
|
47
|
+
const contentLength = Number(responseHeaders['content-length']);
|
|
48
|
+
if (Number.isNaN(contentLength) || contentLength === 0) {
|
|
49
|
+
// Some backend versions have a bug that they are not returning content-length header.
|
|
50
|
+
// In this case `content-length` header is returned as 0.
|
|
51
|
+
// We should not clip the result stream to 0 bytes in such case.
|
|
52
|
+
result = await handler(webBody, 0);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Some backend versions have a bug where they return more data than requested in range.
|
|
56
|
+
// So we have to manually normalize the stream to the expected size.
|
|
57
|
+
const size = ops.range ? ops.range.to - ops.range.from : contentLength;
|
|
58
|
+
const normalizedStream = webBody.pipeThrough(new (class extends TransformStream {
|
|
59
|
+
constructor(sizeBytes, recordOffByOne) {
|
|
60
|
+
super({
|
|
61
|
+
transform(chunk, controller) {
|
|
62
|
+
const truncatedChunk = chunk.slice(0, sizeBytes);
|
|
63
|
+
controller.enqueue(truncatedChunk);
|
|
64
|
+
sizeBytes -= truncatedChunk.length;
|
|
65
|
+
if (!sizeBytes)
|
|
66
|
+
controller.terminate();
|
|
67
|
+
},
|
|
68
|
+
flush(controller) {
|
|
69
|
+
// Some backend versions have a bug where they return 1 less byte than requested in range.
|
|
70
|
+
// We cannot always request one more byte because if this end byte is the last byte of the file,
|
|
71
|
+
// the backend will return 416 (Range Not Satisfiable). So error is thrown to force client to retry the request.
|
|
72
|
+
if (sizeBytes === 1) {
|
|
73
|
+
recordOffByOne();
|
|
74
|
+
controller.error(new OffByOneError());
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
})(size, () => this.offByOneServers.push(urlOrigin)));
|
|
80
|
+
result = await handler(normalizedStream, size);
|
|
81
|
+
}
|
|
34
82
|
handlerSuccess = true;
|
|
35
83
|
return result;
|
|
36
84
|
}
|
|
@@ -59,5 +107,5 @@ async function checkStatusCodeOk(statusCode, webBody, url) {
|
|
|
59
107
|
}
|
|
60
108
|
}
|
|
61
109
|
|
|
62
|
-
export { NetworkError400, RemoteFileDownloader };
|
|
110
|
+
export { NetworkError400, OffByOneError, RemoteFileDownloader, isOffByOneError };
|
|
63
111
|
//# sourceMappingURL=download.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.js","sources":["../../src/helpers/download.ts"],"sourcesContent":["// @TODO Gleb Zakharov\n/* eslint-disable n/no-unsupported-features/node-builtins */\nimport type { Dispatcher } from 'undici';\nimport { request } from 'undici';\nimport { Readable } from 'node:stream';\nimport type { ReadableStream } from 'node:stream/web';\nimport { text } from 'node:stream/consumers';\nimport type { GetContentOptions } from '@milaboratories/pl-model-common';\n\nexport type ContentHandler<T> = (content: ReadableStream, size: number) => Promise<T>;\n\n/** Throws when a status code of the downloading URL was in range [400, 500). */\nexport class NetworkError400 extends Error {\n name = 'NetworkError400';\n}\n\nexport class RemoteFileDownloader {\n constructor(public readonly httpClient: Dispatcher) {}\n\n async withContent<T>(\n url: string,\n reqHeaders: Record<string, string>,\n ops: GetContentOptions,\n handler: ContentHandler<T>,\n ): Promise<T> {\n const headers = { ...reqHeaders };\n\n // Add range header if specified\n if (ops.range) {\n headers['Range'] = `bytes=${ops.range.from}-${ops.range.to - 1}`;\n }\n\n const { statusCode, body, headers: responseHeaders } = await request(url, {\n dispatcher: this.httpClient,\n headers,\n signal: ops.signal,\n });\n ops.signal?.throwIfAborted();\n\n const webBody = Readable.toWeb(body);\n let handlerSuccess = false;\n\n try {\n await checkStatusCodeOk(statusCode, webBody, url);\n ops.signal?.throwIfAborted();\n\n const
|
|
1
|
+
{"version":3,"file":"download.js","sources":["../../src/helpers/download.ts"],"sourcesContent":["// @TODO Gleb Zakharov\n/* eslint-disable n/no-unsupported-features/node-builtins */\nimport type { Dispatcher } from 'undici';\nimport { request } from 'undici';\nimport { Readable } from 'node:stream';\nimport type { ReadableStream } from 'node:stream/web';\nimport { TransformStream } from 'node:stream/web';\nimport { text } from 'node:stream/consumers';\nimport type { GetContentOptions } from '@milaboratories/pl-model-common';\n\nexport type ContentHandler<T> = (content: ReadableStream, size: number) => Promise<T>;\n\n/** Throws when a status code of the downloading URL was in range [400, 500). */\nexport class NetworkError400 extends Error {\n name = 'NetworkError400';\n}\n\n/**\n * There are backend versions that return 1 less byte than requested in range.\n * For such cases, this error will be thrown, so client can retry the request.\n * Dowloader will retry the request with one more byte in range.\n */\nexport class OffByOneError extends Error {\n name = 'OffByOneError';\n}\n\nexport function isOffByOneError(error: unknown): error is OffByOneError {\n return error instanceof Error && error.name === 'OffByOneError';\n}\n\nexport class RemoteFileDownloader {\n private readonly offByOneServers: string[] = [];\n\n constructor(public readonly httpClient: Dispatcher) {}\n\n async withContent<T>(\n url: string,\n reqHeaders: Record<string, string>,\n ops: GetContentOptions,\n handler: ContentHandler<T>,\n ): Promise<T> {\n const headers = { ...reqHeaders };\n const urlOrigin = new URL(url).origin;\n\n // Add range header if specified\n if (ops.range) {\n const offByOne = this.offByOneServers.includes(urlOrigin);\n headers['Range'] = `bytes=${ops.range.from}-${ops.range.to - (offByOne ? 0 : 1)}`;\n }\n\n const { statusCode, body, headers: responseHeaders } = await request(url, {\n dispatcher: this.httpClient,\n headers,\n signal: ops.signal,\n });\n ops.signal?.throwIfAborted();\n\n const webBody = Readable.toWeb(body);\n let handlerSuccess = false;\n\n try {\n await checkStatusCodeOk(statusCode, webBody, url);\n ops.signal?.throwIfAborted();\n\n let result: T | undefined = undefined;\n\n const contentLength = Number(responseHeaders['content-length']);\n if (Number.isNaN(contentLength) || contentLength === 0) {\n // Some backend versions have a bug that they are not returning content-length header.\n // In this case `content-length` header is returned as 0.\n // We should not clip the result stream to 0 bytes in such case.\n result = await handler(webBody, 0);\n } else {\n // Some backend versions have a bug where they return more data than requested in range.\n // So we have to manually normalize the stream to the expected size.\n const size = ops.range ? ops.range.to - ops.range.from : contentLength;\n const normalizedStream = webBody.pipeThrough(new (class extends TransformStream {\n constructor(sizeBytes: number, recordOffByOne: () => void) {\n super({\n transform(chunk: Uint8Array, controller) {\n const truncatedChunk = chunk.slice(0, sizeBytes);\n controller.enqueue(truncatedChunk);\n sizeBytes -= truncatedChunk.length;\n if (!sizeBytes) controller.terminate();\n },\n flush(controller) {\n // Some backend versions have a bug where they return 1 less byte than requested in range.\n // We cannot always request one more byte because if this end byte is the last byte of the file,\n // the backend will return 416 (Range Not Satisfiable). So error is thrown to force client to retry the request.\n if (sizeBytes === 1) {\n recordOffByOne();\n controller.error(new OffByOneError());\n }\n },\n });\n }\n })(size, () => this.offByOneServers.push(urlOrigin)));\n result = await handler(normalizedStream, size);\n }\n\n handlerSuccess = true;\n return result;\n } catch (error) {\n // Cleanup on error (including handler errors)\n if (!handlerSuccess && !webBody.locked) {\n try {\n await webBody.cancel();\n } catch {\n // Ignore cleanup errors\n }\n }\n throw error;\n }\n }\n}\n\nasync function checkStatusCodeOk(statusCode: number, webBody: ReadableStream, url: string) {\n if (statusCode != 200 && statusCode != 206 /* partial content from range request */) {\n const beginning = (await text(webBody)).substring(0, 1000);\n\n if (400 <= statusCode && statusCode < 500) {\n throw new NetworkError400(\n `Http error: statusCode: ${statusCode} `\n + `url: ${url.toString()}, beginning of body: ${beginning}`);\n }\n\n throw new Error(`Http error: statusCode: ${statusCode} url: ${url.toString()}`);\n }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-drivers",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.17",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=20"
|
|
6
6
|
},
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"zod": "~3.23.8",
|
|
34
34
|
"@milaboratories/ts-helpers": "1.4.7",
|
|
35
35
|
"@milaboratories/helpers": "1.8.0",
|
|
36
|
-
"@milaboratories/pl-client": "2.12.1",
|
|
37
36
|
"@milaboratories/pl-model-common": "1.19.19",
|
|
38
|
-
"@milaboratories/pl-
|
|
39
|
-
"@milaboratories/computable": "2.6.8"
|
|
37
|
+
"@milaboratories/pl-client": "2.12.2",
|
|
38
|
+
"@milaboratories/computable": "2.6.8",
|
|
39
|
+
"@milaboratories/pl-tree": "1.7.13"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/decompress": "^4.2.7",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"eslint": "^9.25.1",
|
|
47
47
|
"typescript": "~5.6.3",
|
|
48
48
|
"vitest": "^2.1.9",
|
|
49
|
-
"@milaboratories/eslint-config": "1.0.4",
|
|
50
49
|
"@milaboratories/ts-builder": "1.0.5",
|
|
51
50
|
"@milaboratories/build-configs": "1.0.8",
|
|
51
|
+
"@milaboratories/eslint-config": "1.0.4",
|
|
52
52
|
"@milaboratories/ts-configs": "1.0.6"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
@@ -63,6 +63,7 @@ import { blobKey, pathToKey } from './blob_key';
|
|
|
63
63
|
import { DownloadBlobTask, nonRecoverableError } from './download_blob_task';
|
|
64
64
|
import { FilesCache } from '../helpers/files_cache';
|
|
65
65
|
import { SparseCache, SparseCacheFsFile, SparseCacheFsRanges } from './sparse_cache/cache';
|
|
66
|
+
import { isOffByOneError } from '../../helpers/download';
|
|
66
67
|
|
|
67
68
|
export type DownloadDriverOps = {
|
|
68
69
|
/**
|
|
@@ -319,7 +320,7 @@ export class DownloadDriver implements BlobDriver {
|
|
|
319
320
|
}
|
|
320
321
|
}
|
|
321
322
|
|
|
322
|
-
|
|
323
|
+
const request = () => this.withContent(handle, {
|
|
323
324
|
...options,
|
|
324
325
|
handler: async (content) => {
|
|
325
326
|
const chunks: Uint8Array[] = [];
|
|
@@ -330,6 +331,15 @@ export class DownloadDriver implements BlobDriver {
|
|
|
330
331
|
return Buffer.concat(chunks);
|
|
331
332
|
}
|
|
332
333
|
});
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
return await request();
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (isOffByOneError(error)) {
|
|
339
|
+
return await request();
|
|
340
|
+
}
|
|
341
|
+
throw error;
|
|
342
|
+
}
|
|
333
343
|
}
|
|
334
344
|
|
|
335
345
|
/** Gets a content stream of a blob by a handle and calls handler with it. */
|
|
@@ -363,7 +373,11 @@ export class DownloadDriver implements BlobDriver {
|
|
|
363
373
|
|
|
364
374
|
const handlerPromise = handler(handlerStream, size);
|
|
365
375
|
const _cachePromise = buffer(cacheStream)
|
|
366
|
-
.then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data))
|
|
376
|
+
.then((data) => this.rangesCache.set(key, range ?? { from: 0, to: result.size }, data))
|
|
377
|
+
.catch(() => {
|
|
378
|
+
// Ignore cache errors - they shouldn't affect the main handler result
|
|
379
|
+
// This prevents unhandled promise rejections when the stream fails
|
|
380
|
+
});
|
|
367
381
|
|
|
368
382
|
return await handlerPromise;
|
|
369
383
|
}
|
package/src/helpers/download.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { Dispatcher } from 'undici';
|
|
|
4
4
|
import { request } from 'undici';
|
|
5
5
|
import { Readable } from 'node:stream';
|
|
6
6
|
import type { ReadableStream } from 'node:stream/web';
|
|
7
|
+
import { TransformStream } from 'node:stream/web';
|
|
7
8
|
import { text } from 'node:stream/consumers';
|
|
8
9
|
import type { GetContentOptions } from '@milaboratories/pl-model-common';
|
|
9
10
|
|
|
@@ -14,7 +15,22 @@ export class NetworkError400 extends Error {
|
|
|
14
15
|
name = 'NetworkError400';
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* There are backend versions that return 1 less byte than requested in range.
|
|
20
|
+
* For such cases, this error will be thrown, so client can retry the request.
|
|
21
|
+
* Dowloader will retry the request with one more byte in range.
|
|
22
|
+
*/
|
|
23
|
+
export class OffByOneError extends Error {
|
|
24
|
+
name = 'OffByOneError';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isOffByOneError(error: unknown): error is OffByOneError {
|
|
28
|
+
return error instanceof Error && error.name === 'OffByOneError';
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
export class RemoteFileDownloader {
|
|
32
|
+
private readonly offByOneServers: string[] = [];
|
|
33
|
+
|
|
18
34
|
constructor(public readonly httpClient: Dispatcher) {}
|
|
19
35
|
|
|
20
36
|
async withContent<T>(
|
|
@@ -24,10 +40,12 @@ export class RemoteFileDownloader {
|
|
|
24
40
|
handler: ContentHandler<T>,
|
|
25
41
|
): Promise<T> {
|
|
26
42
|
const headers = { ...reqHeaders };
|
|
43
|
+
const urlOrigin = new URL(url).origin;
|
|
27
44
|
|
|
28
45
|
// Add range header if specified
|
|
29
46
|
if (ops.range) {
|
|
30
|
-
|
|
47
|
+
const offByOne = this.offByOneServers.includes(urlOrigin);
|
|
48
|
+
headers['Range'] = `bytes=${ops.range.from}-${ops.range.to - (offByOne ? 0 : 1)}`;
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
const { statusCode, body, headers: responseHeaders } = await request(url, {
|
|
@@ -44,9 +62,41 @@ export class RemoteFileDownloader {
|
|
|
44
62
|
await checkStatusCodeOk(statusCode, webBody, url);
|
|
45
63
|
ops.signal?.throwIfAborted();
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
65
|
+
let result: T | undefined = undefined;
|
|
66
|
+
|
|
67
|
+
const contentLength = Number(responseHeaders['content-length']);
|
|
68
|
+
if (Number.isNaN(contentLength) || contentLength === 0) {
|
|
69
|
+
// Some backend versions have a bug that they are not returning content-length header.
|
|
70
|
+
// In this case `content-length` header is returned as 0.
|
|
71
|
+
// We should not clip the result stream to 0 bytes in such case.
|
|
72
|
+
result = await handler(webBody, 0);
|
|
73
|
+
} else {
|
|
74
|
+
// Some backend versions have a bug where they return more data than requested in range.
|
|
75
|
+
// So we have to manually normalize the stream to the expected size.
|
|
76
|
+
const size = ops.range ? ops.range.to - ops.range.from : contentLength;
|
|
77
|
+
const normalizedStream = webBody.pipeThrough(new (class extends TransformStream {
|
|
78
|
+
constructor(sizeBytes: number, recordOffByOne: () => void) {
|
|
79
|
+
super({
|
|
80
|
+
transform(chunk: Uint8Array, controller) {
|
|
81
|
+
const truncatedChunk = chunk.slice(0, sizeBytes);
|
|
82
|
+
controller.enqueue(truncatedChunk);
|
|
83
|
+
sizeBytes -= truncatedChunk.length;
|
|
84
|
+
if (!sizeBytes) controller.terminate();
|
|
85
|
+
},
|
|
86
|
+
flush(controller) {
|
|
87
|
+
// Some backend versions have a bug where they return 1 less byte than requested in range.
|
|
88
|
+
// We cannot always request one more byte because if this end byte is the last byte of the file,
|
|
89
|
+
// the backend will return 416 (Range Not Satisfiable). So error is thrown to force client to retry the request.
|
|
90
|
+
if (sizeBytes === 1) {
|
|
91
|
+
recordOffByOne();
|
|
92
|
+
controller.error(new OffByOneError());
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
})(size, () => this.offByOneServers.push(urlOrigin)));
|
|
98
|
+
result = await handler(normalizedStream, size);
|
|
99
|
+
}
|
|
50
100
|
|
|
51
101
|
handlerSuccess = true;
|
|
52
102
|
return result;
|