mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-11 22:29:38 +00:00
ESM Conversion, fix downloads
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
8
src-electron/rename-to-mjs.js
Normal file
8
src-electron/rename-to-mjs.js
Normal 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)
|
||||
}
|
||||
@@ -8,8 +8,8 @@
|
||||
"./**/*.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"module": "CommonJS",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"outDir": "../dist/electron"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user