@milaboratories/pl-drivers 1.11.26 → 1.11.28

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.
@@ -5,6 +5,7 @@ var tsHelpers = require('@milaboratories/ts-helpers');
5
5
  var node_crypto = require('node:crypto');
6
6
  var path = require('node:path');
7
7
  var download = require('../../helpers/download.cjs');
8
+ var download_errors = require('../../helpers/download_errors.cjs');
8
9
  var files_cache = require('../helpers/files_cache.cjs');
9
10
  var plClient = require('@milaboratories/pl-client');
10
11
  var plModelCommon = require('@milaboratories/pl-model-common');
@@ -71,15 +72,15 @@ class DownloadUrlDriver {
71
72
  }
72
73
  getUrlNoCtx(url, w, callerId) {
73
74
  const key = url.toString();
74
- const task = this.urlToDownload.get(key);
75
- if (task !== undefined) {
76
- task.attach(w, callerId);
77
- return task.getUrl();
75
+ const task$1 = this.urlToDownload.get(key);
76
+ if (task$1 !== undefined) {
77
+ task$1.attach(w, callerId);
78
+ return task$1.getUrl();
78
79
  }
79
80
  const newTask = this.setNewTask(w, url, callerId);
80
81
  this.downloadQueue.push({
81
82
  fn: async () => this.downloadUrl(newTask, callerId),
82
- recoverableErrorPredicate: (e) => true,
83
+ recoverableErrorPredicate: (e) => !(e instanceof task.URLAborted) && !download_errors.isDownloadNetworkError400(e),
83
84
  });
84
85
  return newTask.getUrl();
85
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"driver.cjs","sources":["../../../src/drivers/download_url/driver.ts"],"sourcesContent":["import type { ComputableCtx, Watcher } from '@milaboratories/computable';\nimport { Computable } from '@milaboratories/computable';\nimport type {\n MiLogger,\n Signer,\n} from '@milaboratories/ts-helpers';\nimport {\n TaskProcessor,\n} from '@milaboratories/ts-helpers';\nimport { createHash, randomUUID } from 'node:crypto';\nimport * as path from 'node:path';\nimport type { Dispatcher } from 'undici';\nimport { RemoteFileDownloader } from '../../helpers/download';\nimport { FilesCache } from '../helpers/files_cache';\nimport { stringifyWithResourceId } from '@milaboratories/pl-client';\nimport type { BlockUIURL, FrontendDriver } from '@milaboratories/pl-model-common';\nimport { isBlockUIURL } from '@milaboratories/pl-model-common';\nimport { getPathForBlockUIURL } from '../urls/url';\nimport { DownloadByUrlTask, rmRFDir } from './task';\n\nexport interface DownloadUrlSyncReader {\n /** Returns a Computable that (when the time will come)\n * downloads an archive from an URL,\n * extracts it to the local dir and returns a path to that dir. */\n getUrl(url: URL): Computable<UrlResult | undefined>;\n}\n\nexport interface UrlResult {\n /** Path to the downloadable blob along with the signature and a custom protocol,\n * might be undefined when the error happened. */\n url?: BlockUIURL;\n /** Error that happened when the archive were downloaded. */\n error?: string;\n}\n\nexport type DownloadUrlDriverOps = {\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 /** Whether to gunzip the downloaded archive (it will be untared automatically). */\n withGunzip: boolean;\n\n /** Max number of concurrent downloads while calculating computable states\n * derived from this driver.\n * */\n nConcurrentDownloads: number;\n};\n\n/** Downloads .tar or .tar.gz archives by given URLs\n * and extracts them into saveDir. */\nexport class DownloadUrlDriver implements DownloadUrlSyncReader, FrontendDriver {\n private readonly downloadHelper: RemoteFileDownloader;\n\n private urlToDownload: Map<string, DownloadByUrlTask> = new Map();\n private downloadQueue: TaskProcessor;\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<DownloadByUrlTask>;\n\n constructor(\n private readonly logger: MiLogger,\n httpClient: Dispatcher,\n private readonly saveDir: string,\n private readonly signer: Signer,\n private readonly opts: DownloadUrlDriverOps = {\n cacheSoftSizeBytes: 1 * 1024 * 1024 * 1024, // 1 GB\n withGunzip: true,\n nConcurrentDownloads: 50,\n },\n ) {\n this.downloadQueue = new TaskProcessor(this.logger, this.opts.nConcurrentDownloads);\n this.cache = new FilesCache(this.opts.cacheSoftSizeBytes);\n this.downloadHelper = new RemoteFileDownloader(httpClient);\n }\n\n /** Use to get a path result inside a computable context */\n getUrl(url: URL, ctx: ComputableCtx): UrlResult | undefined;\n\n /** Returns a Computable that do the work */\n getUrl(url: URL): Computable<UrlResult | undefined>;\n\n /** Returns a computable that returns a custom protocol URL to the downloaded and unarchived path. */\n getUrl(\n url: URL,\n ctx?: ComputableCtx,\n ): Computable<UrlResult | undefined> | UrlResult | undefined {\n // wrap result as computable, if we were not given an existing computable context\n if (ctx === undefined) return Computable.make((c) => this.getUrl(url, c));\n\n const callerId = randomUUID();\n\n // read as ~ golang's defer\n ctx.addOnDestroy(() => this.releasePath(url, callerId));\n\n const result = this.getUrlNoCtx(url, ctx.watcher, callerId);\n if (result?.url === undefined)\n ctx.markUnstable(\n `a path to the downloaded and untared archive might be undefined. The current result: ${result}`,\n );\n\n return result;\n }\n\n getUrlNoCtx(url: URL, w: Watcher, callerId: string) {\n const key = url.toString();\n const task = this.urlToDownload.get(key);\n\n if (task !== undefined) {\n task.attach(w, callerId);\n return task.getUrl();\n }\n\n const newTask = this.setNewTask(w, url, callerId);\n this.downloadQueue.push({\n fn: async () => this.downloadUrl(newTask, callerId),\n recoverableErrorPredicate: (e) => true,\n });\n\n return newTask.getUrl();\n }\n\n getPathForBlockUI(url: string): string {\n if (!isBlockUIURL(url)) {\n throw new Error(`getPathForBlockUI: ${url} is invalid`);\n }\n\n return getPathForBlockUIURL(this.signer, url, this.saveDir);\n }\n\n /** Downloads and extracts a tar archive if it wasn't downloaded yet. */\n async downloadUrl(task: DownloadByUrlTask, callerId: string) {\n await task.download(this.downloadHelper, this.opts.withGunzip);\n // Might be undefined if a error happened\n if (task.getUrl()?.url !== undefined) this.cache.addCache(task, callerId);\n }\n\n /** Removes a directory and aborts a downloading task when all callers\n * are not interested in it. */\n async releasePath(url: URL, callerId: string): Promise<void> {\n const key = url.toString();\n const task = this.urlToDownload.get(key);\n if (task == undefined) return;\n\n if (this.cache.existsFile(task.path)) {\n const toDelete = this.cache.removeFile(task.path, callerId);\n\n await Promise.all(\n toDelete.map(async (task: DownloadByUrlTask) => {\n await rmRFDir(task.path);\n this.cache.removeCache(task);\n\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was removed`\n + `from cache along with ${stringifyWithResourceId(toDelete.map((t) => t.info()))}`,\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 /** Removes all files from a hard drive. */\n async releaseAll() {\n this.downloadQueue.stop();\n\n await Promise.all(\n Array.from(this.urlToDownload.entries()).map(async ([id, task]) => {\n await rmRFDir(task.path);\n this.cache.removeCache(task);\n\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was released when the driver was closed`,\n );\n }),\n );\n }\n\n private setNewTask(w: Watcher, url: URL, callerId: string) {\n const result = new DownloadByUrlTask(\n this.logger,\n this.getFilePath(url),\n url,\n this.signer,\n this.saveDir,\n );\n result.attach(w, callerId);\n this.urlToDownload.set(url.toString(), result);\n\n return result;\n }\n\n private removeTask(task: DownloadByUrlTask, reason: string) {\n task.abort(reason);\n task.change.markChanged(`task for url ${task.url} removed: ${reason}`);\n this.urlToDownload.delete(task.url.toString());\n }\n\n private getFilePath(url: URL): string {\n const sha256 = createHash('sha256').update(url.toString()).digest('hex');\n return path.join(this.saveDir, sha256);\n }\n}\n"],"names":["TaskProcessor","FilesCache","RemoteFileDownloader","Computable","randomUUID","url","isBlockUIURL","getPathForBlockUIURL","task","rmRFDir","stringifyWithResourceId","DownloadByUrlTask","createHash","path"],"mappings":"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"}
1
+ {"version":3,"file":"driver.cjs","sources":["../../../src/drivers/download_url/driver.ts"],"sourcesContent":["import type { ComputableCtx, Watcher } from '@milaboratories/computable';\nimport { Computable } from '@milaboratories/computable';\nimport type {\n MiLogger,\n Signer,\n} from '@milaboratories/ts-helpers';\nimport {\n TaskProcessor,\n} from '@milaboratories/ts-helpers';\nimport { createHash, randomUUID } from 'node:crypto';\nimport * as path from 'node:path';\nimport type { Dispatcher } from 'undici';\nimport { RemoteFileDownloader } from '../../helpers/download';\nimport { isDownloadNetworkError400 } from '../../helpers/download_errors';\nimport { FilesCache } from '../helpers/files_cache';\nimport { stringifyWithResourceId } from '@milaboratories/pl-client';\nimport type { BlockUIURL, FrontendDriver } from '@milaboratories/pl-model-common';\nimport { isBlockUIURL } from '@milaboratories/pl-model-common';\nimport { getPathForBlockUIURL } from '../urls/url';\nimport { DownloadByUrlTask, rmRFDir, URLAborted } from './task';\n\nexport interface DownloadUrlSyncReader {\n /** Returns a Computable that (when the time will come)\n * downloads an archive from an URL,\n * extracts it to the local dir and returns a path to that dir. */\n getUrl(url: URL): Computable<UrlResult | undefined>;\n}\n\nexport interface UrlResult {\n /** Path to the downloadable blob along with the signature and a custom protocol,\n * might be undefined when the error happened. */\n url?: BlockUIURL;\n /** Error that happened when the archive were downloaded. */\n error?: string;\n}\n\nexport type DownloadUrlDriverOps = {\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 /** Whether to gunzip the downloaded archive (it will be untared automatically). */\n withGunzip: boolean;\n\n /** Max number of concurrent downloads while calculating computable states\n * derived from this driver.\n * */\n nConcurrentDownloads: number;\n};\n\n/** Downloads .tar or .tar.gz archives by given URLs\n * and extracts them into saveDir. */\nexport class DownloadUrlDriver implements DownloadUrlSyncReader, FrontendDriver {\n private readonly downloadHelper: RemoteFileDownloader;\n\n private urlToDownload: Map<string, DownloadByUrlTask> = new Map();\n private downloadQueue: TaskProcessor;\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<DownloadByUrlTask>;\n\n constructor(\n private readonly logger: MiLogger,\n httpClient: Dispatcher,\n private readonly saveDir: string,\n private readonly signer: Signer,\n private readonly opts: DownloadUrlDriverOps = {\n cacheSoftSizeBytes: 1 * 1024 * 1024 * 1024, // 1 GB\n withGunzip: true,\n nConcurrentDownloads: 50,\n },\n ) {\n this.downloadQueue = new TaskProcessor(this.logger, this.opts.nConcurrentDownloads);\n this.cache = new FilesCache(this.opts.cacheSoftSizeBytes);\n this.downloadHelper = new RemoteFileDownloader(httpClient);\n }\n\n /** Use to get a path result inside a computable context */\n getUrl(url: URL, ctx: ComputableCtx): UrlResult | undefined;\n\n /** Returns a Computable that do the work */\n getUrl(url: URL): Computable<UrlResult | undefined>;\n\n /** Returns a computable that returns a custom protocol URL to the downloaded and unarchived path. */\n getUrl(\n url: URL,\n ctx?: ComputableCtx,\n ): Computable<UrlResult | undefined> | UrlResult | undefined {\n // wrap result as computable, if we were not given an existing computable context\n if (ctx === undefined) return Computable.make((c) => this.getUrl(url, c));\n\n const callerId = randomUUID();\n\n // read as ~ golang's defer\n ctx.addOnDestroy(() => this.releasePath(url, callerId));\n\n const result = this.getUrlNoCtx(url, ctx.watcher, callerId);\n if (result?.url === undefined)\n ctx.markUnstable(\n `a path to the downloaded and untared archive might be undefined. The current result: ${result}`,\n );\n\n return result;\n }\n\n getUrlNoCtx(url: URL, w: Watcher, callerId: string) {\n const key = url.toString();\n const task = this.urlToDownload.get(key);\n\n if (task !== undefined) {\n task.attach(w, callerId);\n return task.getUrl();\n }\n\n const newTask = this.setNewTask(w, url, callerId);\n this.downloadQueue.push({\n fn: async () => this.downloadUrl(newTask, callerId),\n recoverableErrorPredicate: (e) => !(e instanceof URLAborted) && !isDownloadNetworkError400(e),\n });\n\n return newTask.getUrl();\n }\n\n getPathForBlockUI(url: string): string {\n if (!isBlockUIURL(url)) {\n throw new Error(`getPathForBlockUI: ${url} is invalid`);\n }\n\n return getPathForBlockUIURL(this.signer, url, this.saveDir);\n }\n\n /** Downloads and extracts a tar archive if it wasn't downloaded yet. */\n async downloadUrl(task: DownloadByUrlTask, callerId: string) {\n await task.download(this.downloadHelper, this.opts.withGunzip);\n // Might be undefined if a error happened\n if (task.getUrl()?.url !== undefined) this.cache.addCache(task, callerId);\n }\n\n /** Removes a directory and aborts a downloading task when all callers\n * are not interested in it. */\n async releasePath(url: URL, callerId: string): Promise<void> {\n const key = url.toString();\n const task = this.urlToDownload.get(key);\n if (task == undefined) return;\n\n if (this.cache.existsFile(task.path)) {\n const toDelete = this.cache.removeFile(task.path, callerId);\n\n await Promise.all(\n toDelete.map(async (task: DownloadByUrlTask) => {\n await rmRFDir(task.path);\n this.cache.removeCache(task);\n\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was removed`\n + `from cache along with ${stringifyWithResourceId(toDelete.map((t) => t.info()))}`,\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 /** Removes all files from a hard drive. */\n async releaseAll() {\n this.downloadQueue.stop();\n\n await Promise.all(\n Array.from(this.urlToDownload.entries()).map(async ([id, task]) => {\n await rmRFDir(task.path);\n this.cache.removeCache(task);\n\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was released when the driver was closed`,\n );\n }),\n );\n }\n\n private setNewTask(w: Watcher, url: URL, callerId: string) {\n const result = new DownloadByUrlTask(\n this.logger,\n this.getFilePath(url),\n url,\n this.signer,\n this.saveDir,\n );\n result.attach(w, callerId);\n this.urlToDownload.set(url.toString(), result);\n\n return result;\n }\n\n private removeTask(task: DownloadByUrlTask, reason: string) {\n task.abort(reason);\n task.change.markChanged(`task for url ${task.url} removed: ${reason}`);\n this.urlToDownload.delete(task.url.toString());\n }\n\n private getFilePath(url: URL): string {\n const sha256 = createHash('sha256').update(url.toString()).digest('hex');\n return path.join(this.saveDir, sha256);\n }\n}\n"],"names":["TaskProcessor","FilesCache","RemoteFileDownloader","Computable","randomUUID","task","URLAborted","isDownloadNetworkError400","url","isBlockUIURL","getPathForBlockUIURL","rmRFDir","stringifyWithResourceId","DownloadByUrlTask","createHash","path"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../../src/drivers/download_url/driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,KAAK,EACV,QAAQ,EACR,MAAM,EACP,MAAM,4BAA4B,CAAC;AAMpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAIzC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGlF,OAAO,EAAE,iBAAiB,EAAW,MAAM,QAAQ,CAAC;AAEpD,MAAM,WAAW,qBAAqB;IACpC;;sEAEkE;IAClE,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,SAAS;IACxB;qDACiD;IACjD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;SAGK;IACL,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mFAAmF;IACnF,UAAU,EAAE,OAAO,CAAC;IAEpB;;SAEK;IACL,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF;qCACqC;AACrC,qBAAa,iBAAkB,YAAW,qBAAqB,EAAE,cAAc;IAW3E,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAdvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAuB;IAEtD,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,aAAa,CAAgB;IAErC;mCAC+B;IAC/B,OAAO,CAAC,KAAK,CAAgC;gBAG1B,MAAM,EAAE,QAAQ,EACjC,UAAU,EAAE,UAAU,EACL,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,oBAItB;IAOH,2DAA2D;IAC3D,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,aAAa,GAAG,SAAS,GAAG,SAAS;IAE3D,4CAA4C;IAC5C,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAAC,SAAS,GAAG,SAAS,CAAC;IAwBnD,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM;IAkBlD,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAQtC,wEAAwE;IAClE,WAAW,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM;IAM3D;mCAC+B;IACzB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B5D,2CAA2C;IACrC,UAAU;IAgBhB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;CAIpB"}
1
+ {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../../src/drivers/download_url/driver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,KAAK,EACV,QAAQ,EACR,MAAM,EACP,MAAM,4BAA4B,CAAC;AAMpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAKzC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGlF,OAAO,EAAE,iBAAiB,EAAuB,MAAM,QAAQ,CAAC;AAEhE,MAAM,WAAW,qBAAqB;IACpC;;sEAEkE;IAClE,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,SAAS;IACxB;qDACiD;IACjD,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;SAGK;IACL,kBAAkB,EAAE,MAAM,CAAC;IAE3B,mFAAmF;IACnF,UAAU,EAAE,OAAO,CAAC;IAEpB;;SAEK;IACL,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF;qCACqC;AACrC,qBAAa,iBAAkB,YAAW,qBAAqB,EAAE,cAAc;IAW3E,OAAO,CAAC,QAAQ,CAAC,MAAM;IAEvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAdvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAuB;IAEtD,OAAO,CAAC,aAAa,CAA6C;IAClE,OAAO,CAAC,aAAa,CAAgB;IAErC;mCAC+B;IAC/B,OAAO,CAAC,KAAK,CAAgC;gBAG1B,MAAM,EAAE,QAAQ,EACjC,UAAU,EAAE,UAAU,EACL,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,oBAItB;IAOH,2DAA2D;IAC3D,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,aAAa,GAAG,SAAS,GAAG,SAAS;IAE3D,4CAA4C;IAC5C,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAAC,SAAS,GAAG,SAAS,CAAC;IAwBnD,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM;IAkBlD,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAQtC,wEAAwE;IAClE,WAAW,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM;IAM3D;mCAC+B;IACzB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B5D,2CAA2C;IACrC,UAAU;IAgBhB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;CAIpB"}
@@ -3,11 +3,12 @@ import { TaskProcessor } from '@milaboratories/ts-helpers';
3
3
  import { randomUUID, createHash } from 'node:crypto';
4
4
  import * as path from 'node:path';
5
5
  import { RemoteFileDownloader } from '../../helpers/download.js';
6
+ import { isDownloadNetworkError400 } from '../../helpers/download_errors.js';
6
7
  import { FilesCache } from '../helpers/files_cache.js';
7
8
  import { stringifyWithResourceId } from '@milaboratories/pl-client';
8
9
  import { isBlockUIURL } from '@milaboratories/pl-model-common';
9
10
  import { getPathForBlockUIURL } from '../urls/url.js';
10
- import { rmRFDir, DownloadByUrlTask } from './task.js';
11
+ import { URLAborted, rmRFDir, DownloadByUrlTask } from './task.js';
11
12
 
12
13
  /** Downloads .tar or .tar.gz archives by given URLs
13
14
  * and extracts them into saveDir. */
@@ -58,7 +59,7 @@ class DownloadUrlDriver {
58
59
  const newTask = this.setNewTask(w, url, callerId);
59
60
  this.downloadQueue.push({
60
61
  fn: async () => this.downloadUrl(newTask, callerId),
61
- recoverableErrorPredicate: (e) => true,
62
+ recoverableErrorPredicate: (e) => !(e instanceof URLAborted) && !isDownloadNetworkError400(e),
62
63
  });
63
64
  return newTask.getUrl();
64
65
  }
@@ -1 +1 @@
1
- {"version":3,"file":"driver.js","sources":["../../../src/drivers/download_url/driver.ts"],"sourcesContent":["import type { ComputableCtx, Watcher } from '@milaboratories/computable';\nimport { Computable } from '@milaboratories/computable';\nimport type {\n MiLogger,\n Signer,\n} from '@milaboratories/ts-helpers';\nimport {\n TaskProcessor,\n} from '@milaboratories/ts-helpers';\nimport { createHash, randomUUID } from 'node:crypto';\nimport * as path from 'node:path';\nimport type { Dispatcher } from 'undici';\nimport { RemoteFileDownloader } from '../../helpers/download';\nimport { FilesCache } from '../helpers/files_cache';\nimport { stringifyWithResourceId } from '@milaboratories/pl-client';\nimport type { BlockUIURL, FrontendDriver } from '@milaboratories/pl-model-common';\nimport { isBlockUIURL } from '@milaboratories/pl-model-common';\nimport { getPathForBlockUIURL } from '../urls/url';\nimport { DownloadByUrlTask, rmRFDir } from './task';\n\nexport interface DownloadUrlSyncReader {\n /** Returns a Computable that (when the time will come)\n * downloads an archive from an URL,\n * extracts it to the local dir and returns a path to that dir. */\n getUrl(url: URL): Computable<UrlResult | undefined>;\n}\n\nexport interface UrlResult {\n /** Path to the downloadable blob along with the signature and a custom protocol,\n * might be undefined when the error happened. */\n url?: BlockUIURL;\n /** Error that happened when the archive were downloaded. */\n error?: string;\n}\n\nexport type DownloadUrlDriverOps = {\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 /** Whether to gunzip the downloaded archive (it will be untared automatically). */\n withGunzip: boolean;\n\n /** Max number of concurrent downloads while calculating computable states\n * derived from this driver.\n * */\n nConcurrentDownloads: number;\n};\n\n/** Downloads .tar or .tar.gz archives by given URLs\n * and extracts them into saveDir. */\nexport class DownloadUrlDriver implements DownloadUrlSyncReader, FrontendDriver {\n private readonly downloadHelper: RemoteFileDownloader;\n\n private urlToDownload: Map<string, DownloadByUrlTask> = new Map();\n private downloadQueue: TaskProcessor;\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<DownloadByUrlTask>;\n\n constructor(\n private readonly logger: MiLogger,\n httpClient: Dispatcher,\n private readonly saveDir: string,\n private readonly signer: Signer,\n private readonly opts: DownloadUrlDriverOps = {\n cacheSoftSizeBytes: 1 * 1024 * 1024 * 1024, // 1 GB\n withGunzip: true,\n nConcurrentDownloads: 50,\n },\n ) {\n this.downloadQueue = new TaskProcessor(this.logger, this.opts.nConcurrentDownloads);\n this.cache = new FilesCache(this.opts.cacheSoftSizeBytes);\n this.downloadHelper = new RemoteFileDownloader(httpClient);\n }\n\n /** Use to get a path result inside a computable context */\n getUrl(url: URL, ctx: ComputableCtx): UrlResult | undefined;\n\n /** Returns a Computable that do the work */\n getUrl(url: URL): Computable<UrlResult | undefined>;\n\n /** Returns a computable that returns a custom protocol URL to the downloaded and unarchived path. */\n getUrl(\n url: URL,\n ctx?: ComputableCtx,\n ): Computable<UrlResult | undefined> | UrlResult | undefined {\n // wrap result as computable, if we were not given an existing computable context\n if (ctx === undefined) return Computable.make((c) => this.getUrl(url, c));\n\n const callerId = randomUUID();\n\n // read as ~ golang's defer\n ctx.addOnDestroy(() => this.releasePath(url, callerId));\n\n const result = this.getUrlNoCtx(url, ctx.watcher, callerId);\n if (result?.url === undefined)\n ctx.markUnstable(\n `a path to the downloaded and untared archive might be undefined. The current result: ${result}`,\n );\n\n return result;\n }\n\n getUrlNoCtx(url: URL, w: Watcher, callerId: string) {\n const key = url.toString();\n const task = this.urlToDownload.get(key);\n\n if (task !== undefined) {\n task.attach(w, callerId);\n return task.getUrl();\n }\n\n const newTask = this.setNewTask(w, url, callerId);\n this.downloadQueue.push({\n fn: async () => this.downloadUrl(newTask, callerId),\n recoverableErrorPredicate: (e) => true,\n });\n\n return newTask.getUrl();\n }\n\n getPathForBlockUI(url: string): string {\n if (!isBlockUIURL(url)) {\n throw new Error(`getPathForBlockUI: ${url} is invalid`);\n }\n\n return getPathForBlockUIURL(this.signer, url, this.saveDir);\n }\n\n /** Downloads and extracts a tar archive if it wasn't downloaded yet. */\n async downloadUrl(task: DownloadByUrlTask, callerId: string) {\n await task.download(this.downloadHelper, this.opts.withGunzip);\n // Might be undefined if a error happened\n if (task.getUrl()?.url !== undefined) this.cache.addCache(task, callerId);\n }\n\n /** Removes a directory and aborts a downloading task when all callers\n * are not interested in it. */\n async releasePath(url: URL, callerId: string): Promise<void> {\n const key = url.toString();\n const task = this.urlToDownload.get(key);\n if (task == undefined) return;\n\n if (this.cache.existsFile(task.path)) {\n const toDelete = this.cache.removeFile(task.path, callerId);\n\n await Promise.all(\n toDelete.map(async (task: DownloadByUrlTask) => {\n await rmRFDir(task.path);\n this.cache.removeCache(task);\n\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was removed`\n + `from cache along with ${stringifyWithResourceId(toDelete.map((t) => t.info()))}`,\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 /** Removes all files from a hard drive. */\n async releaseAll() {\n this.downloadQueue.stop();\n\n await Promise.all(\n Array.from(this.urlToDownload.entries()).map(async ([id, task]) => {\n await rmRFDir(task.path);\n this.cache.removeCache(task);\n\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was released when the driver was closed`,\n );\n }),\n );\n }\n\n private setNewTask(w: Watcher, url: URL, callerId: string) {\n const result = new DownloadByUrlTask(\n this.logger,\n this.getFilePath(url),\n url,\n this.signer,\n this.saveDir,\n );\n result.attach(w, callerId);\n this.urlToDownload.set(url.toString(), result);\n\n return result;\n }\n\n private removeTask(task: DownloadByUrlTask, reason: string) {\n task.abort(reason);\n task.change.markChanged(`task for url ${task.url} removed: ${reason}`);\n this.urlToDownload.delete(task.url.toString());\n }\n\n private getFilePath(url: URL): string {\n const sha256 = createHash('sha256').update(url.toString()).digest('hex');\n return path.join(this.saveDir, sha256);\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"driver.js","sources":["../../../src/drivers/download_url/driver.ts"],"sourcesContent":["import type { ComputableCtx, Watcher } from '@milaboratories/computable';\nimport { Computable } from '@milaboratories/computable';\nimport type {\n MiLogger,\n Signer,\n} from '@milaboratories/ts-helpers';\nimport {\n TaskProcessor,\n} from '@milaboratories/ts-helpers';\nimport { createHash, randomUUID } from 'node:crypto';\nimport * as path from 'node:path';\nimport type { Dispatcher } from 'undici';\nimport { RemoteFileDownloader } from '../../helpers/download';\nimport { isDownloadNetworkError400 } from '../../helpers/download_errors';\nimport { FilesCache } from '../helpers/files_cache';\nimport { stringifyWithResourceId } from '@milaboratories/pl-client';\nimport type { BlockUIURL, FrontendDriver } from '@milaboratories/pl-model-common';\nimport { isBlockUIURL } from '@milaboratories/pl-model-common';\nimport { getPathForBlockUIURL } from '../urls/url';\nimport { DownloadByUrlTask, rmRFDir, URLAborted } from './task';\n\nexport interface DownloadUrlSyncReader {\n /** Returns a Computable that (when the time will come)\n * downloads an archive from an URL,\n * extracts it to the local dir and returns a path to that dir. */\n getUrl(url: URL): Computable<UrlResult | undefined>;\n}\n\nexport interface UrlResult {\n /** Path to the downloadable blob along with the signature and a custom protocol,\n * might be undefined when the error happened. */\n url?: BlockUIURL;\n /** Error that happened when the archive were downloaded. */\n error?: string;\n}\n\nexport type DownloadUrlDriverOps = {\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 /** Whether to gunzip the downloaded archive (it will be untared automatically). */\n withGunzip: boolean;\n\n /** Max number of concurrent downloads while calculating computable states\n * derived from this driver.\n * */\n nConcurrentDownloads: number;\n};\n\n/** Downloads .tar or .tar.gz archives by given URLs\n * and extracts them into saveDir. */\nexport class DownloadUrlDriver implements DownloadUrlSyncReader, FrontendDriver {\n private readonly downloadHelper: RemoteFileDownloader;\n\n private urlToDownload: Map<string, DownloadByUrlTask> = new Map();\n private downloadQueue: TaskProcessor;\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<DownloadByUrlTask>;\n\n constructor(\n private readonly logger: MiLogger,\n httpClient: Dispatcher,\n private readonly saveDir: string,\n private readonly signer: Signer,\n private readonly opts: DownloadUrlDriverOps = {\n cacheSoftSizeBytes: 1 * 1024 * 1024 * 1024, // 1 GB\n withGunzip: true,\n nConcurrentDownloads: 50,\n },\n ) {\n this.downloadQueue = new TaskProcessor(this.logger, this.opts.nConcurrentDownloads);\n this.cache = new FilesCache(this.opts.cacheSoftSizeBytes);\n this.downloadHelper = new RemoteFileDownloader(httpClient);\n }\n\n /** Use to get a path result inside a computable context */\n getUrl(url: URL, ctx: ComputableCtx): UrlResult | undefined;\n\n /** Returns a Computable that do the work */\n getUrl(url: URL): Computable<UrlResult | undefined>;\n\n /** Returns a computable that returns a custom protocol URL to the downloaded and unarchived path. */\n getUrl(\n url: URL,\n ctx?: ComputableCtx,\n ): Computable<UrlResult | undefined> | UrlResult | undefined {\n // wrap result as computable, if we were not given an existing computable context\n if (ctx === undefined) return Computable.make((c) => this.getUrl(url, c));\n\n const callerId = randomUUID();\n\n // read as ~ golang's defer\n ctx.addOnDestroy(() => this.releasePath(url, callerId));\n\n const result = this.getUrlNoCtx(url, ctx.watcher, callerId);\n if (result?.url === undefined)\n ctx.markUnstable(\n `a path to the downloaded and untared archive might be undefined. The current result: ${result}`,\n );\n\n return result;\n }\n\n getUrlNoCtx(url: URL, w: Watcher, callerId: string) {\n const key = url.toString();\n const task = this.urlToDownload.get(key);\n\n if (task !== undefined) {\n task.attach(w, callerId);\n return task.getUrl();\n }\n\n const newTask = this.setNewTask(w, url, callerId);\n this.downloadQueue.push({\n fn: async () => this.downloadUrl(newTask, callerId),\n recoverableErrorPredicate: (e) => !(e instanceof URLAborted) && !isDownloadNetworkError400(e),\n });\n\n return newTask.getUrl();\n }\n\n getPathForBlockUI(url: string): string {\n if (!isBlockUIURL(url)) {\n throw new Error(`getPathForBlockUI: ${url} is invalid`);\n }\n\n return getPathForBlockUIURL(this.signer, url, this.saveDir);\n }\n\n /** Downloads and extracts a tar archive if it wasn't downloaded yet. */\n async downloadUrl(task: DownloadByUrlTask, callerId: string) {\n await task.download(this.downloadHelper, this.opts.withGunzip);\n // Might be undefined if a error happened\n if (task.getUrl()?.url !== undefined) this.cache.addCache(task, callerId);\n }\n\n /** Removes a directory and aborts a downloading task when all callers\n * are not interested in it. */\n async releasePath(url: URL, callerId: string): Promise<void> {\n const key = url.toString();\n const task = this.urlToDownload.get(key);\n if (task == undefined) return;\n\n if (this.cache.existsFile(task.path)) {\n const toDelete = this.cache.removeFile(task.path, callerId);\n\n await Promise.all(\n toDelete.map(async (task: DownloadByUrlTask) => {\n await rmRFDir(task.path);\n this.cache.removeCache(task);\n\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was removed`\n + `from cache along with ${stringifyWithResourceId(toDelete.map((t) => t.info()))}`,\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 /** Removes all files from a hard drive. */\n async releaseAll() {\n this.downloadQueue.stop();\n\n await Promise.all(\n Array.from(this.urlToDownload.entries()).map(async ([id, task]) => {\n await rmRFDir(task.path);\n this.cache.removeCache(task);\n\n this.removeTask(\n task,\n `the task ${stringifyWithResourceId(task.info())} was released when the driver was closed`,\n );\n }),\n );\n }\n\n private setNewTask(w: Watcher, url: URL, callerId: string) {\n const result = new DownloadByUrlTask(\n this.logger,\n this.getFilePath(url),\n url,\n this.signer,\n this.saveDir,\n );\n result.attach(w, callerId);\n this.urlToDownload.set(url.toString(), result);\n\n return result;\n }\n\n private removeTask(task: DownloadByUrlTask, reason: string) {\n task.abort(reason);\n task.change.markChanged(`task for url ${task.url} removed: ${reason}`);\n this.urlToDownload.delete(task.url.toString());\n }\n\n private getFilePath(url: URL): string {\n const sha256 = createHash('sha256').update(url.toString()).digest('hex');\n return path.join(this.saveDir, sha256);\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.11.26",
3
+ "version": "1.11.28",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -32,12 +32,12 @@
32
32
  "undici": "~7.16.0",
33
33
  "upath": "^2.0.1",
34
34
  "zod": "~3.23.8",
35
- "@milaboratories/computable": "2.7.4",
36
- "@milaboratories/pl-tree": "1.8.20",
37
- "@milaboratories/pl-client": "2.16.13",
38
35
  "@milaboratories/ts-helpers": "1.5.4",
39
36
  "@milaboratories/helpers": "1.12.0",
40
- "@milaboratories/pl-model-common": "1.21.10"
37
+ "@milaboratories/pl-client": "2.16.14",
38
+ "@milaboratories/pl-model-common": "1.21.10",
39
+ "@milaboratories/computable": "2.7.4",
40
+ "@milaboratories/pl-tree": "1.8.21"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/decompress": "^4.2.7",
@@ -49,10 +49,10 @@
49
49
  "typescript": "~5.6.3",
50
50
  "vitest": "^4.0.7",
51
51
  "@milaboratories/eslint-config": "1.0.5",
52
- "@milaboratories/build-configs": "1.0.9",
53
- "@milaboratories/ts-builder": "1.0.6",
54
- "@milaboratories/ts-configs": "1.0.6",
55
- "@milaboratories/test-helpers": "1.1.1"
52
+ "@milaboratories/ts-builder": "1.2.0",
53
+ "@milaboratories/build-configs": "1.2.0",
54
+ "@milaboratories/test-helpers": "1.1.1",
55
+ "@milaboratories/ts-configs": "1.2.0"
56
56
  },
57
57
  "scripts": {
58
58
  "type-check": "ts-builder types --target node",
@@ -9,7 +9,7 @@ import * as path from 'node:path';
9
9
  import { DownloadUrlDriver } from './driver';
10
10
  import { test, expect } from 'vitest';
11
11
 
12
- test('should download a tar archive and extracts its content and then deleted', async () => {
12
+ test.skip('should download a tar archive and extracts its content and then deleted', async () => {
13
13
  await TestHelpers.withTempRoot(async (client) => {
14
14
  const logger = new ConsoleLoggerAdapter();
15
15
  const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test1-'));
@@ -44,31 +44,28 @@ test('should download a tar archive and extracts its content and then deleted',
44
44
  }, 45000);
45
45
 
46
46
  test('should show a error when 404 status code', async () => {
47
- try {
48
- await TestHelpers.withTempRoot(async (client) => {
49
- const logger = new ConsoleLoggerAdapter();
50
- const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test1-'));
51
- const driver = new DownloadUrlDriver(logger, client.httpDispatcher, dir, genSigner());
52
-
53
- const url = new URL(
54
- 'https://block.registry.platforma.bio/releases/v1/milaboratory/NOT_FOUND',
55
- );
56
-
57
- const c = driver.getUrl(url);
58
-
59
- const url1 = await c.getValue();
60
- expect(url1).toBeUndefined();
61
-
62
- await c.awaitChange();
63
-
64
- const url2 = await c.getValue();
65
- expect(url2).not.toBeUndefined();
66
- expect(url2?.error).not.toBeUndefined();
67
- });
68
- } catch (e) {
69
- console.log('HERE: ', e);
70
- }
71
- });
47
+ await TestHelpers.withTempRoot(async (client) => {
48
+ const logger = new ConsoleLoggerAdapter();
49
+ const dir = await fsp.mkdtemp(path.join(os.tmpdir(), 'test1-'));
50
+ const driver = new DownloadUrlDriver(logger, client.httpDispatcher, dir, genSigner());
51
+
52
+ const url = new URL(
53
+ 'https://block.registry.platforma.bio/releases/v1/milaboratory/NOT_FOUND',
54
+ );
55
+
56
+ const c = driver.getUrl(url);
57
+
58
+ const url1 = await c.getValue();
59
+ expect(url1).toBeUndefined();
60
+
61
+ await c.awaitChange();
62
+
63
+ const url2 = await c.getValue();
64
+ expect(url2).not.toBeUndefined();
65
+ expect(url2?.error).not.toBeUndefined();
66
+ expect(url2?.url).toBeUndefined();
67
+ });
68
+ }, 45000);
72
69
 
73
70
  test('should abort a downloading process when we reset a state of a computable', async () => {
74
71
  await TestHelpers.withTempRoot(async (client) => {
@@ -11,12 +11,13 @@ import { createHash, randomUUID } from 'node:crypto';
11
11
  import * as path from 'node:path';
12
12
  import type { Dispatcher } from 'undici';
13
13
  import { RemoteFileDownloader } from '../../helpers/download';
14
+ import { isDownloadNetworkError400 } from '../../helpers/download_errors';
14
15
  import { FilesCache } from '../helpers/files_cache';
15
16
  import { stringifyWithResourceId } from '@milaboratories/pl-client';
16
17
  import type { BlockUIURL, FrontendDriver } from '@milaboratories/pl-model-common';
17
18
  import { isBlockUIURL } from '@milaboratories/pl-model-common';
18
19
  import { getPathForBlockUIURL } from '../urls/url';
19
- import { DownloadByUrlTask, rmRFDir } from './task';
20
+ import { DownloadByUrlTask, rmRFDir, URLAborted } from './task';
20
21
 
21
22
  export interface DownloadUrlSyncReader {
22
23
  /** Returns a Computable that (when the time will come)
@@ -117,7 +118,7 @@ export class DownloadUrlDriver implements DownloadUrlSyncReader, FrontendDriver
117
118
  const newTask = this.setNewTask(w, url, callerId);
118
119
  this.downloadQueue.push({
119
120
  fn: async () => this.downloadUrl(newTask, callerId),
120
- recoverableErrorPredicate: (e) => true,
121
+ recoverableErrorPredicate: (e) => !(e instanceof URLAborted) && !isDownloadNetworkError400(e),
121
122
  });
122
123
 
123
124
  return newTask.getUrl();