diff --git a/package.json b/package.json index 5e20353..933141e 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "lodash": "^4.17.21", "mkdirp": "^3.0.1", "mv": "^2.1.1", - "parse-sng": "^3.1.1", + "parse-sng": "^3.1.2", "rimraf": "^5.0.5", "rxjs": "~7.8.1", "sanitize-filename": "^1.6.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbb8320..ec7a14d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,8 +57,8 @@ dependencies: specifier: ^2.1.1 version: 2.1.1 parse-sng: - specifier: ^3.1.1 - version: 3.1.1 + specifier: ^3.1.2 + version: 3.1.2 rimraf: specifier: ^5.0.5 version: 5.0.5 @@ -8370,8 +8370,8 @@ packages: engines: {node: '>= 0.10'} dev: true - /parse-sng@3.1.1: - resolution: {integrity: sha512-BOU9Y4LE5VsEc76lZonVXcbKrbGbetzdP28avnWvioNEhACzTEhtqDdtNmgsXiMO3lvkveRcM5wp6hEniSIQkA==} + /parse-sng@3.1.2: + resolution: {integrity: sha512-gAFsAJ6yaU2uH8UeSRC+EYFAN/urm/ICRJSLSwP1sqVBEF/VhN0XAxNNFTeLGsgdv1V2gx6pLA8sS9M6YX/FCA==} dependencies: binary-parser: 2.2.1 events: 3.3.0 @@ -9193,7 +9193,7 @@ packages: lodash: 4.17.21 midievents: 2.0.0 midifile: 2.0.0 - parse-sng: 3.1.1 + parse-sng: 3.1.2 sanitize-filename: 1.6.3 sharp: 0.32.6 stream-audio-fingerprint: github.com/Geomitron/stream-audio-fingerprint/197e8a7ff60165b18bf1debc23dab4814996a20d diff --git a/src-angular/app/core/services/search.service.ts b/src-angular/app/core/services/search.service.ts index 0f0351b..38cedad 100644 --- a/src-angular/app/core/services/search.service.ts +++ b/src-angular/app/core/services/search.service.ts @@ -60,7 +60,7 @@ export class SearchService { this.search().subscribe() } - get areMorePages() { return this.songsResponse.page && this.groupedSongs.length === this.songsResponse.page * resultsPerPage } + get areMorePages() { return this.songsResponse?.page && this.groupedSongs.length === this.songsResponse.page * resultsPerPage } /** * General search, uses the `/search?q=` endpoint. diff --git a/src-electron/ipc/download/ChartDownload.ts b/src-electron/ipc/download/ChartDownload.ts index 5e61845..46f20bf 100644 --- a/src-electron/ipc/download/ChartDownload.ts +++ b/src-electron/ipc/download/ChartDownload.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'crypto' import EventEmitter from 'events' -import { createWriteStream, WriteStream } from 'fs' +import { createWriteStream } from 'fs' import { access, constants } from 'fs/promises' import { round, throttle } from 'lodash' import { mkdirp } from 'mkdirp' @@ -8,6 +8,8 @@ import mv from 'mv' import { SngStream } from 'parse-sng' import { join } from 'path' import { rimraf } from 'rimraf' +import { Readable } from 'stream' +import { ReadableStream } from 'stream/web' import { inspect } from 'util' import { tempPath } from '../../../src-shared/Paths' @@ -150,48 +152,20 @@ export class ChartDownload { const fileSize = BigInt(sngResponse.headers.get('Content-Length')!) if (this.isSng) { - const writeStream = createWriteStream(join(this.tempPath, this.destinationName)) - const reader = sngResponse.body.getReader() - let downloadedByteCount = BigInt(0) + const sngStream = Readable.fromWeb(sngResponse.body as ReadableStream, { highWaterMark: 2e+9 }) - // eslint-disable-next-line no-constant-condition - while (true) { - let result: ReadableStreamReadResult - try { - result = await reader.read() - } catch (err) { - throw { header: 'Failed to download the chart file', body: inspect(err) } - } + sngStream.pipe(createWriteStream(join(this.tempPath, this.destinationName), { highWaterMark: 2e+9 })) - if (this._canceled) { - await reader.cancel() - writeStream.end() - return - } - - if (result.done) { writeStream.end(); return } - - downloadedByteCount += BigInt(result.value.length) - const downloadPercent = round(100 * Number(downloadedByteCount / BigInt(1000)) / Number(fileSize / BigInt(1000)), 1) - this.showProgress(`Downloading... (${downloadPercent}%)`, downloadPercent) - - await new Promise((resolve, reject) => { - writeStream.write(result.value, err => { - if (err) { - reject({ header: 'Failed to download the chart file', body: inspect(err) }) - } else { - resolve() - } - }) + await new Promise((resolve, reject) => { + let downloadedByteCount = BigInt(0) + sngStream.on('end', resolve) + sngStream.on('error', err => reject(err)) + sngStream.on('data', data => { + downloadedByteCount += BigInt(data.length) + const downloadPercent = round(100 * Number(downloadedByteCount / BigInt(1000)) / Number(fileSize / BigInt(1000)), 1) + this.showProgress(`Downloading... (${downloadPercent}%)`, downloadPercent) }) - - if (writeStream.writableNeedDrain) { - await new Promise((resolve, reject) => { - writeStream.once('drain', resolve) - writeStream.once('error', err => reject({ header: 'Failed to download the chart file', body: inspect(err) })) - }) - } - } + }) } else { const sngStream = new SngStream(() => sngResponse.body!, { generateSongIni: true }) let downloadedByteCount = BigInt(0) @@ -200,68 +174,19 @@ export class ChartDownload { await new Promise((resolve, reject) => { sngStream.on('file', async (fileName, fileStream) => { - let writeStream: WriteStream - let reader: ReadableStreamDefaultReader - try { - writeStream = createWriteStream(join(this.tempPath, this.destinationName, fileName)) - writeStream.on('error', () => { /** Surpress unhandled promise rejection */ }) - reader = fileStream.getReader() - } catch (err) { - reject(err) - return - } + const nodeFileStream = Readable.fromWeb(fileStream as ReadableStream, { highWaterMark: 2e+9 }) + nodeFileStream.pipe(createWriteStream(join(this.tempPath, this.destinationName, fileName), { highWaterMark: 2e+9 })) - try { - // eslint-disable-next-line no-constant-condition - while (true) { - let result: ReadableStreamReadResult - try { - result = await reader.read() - } catch (err) { - throw { header: 'Failed to download the chart file', body: inspect(err) } - } - - if (this._canceled) { - await reader.cancel() - writeStream.end() - resolve() - return - } - - if (result.done) { writeStream.end(); return } - - downloadedByteCount += BigInt(result.value.length) + await new Promise((resolve, reject) => { + nodeFileStream.on('end', resolve) + nodeFileStream.on('error', err => reject(err)) + nodeFileStream.on('data', data => { + downloadedByteCount += BigInt(data.length) const downloadPercent = round(100 * Number(downloadedByteCount / BigInt(1000)) / Number(fileSize / BigInt(1000)), 1) this.showProgress(`Downloading "${fileName}"... (${downloadPercent}%)`, downloadPercent) - - await new Promise((resolve, reject) => { - writeStream.write(result.value, err => { - if (err) { - reject({ header: 'Failed to download the chart file', body: inspect(err) }) - } else { - resolve() - } - }) - }) - - if (writeStream.writableNeedDrain) { - await new Promise((resolve, reject) => { - writeStream.once('drain', resolve) - writeStream.once('error', err => reject({ - header: 'Failed to download the chart file', - body: inspect(err), - })) - }) - } - } - } catch (err) { - try { - await reader.cancel() - } catch (err) { /** ignore; error already reported */ } - writeStream.end() - reject(err) - } + }) + }) }) sngStream.on('end', resolve) sngStream.on('error', err => reject(err))