ESM Conversion, fix downloads

This commit is contained in:
Geomitron
2024-07-09 19:50:44 -05:00
parent bfceb52f70
commit 26c6f5e6f5
25 changed files with 175 additions and 115 deletions

View File

@@ -6,10 +6,12 @@
"files": ["*.ts"],
"parserOptions": {
"project": ["tsconfig.json"],
"sourceType": "module",
"createDefaultProgram": true
},
"plugins": [
"prefer-arrow"
"prefer-arrow",
"import"
],
"extends": [
"eslint:recommended",
@@ -17,6 +19,7 @@
"plugin:@angular-eslint/recommended"
],
"rules": {
"import/extensions": ["error", "ignorePackages"],
"semi": "off",
"@typescript-eslint/semi": ["error", "never"],
"@typescript-eslint/consistent-type-definitions": "error",

View File

@@ -11,7 +11,7 @@ This is the desktop version of [Chorus Encore](https://www.enchor.us/).
## Setup
Head over to the [Releases](https://github.com/Geomitron/Bridge/releases) page to download the install wizard. (Windows, Mac, and Linux versions are available)
Head over to the [Releases](https://github.com/Geomitron/Bridge/releases) page to download the install wizard. (Windows 10/11, Mac, and Linux versions are available)
## Features

View File

@@ -4,6 +4,7 @@
"description": "A rhythm game chart searching and downloading tool.",
"homepage": "https://github.com/Geomitron/Bridge",
"license": "GPL-3.0",
"type": "module",
"author": {
"name": "Geo",
"email": "geomitron@gmail.com"
@@ -17,7 +18,7 @@
"scripts": {
"start": "concurrently \"npm run start:angular\" \"npm run start:electron\" -n angular,electron -c red,yellow",
"start:angular": "ng serve",
"start:electron": "nodemon --exec \"tsc -p src-electron/tsconfig.electron.json && electron ./dist/electron/src-electron/main.js --dev\" --watch src-electron/ -e ts",
"start:electron": "nodemon --exec \"tsc -p src-electron/tsconfig.electron.json && node src-electron/rename-to-mjs.js && electron ./dist/electron/src-electron/main.js --dev\" --watch src-electron/ -e ts",
"clean": "rimraf dist release",
"build:windows": "ng build -c production && tsc -p src-electron/tsconfig.electron.json && electron-builder build --windows",
"build:mac": "ng build -c production && tsc -p src-electron/tsconfig.electron.json && electron-builder build --mac",
@@ -86,4 +87,4 @@
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5"
}
}
}

View File

@@ -1,6 +1,6 @@
import { Component, Input } from '@angular/core'
import { capitalize } from 'lodash'
import _ from 'lodash'
import { Instrument } from 'scan-chart'
import { ChartData } from 'src-shared/interfaces/search.interface'
import { instrumentToDiff } from 'src-shared/UtilFunctions'
@@ -27,7 +27,7 @@ export class ChartSidebarInstrumentComponent {
.map(nc => nc.difficulty)
if (difficulties.length === 1) {
return capitalize(difficulties[0])
return _.capitalize(difficulties[0])
}
let str = ''

View File

@@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { FormControl, NonNullableFormBuilder, Validators } from '@angular/forms'
import { sortBy } from 'lodash'
import _ from 'lodash'
import { environment } from 'src-angular/environments/environment'
import { ChartData } from 'src-shared/interfaces/search.interface'
import { driveLink } from 'src-shared/UtilFunctions'
@@ -53,7 +53,7 @@ export class ChartSidebarMenutComponent implements OnInit {
get reportExtraInfo() { return this.reportForm.get('reportExtraInfo')! }
get displayVersions() {
return sortBy(this.chartVersions, v => v.modifiedTime).reverse()
return _.sortBy(this.chartVersions, v => v.modifiedTime).reverse()
}
getVersionBreadcrumbs(version: ChartData) {

View File

@@ -122,7 +122,7 @@
</div>
</div>
<div class="join">
<button class="btn rounded-md flex-1 join-item btn-primary" (click)="onDownloadClicked()">Download</button>
<button class="btn rounded-md flex-1 join-item btn-primary capitalize" (click)="onDownloadClicked()">Download</button>
<div
#menu
class="cursor-pointer bg-neutral rounded-md join-item dropdown dropdown-top dropdown-end p-1 flex items-center"

View File

@@ -1,7 +1,7 @@
import { Component, ElementRef, HostBinding, OnInit, Renderer2, ViewChild } from '@angular/core'
import { FormControl } from '@angular/forms'
import { capitalize, chain, compact, flatMap, intersection, round, sortBy } from 'lodash'
import _ from 'lodash'
import { ChartIssueType, Difficulty, FolderIssueType, Instrument, MetadataIssueType, NoteIssueType, TrackIssueType } from 'scan-chart'
import { DownloadService } from 'src-angular/app/core/services/download.service'
import { SearchService } from 'src-angular/app/core/services/search.service'
@@ -68,7 +68,7 @@ export class ChartSidebarComponent implements OnInit {
}
public get albumArtMd5() {
return flatMap(this.charts ?? []).find(c => !!c.albumArtMd5)?.albumArtMd5 || null
return _.flatMap(this.charts ?? []).find(c => !!c.albumArtMd5)?.albumArtMd5 || null
}
public get hasIcons() { return !!this.searchService.availableIcons }
@@ -90,7 +90,7 @@ export class ChartSidebarComponent implements OnInit {
}
public get extraLengthSeconds() {
return round((this.selectedChart!.notesData.length - this.selectedChart!.notesData.effectiveLength) / 1000, 1)
return _.round((this.selectedChart!.notesData.length - this.selectedChart!.notesData.effectiveLength) / 1000, 1)
}
public get hasIssues() {
@@ -116,7 +116,7 @@ export class ChartSidebarComponent implements OnInit {
}
}
public get folderIssues() {
return chain(this.selectedChart!.folderIssues)
return _.chain(this.selectedChart!.folderIssues)
.filter(i => !['albumArtSize', 'invalidIni', 'multipleVideo', 'badIniLine'].includes(i.folderIssue))
.map(i => i.folderIssue)
.uniq()
@@ -157,16 +157,16 @@ export class ChartSidebarComponent implements OnInit {
}
public get trackIssuesGroups() {
return chain([
return _.chain([
...this.selectedChart!.notesData.trackIssues.map(i => ({ ...i, issues: i.trackIssues })),
...this.selectedChart!.notesData.noteIssues.map(i => ({ ...i, issues: i.noteIssues.map(ni => ni.issueType) })),
])
.sortBy(g => instruments.indexOf(g.instrument), g => difficulties.indexOf(g.difficulty))
.groupBy(g => `${capitalize(g.instrument)} - ${capitalize(g.difficulty)} Issues Found:`)
.groupBy(g => `${_.capitalize(g.instrument)} - ${_.capitalize(g.difficulty)} Issues Found:`)
.toPairs()
.map(([groupName, group]) => ({
groupName,
issues: chain(group)
issues: _.chain(group)
.flatMap(g => g.issues)
.filter(i => i !== 'babySustain' && i !== 'noNotesOnNonemptyTrack')
.uniq()
@@ -193,9 +193,9 @@ export class ChartSidebarComponent implements OnInit {
public get boolProperties(): ({ value: boolean; text: string })[] {
const notesData = this.selectedChart!.notesData
const showGuitarlikeProperties = intersection(this.instruments, this.guitarlikeInstruments).length > 0
const showDrumlikeProperties = intersection(this.instruments, ['drums']).length > 0
return compact([
const showGuitarlikeProperties = _.intersection(this.instruments, this.guitarlikeInstruments).length > 0
const showDrumlikeProperties = _.intersection(this.instruments, ['drums']).length > 0
return _.compact([
showGuitarlikeProperties ? { value: notesData.hasSoloSections, text: 'Solo Sections' } : null,
{ value: notesData.hasLyrics, text: 'Lyrics' },
showGuitarlikeProperties ? { value: notesData.hasForcedNotes, text: 'Forced Notes' } : null,
@@ -211,10 +211,10 @@ export class ChartSidebarComponent implements OnInit {
* Displays the information for the selected song.
*/
async onRowClicked(song: ChartData[]) {
this.charts = chain(song)
this.charts = _.chain(song)
.groupBy(c => c.versionGroupId)
.values()
.map(versionGroup => sortBy(versionGroup, vg => vg.modifiedTime).reverse())
.map(versionGroup => _.sortBy(versionGroup, vg => vg.modifiedTime).reverse())
.value()
if (this.selectedChart?.albumArtMd5 !== this.charts[0][0].albumArtMd5) {
this.albumLoading = true
@@ -238,7 +238,7 @@ export class ChartSidebarComponent implements OnInit {
}
public get instruments(): Instrument[] {
if (!this.selectedChart) { return [] }
return chain(this.selectedChart.notesData.noteCounts)
return _.chain(this.selectedChart.notesData.noteCounts)
.map(nc => nc.instrument)
.uniq()
.sortBy(i => instruments.indexOf(i))
@@ -251,7 +251,7 @@ export class ChartSidebarComponent implements OnInit {
}
public get difficulties(): Difficulty[] {
if (!this.selectedChart) { return [] }
return chain(this.selectedChart.notesData.noteCounts)
return _.chain(this.selectedChart.notesData.noteCounts)
.filter(nc => nc.instrument === this.instrumentDropdown.value && nc.count > 0)
.map(nc => nc.difficulty)
.sortBy(d => difficulties.indexOf(d))
@@ -262,7 +262,7 @@ export class ChartSidebarComponent implements OnInit {
if (this.noteCount < 2) {
return 0
} else {
return round(this.noteCount / (this.selectedChart!.notesData.effectiveLength / 1000), 1)
return _.round(this.noteCount / (this.selectedChart!.notesData.effectiveLength / 1000), 1)
}
}

View File

@@ -1,7 +1,7 @@
import { Component, ElementRef, EventEmitter, HostBinding, HostListener, OnInit, Output, QueryList, ViewChild, ViewChildren } from '@angular/core'
import { Router } from '@angular/router'
import { sortBy } from 'lodash'
import _ from 'lodash'
import { SettingsService } from 'src-angular/app/core/services/settings.service'
import { ChartData } from 'src-shared/interfaces/search.interface'
@@ -74,7 +74,7 @@ export class ResultTableComponent implements OnInit {
private updateSort() {
const col = this.sortColumn
if (col !== null) {
const groupedSongs = sortBy(this.searchService.groupedSongs, song => song[0][col]?.toLowerCase())
const groupedSongs = _.sortBy(this.searchService.groupedSongs, song => song[0][col]?.toLowerCase())
if (this.sortDirection === 'descending') { groupedSongs.reverse() }
this.searchService.groupedSongs = groupedSongs
}

View File

@@ -1,6 +1,6 @@
import { Component, ElementRef, ViewChild } from '@angular/core'
import { keys, pickBy } from 'lodash'
import _ from 'lodash'
import { DownloadService } from '../../../core/services/download.service'
import { SearchService } from '../../../core/services/search.service'
@@ -27,7 +27,7 @@ export class StatusBarComponent {
}
get selectedGroupIds() {
return keys(pickBy(this.selectionService.selections)).map(k => Number(k))
return _.keys(_.pickBy(this.selectionService.selections)).map(k => Number(k))
}
async downloadSelected() {

View File

@@ -1,7 +1,7 @@
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'
import { FormControl } from '@angular/forms'
import { capitalize } from 'lodash'
import _ from 'lodash'
import { SettingsService } from 'src-angular/app/core/services/settings.service'
import { themes } from 'src-shared/Settings'
@@ -91,7 +91,7 @@ export class SettingsComponent implements OnInit {
}
capitalize(text: string) {
return capitalize(text)
return _.capitalize(text)
}
downloadUpdate() {

View File

@@ -1,6 +1,6 @@
import { EventEmitter, Injectable, NgZone } from '@angular/core'
import { assign } from 'lodash'
import _ from 'lodash'
import { ChartData } from 'src-shared/interfaces/search.interface'
import { removeStyleTags } from 'src-shared/UtilFunctions'
@@ -22,7 +22,7 @@ export class DownloadService {
} else if (downloadIndex === -1) {
this.downloads.push(download)
} else {
assign(this.downloads[downloadIndex], download)
_.assign(this.downloads[downloadIndex], download)
}
}))
}

View File

@@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'
import { EventEmitter, Injectable } from '@angular/core'
import { FormControl } from '@angular/forms'
import { chain, xorBy } from 'lodash'
import _ from 'lodash'
import { catchError, mergeMap, tap, throwError, timer } from 'rxjs'
import { Difficulty, Instrument } from 'scan-chart'
import { environment } from 'src-angular/environments/environment'
@@ -103,7 +103,7 @@ export class SearchService {
if (!nextPage) {
// Don't reload results if they are the same
if (this.groupedSongs
&& xorBy(this.songsResponse!.data, response.data, r => r.chartId).length === 0
&& _.xorBy(this.songsResponse!.data, response.data, r => r.chartId).length === 0
&& this.songsResponse!.found === response.found) {
return
} else {
@@ -113,7 +113,7 @@ export class SearchService {
this.songsResponse = response
this.groupedSongs.push(
...chain(response.data)
..._.chain(response.data)
.groupBy(c => c.songId ?? -1 * c.chartId)
.values()
.value()
@@ -148,7 +148,7 @@ export class SearchService {
// Don't reload results if they are the same
if (this.groupedSongs
&& xorBy(this.songsResponse!.data, response.data, r => r.chartId).length === 0
&& _.xorBy(this.songsResponse!.data, response.data, r => r.chartId).length === 0
&& this.songsResponse!.found === response.found) {
return
} else {
@@ -158,7 +158,7 @@ export class SearchService {
this.songsResponse = response
this.groupedSongs.push(
...chain(response.data)
..._.chain(response.data)
.groupBy(c => c.songId ?? -1 * c.chartId)
.values()
.value()

View File

@@ -7,6 +7,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body class="scrollbar">
<app-root />
<app-root></app-root>
</body>
</html>

View File

@@ -3,9 +3,9 @@ import { basename, parse } from 'path'
import sanitize from 'sanitize-filename'
import { inspect } from 'util'
import { lower } from '../src-shared/UtilFunctions'
import { settings } from './ipc/SettingsHandler.ipc'
import { emitIpcEvent } from './main'
import { lower } from '../src-shared/UtilFunctions.js'
import { settings } from './ipc/SettingsHandler.ipc.js'
import { emitIpcEvent } from './main.js'
/**
* @returns The relative filepath from the library folder to `absoluteFilepath`.

View File

@@ -1,8 +1,8 @@
import { IpcInvokeHandlers, IpcToMainEmitHandlers } from '../src-shared/interfaces/ipc.interface'
import { download } from './ipc/DownloadHandler.ipc'
import { getSettings, setSettings } from './ipc/SettingsHandler.ipc'
import { downloadUpdate, getCurrentVersion, getUpdateAvailable, quitAndInstall, retryUpdate } from './ipc/UpdateHandler.ipc'
import { isMaximized, maximize, minimize, openUrl, quit, restore, showFile, showFolder, showOpenDialog, toggleDevTools } from './ipc/UtilHandlers.ipc'
import { IpcInvokeHandlers, IpcToMainEmitHandlers } from '../src-shared/interfaces/ipc.interface.js'
import { download } from './ipc/DownloadHandler.ipc.js'
import { getSettings, setSettings } from './ipc/SettingsHandler.ipc.js'
import { downloadUpdate, getCurrentVersion, getUpdateAvailable, quitAndInstall, retryUpdate } from './ipc/UpdateHandler.ipc.js'
import { isMaximized, maximize, minimize, openUrl, quit, restore, showFile, showFolder, showOpenDialog, toggleDevTools } from './ipc/UtilHandlers.ipc.js'
export function getIpcInvokeHandlers(): IpcInvokeHandlers {
return {

View File

@@ -1,5 +1,5 @@
import { Download } from '../../src-shared/interfaces/download.interface'
import { DownloadQueue } from './download/DownloadQueue'
import { Download } from '../../src-shared/interfaces/download.interface.js'
import { DownloadQueue } from './download/DownloadQueue.js'
const downloadQueue: DownloadQueue = new DownloadQueue()

View File

@@ -1,26 +1,25 @@
import { readFileSync } from 'fs'
import { writeFile } from 'fs/promises'
import { cloneDeep } from 'lodash'
import _ from 'lodash'
import { mkdirp } from 'mkdirp'
import { inspect } from 'util'
import { dataPath, settingsPath, tempPath, themesPath } from '../../src-shared/Paths'
import { defaultSettings, Settings } from '../../src-shared/Settings'
import { devLog } from '../ElectronUtilFunctions'
import { dataPath, settingsPath, tempPath, themesPath } from '../../src-shared/Paths.js'
import { defaultSettings, Settings } from '../../src-shared/Settings.js'
export let settings = readSettings()
function readSettings() {
try {
const settings = JSON.parse(readFileSync(settingsPath, 'utf8')) as Partial<Settings>
return Object.assign(cloneDeep(defaultSettings), settings)
return Object.assign(_.cloneDeep(defaultSettings), settings)
} catch (err) {
if (err?.code === 'ENOENT') {
saveSettings(cloneDeep(defaultSettings))
saveSettings(_.cloneDeep(defaultSettings))
} else {
devLog('Failed to load settings. Default settings will be used.\n' + inspect(err))
console.error('Failed to load settings. Default settings will be used.\n' + inspect(err))
}
return cloneDeep(defaultSettings)
return _.cloneDeep(defaultSettings)
}
}
@@ -51,6 +50,6 @@ async function saveSettings(settings: Settings) {
await writeFile(settingsPath, JSON.stringify(settings, undefined, 2), 'utf8')
} catch (err) {
devLog('Failed to save settings.\n' + inspect(err))
console.error('Failed to save settings.\n' + inspect(err))
}
}

View File

@@ -1,26 +1,26 @@
import { autoUpdater, UpdateInfo } from 'electron-updater'
import electronUpdater from 'electron-updater'
import { inspect } from 'util'
import { UpdateProgress } from '../../src-shared/interfaces/update.interface'
import { emitIpcEvent } from '../main'
import { UpdateProgress } from '../../src-shared/interfaces/update.interface.js'
import { emitIpcEvent } from '../main.js'
let updateAvailable: boolean | null = false
let downloading = false
autoUpdater.autoDownload = false
autoUpdater.logger = null
electronUpdater.autoUpdater.autoDownload = false
electronUpdater.autoUpdater.logger = null
autoUpdater.on('error', (err: Error) => {
electronUpdater.autoUpdater.on('error', (err: Error) => {
updateAvailable = null
emitIpcEvent('updateError', inspect(err))
})
autoUpdater.on('update-available', (info: UpdateInfo) => {
electronUpdater.autoUpdater.on('update-available', (info: electronUpdater.UpdateInfo) => {
updateAvailable = true
emitIpcEvent('updateAvailable', info)
})
autoUpdater.on('update-not-available', () => {
electronUpdater.autoUpdater.on('update-not-available', () => {
updateAvailable = false
emitIpcEvent('updateAvailable', null)
})
@@ -28,7 +28,7 @@ autoUpdater.on('update-not-available', () => {
export async function retryUpdate() {
try {
await autoUpdater.checkForUpdates()
await electronUpdater.autoUpdater.checkForUpdates()
} catch (err) {
updateAvailable = null
emitIpcEvent('updateError', inspect(err))
@@ -43,7 +43,7 @@ export async function getUpdateAvailable() {
* @returns the current version of Bridge.
*/
export async function getCurrentVersion() {
return autoUpdater.currentVersion.raw
return electronUpdater.autoUpdater.currentVersion.raw
}
/**
@@ -53,20 +53,20 @@ export function downloadUpdate() {
if (downloading) { return }
downloading = true
autoUpdater.on('download-progress', (updateProgress: UpdateProgress) => {
electronUpdater.autoUpdater.on('download-progress', (updateProgress: UpdateProgress) => {
emitIpcEvent('updateProgress', updateProgress)
})
autoUpdater.on('update-downloaded', () => {
electronUpdater.autoUpdater.on('update-downloaded', () => {
emitIpcEvent('updateDownloaded', undefined)
})
autoUpdater.downloadUpdate()
electronUpdater.autoUpdater.downloadUpdate()
}
/**
* Immediately closes the application and installs the update.
*/
export function quitAndInstall() {
autoUpdater.quitAndInstall() // autoUpdater installs a downloaded update on the next program restart by default
electronUpdater.autoUpdater.quitAndInstall() // autoUpdater installs a downloaded update on the next program restart by default
}

View File

@@ -1,6 +1,6 @@
import { app, dialog, OpenDialogOptions, shell } from 'electron'
import { mainWindow } from '../main'
import { mainWindow } from '../main.js'
/**
* Opens `url` in the default browser.

View File

@@ -2,7 +2,9 @@ import { randomUUID } from 'crypto'
import EventEmitter from 'events'
import { createWriteStream } from 'fs'
import { access, constants } from 'fs/promises'
import { round, throttle } from 'lodash'
import { IncomingMessage } from 'http'
import https from 'https'
import _ from 'lodash'
import { mkdirp } from 'mkdirp'
import mv from 'mv'
import { SngStream } from 'parse-sng'
@@ -10,12 +12,12 @@ import { join } from 'path'
import { rimraf } from 'rimraf'
import { Readable } from 'stream'
import { ReadableStream } from 'stream/web'
import { Agent, fetch, setGlobalDispatcher } from 'undici'
import { Agent, setGlobalDispatcher } from 'undici'
import { inspect } from 'util'
import { tempPath } from '../../../src-shared/Paths'
import { sanitizeFilename } from '../../ElectronUtilFunctions'
import { getSettings } from '../SettingsHandler.ipc'
import { tempPath } from '../../../src-shared/Paths.js'
import { sanitizeFilename } from '../../ElectronUtilFunctions.js'
import { getSettings } from '../SettingsHandler.ipc.js'
setGlobalDispatcher(new Agent({ connect: { timeout: 60_000 } }))
@@ -64,7 +66,7 @@ export class ChartDownload {
private destinationName: string
private isSng: boolean
private showProgress = throttle((description: string, percent: number | null = null) => {
private showProgress = _.throttle((description: string, percent: number | null = null) => {
this.eventEmitter.emit('progress', { header: description, body: '' }, percent)
}, 10, { leading: true, trailing: true })
@@ -148,36 +150,34 @@ export class ChartDownload {
}
private async downloadChart() {
const sngResponse = await fetch(`https://files.enchor.us/${this.md5}.sng`, { mode: 'cors', referrerPolicy: 'no-referrer' })
if (!sngResponse.ok || !sngResponse.body) {
throw { header: 'Failed to download the chart file', body: `Response code ${sngResponse.status}: ${sngResponse.statusText}` }
}
const fileSize = BigInt(sngResponse.headers.get('Content-Length')!)
const { response, abortController } = await getDownloadStream(this.md5)
const fileSize = BigInt(response.headers['content-length']!)
if (this.isSng) {
const sngStream = Readable.fromWeb(sngResponse.body as ReadableStream<Uint8Array>, { highWaterMark: 2e+9 })
sngStream.pipe(createWriteStream(join(this.tempPath, this.destinationName), { highWaterMark: 2e+9 }))
response.pipe(createWriteStream(join(this.tempPath, this.destinationName), { highWaterMark: 2e+9 }))
await new Promise<void>((resolve, reject) => {
let downloadedByteCount = BigInt(0)
sngStream.on('end', resolve)
sngStream.on('error', err => reject(err))
sngStream.on('data', data => {
response.on('end', resolve)
response.on('error', err => reject(err))
response.on('data', data => {
downloadedByteCount += BigInt(data.length)
const downloadPercent = round(100 * Number(downloadedByteCount / BigInt(1000)) / Number(fileSize / BigInt(1000)), 1)
const downloadPercent = _.round(100 * Number(downloadedByteCount / BigInt(1000)) / Number(fileSize / BigInt(1000)), 1)
this.showProgress(`Downloading... (${downloadPercent}%)`, downloadPercent)
if (this._canceled) {
abortController.abort()
}
})
})
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sngStream = new SngStream(() => sngResponse.body! as any, { generateSongIni: true })
const sngStream = new SngStream(Readable.toWeb(response) as any, { generateSongIni: true })
let downloadedByteCount = BigInt(0)
await mkdirp(join(this.tempPath, this.destinationName))
await new Promise<void>((resolve, reject) => {
sngStream.on('file', async (fileName, fileStream) => {
sngStream.on('file', async (fileName, fileStream, nextFile) => {
const nodeFileStream = Readable.fromWeb(fileStream as ReadableStream<Uint8Array>, { highWaterMark: 2e+9 })
nodeFileStream.pipe(createWriteStream(join(this.tempPath, this.destinationName, fileName), { highWaterMark: 2e+9 }))
@@ -185,15 +185,32 @@ export class ChartDownload {
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)
if (this._canceled) {
abortController.abort()
} else {
downloadedByteCount += BigInt(data.length)
const downloadPercent =
_.round(100 * Number(downloadedByteCount / BigInt(1000)) / Number(fileSize / BigInt(1000)), 1)
this.showProgress(`Downloading "${fileName}"... (${downloadPercent}%)`, downloadPercent)
}
})
})
if (nextFile) {
nextFile()
} else {
resolve()
}
})
sngStream.on('error', err => {
if (err instanceof Error && err.message === 'aborted') {
// Errors from cancelling downloads are intentional
resolve()
} else {
reject(err)
}
})
sngStream.on('end', resolve)
sngStream.on('error', err => reject(err))
sngStream.start()
})
@@ -232,3 +249,30 @@ export class ChartDownload {
this.eventEmitter.emit('end', destinationPath)
}
}
function getDownloadStream(md5: string): Promise<{ response: IncomingMessage; abortController: AbortController }> {
const abortController = new AbortController()
return new Promise((resolve, reject) => {
const request = https.get(`https://files.enchor.us/${md5}.sng`, {
headers: {
'mode': 'cors',
// eslint-disable-next-line @typescript-eslint/naming-convention
'referrer-policy': 'no-referrer',
},
timeout: 20000,
signal: abortController.signal,
})
request.on('response', response => {
if (response.statusCode !== 200 || !response.headers['content-length']) {
reject({
header: 'Failed to download the chart file',
body: `Response code ${response.statusCode}: ${response.statusMessage}`,
})
return
}
resolve({ response, abortController })
})
})
}

View File

@@ -1,5 +1,5 @@
import { emitIpcEvent } from '../../main'
import { ChartDownload } from './ChartDownload'
import { emitIpcEvent } from '../../main.js'
import { ChartDownload } from './ChartDownload.js'
export class DownloadQueue {

View File

@@ -4,13 +4,18 @@ import windowStateKeeper from 'electron-window-state'
import * as path from 'path'
import * as url from 'url'
import { IpcFromMainEmitEvents } from '../src-shared/interfaces/ipc.interface'
import { dataPath } from '../src-shared/Paths'
import { retryUpdate } from './ipc/UpdateHandler.ipc'
import { getIpcInvokeHandlers, getIpcToMainEmitHandlers } from './IpcHandler'
import { IpcFromMainEmitEvents } from '../src-shared/interfaces/ipc.interface.js'
import { dataPath } from '../src-shared/Paths.js'
import { retryUpdate } from './ipc/UpdateHandler.ipc.js'
import { getIpcInvokeHandlers, getIpcToMainEmitHandlers } from './IpcHandler.js'
electronUnhandled({ showDialog: true, logger: err => console.log('Error: Unhandled Rejection:', err) })
import { dirname } from 'path'
const _filename = url.fileURLToPath(import.meta.url)
const _dirname = dirname(_filename)
export let mainWindow: BrowserWindow
const args = process.argv.slice(1)
const isDevBuild = args.some(val => val === '--dev')
@@ -119,7 +124,7 @@ function createBrowserWindow(windowState: windowStateKeeper.State) {
frame: false,
title: 'Bridge',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
preload: path.join(_dirname, 'preload.mjs'),
allowRunningInsecureContent: (isDevBuild) ? true : false,
textAreasAreResizable: false,
},
@@ -129,7 +134,7 @@ function createBrowserWindow(windowState: windowStateKeeper.State) {
}
if (process.platform === 'linux' && !isDevBuild) {
options = Object.assign(options, { icon: path.join(__dirname, '..', 'assets', 'images', 'system', 'icons', 'png', '48x48.png') })
options = Object.assign(options, { icon: path.join(_dirname, '..', 'assets', 'images', 'system', 'icons', 'png', '48x48.png') })
}
return new BrowserWindow(options)
@@ -151,7 +156,7 @@ async function loadWindow(retries = 0) {
function getLoadUrl() {
return url.format({
protocol: isDevBuild ? 'http:' : 'file:',
pathname: isDevBuild ? '//localhost:4200/' : path.join(__dirname, '..', '..', 'angular', 'index.html'),
pathname: isDevBuild ? '//localhost:4200/' : path.join(_dirname, '..', '..', 'angular', 'index.html'),
slashes: true,
})
}

View File

@@ -1,18 +1,18 @@
import { contextBridge, ipcRenderer } from 'electron'
import electron from 'electron'
import { ContextBridgeApi, IpcFromMainEmitEvents, IpcInvokeEvents, IpcToMainEmitEvents } from '../src-shared/interfaces/ipc.interface'
import { ContextBridgeApi, IpcFromMainEmitEvents, IpcInvokeEvents, IpcToMainEmitEvents } from '../src-shared/interfaces/ipc.interface.js'
function getInvoker<K extends keyof IpcInvokeEvents>(key: K) {
return (data: IpcInvokeEvents[K]['input']) => ipcRenderer.invoke(key, data) as Promise<IpcInvokeEvents[K]['output']>
return (data: IpcInvokeEvents[K]['input']) => electron.ipcRenderer.invoke(key, data) as Promise<IpcInvokeEvents[K]['output']>
}
function getEmitter<K extends keyof IpcToMainEmitEvents>(key: K) {
return (data: IpcToMainEmitEvents[K]) => ipcRenderer.send(key, data)
return (data: IpcToMainEmitEvents[K]) => electron.ipcRenderer.send(key, data)
}
function getListenerAdder<K extends keyof IpcFromMainEmitEvents>(key: K) {
return (listener: (data: IpcFromMainEmitEvents[K]) => void) => {
ipcRenderer.on(key, (_event, ...results) => listener(results[0]))
electron.ipcRenderer.on(key, (_event, ...results) => listener(results[0]))
}
}
@@ -52,4 +52,4 @@ const electronApi: ContextBridgeApi = {
},
}
contextBridge.exposeInMainWorld('electron', electronApi)
electron.contextBridge.exposeInMainWorld('electron', electronApi)

View File

@@ -0,0 +1,8 @@
import fs from 'fs'
const filePath = './dist/electron/src-electron/preload.js'
const newFilePath = './dist/electron/src-electron/preload.mjs'
if (fs.existsSync(filePath)) {
fs.renameSync(filePath, newFilePath)
}

View File

@@ -8,8 +8,8 @@
"./**/*.ts"
],
"compilerOptions": {
"target": "ES5",
"module": "CommonJS",
"target": "ESNext",
"module": "ESNext",
"outDir": "../dist/electron"
}
}