Fix compiler issues

This commit is contained in:
Geomitron
2023-11-27 18:49:30 -06:00
parent cddec0d9d4
commit 558d76f582
16 changed files with 580 additions and 566 deletions

View File

@@ -1,12 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@@ -15,8 +15,8 @@
"main": "dist/electron/main.js",
"private": true,
"scripts": {
"start": "run-p serve:angular serve:electron",
"serve:electron": "wait-on http-get://localhost:4200/ && nodemon --exec \"tsc -p tsconfig.electron.json && electron . --dev\" --watch src/electron -e ts",
"start": "concurrently \"npm run serve:angular\" \"npm run serve:electron\" -n angular,electron -c red,yellow",
"serve:electron": "nodemon --exec \"tsc -p tsconfig.electron.json && electron . --dev\" --watch src/electron -e ts",
"lint": "ng lint",
"clean": "rimraf dist release",
"build:windows": "ng build -c production && tsc -p tsconfig.electron.json && electron-builder build --windows",
@@ -44,6 +44,7 @@
"fomantic-ui": "^2.8.3",
"jquery": "^3.5.1",
"jsonfile": "^6.0.1",
"lodash": "^4.17.21",
"mv": "^2.1.1",
"randombytes": "^2.1.0",
"rimraf": "^5.0.5",
@@ -64,6 +65,7 @@
"@angular/language-service": "^17.0.4",
"@types/cli-color": "^2.0.0",
"@types/jsonfile": "^6.0.0",
"@types/lodash": "^4.14.202",
"@types/mv": "^2.1.0",
"@types/node": "^18.16.0",
"@types/randombytes": "^2.0.0",
@@ -87,4 +89,4 @@
"tsx": "^4.4.0",
"typescript": "^5.2.2"
}
}
}

10
pnpm-lock.yaml generated
View File

@@ -59,6 +59,9 @@ dependencies:
jsonfile:
specifier: ^6.0.1
version: 6.1.0
lodash:
specifier: ^4.17.21
version: 4.17.21
mv:
specifier: ^2.1.1
version: 2.1.1
@@ -115,6 +118,9 @@ devDependencies:
'@types/jsonfile':
specifier: ^6.0.0
version: 6.1.4
'@types/lodash':
specifier: ^4.14.202
version: 4.14.202
'@types/mv':
specifier: ^2.1.0
version: 2.1.4
@@ -3294,6 +3300,10 @@ packages:
dependencies:
'@types/node': 14.18.63
/@types/lodash@4.14.202:
resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==}
dev: true
/@types/mime@1.3.5:
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
dev: true

View File

@@ -173,7 +173,7 @@ export class ChartSidebarComponent implements OnInit {
* @returns a string describing the difficulty number in the selected version.
*/
private getDiffNumber(instrument: Instrument) {
const diffNumber: number = this.selectedVersion[`diff_${instrument}`]
const diffNumber: number = (this.selectedVersion as any)[`diff_${instrument}`]
return diffNumber == -1 || diffNumber == undefined ? 'Unknown' : String(diffNumber)
}

View File

@@ -1,12 +1,15 @@
import { Directive, ElementRef, Input } from '@angular/core'
import * as _ from 'underscore'
import * as _ from 'lodash'
@Directive({
selector: '[appProgressBar]',
})
export class ProgressBarDirective {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _$progressBar: any
progress: (percent: number) => void
@Input() set percent(percent: number) {
@@ -23,5 +26,4 @@ export class ProgressBarDirective {
}
return this._$progressBar
}
private _$progressBar: any
}

View File

@@ -7,10 +7,10 @@ import { ElectronService } from './electron.service'
})
export class AlbumArtService {
constructor(private electronService: ElectronService) { }
private imageCache: { [songID: number]: string } = {}
constructor(private electronService: ElectronService) { }
async getImage(songID: number): Promise<string | null> {
if (this.imageCache[songID] == undefined) {
const albumArtResult = await this.electronService.invoke('album-art', songID)

View File

@@ -5,11 +5,6 @@ import { SearchService } from './search.service'
// Note: this class prevents event cycles by only emitting events if the checkbox changes
interface SelectionEvent {
songID: number
selected: boolean
}
@Injectable({
providedIn: 'root',
})

View File

@@ -1,14 +1,13 @@
import { join, parse } from 'path'
import { parse } from 'path'
import { rimraf } from 'rimraf'
import { NewDownload, ProgressType } from 'src/electron/shared/interfaces/download.interface'
import { DriveFile } from 'src/electron/shared/interfaces/songDetails.interface'
import { emitIPCEvent } from '../../main'
import { hasVideoExtension } from '../../shared/ElectronUtilFunctions'
import { interpolate, sanitizeFilename } from '../../shared/UtilFunctions'
import { sanitizeFilename } from '../../shared/UtilFunctions'
import { getSettings } from '../SettingsHandler.ipc'
import { FileDownloader, getDownloader } from './FileDownloader'
import { FileExtractor } from './FileExtractor'
// import { FileDownloader, getDownloader } from './FileDownloader'
import { FilesystemChecker } from './FilesystemChecker'
import { FileTransfer } from './FileTransfer'
@@ -143,23 +142,23 @@ export class ChartDownload {
for (let i = 0; i < this.files.length; i++) {
let wasCanceled = false
this.cancelFn = () => { wasCanceled = true }
const downloader = getDownloader(this.files[i].webContentLink, join(this.tempPath, this.files[i].name))
// const downloader = getDownloader(this.files[i].webContentLink, join(this.tempPath, this.files[i].name))
if (wasCanceled) { return }
this.cancelFn = () => downloader.cancelDownload()
// this.cancelFn = () => downloader.cancelDownload()
const downloadComplete = this.addDownloadEventListeners(downloader, i)
downloader.beginDownload()
await downloadComplete
// const downloadComplete = this.addDownloadEventListeners(downloader, i)
// downloader.beginDownload()
// await downloadComplete
}
// EXTRACT FILES
if (this.isArchive) {
const extractor = new FileExtractor(this.tempPath)
this.cancelFn = () => extractor.cancelExtract()
// const extractor = new FileExtractor(this.tempPath)
// this.cancelFn = () => extractor.cancelExtract()
const extractComplete = this.addExtractorEventListeners(extractor)
extractor.beginExtract()
await extractComplete
// const extractComplete = this.addExtractorEventListeners(extractor)
// extractor.beginExtract()
// await extractComplete
}
// TRANSFER FILES
@@ -196,68 +195,68 @@ export class ChartDownload {
* Defines what happens in response to `FileDownloader` events.
* @returns a `Promise` that resolves when the download finishes.
*/
private addDownloadEventListeners(downloader: FileDownloader, fileIndex: number) {
let downloadHeader = `[${this.files[fileIndex].name}] (file ${fileIndex + 1}/${this.files.length})`
let downloadStartPoint = 0 // How far into the individual file progress portion the download progress starts
let fileProgress = 0
// private addDownloadEventListeners(downloader: FileDownloader, fileIndex: number) {
// let downloadHeader = `[${this.files[fileIndex].name}] (file ${fileIndex + 1}/${this.files.length})`
// let downloadStartPoint = 0 // How far into the individual file progress portion the download progress starts
// let fileProgress = 0
downloader.on('waitProgress', (remainingSeconds: number, totalSeconds: number) => {
downloadStartPoint = this.individualFileProgressPortion / 2
this.percent =
this._allFilesProgress + interpolate(remainingSeconds, totalSeconds, 0, 0, this.individualFileProgressPortion / 2)
this.updateGUI(downloadHeader, `Waiting for Google rate limit... (${remainingSeconds}s)`, 'good')
})
// downloader.on('waitProgress', (remainingSeconds: number, totalSeconds: number) => {
// downloadStartPoint = this.individualFileProgressPortion / 2
// this.percent =
// this._allFilesProgress + interpolate(remainingSeconds, totalSeconds, 0, 0, this.individualFileProgressPortion / 2)
// this.updateGUI(downloadHeader, `Waiting for Google rate limit... (${remainingSeconds}s)`, 'good')
// })
downloader.on('requestSent', () => {
fileProgress = downloadStartPoint
this.percent = this._allFilesProgress + fileProgress
this.updateGUI(downloadHeader, 'Sending request...', 'good')
})
// downloader.on('requestSent', () => {
// fileProgress = downloadStartPoint
// this.percent = this._allFilesProgress + fileProgress
// this.updateGUI(downloadHeader, 'Sending request...', 'good')
// })
downloader.on('downloadProgress', (bytesDownloaded: number) => {
downloadHeader = `[${this.files[fileIndex].name}] (file ${fileIndex + 1}/${this.files.length})`
const size = Number(this.files[fileIndex].size)
fileProgress = interpolate(bytesDownloaded, 0, size, downloadStartPoint, this.individualFileProgressPortion)
this.percent = this._allFilesProgress + fileProgress
this.updateGUI(downloadHeader, `Downloading... (${Math.round(1000 * bytesDownloaded / size) / 10}%)`, 'good')
})
// downloader.on('downloadProgress', (bytesDownloaded: number) => {
// downloadHeader = `[${this.files[fileIndex].name}] (file ${fileIndex + 1}/${this.files.length})`
// const size = Number(this.files[fileIndex].size)
// fileProgress = interpolate(bytesDownloaded, 0, size, downloadStartPoint, this.individualFileProgressPortion)
// this.percent = this._allFilesProgress + fileProgress
// this.updateGUI(downloadHeader, `Downloading... (${Math.round(1000 * bytesDownloaded / size) / 10}%)`, 'good')
// })
downloader.on('error', this.handleError.bind(this))
// downloader.on('error', this.handleError.bind(this))
return new Promise<void>(resolve => {
downloader.on('complete', () => {
this._allFilesProgress += this.individualFileProgressPortion
resolve()
})
})
}
// return new Promise<void>(resolve => {
// downloader.on('complete', () => {
// this._allFilesProgress += this.individualFileProgressPortion
// resolve()
// })
// })
// }
/**
* Defines what happens in response to `FileExtractor` events.
* @returns a `Promise` that resolves when the extraction finishes.
*/
private addExtractorEventListeners(extractor: FileExtractor) {
let archive = ''
// private addExtractorEventListeners(extractor: FileExtractor) {
// let archive = ''
extractor.on('start', filename => {
archive = filename
this.updateGUI(`[${archive}]`, 'Extracting...', 'good')
})
// extractor.on('start', filename => {
// archive = filename
// this.updateGUI(`[${archive}]`, 'Extracting...', 'good')
// })
extractor.on('extractProgress', (percent, filecount) => {
this.percent = interpolate(percent, 0, 100, 80, 95)
this.updateGUI(`[${archive}] (${filecount} file${filecount == 1 ? '' : 's'} extracted)`, `Extracting... (${percent}%)`, 'good')
})
// extractor.on('extractProgress', (percent, filecount) => {
// this.percent = interpolate(percent, 0, 100, 80, 95)
// this.updateGUI(`[${archive}] (${filecount} file${filecount == 1 ? '' : 's'} extracted)`, `Extracting... (${percent}%)`, 'good')
// })
extractor.on('error', this.handleError.bind(this))
// extractor.on('error', this.handleError.bind(this))
return new Promise<void>(resolve => {
extractor.on('complete', () => {
this.percent = 95
resolve()
})
})
}
// return new Promise<void>(resolve => {
// extractor.on('complete', () => {
// this.percent = 95
// resolve()
// })
// })
// }
/**
* Defines what happens in response to `FileTransfer` events.

View File

@@ -1,368 +1,368 @@
import Bottleneck from 'bottleneck'
import { createWriteStream, writeFile as _writeFile } from 'fs'
import { google } from 'googleapis'
import * as needle from 'needle'
import { join } from 'path'
import { Readable } from 'stream'
import { inspect, promisify } from 'util'
// import Bottleneck from 'bottleneck'
// import { createWriteStream, writeFile as _writeFile } from 'fs'
// import { google } from 'googleapis'
// import * as needle from 'needle'
// import { join } from 'path'
// import { Readable } from 'stream'
// import { inspect, promisify } from 'util'
import { devLog } from '../../shared/ElectronUtilFunctions'
import { tempPath } from '../../shared/Paths'
import { AnyFunction } from '../../shared/UtilFunctions'
import { DownloadError } from './ChartDownload'
// TODO: replace needle with got (for cancel() method) (if before-headers event is possible?)
import { googleTimer } from './GoogleTimer'
// import { devLog } from '../../shared/ElectronUtilFunctions'
// import { tempPath } from '../../shared/Paths'
// import { AnyFunction } from '../../shared/UtilFunctions'
// import { DownloadError } from './ChartDownload'
// // TODO: replace needle with got (for cancel() method) (if before-headers event is possible?)
// import { googleTimer } from './GoogleTimer'
const drive = google.drive('v3')
const limiter = new Bottleneck({
minTime: 200, // Wait 200 ms between API requests
})
// const drive = google.drive('v3')
// const limiter = new Bottleneck({
// minTime: 200, // Wait 200 ms between API requests
// })
const RETRY_MAX = 2
const writeFile = promisify(_writeFile)
// const RETRY_MAX = 2
// const writeFile = promisify(_writeFile)
interface EventCallback {
'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
/** Note: this event can be called multiple times if the connection times out or a large file is downloaded */
'requestSent': () => void
'downloadProgress': (bytesDownloaded: number) => void
/** Note: after calling retry, the event lifecycle restarts */
'error': (err: DownloadError, retry: () => void) => void
'complete': () => void
}
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
export type FileDownloader = APIFileDownloader | SlowFileDownloader
// interface EventCallback {
// 'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
// /** Note: this event can be called multiple times if the connection times out or a large file is downloaded */
// 'requestSent': () => void
// 'downloadProgress': (bytesDownloaded: number) => void
// /** Note: after calling retry, the event lifecycle restarts */
// 'error': (err: DownloadError, retry: () => void) => void
// 'complete': () => void
// }
// type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
// export type FileDownloader = APIFileDownloader | SlowFileDownloader
const downloadErrors = {
timeout: (type: string) => { return { header: 'Timeout', body: `The download server could not be reached. (type=${type})` } },
connectionError: (err: Error) => { return { header: 'Connection Error', body: `${err.name}: ${err.message}` } },
responseError: (statusCode: string) => { return { header: 'Connection failed', body: `Server returned status code: ${statusCode}` } },
htmlError: (path: string) => { return { header: 'Download server returned HTML instead of a file.', body: path, isLink: true } },
linkError: (url: string) => { return { header: 'Invalid link', body: `The download link is not formatted correctly: ${url}` } },
}
// const downloadErrors = {
// timeout: (type: string) => { return { header: 'Timeout', body: `The download server could not be reached. (type=${type})` } },
// connectionError: (err: Error) => { return { header: 'Connection Error', body: `${err.name}: ${err.message}` } },
// responseError: (statusCode: string) => { return { header: 'Connection failed', body: `Server returned status code: ${statusCode}` } },
// htmlError: (path: string) => { return { header: 'Download server returned HTML instead of a file.', body: path, isLink: true } },
// linkError: (url: string) => { return { header: 'Invalid link', body: `The download link is not formatted correctly: ${url}` } },
// }
/**
* Downloads a file from `url` to `fullPath`.
* Will handle google drive virus scan warnings. Provides event listeners for download progress.
* On error, provides the ability to retry.
* Will only send download requests once every `getSettings().rateLimitDelay` seconds if a Google account has not been authenticated.
* @param url The download link.
* @param fullPath The full path to where this file should be stored (including the filename).
*/
export function getDownloader(url: string, fullPath: string): FileDownloader {
return new SlowFileDownloader(url, fullPath)
}
// /**
// * Downloads a file from `url` to `fullPath`.
// * Will handle google drive virus scan warnings. Provides event listeners for download progress.
// * On error, provides the ability to retry.
// * Will only send download requests once every `getSettings().rateLimitDelay` seconds.
// */
// class SlowFileDownloader {
/**
* Downloads a file from `url` to `fullPath`.
* On error, provides the ability to retry.
*/
class APIFileDownloader {
private readonly URL_REGEX = /uc\?id=([^&]*)&export=download/u
// private callbacks = {} as Callbacks
// private retryCount: number
// private wasCanceled = false
// private req: NodeJS.ReadableStream
private callbacks = {} as Callbacks
private retryCount: number
private wasCanceled = false
private fileID: string
private downloadStream: Readable
// /**
// * @param url The download link.
// * @param fullPath The full path to where this file should be stored (including the filename).
// */
// constructor(private url: string, private fullPath: string) { }
/**
* @param url The download link.
* @param fullPath The full path to where this file should be stored (including the filename).
*/
constructor(private url: string, private fullPath: string) {
// url looks like: "https://drive.google.com/uc?id=1TlxtOZlVgRgX7-1tyW0d5QzXVfL-MC3Q&export=download"
this.fileID = this.URL_REGEX.exec(url)[1]
}
// /**
// * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
// */
// on<E extends keyof EventCallback>(event: E, callback: EventCallback[E]) {
// this.callbacks[event] = callback
// }
/**
* Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
*/
on<E extends keyof EventCallback>(event: E, callback: EventCallback[E]) {
this.callbacks[event] = callback
}
// /**
// * Download the file after waiting for the google rate limit.
// */
// beginDownload() {
// googleTimer.on('waitProgress', this.cancelable((remainingSeconds, totalSeconds) => {
// this.callbacks.waitProgress(remainingSeconds, totalSeconds)
// }))
/**
* Download the file after waiting for the google rate limit.
*/
beginDownload() {
if (this.fileID == undefined) {
this.failDownload(downloadErrors.linkError(this.url))
}
// googleTimer.on('complete', this.cancelable(() => {
// this.requestDownload()
// }))
// }
this.startDownloadStream()
}
// /**
// * Sends a request to download the file at `this.url`.
// * @param cookieHeader the "cookie=" header to include this request.
// */
// private requestDownload(cookieHeader?: string) {
// this.callbacks.requestSent()
// this.req = needle.get(this.url, {
// 'follow_max': 10,
// 'open_timeout': 5000,
// 'headers': Object.assign({
// 'Referer': this.url,
// 'Accept': '*/*',
// },
// (cookieHeader ? { 'Cookie': cookieHeader } : undefined)
// ),
// })
/**
* Uses the Google Drive API to start a download stream for the file with `this.fileID`.
*/
private startDownloadStream() {
limiter.schedule(this.cancelable(async () => {
this.callbacks.requestSent()
try {
this.downloadStream = (await drive.files.get({
fileId: this.fileID,
alt: 'media',
}, {
responseType: 'stream',
})).data
// this.req.on('timeout', this.cancelable((type: string) => {
// this.retryCount++
// if (this.retryCount <= RETRY_MAX) {
// devLog(`TIMEOUT: Retry attempt ${this.retryCount}...`)
// this.requestDownload(cookieHeader)
// } else {
// this.failDownload(downloadErrors.timeout(type))
// }
// }))
if (this.wasCanceled) { return }
// this.req.on('err', this.cancelable((err: Error) => {
// this.failDownload(downloadErrors.connectionError(err))
// }))
this.handleDownloadResponse()
} catch (err) {
this.retryCount++
if (this.retryCount <= RETRY_MAX) {
devLog(`Failed to get file: Retry attempt ${this.retryCount}...`)
if (this.wasCanceled) { return }
this.startDownloadStream()
} else {
devLog(inspect(err))
if (err?.code && err?.response?.statusText) {
this.failDownload(downloadErrors.responseError(`${err.code} (${err.response.statusText})`))
} else {
this.failDownload(downloadErrors.responseError(err?.code ?? 'unknown'))
}
}
}
}))
}
// this.req.on('header', this.cancelable((statusCode, headers: Headers) => {
// if (statusCode != 200) {
// this.failDownload(downloadErrors.responseError(statusCode))
// return
// }
/**
* Pipes the data from a download response to `this.fullPath`.
* @param req The download request.
*/
private handleDownloadResponse() {
this.callbacks.downloadProgress(0)
let downloadedSize = 0
const writeStream = createWriteStream(this.fullPath)
// if (headers['content-type'].startsWith('text/html')) {
// this.handleHTMLResponse(headers['set-cookie'])
// } else {
// this.handleDownloadResponse()
// }
// }))
// }
try {
this.downloadStream.pipe(writeStream)
} catch (err) {
this.failDownload(downloadErrors.connectionError(err))
}
// /**
// * A Google Drive HTML response to a download request usually means this is the "file too large to scan for viruses" warning.
// * This function sends the request that results from clicking "download anyway", or generates an error if it can't be found.
// * @param cookieHeader The "cookie=" header of this request.
// */
// private handleHTMLResponse(cookieHeader: string) {
// let virusScanHTML = ''
// this.req.on('data', this.cancelable(data => virusScanHTML += data))
// this.req.on('done', this.cancelable((err: Error) => {
// if (err) {
// this.failDownload(downloadErrors.connectionError(err))
// } else {
// try {
// const confirmTokenRegex = /confirm=([0-9A-Za-z\-_]+)&/g
// const confirmTokenResults = confirmTokenRegex.exec(virusScanHTML)
// const confirmToken = confirmTokenResults[1]
// const downloadID = this.url.substr(this.url.indexOf('id=') + 'id='.length)
// this.url = `https://drive.google.com/uc?confirm=${confirmToken}&id=${downloadID}`
// const warningCode = /download_warning_([^=]*)=/.exec(cookieHeader)[1]
// const NID = /NID=([^;]*);/.exec(cookieHeader)[1].replace('=', '%')
// const newHeader = `download_warning_${warningCode}=${confirmToken}; NID=${NID}`
// this.requestDownload(newHeader)
// } catch (e) {
// this.saveHTMLError(virusScanHTML).then(path => {
// this.failDownload(downloadErrors.htmlError(path))
// })
// }
// }
// }))
// }
this.downloadStream.on('data', this.cancelable((chunk: Buffer) => {
downloadedSize += chunk.length
}))
// /**
// * Pipes the data from a download response to `this.fullPath`.
// * @param req The download request.
// */
// private handleDownloadResponse() {
// this.callbacks.downloadProgress(0)
// let downloadedSize = 0
// this.req.pipe(createWriteStream(this.fullPath))
// this.req.on('data', this.cancelable(data => {
// downloadedSize += data.length
// this.callbacks.downloadProgress(downloadedSize)
// }))
const progressUpdater = setInterval(() => {
this.callbacks.downloadProgress(downloadedSize)
}, 100)
// this.req.on('err', this.cancelable((err: Error) => {
// this.failDownload(downloadErrors.connectionError(err))
// }))
this.downloadStream.on('error', this.cancelable((err: Error) => {
clearInterval(progressUpdater)
this.failDownload(downloadErrors.connectionError(err))
}))
// this.req.on('end', this.cancelable(() => {
// this.callbacks.complete()
// }))
// }
this.downloadStream.on('end', this.cancelable(() => {
clearInterval(progressUpdater)
writeStream.end()
this.downloadStream.destroy()
this.downloadStream = null
// private async saveHTMLError(text: string) {
// const errorPath = join(tempPath, 'HTMLError.html')
// await writeFile(errorPath, text)
// return errorPath
// }
this.callbacks.complete()
}))
}
// /**
// * Display an error message and provide a function to retry the download.
// */
// private failDownload(error: DownloadError) {
// this.callbacks.error(error, this.cancelable(() => this.beginDownload()))
// }
/**
* Display an error message and provide a function to retry the download.
*/
private failDownload(error: DownloadError) {
this.callbacks.error(error, this.cancelable(() => this.beginDownload()))
}
// /**
// * Stop the process of downloading the file. (no more events will be fired after this is called)
// */
// cancelDownload() {
// this.wasCanceled = true
// googleTimer.cancelTimer() // Prevents timer from trying to activate a download and resetting
// if (this.req) {
// // TODO: destroy request
// }
// }
/**
* Stop the process of downloading the file. (no more events will be fired after this is called)
*/
cancelDownload() {
this.wasCanceled = true
googleTimer.cancelTimer() // Prevents timer from trying to activate a download and resetting
if (this.downloadStream) {
this.downloadStream.destroy()
}
}
// /**
// * Wraps a function that is able to be prevented if `this.cancelDownload()` was called.
// */
// private cancelable<F extends AnyFunction>(fn: F) {
// return (...args: Parameters<F>): ReturnType<F> => {
// if (this.wasCanceled) { return }
// return fn(...Array.from(args))
// }
// }
// }
/**
* Wraps a function that is able to be prevented if `this.cancelDownload()` was called.
*/
private cancelable<F extends AnyFunction>(fn: F) {
return (...args: Parameters<F>): ReturnType<F> => {
if (this.wasCanceled) { return }
return fn(...Array.from(args))
}
}
}
// /**
// * Downloads a file from `url` to `fullPath`.
// * On error, provides the ability to retry.
// */
// class APIFileDownloader {
// private readonly URL_REGEX = /uc\?id=([^&]*)&export=download/u
/**
* Downloads a file from `url` to `fullPath`.
* Will handle google drive virus scan warnings. Provides event listeners for download progress.
* On error, provides the ability to retry.
* Will only send download requests once every `getSettings().rateLimitDelay` seconds.
*/
class SlowFileDownloader {
// private callbacks = {} as Callbacks
// private retryCount: number
// private wasCanceled = false
// private fileID: string
// private downloadStream: Readable
private callbacks = {} as Callbacks
private retryCount: number
private wasCanceled = false
private req: NodeJS.ReadableStream
// /**
// * @param url The download link.
// * @param fullPath The full path to where this file should be stored (including the filename).
// */
// constructor(private url: string, private fullPath: string) {
// // url looks like: "https://drive.google.com/uc?id=1TlxtOZlVgRgX7-1tyW0d5QzXVfL-MC3Q&export=download"
// this.fileID = this.URL_REGEX.exec(url)[1]
// }
/**
* @param url The download link.
* @param fullPath The full path to where this file should be stored (including the filename).
*/
constructor(private url: string, private fullPath: string) { }
// /**
// * Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
// */
// on<E extends keyof EventCallback>(event: E, callback: EventCallback[E]) {
// this.callbacks[event] = callback
// }
/**
* Calls `callback` when `event` fires. (no events will be fired after `this.cancelDownload()` is called)
*/
on<E extends keyof EventCallback>(event: E, callback: EventCallback[E]) {
this.callbacks[event] = callback
}
// /**
// * Download the file after waiting for the google rate limit.
// */
// beginDownload() {
// if (this.fileID == undefined) {
// this.failDownload(downloadErrors.linkError(this.url))
// }
/**
* Download the file after waiting for the google rate limit.
*/
beginDownload() {
googleTimer.on('waitProgress', this.cancelable((remainingSeconds, totalSeconds) => {
this.callbacks.waitProgress(remainingSeconds, totalSeconds)
}))
// this.startDownloadStream()
// }
googleTimer.on('complete', this.cancelable(() => {
this.requestDownload()
}))
}
// /**
// * Uses the Google Drive API to start a download stream for the file with `this.fileID`.
// */
// private startDownloadStream() {
// limiter.schedule(this.cancelable(async () => {
// this.callbacks.requestSent()
// try {
// this.downloadStream = (await drive.files.get({
// fileId: this.fileID,
// alt: 'media',
// }, {
// responseType: 'stream',
// })).data
/**
* Sends a request to download the file at `this.url`.
* @param cookieHeader the "cookie=" header to include this request.
*/
private requestDownload(cookieHeader?: string) {
this.callbacks.requestSent()
this.req = needle.get(this.url, {
'follow_max': 10,
'open_timeout': 5000,
'headers': Object.assign({
'Referer': this.url,
'Accept': '*/*',
},
(cookieHeader ? { 'Cookie': cookieHeader } : undefined)
),
})
// if (this.wasCanceled) { return }
this.req.on('timeout', this.cancelable((type: string) => {
this.retryCount++
if (this.retryCount <= RETRY_MAX) {
devLog(`TIMEOUT: Retry attempt ${this.retryCount}...`)
this.requestDownload(cookieHeader)
} else {
this.failDownload(downloadErrors.timeout(type))
}
}))
// this.handleDownloadResponse()
// } catch (err) {
// this.retryCount++
// if (this.retryCount <= RETRY_MAX) {
// devLog(`Failed to get file: Retry attempt ${this.retryCount}...`)
// if (this.wasCanceled) { return }
// this.startDownloadStream()
// } else {
// devLog(inspect(err))
// if (err?.code && err?.response?.statusText) {
// this.failDownload(downloadErrors.responseError(`${err.code} (${err.response.statusText})`))
// } else {
// this.failDownload(downloadErrors.responseError(err?.code ?? 'unknown'))
// }
// }
// }
// }))
// }
this.req.on('err', this.cancelable((err: Error) => {
this.failDownload(downloadErrors.connectionError(err))
}))
// /**
// * Pipes the data from a download response to `this.fullPath`.
// * @param req The download request.
// */
// private handleDownloadResponse() {
// this.callbacks.downloadProgress(0)
// let downloadedSize = 0
// const writeStream = createWriteStream(this.fullPath)
this.req.on('header', this.cancelable((statusCode, headers: Headers) => {
if (statusCode != 200) {
this.failDownload(downloadErrors.responseError(statusCode))
return
}
// try {
// this.downloadStream.pipe(writeStream)
// } catch (err) {
// this.failDownload(downloadErrors.connectionError(err))
// }
if (headers['content-type'].startsWith('text/html')) {
this.handleHTMLResponse(headers['set-cookie'])
} else {
this.handleDownloadResponse()
}
}))
}
// this.downloadStream.on('data', this.cancelable((chunk: Buffer) => {
// downloadedSize += chunk.length
// }))
/**
* A Google Drive HTML response to a download request usually means this is the "file too large to scan for viruses" warning.
* This function sends the request that results from clicking "download anyway", or generates an error if it can't be found.
* @param cookieHeader The "cookie=" header of this request.
*/
private handleHTMLResponse(cookieHeader: string) {
let virusScanHTML = ''
this.req.on('data', this.cancelable(data => virusScanHTML += data))
this.req.on('done', this.cancelable((err: Error) => {
if (err) {
this.failDownload(downloadErrors.connectionError(err))
} else {
try {
const confirmTokenRegex = /confirm=([0-9A-Za-z\-_]+)&/g
const confirmTokenResults = confirmTokenRegex.exec(virusScanHTML)
const confirmToken = confirmTokenResults[1]
const downloadID = this.url.substr(this.url.indexOf('id=') + 'id='.length)
this.url = `https://drive.google.com/uc?confirm=${confirmToken}&id=${downloadID}`
const warningCode = /download_warning_([^=]*)=/.exec(cookieHeader)[1]
const NID = /NID=([^;]*);/.exec(cookieHeader)[1].replace('=', '%')
const newHeader = `download_warning_${warningCode}=${confirmToken}; NID=${NID}`
this.requestDownload(newHeader)
} catch (e) {
this.saveHTMLError(virusScanHTML).then(path => {
this.failDownload(downloadErrors.htmlError(path))
})
}
}
}))
}
// const progressUpdater = setInterval(() => {
// this.callbacks.downloadProgress(downloadedSize)
// }, 100)
/**
* Pipes the data from a download response to `this.fullPath`.
* @param req The download request.
*/
private handleDownloadResponse() {
this.callbacks.downloadProgress(0)
let downloadedSize = 0
this.req.pipe(createWriteStream(this.fullPath))
this.req.on('data', this.cancelable(data => {
downloadedSize += data.length
this.callbacks.downloadProgress(downloadedSize)
}))
// this.downloadStream.on('error', this.cancelable((err: Error) => {
// clearInterval(progressUpdater)
// this.failDownload(downloadErrors.connectionError(err))
// }))
this.req.on('err', this.cancelable((err: Error) => {
this.failDownload(downloadErrors.connectionError(err))
}))
// this.downloadStream.on('end', this.cancelable(() => {
// clearInterval(progressUpdater)
// writeStream.end()
// this.downloadStream.destroy()
// this.downloadStream = null
this.req.on('end', this.cancelable(() => {
this.callbacks.complete()
}))
}
// this.callbacks.complete()
// }))
// }
private async saveHTMLError(text: string) {
const errorPath = join(tempPath, 'HTMLError.html')
await writeFile(errorPath, text)
return errorPath
}
// /**
// * Display an error message and provide a function to retry the download.
// */
// private failDownload(error: DownloadError) {
// this.callbacks.error(error, this.cancelable(() => this.beginDownload()))
// }
/**
* Display an error message and provide a function to retry the download.
*/
private failDownload(error: DownloadError) {
this.callbacks.error(error, this.cancelable(() => this.beginDownload()))
}
// /**
// * Stop the process of downloading the file. (no more events will be fired after this is called)
// */
// cancelDownload() {
// this.wasCanceled = true
// googleTimer.cancelTimer() // Prevents timer from trying to activate a download and resetting
// if (this.downloadStream) {
// this.downloadStream.destroy()
// }
// }
/**
* Stop the process of downloading the file. (no more events will be fired after this is called)
*/
cancelDownload() {
this.wasCanceled = true
googleTimer.cancelTimer() // Prevents timer from trying to activate a download and resetting
if (this.req) {
// TODO: destroy request
}
}
// /**
// * Wraps a function that is able to be prevented if `this.cancelDownload()` was called.
// */
// private cancelable<F extends AnyFunction>(fn: F) {
// return (...args: Parameters<F>): ReturnType<F> => {
// if (this.wasCanceled) { return }
// return fn(...Array.from(args))
// }
// }
// }
/**
* Wraps a function that is able to be prevented if `this.cancelDownload()` was called.
*/
private cancelable<F extends AnyFunction>(fn: F) {
return (...args: Parameters<F>): ReturnType<F> => {
if (this.wasCanceled) { return }
return fn(...Array.from(args))
}
}
}
// /**
// * Downloads a file from `url` to `fullPath`.
// * Will handle google drive virus scan warnings. Provides event listeners for download progress.
// * On error, provides the ability to retry.
// * Will only send download requests once every `getSettings().rateLimitDelay` seconds if a Google account has not been authenticated.
// * @param url The download link.
// * @param fullPath The full path to where this file should be stored (including the filename).
// */
// export function getDownloader(url: string, fullPath: string): FileDownloader {
// return new SlowFileDownloader(url, fullPath)
// }

View File

@@ -1,164 +1,170 @@
import * as zipBin from '7zip-bin'
import { mkdir as _mkdir, readdir, unlink } from 'fs'
import * as node7z from 'node-7z'
import * as unrarjs from 'node-unrar-js' // TODO find better rar library that has async extraction
import { FailReason } from 'node-unrar-js/dist/js/extractor'
import { extname, join } from 'path'
import { promisify } from 'util'
// import * as zipBin from '7zip-bin'
// import { mkdir as _mkdir, readdir, unlink } from 'fs'
// import * as node7z from 'node-7z'
// import * as unrarjs from 'node-unrar-js' // TODO find better rar library that has async extraction
// import { FailReason } from 'node-unrar-js/dist/js/extractor'
// import { extname, join } from 'path'
// import { promisify } from 'util'
import { devLog } from '../../shared/ElectronUtilFunctions'
import { AnyFunction } from '../../shared/UtilFunctions'
import { DownloadError } from './ChartDownload'
// import { devLog } from '../../shared/ElectronUtilFunctions'
// import { AnyFunction } from '../../shared/UtilFunctions'
// import { DownloadError } from './ChartDownload'
const mkdir = promisify(_mkdir)
// const mkdir = promisify(_mkdir)
interface EventCallback {
'start': (filename: string) => void
'extractProgress': (percent: number, fileCount: number) => void
'error': (err: DownloadError, retry: () => void | Promise<void>) => void
'complete': () => void
}
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
// interface EventCallback {
// 'start': (filename: string) => void
// 'extractProgress': (percent: number, fileCount: number) => void
// 'error': (err: DownloadError, retry: () => void | Promise<void>) => void
// 'complete': () => void
// }
// type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
const extractErrors = {
readError: (err: NodeJS.ErrnoException) => { return { header: `Failed to read file (${err.code})`, body: `${err.name}: ${err.message}` } },
emptyError: () => { return { header: 'Failed to extract archive', body: 'File archive was downloaded but could not be found' } },
rarmkdirError: (err: NodeJS.ErrnoException, sourceFile: string) => {
return { header: `Extracting archive failed. (${err.code})`, body: `${err.name}: ${err.message} (${sourceFile})` }
},
rarextractError: (result: { reason: FailReason; msg: string }, sourceFile: string) => {
return { header: `Extracting archive failed: ${result.reason}`, body: `${result.msg} (${sourceFile})` }
},
}
// const extractErrors = {
// readError: (err: NodeJS.ErrnoException) => ({ header: `Failed to read file (${err.code})`, body: `${err.name}: ${err.message}` }),
// emptyError: () => ({ header: 'Failed to extract archive', body: 'File archive was downloaded but could not be found' }),
// rarmkdirError: (err: NodeJS.ErrnoException, sourceFile: string) => {
// return { header: `Extracting archive failed. (${err.code})`, body: `${err.name}: ${err.message} (${sourceFile})` }
// },
// rarextractError: (result: { reason: FailReason; msg: string }, sourceFile: string) => {
// return { header: `Extracting archive failed: ${result.reason}`, body: `${result.msg} (${sourceFile})` }
// },
// }
export class FileExtractor {
// export class FileExtractor {
private callbacks = {} as Callbacks
private wasCanceled = false
constructor(private sourceFolder: string) { }
// private callbacks = {} as Callbacks
// private wasCanceled = false
// constructor(private sourceFolder: string) { }
/**
* Calls `callback` when `event` fires. (no events will be fired after `this.cancelExtract()` is called)
*/
on<E extends keyof EventCallback>(event: E, callback: EventCallback[E]) {
this.callbacks[event] = callback
}
// /**
// * Calls `callback` when `event` fires. (no events will be fired after `this.cancelExtract()` is called)
// */
// on<E extends keyof EventCallback>(event: E, callback: EventCallback[E]) {
// this.callbacks[event] = callback
// }
/**
* Extract the chart from `this.sourceFolder`. (assumes there is exactly one archive file in that folder)
*/
beginExtract() {
setTimeout(this.cancelable(() => {
readdir(this.sourceFolder, (err, files) => {
if (err) {
this.callbacks.error(extractErrors.readError(err), () => this.beginExtract())
} else if (files.length == 0) {
this.callbacks.error(extractErrors.emptyError(), () => this.beginExtract())
} else {
this.callbacks.start(files[0])
this.extract(join(this.sourceFolder, files[0]), extname(files[0]) == '.rar')
}
})
}), 100) // Wait for filesystem to process downloaded file
}
// /**
// * Extract the chart from `this.sourceFolder`. (assumes there is exactly one archive file in that folder)
// */
// beginExtract() {
// setTimeout(this.cancelable(() => {
// readdir(this.sourceFolder, (err, files) => {
// if (err) {
// this.callbacks.error(extractErrors.readError(err), () => this.beginExtract())
// } else if (files.length == 0) {
// this.callbacks.error(extractErrors.emptyError(), () => this.beginExtract())
// } else {
// this.callbacks.start(files[0])
// this.extract(join(this.sourceFolder, files[0]), extname(files[0]) == '.rar')
// }
// })
// }), 100) // Wait for filesystem to process downloaded file
// }
/**
* Extracts the file at `fullPath` to `this.sourceFolder`.
*/
private async extract(fullPath: string, useRarExtractor: boolean) {
if (useRarExtractor) {
await this.extractRar(fullPath) // Use node-unrar-js to extract the archive
} else {
this.extract7z(fullPath) // Use node-7z to extract the archive
}
}
// /**
// * Extracts the file at `fullPath` to `this.sourceFolder`.
// */
// private async extract(fullPath: string, useRarExtractor: boolean) {
// if (useRarExtractor) {
// await this.extractRar(fullPath) // Use node-unrar-js to extract the archive
// } else {
// this.extract7z(fullPath) // Use node-7z to extract the archive
// }
// }
/**
* Extracts a .rar archive found at `fullPath` and puts the extracted results in `this.sourceFolder`.
* @throws an `ExtractError` if this fails.
*/
private async extractRar(fullPath: string) {
const extractor = unrarjs.createExtractorFromFile(fullPath, this.sourceFolder)
// /**
// * Extracts a .rar archive found at `fullPath` and puts the extracted results in `this.sourceFolder`.
// * @throws an `ExtractError` if this fails.
// */
// private async extractRar(fullPath: string) {
// const extractor = unrarjs.createExtractorFromFile(fullPath, this.sourceFolder)
const fileList = extractor.getFileList()
// const fileList = extractor.getFileList()
if (fileList[0].state != 'FAIL') {
// if (fileList[0].state != 'FAIL') {
// Create directories for nested archives (because unrarjs didn't feel like handling that automatically)
const headers = fileList[1].fileHeaders
for (const header of headers) {
if (header.flags.directory) {
try {
await mkdir(join(this.sourceFolder, header.name), { recursive: true })
} catch (err) {
this.callbacks.error(extractErrors.rarmkdirError(err, fullPath), () => this.extract(fullPath, extname(fullPath) == '.rar'))
return
}
}
}
}
// // Create directories for nested archives (because unrarjs didn't feel like handling that automatically)
// const headers = fileList[1].fileHeaders
// for (const header of headers) {
// if (header.flags.directory) {
// try {
// await mkdir(join(this.sourceFolder, header.name), { recursive: true })
// } catch (err) {
// this.callbacks.error(
// extractErrors.rarmkdirError(err, fullPath),
// () => this.extract(fullPath, extname(fullPath) == '.rar'),
// )
// return
// }
// }
// }
// }
const extractResult = extractor.extractAll()
// const extractResult = extractor.extractAll()
if (extractResult[0].state == 'FAIL') {
this.callbacks.error(extractErrors.rarextractError(extractResult[0], fullPath), () => this.extract(fullPath, extname(fullPath) == '.rar'))
} else {
this.deleteArchive(fullPath)
}
}
// if (extractResult[0].state == 'FAIL') {
// this.callbacks.error(
// extractErrors.rarextractError(extractResult[0], fullPath),
// () => this.extract(fullPath, extname(fullPath) == '.rar'),
// )
// } else {
// this.deleteArchive(fullPath)
// }
// }
/**
* Extracts a .zip or .7z archive found at `fullPath` and puts the extracted results in `this.sourceFolder`.
*/
private extract7z(fullPath: string) {
const zipBinPath = zipBin.path7za.replace('app.asar', 'app.asar.unpacked') // I love electron-builder packaging :)
const stream = node7z.extractFull(fullPath, this.sourceFolder, { $progress: true, $bin: zipBinPath })
// /**
// * Extracts a .zip or .7z archive found at `fullPath` and puts the extracted results in `this.sourceFolder`.
// */
// private extract7z(fullPath: string) {
// const zipBinPath = zipBin.path7za.replace('app.asar', 'app.asar.unpacked') // I love electron-builder packaging :)
// const stream = node7z.extractFull(fullPath, this.sourceFolder, { $progress: true, $bin: zipBinPath })
stream.on('progress', this.cancelable((progress: { percent: number; fileCount: number }) => {
this.callbacks.extractProgress(progress.percent, isNaN(progress.fileCount) ? 0 : progress.fileCount)
}))
// stream.on('progress', this.cancelable((progress: { percent: number; fileCount: number }) => {
// this.callbacks.extractProgress(progress.percent, isNaN(progress.fileCount) ? 0 : progress.fileCount)
// }))
let extractErrorOccured = false
stream.on('error', this.cancelable(() => {
extractErrorOccured = true
devLog(`Failed to extract [${fullPath}]; retrying with .rar extractor...`)
this.extract(fullPath, true)
}))
// let extractErrorOccured = false
// stream.on('error', this.cancelable(() => {
// extractErrorOccured = true
// devLog(`Failed to extract [${fullPath}]; retrying with .rar extractor...`)
// this.extract(fullPath, true)
// }))
stream.on('end', this.cancelable(() => {
if (!extractErrorOccured) {
this.deleteArchive(fullPath)
}
}))
}
// stream.on('end', this.cancelable(() => {
// if (!extractErrorOccured) {
// this.deleteArchive(fullPath)
// }
// }))
// }
/**
* Tries to delete the archive at `fullPath`.
*/
private deleteArchive(fullPath: string) {
unlink(fullPath, this.cancelable(err => {
if (err && err.code != 'ENOENT') {
devLog(`Warning: failed to delete archive at [${fullPath}]`)
}
// /**
// * Tries to delete the archive at `fullPath`.
// */
// private deleteArchive(fullPath: string) {
// unlink(fullPath, this.cancelable(err => {
// if (err && err.code != 'ENOENT') {
// devLog(`Warning: failed to delete archive at [${fullPath}]`)
// }
this.callbacks.complete()
}))
}
// this.callbacks.complete()
// }))
// }
/**
* Stop the process of extracting the file. (no more events will be fired after this is called)
*/
cancelExtract() {
this.wasCanceled = true
}
// /**
// * Stop the process of extracting the file. (no more events will be fired after this is called)
// */
// cancelExtract() {
// this.wasCanceled = true
// }
/**
* Wraps a function that is able to be prevented if `this.cancelExtract()` was called.
*/
private cancelable<F extends AnyFunction>(fn: F) {
return (...args: Parameters<F>): ReturnType<F> => {
if (this.wasCanceled) { return }
return fn(...Array.from(args))
}
}
}
// /**
// * Wraps a function that is able to be prevented if `this.cancelExtract()` was called.
// */
// private cancelable<F extends AnyFunction>(fn: F) {
// return (...args: Parameters<F>): ReturnType<F> => {
// if (this.wasCanceled) { return }
// return fn(...Array.from(args))
// }
// }
// }

View File

@@ -1,5 +1,5 @@
import { Dirent, readdir as _readdir } from 'fs'
import * as mv from 'mv'
import mv from 'mv'
import { join } from 'path'
import { rimraf } from 'rimraf'
import { promisify } from 'util'
@@ -20,7 +20,10 @@ const transferErrors = {
readError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to read file.'),
deleteError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete file.'),
rimrafError: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to delete folder.'),
mvError: (err: NodeJS.ErrnoException) => fsError(err, `Failed to move folder to library.${err.code == 'EPERM' ? ' (does the chart already exist?)' : ''}`),
mvError: (err: NodeJS.ErrnoException) => fsError(
err,
`Failed to move folder to library.${err.code == 'EPERM' ? ' (does the chart already exist?)' : ''}`,
),
}
function fsError(err: NodeJS.ErrnoException, description: string) {

View File

@@ -19,7 +19,7 @@ interface EventCallback {
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
const filesystemErrors = {
libraryFolder: () => { return { header: 'Library folder not specified', body: 'Please go to the settings to set your library folder.' } },
libraryFolder: () => ({ header: 'Library folder not specified', body: 'Please go to the settings to set your library folder.' }),
libraryAccess: (err: NodeJS.ErrnoException) => fsError(err, 'Failed to access library folder.'),
destinationFolderExists: (destinationPath: string) => {
return { header: 'This chart already exists in your library folder.', body: destinationPath, isLink: true }

View File

@@ -1,5 +1,5 @@
import { app, BrowserWindow, ipcMain } from 'electron'
import * as windowStateKeeper from 'electron-window-state'
import windowStateKeeper from 'electron-window-state'
import * as path from 'path'
import * as url from 'url'
@@ -9,12 +9,13 @@ import { updateChecker } from './ipc/UpdateHandler.ipc'
import { getIPCEmitHandlers, getIPCInvokeHandlers, IPCEmitEvents } from './shared/IPCHandler'
import { dataPath } from './shared/Paths'
require('electron-unhandled')({ showDialog: true })
import unhandled = require('electron-unhandled')
unhandled({ showDialog: true })
export let mainWindow: BrowserWindow
const args = process.argv.slice(1)
const isDevBuild = args.some(val => val == '--dev')
const remote = require('@electron/remote/main')
import remote = require('@electron/remote/main')
remote.initialize()
@@ -66,7 +67,7 @@ function handleOSXWindowClosed() {
/**
* Launches and initializes Bridge's main window.
*/
function createBridgeWindow() {
async function createBridgeWindow() {
// Load window size and maximized/restored state from previous session
const windowState = windowStateKeeper({
@@ -89,7 +90,7 @@ function createBridgeWindow() {
getIPCEmitHandlers().map(handler => ipcMain.on(handler.event, (_event, ...args) => handler.handler(args[0])))
// Load angular app
mainWindow.loadURL(getLoadUrl())
await loadWindow()
if (isDevBuild) {
mainWindow.webContents.openDevTools()
@@ -115,10 +116,9 @@ function createBrowserWindow(windowState: windowStateKeeper.State) {
frame: false,
title: 'Bridge',
webPreferences: {
nodeIntegration: true,
// preload:
allowRunningInsecureContent: (isDevBuild) ? true : false,
textAreasAreResizable: false,
contextIsolation: false,
},
simpleFullscreen: true,
fullscreenable: false,
@@ -132,6 +132,15 @@ function createBrowserWindow(windowState: windowStateKeeper.State) {
return new BrowserWindow(options)
}
async function loadWindow(retries = 0) {
if (retries > 10) { throw new Error('Angular frontend did not load') }
try {
await mainWindow.loadURL(getLoadUrl())
} catch (err) {
await loadWindow(retries + 1)
}
}
/**
* Load from localhost during development; load from index.html in production
*/

View File

@@ -23,6 +23,6 @@ export function hasVideoExtension(name: string) {
* Log a message in the main BrowserWindow's console.
* Note: Error objects can't be serialized by this; use inspect(err) before passing it here.
*/
export function devLog(...messages: any[]) {
export function devLog(...messages: unknown[]) {
emitIPCEvent('log', messages)
}

View File

@@ -1,6 +1,5 @@
import * as randomBytes from 'randombytes'
const sanitize = require('sanitize-filename')
import randomBytes from 'randombytes'
import sanitize from 'sanitize-filename'
// WARNING: do not import anything related to Electron; the code will not compile correctly.

View File

@@ -7,6 +7,7 @@
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",