From 95ba0a95c900ae3fb6a55a1779a555a7d070ed42 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Fri, 5 Dec 2025 18:14:33 -0800 Subject: [PATCH] fix: download util improvements --- admin/app/services/docker_service.ts | 30 ++++++++----- admin/app/utils/downloads.ts | 66 +++++++++++++++++++++------- admin/app/utils/url.ts | 0 3 files changed, 69 insertions(+), 27 deletions(-) delete mode 100644 admin/app/utils/url.ts diff --git a/admin/app/services/docker_service.ts b/admin/app/services/docker_service.ts index decd9f4..7d07492 100644 --- a/admin/app/services/docker_service.ts +++ b/admin/app/services/docker_service.ts @@ -333,6 +333,7 @@ export class DockerService { 'https://github.com/Crosstalk-Solutions/project-nomad/raw/refs/heads/master/install/wikipedia_en_100_mini_2025-06.zim' const zimPath = '/zim/wikipedia_en_100_mini_2025-06.zim' const filepath = path.join(DockerService.NOMAD_STORAGE_ABS_PATH, zimPath) + logger.info(`[DockerService] Kiwix Serve pre-install: Downloading ZIM file to ${filepath}`) this._broadcast( DockerService.KIWIX_SERVICE_NAME, @@ -345,17 +346,26 @@ export class DockerService { `Downloading Wikipedia ZIM file from ${WIKIPEDIA_ZIM_URL}. This may take some time...` ) - await doSimpleDownload({ - url: WIKIPEDIA_ZIM_URL, - filepath, - timeout: 60000, - }) + try { + await doSimpleDownload({ + url: WIKIPEDIA_ZIM_URL, + filepath, + timeout: 60000, + }) - this._broadcast( - DockerService.KIWIX_SERVICE_NAME, - 'preinstall', - `Downloaded Wikipedia ZIM file to ${filepath}` - ) + this._broadcast( + DockerService.KIWIX_SERVICE_NAME, + 'preinstall', + `Downloaded Wikipedia ZIM file to ${filepath}` + ) + } catch (error) { + this._broadcast( + DockerService.KIWIX_SERVICE_NAME, + 'preinstall-error', + `Failed to download Wikipedia ZIM file: ${error.message}` + ) + throw new Error(`Pre-install action failed: ${error.message}`) + } } private _broadcast(service: string, status: string, message: string) { diff --git a/admin/app/utils/downloads.ts b/admin/app/utils/downloads.ts index 81c6222..8e3b5eb 100644 --- a/admin/app/utils/downloads.ts +++ b/admin/app/utils/downloads.ts @@ -5,7 +5,7 @@ import { DoResumableDownloadWithRetryParams, DoSimpleDownloadParams, } from '../../types/downloads.js' -import axios from 'axios' +import axios, { AxiosResponse } from 'axios' import { Transform } from 'stream' import { deleteFileIfExists, ensureDirectoryExists, getFileStatsIfExists } from './fs.js' import { createWriteStream } from 'fs' @@ -21,24 +21,56 @@ export async function doSimpleDownload({ timeout = 30000, signal, }: DoSimpleDownloadParams): Promise { - const dirname = path.dirname(filepath) - await ensureDirectoryExists(dirname) + return new Promise(async (resolve, reject) => { + let response: AxiosResponse | undefined + let writer: ReturnType | undefined - const response = await axios.get(url, { - responseType: 'stream', - signal, - timeout, - }) - const writer = createWriteStream(filepath) - response.data.pipe(writer) + const cleanup = (err?: Error) => { + try { + response?.data?.destroy?.() + } catch {} + try { + writer?.destroy?.() + } catch {} + if (err) { + try { + logger.error(`Download failed for ${url}: ${err.message}`) + } catch {} + reject(err) + } + } - return new Promise((resolve, reject) => { - writer.on('finish', () => { - resolve(filepath) - }) - writer.on('error', (error) => { - reject(error) - }) + try { + const dirname = path.dirname(filepath) + await ensureDirectoryExists(dirname) + + response = await axios.get(url, { + responseType: 'stream', + signal, + timeout, + }) + + writer = createWriteStream(filepath) + response?.data.pipe(writer) + + response?.data.on('error', cleanup) + writer?.on('error', cleanup) + + writer?.on('finish', () => { + cleanup() + resolve(filepath) + }) + + signal?.addEventListener( + 'abort', + () => { + cleanup(new Error('Download aborted')) + }, + { once: true } + ) + } catch (error) { + cleanup(error as Error) + } }) } diff --git a/admin/app/utils/url.ts b/admin/app/utils/url.ts deleted file mode 100644 index e69de29..0000000