Restructure; use DaisyUI

This commit is contained in:
Geomitron
2023-11-28 19:50:45 -06:00
parent 49c3f38f99
commit 2eef4d0bee
727 changed files with 1283 additions and 298840 deletions

View File

@@ -1,12 +1,12 @@
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 { sanitizeFilename } from '../../shared/UtilFunctions'
import { getSettings } from '../SettingsHandler.ipc'
import { NewDownload, ProgressType } from '../../../src-shared/interfaces/download.interface'
import { DriveFile } from '../../../src-shared/interfaces/songDetails.interface'
import { sanitizeFilename } from '../../../src-shared/UtilFunctions'
import { hasVideoExtension } from '../../ElectronUtilFunctions'
import { emitIpcEvent } from '../../main'
import { settings } from '../SettingsHandler.ipc'
// import { FileDownloader, getDownloader } from './FileDownloader'
import { FilesystemChecker } from './FilesystemChecker'
import { FileTransfer } from './FileTransfer'
@@ -22,8 +22,8 @@ export interface DownloadError { header: string; body: string; isLink?: boolean
export class ChartDownload {
private retryFn: () => void | Promise<void>
private cancelFn: () => void
private retryFn: undefined | (() => void | Promise<void>)
private cancelFn: undefined | (() => void)
private callbacks = {} as Callbacks
private files: DriveFile[]
@@ -62,7 +62,7 @@ export class ChartDownload {
filterDownloadFiles(files: DriveFile[]) {
return files.filter(file => {
return (file.name != 'ch.dat') && (getSettings().downloadVideos || !hasVideoExtension(file.name))
return (file.name !== 'ch.dat') && (settings.downloadVideos || !hasVideoExtension(file.name))
})
}
@@ -70,7 +70,7 @@ export class ChartDownload {
* Retries the last failed step if it is running.
*/
retry() { // Only allow it to be called once
if (this.retryFn != undefined) {
if (this.retryFn !== undefined) {
this._hasFailed = false
const retryFn = this.retryFn
this.retryFn = undefined
@@ -89,7 +89,7 @@ export class ChartDownload {
* Cancels the download if it is running.
*/
cancel() { // Only allow it to be called once
if (this.cancelFn != undefined) {
if (this.cancelFn !== undefined) {
const cancelFn = this.cancelFn
this.cancelFn = undefined
cancelFn()
@@ -105,7 +105,7 @@ export class ChartDownload {
private updateGUI(header: string, description: string, type: ProgressType, isLink = false) {
if (this.wasCanceled) { return }
emitIPCEvent('download-updated', {
emitIpcEvent('downloadUpdated', {
versionID: this.versionID,
title: `${this.data.chartName} - ${this.data.artist}`,
header: header,
@@ -122,7 +122,7 @@ export class ChartDownload {
private handleError(err: DownloadError, retry: () => void) {
this._hasFailed = true
this.retryFn = retry
this.updateGUI(err.header, err.body, 'error', err.isLink == true)
this.updateGUI(err.header, err.body, 'error', err.isLink === true)
this.callbacks.error()
}
@@ -245,7 +245,7 @@ export class ChartDownload {
// 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')
// this.updateGUI(`[${archive}] (${filecount} file${filecount === 1 ? '' : 's'} extracted)`, `Extracting... (${percent}%)`, 'good')
// })
// extractor.on('error', this.handleError.bind(this))

View File

@@ -1,86 +1,82 @@
import { Download } from '../../shared/interfaces/download.interface'
import { IPCEmitHandler } from '../../shared/IPCHandler'
import { Download } from '../../../src-shared/interfaces/download.interface'
import { ChartDownload } from './ChartDownload'
import { DownloadQueue } from './DownloadQueue'
class DownloadHandler implements IPCEmitHandler<'download'> {
event = 'download' as const
const downloadQueue: DownloadQueue = new DownloadQueue()
const retryWaiting: ChartDownload[] = []
downloadQueue: DownloadQueue = new DownloadQueue()
currentDownload: ChartDownload = undefined
retryWaiting: ChartDownload[] = []
let currentDownload: ChartDownload | undefined = undefined
handler(data: Download) {
switch (data.action) {
case 'add': this.addDownload(data); break
case 'retry': this.retryDownload(data); break
case 'cancel': this.cancelDownload(data); break
}
export async function download(data: Download) {
switch (data.action) {
case 'add': addDownload(data); break
case 'retry': retryDownload(data); break
case 'cancel': cancelDownload(data); break
}
}
function addDownload(data: Download) {
const filesHash = data.data!.driveData.filesHash // Note: using versionID would cause chart packs to download multiple times
if (currentDownload?.hash === filesHash || downloadQueue.isDownloadingLink(filesHash)) {
return
}
private addDownload(data: Download) {
const filesHash = data.data.driveData.filesHash // Note: using versionID would cause chart packs to download multiple times
if (this.currentDownload?.hash == filesHash || this.downloadQueue.isDownloadingLink(filesHash)) {
return
}
const newDownload = new ChartDownload(data.versionID, data.data!)
addDownloadEventListeners(newDownload)
if (currentDownload === undefined) {
currentDownload = newDownload
newDownload.beginDownload()
} else {
downloadQueue.push(newDownload)
}
}
const newDownload = new ChartDownload(data.versionID, data.data)
this.addDownloadEventListeners(newDownload)
if (this.currentDownload == undefined) {
this.currentDownload = newDownload
newDownload.beginDownload()
function retryDownload(data: Download) {
const index = retryWaiting.findIndex(download => download.versionID === data.versionID)
if (index !== -1) {
const retryDownload = retryWaiting.splice(index, 1)[0]
retryDownload.displayRetrying()
if (currentDownload === undefined) {
currentDownload = retryDownload
retryDownload.retry()
} else {
this.downloadQueue.push(newDownload)
}
}
private retryDownload(data: Download) {
const index = this.retryWaiting.findIndex(download => download.versionID == data.versionID)
if (index != -1) {
const retryDownload = this.retryWaiting.splice(index, 1)[0]
retryDownload.displayRetrying()
if (this.currentDownload == undefined) {
this.currentDownload = retryDownload
retryDownload.retry()
} else {
this.downloadQueue.push(retryDownload)
}
}
}
private cancelDownload(data: Download) {
if (this.currentDownload?.versionID == data.versionID) {
this.currentDownload.cancel()
this.currentDownload = undefined
this.startNextDownload()
} else {
this.downloadQueue.remove(data.versionID)
}
}
private addDownloadEventListeners(download: ChartDownload) {
download.on('complete', () => {
this.currentDownload = undefined
this.startNextDownload()
})
download.on('error', () => {
this.retryWaiting.push(this.currentDownload)
this.currentDownload = undefined
this.startNextDownload()
})
}
private startNextDownload() {
if (!this.downloadQueue.isEmpty()) {
this.currentDownload = this.downloadQueue.shift()
if (this.currentDownload.hasFailed) {
this.currentDownload.retry()
} else {
this.currentDownload.beginDownload()
}
downloadQueue.push(retryDownload)
}
}
}
export const downloadHandler = new DownloadHandler()
function cancelDownload(data: Download) {
if (currentDownload?.versionID === data.versionID) {
currentDownload.cancel()
currentDownload = undefined
startNextDownload()
} else {
downloadQueue.remove(data.versionID)
}
}
function addDownloadEventListeners(download: ChartDownload) {
download.on('complete', () => {
currentDownload = undefined
startNextDownload()
})
download.on('error', () => {
if (currentDownload) {
retryWaiting.push(currentDownload)
currentDownload = undefined
}
startNextDownload()
})
}
function startNextDownload() {
currentDownload = downloadQueue.shift()
if (currentDownload) {
if (currentDownload.hasFailed) {
currentDownload.retry()
} else {
currentDownload.beginDownload()
}
}
}

View File

@@ -1,6 +1,6 @@
import Comparators from 'comparators'
import Comparators, { Comparator } from 'comparators'
import { emitIPCEvent } from '../../main'
import { emitIpcEvent } from '../../main'
import { ChartDownload } from './ChartDownload'
export class DownloadQueue {
@@ -8,11 +8,11 @@ export class DownloadQueue {
private downloadQueue: ChartDownload[] = []
isDownloadingLink(filesHash: string) {
return this.downloadQueue.some(download => download.hash == filesHash)
return this.downloadQueue.some(download => download.hash === filesHash)
}
isEmpty() {
return this.downloadQueue.length == 0
return this.downloadQueue.length === 0
}
push(chartDownload: ChartDownload) {
@@ -25,27 +25,27 @@ export class DownloadQueue {
}
get(versionID: number) {
return this.downloadQueue.find(download => download.versionID == versionID)
return this.downloadQueue.find(download => download.versionID === versionID)
}
remove(versionID: number) {
const index = this.downloadQueue.findIndex(download => download.versionID == versionID)
if (index != -1) {
const index = this.downloadQueue.findIndex(download => download.versionID === versionID)
if (index !== -1) {
this.downloadQueue[index].cancel()
this.downloadQueue.splice(index, 1)
emitIPCEvent('queue-updated', this.downloadQueue.map(download => download.versionID))
emitIpcEvent('queueUpdated', this.downloadQueue.map(download => download.versionID))
}
}
private sort() {
let comparator = Comparators.comparing('allFilesProgress', { reversed: true })
let comparator: Comparator<unknown> | undefined = Comparators.comparing('allFilesProgress', { reversed: true })
const prioritizeArchives = true
if (prioritizeArchives) {
comparator = comparator.thenComparing('isArchive', { reversed: true })
comparator = comparator.thenComparing?.('isArchive', { reversed: true }) || undefined
}
this.downloadQueue.sort(comparator)
emitIPCEvent('queue-updated', this.downloadQueue.map(download => download.versionID))
emitIpcEvent('queueUpdated', this.downloadQueue.map(download => download.versionID))
}
}

View File

@@ -112,7 +112,7 @@
// }))
// this.req.on('header', this.cancelable((statusCode, headers: Headers) => {
// if (statusCode != 200) {
// if (statusCode !== 200) {
// this.failDownload(downloadErrors.responseError(statusCode))
// return
// }
@@ -246,7 +246,7 @@
// * Download the file after waiting for the google rate limit.
// */
// beginDownload() {
// if (this.fileID == undefined) {
// if (this.fileID === undefined) {
// this.failDownload(downloadErrors.linkError(this.url))
// }

View File

@@ -52,11 +52,11 @@
// readdir(this.sourceFolder, (err, files) => {
// if (err) {
// this.callbacks.error(extractErrors.readError(err), () => this.beginExtract())
// } else if (files.length == 0) {
// } 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')
// this.extract(join(this.sourceFolder, files[0]), extname(files[0]) === '.rar')
// }
// })
// }), 100) // Wait for filesystem to process downloaded file
@@ -82,7 +82,7 @@
// 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
@@ -93,7 +93,7 @@
// } catch (err) {
// this.callbacks.error(
// extractErrors.rarmkdirError(err, fullPath),
// () => this.extract(fullPath, extname(fullPath) == '.rar'),
// () => this.extract(fullPath, extname(fullPath) === '.rar'),
// )
// return
// }
@@ -103,10 +103,10 @@
// const extractResult = extractor.extractAll()
// if (extractResult[0].state == 'FAIL') {
// if (extractResult[0].state === 'FAIL') {
// this.callbacks.error(
// extractErrors.rarextractError(extractResult[0], fullPath),
// () => this.extract(fullPath, extname(fullPath) == '.rar'),
// () => this.extract(fullPath, extname(fullPath) === '.rar'),
// )
// } else {
// this.deleteArchive(fullPath)
@@ -143,7 +143,7 @@
// */
// private deleteArchive(fullPath: string) {
// unlink(fullPath, this.cancelable(err => {
// if (err && err.code != 'ENOENT') {
// if (err && err.code !== 'ENOENT') {
// devLog(`Warning: failed to delete archive at [${fullPath}]`)
// }

View File

@@ -4,7 +4,7 @@ import { join } from 'path'
import { rimraf } from 'rimraf'
import { promisify } from 'util'
import { getSettings } from '../SettingsHandler.ipc'
import { settings } from '../SettingsHandler.ipc'
import { DownloadError } from './ChartDownload'
const readdir = promisify(_readdir)
@@ -17,12 +17,13 @@ interface EventCallback {
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
const transferErrors = {
libraryError: () => ({ header: 'Library folder not specified', body: 'Please go to the settings to set your library folder.' }),
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?)' : ''}`,
`Failed to move folder to library.${err.code === 'EPERM' ? ' (does the chart already exist?)' : ''}`,
),
}
@@ -34,10 +35,10 @@ export class FileTransfer {
private callbacks = {} as Callbacks
private wasCanceled = false
private destinationFolder: string
private destinationFolder: string | null
private nestedSourceFolder: string // The top-level folder that is copied to the library folder
constructor(private sourceFolder: string, destinationFolderName: string) {
this.destinationFolder = join(getSettings().libraryPath, destinationFolderName)
this.destinationFolder = settings.libraryPath ? join(settings.libraryPath, destinationFolderName) : null
this.nestedSourceFolder = sourceFolder
}
@@ -49,10 +50,14 @@ export class FileTransfer {
}
async beginTransfer() {
this.callbacks.start(this.destinationFolder)
await this.cleanFolder()
if (this.wasCanceled) { return }
this.moveFolder()
if (!this.destinationFolder) {
this.callbacks.error(transferErrors.libraryError(), () => this.beginTransfer())
} else {
this.callbacks.start(this.destinationFolder)
await this.cleanFolder()
if (this.wasCanceled) { return }
this.moveFolder()
}
}
/**
@@ -68,7 +73,7 @@ export class FileTransfer {
}
// Remove nested folders
if (files.length == 1 && !files[0].isFile()) {
if (files.length === 1 && !files[0].isFile()) {
this.nestedSourceFolder = join(this.nestedSourceFolder, files[0].name)
await this.cleanFolder()
return
@@ -76,7 +81,7 @@ export class FileTransfer {
// Delete '__MACOSX' folder
for (const file of files) {
if (!file.isFile() && file.name == '__MACOSX') {
if (!file.isFile() && file.name === '__MACOSX') {
try {
await rimraf(join(this.nestedSourceFolder, file.name))
} catch (err) {
@@ -93,14 +98,18 @@ export class FileTransfer {
* Moves the downloaded chart to the library path.
*/
private moveFolder() {
mv(this.nestedSourceFolder, this.destinationFolder, { mkdirp: true }, err => {
if (err) {
this.callbacks.error(transferErrors.mvError(err), () => this.moveFolder())
} else {
rimraf(this.sourceFolder) // Delete temp folder
this.callbacks.complete()
}
})
if (!this.destinationFolder) {
this.callbacks.error(transferErrors.libraryError(), () => this.moveFolder())
} else {
mv(this.nestedSourceFolder, this.destinationFolder, { mkdirp: true }, err => {
if (err) {
this.callbacks.error(transferErrors.mvError(err), () => this.moveFolder())
} else {
rimraf(this.sourceFolder) // Delete temp folder
this.callbacks.complete()
}
})
}
}
/**

View File

@@ -1,16 +1,12 @@
import { randomBytes as _randomBytes } from 'crypto'
import { access, constants, mkdir } from 'fs'
import { join } from 'path'
import { promisify } from 'util'
import { devLog } from '../../shared/ElectronUtilFunctions'
import { tempPath } from '../../shared/Paths'
import { AnyFunction } from '../../shared/UtilFunctions'
import { getSettings } from '../SettingsHandler.ipc'
import { tempPath } from '../../../src-shared/Paths'
import { AnyFunction } from '../../../src-shared/UtilFunctions'
import { devLog } from '../../ElectronUtilFunctions'
import { settings } from '../SettingsHandler.ipc'
import { DownloadError } from './ChartDownload'
const randomBytes = promisify(_randomBytes)
interface EventCallback {
'start': () => void
'error': (err: DownloadError, retry: () => void | Promise<void>) => void
@@ -19,7 +15,7 @@ interface EventCallback {
type Callbacks = { [E in keyof EventCallback]: EventCallback[E] }
const filesystemErrors = {
libraryFolder: () => ({ header: 'Library folder not specified', body: 'Please go to the settings to set your library folder.' }),
libraryError: () => ({ 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 }
@@ -56,10 +52,10 @@ export class FilesystemChecker {
* Verifies that the user has specified a library folder.
*/
private checkLibraryFolder() {
if (getSettings().libraryPath == undefined) {
this.callbacks.error(filesystemErrors.libraryFolder(), () => this.beginCheck())
if (settings.libraryPath === undefined) {
this.callbacks.error(filesystemErrors.libraryError(), () => this.beginCheck())
} else {
access(getSettings().libraryPath, constants.W_OK, this.cancelable(err => {
access(settings.libraryPath, constants.W_OK, this.cancelable(err => {
if (err) {
this.callbacks.error(filesystemErrors.libraryAccess(err), () => this.beginCheck())
} else {
@@ -73,21 +69,25 @@ export class FilesystemChecker {
* Checks that the destination folder doesn't already exist.
*/
private checkDestinationFolder() {
const destinationPath = join(getSettings().libraryPath, this.destinationFolderName)
access(destinationPath, constants.F_OK, this.cancelable(err => {
if (err) { // File does not exist
this.createDownloadFolder()
} else {
this.callbacks.error(filesystemErrors.destinationFolderExists(destinationPath), () => this.beginCheck())
}
}))
if (!settings.libraryPath) {
this.callbacks.error(filesystemErrors.libraryError(), () => this.beginCheck())
} else {
const destinationPath = join(settings.libraryPath, this.destinationFolderName)
access(destinationPath, constants.F_OK, this.cancelable(err => {
if (err) { // File does not exist
this.createDownloadFolder()
} else {
this.callbacks.error(filesystemErrors.destinationFolderExists(destinationPath), () => this.beginCheck())
}
}))
}
}
/**
* Attempts to create a unique folder in Bridge's data paths.
*/
private async createDownloadFolder(retryCount = 0) {
const tempChartPath = join(tempPath, `chart_${(await randomBytes(5)).toString('hex')}`)
const tempChartPath = join(tempPath, `chart_TODO_MAKE_UNIQUE`)
mkdir(tempChartPath, this.cancelable(err => {
if (err) {
@@ -114,7 +114,7 @@ export class FilesystemChecker {
* Wraps a function that is able to be prevented if `this.cancelCheck()` was called.
*/
private cancelable<F extends AnyFunction>(fn: F) {
return (...args: Parameters<F>): ReturnType<F> => {
return (...args: Parameters<F>): ReturnType<F> | void => {
if (this.wasCanceled) { return }
return fn(...Array.from(args))
}

View File

@@ -1,4 +1,4 @@
import { getSettings } from '../SettingsHandler.ipc'
import { settings } from '../SettingsHandler.ipc'
interface EventCallback {
'waitProgress': (remainingSeconds: number, totalSeconds: number) => void
@@ -33,10 +33,10 @@ class GoogleTimer {
* Check the state of the callbacks and call them if necessary.
*/
private updateCallbacks() {
if (this.hasTimerEnded() && this.callbacks.complete != undefined) {
if (this.hasTimerEnded() && this.callbacks.complete !== undefined) {
this.endTimer()
} else if (this.callbacks.waitProgress != undefined) {
const delay = getSettings().rateLimitDelay
} else if (this.callbacks.waitProgress !== undefined) {
const delay = settings.rateLimitDelay
this.callbacks.waitProgress(delay - this.rateLimitCounter, delay)
}
}
@@ -52,7 +52,7 @@ class GoogleTimer {
* Checks if enough time has elapsed since the last timer activation.
*/
private hasTimerEnded() {
return this.rateLimitCounter > getSettings().rateLimitDelay
return this.rateLimitCounter > settings.rateLimitDelay
}
/**
@@ -62,7 +62,7 @@ class GoogleTimer {
this.rateLimitCounter = 0
const completeCallback = this.callbacks.complete
this.callbacks = {}
completeCallback()
completeCallback?.()
}
}