diff --git a/src/app/components/browse/browse.component.ts b/src/app/components/browse/browse.component.ts index 4d7c3ca..12fb6c7 100644 --- a/src/app/components/browse/browse.component.ts +++ b/src/app/components/browse/browse.component.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild } from '@angular/core' +import { Component, ViewChild, AfterViewInit } from '@angular/core' import { ChartSidebarComponent } from './chart-sidebar/chart-sidebar.component' import { StatusBarComponent } from './status-bar/status-bar.component' import { SongResult } from 'src/electron/shared/interfaces/search.interface' @@ -9,7 +9,7 @@ import { ResultTableComponent } from './result-table/result-table.component' templateUrl: './browse.component.html', styleUrls: ['./browse.component.scss'] }) -export class BrowseComponent { +export class BrowseComponent implements AfterViewInit { @ViewChild('resultTable', { static: true }) resultTable: ResultTableComponent @ViewChild('chartSidebar', { static: true }) chartSidebar: ChartSidebarComponent @@ -17,6 +17,24 @@ export class BrowseComponent { constructor() { } + ngAfterViewInit() { + const $tableColumn = $('#table-column') + $tableColumn.visibility({ + once: false, + continuous: true, + context: $tableColumn, + observeChanges: true, + onUpdate: () => { + let pos = $tableColumn[0].scrollTop + $tableColumn[0].offsetHeight + let max = $tableColumn[0].scrollHeight + if (pos >= max - 5) { + // TODO: load more results (should be debounced or something; wait until results have loaded before sending the request for more) + console.log('UPDATE SCROLL') + } + } + }) + } + onResultsUpdated(results: SongResult[]) { this.resultTable.results = results this.resultTable.onNewSearch() @@ -25,4 +43,8 @@ export class BrowseComponent { this.statusBar.resultCount = results.length this.statusBar.selectedResults = [] } + + loadMoreResults() { + // TODO: use the same query as the current search, but append more results if there are any more to be viewed + } } \ No newline at end of file diff --git a/src/app/components/browse/chart-sidebar/chart-sidebar.component.ts b/src/app/components/browse/chart-sidebar/chart-sidebar.component.ts index 454156d..4eb7d88 100644 --- a/src/app/components/browse/chart-sidebar/chart-sidebar.component.ts +++ b/src/app/components/browse/chart-sidebar/chart-sidebar.component.ts @@ -172,7 +172,7 @@ export class ChartSidebarComponent { this.selectedVersion.versionID, { avTagName: this.selectedVersion.avTagName, artist: this.songResult.artist, - charter: this.selectedVersion.charters, + charter: this.selectedVersion.charters, //TODO: get the charter name associated with this particular version links: JSON.parse(this.selectedVersion.downloadLink) }) } diff --git a/src/app/components/toolbar/toolbar.component.html b/src/app/components/toolbar/toolbar.component.html index db16f05..3c393d0 100644 --- a/src/app/components/toolbar/toolbar.component.html +++ b/src/app/components/toolbar/toolbar.component.html @@ -6,7 +6,7 @@ \ No newline at end of file diff --git a/src/app/components/toolbar/toolbar.component.ts b/src/app/components/toolbar/toolbar.component.ts index ef71592..41cf07d 100644 --- a/src/app/components/toolbar/toolbar.component.ts +++ b/src/app/components/toolbar/toolbar.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core' +import { Component, OnInit, ChangeDetectorRef } from '@angular/core' import { ElectronService } from '../../core/services/electron.service' @Component({ @@ -10,17 +10,25 @@ export class ToolbarComponent implements OnInit { isMaximized: boolean - constructor(private electronService: ElectronService) { } + constructor(private electronService: ElectronService, private ref: ChangeDetectorRef) { } ngOnInit() { this.isMaximized = this.electronService.currentWindow.isMaximized() + this.electronService.currentWindow.on('unmaximize', () => { + this.isMaximized = false + this.ref.detectChanges() + }) + this.electronService.currentWindow.on('maximize', () => { + this.isMaximized = true + this.ref.detectChanges() + }) } minimize() { this.electronService.currentWindow.minimize() } - maximize() { + toggleMaximized() { if (this.isMaximized) { this.electronService.currentWindow.restore() } else { diff --git a/src/electron/ipc/SettingsHandler.ipc.ts b/src/electron/ipc/SettingsHandler.ipc.ts index dbd3194..70f8fb1 100644 --- a/src/electron/ipc/SettingsHandler.ipc.ts +++ b/src/electron/ipc/SettingsHandler.ipc.ts @@ -1,13 +1,13 @@ -import { exists as _exists, mkdir as _mkdir, readFile as _readFile, writeFile as _writeFile } from 'fs' +import * as fs from 'fs' import { dataPath, tempPath, themesPath, settingsPath } from '../shared/Paths' import { promisify } from 'util' import { IPCInvokeHandler, IPCEmitHandler } from '../shared/IPCHandler' import { defaultSettings, Settings } from '../shared/Settings' -const exists = promisify(_exists) -const mkdir = promisify(_mkdir) -const readFile = promisify(_readFile) -const writeFile = promisify(_writeFile) +const exists = promisify(fs.exists) +const mkdir = promisify(fs.mkdir) +const readFile = promisify(fs.readFile) +const writeFile = promisify(fs.writeFile) let settings: Settings diff --git a/src/electron/ipc/download/FileExtractor.ts b/src/electron/ipc/download/FileExtractor.ts index 77314c7..7f59d41 100644 --- a/src/electron/ipc/download/FileExtractor.ts +++ b/src/electron/ipc/download/FileExtractor.ts @@ -1,21 +1,20 @@ import { DownloadError } from './FileDownloader' -import { readdir as _readdir, unlink as _unlink, lstat as _lstat, copyFile as _copyFile, - rmdir as _rmdir, access as _access, mkdir as _mkdir, constants } from 'fs' +import * as fs from 'fs' import { promisify } from 'util' import { join, extname } from 'path' import * as node7z from 'node-7z' import * as zipBin from '7zip-bin' -import * as unrarjs from 'node-unrar-js' import { getSettingsHandler } from '../SettingsHandler.ipc' +import { extractRar } from './RarExtractor' const getSettings = getSettingsHandler.getSettings -const readdir = promisify(_readdir) -const unlink = promisify(_unlink) -const lstat = promisify(_lstat) -const copyFile = promisify(_copyFile) -const rmdir = promisify(_rmdir) -const access = promisify(_access) -const mkdir = promisify(_mkdir) +const readdir = promisify(fs.readdir) +const unlink = promisify(fs.unlink) +const lstat = promisify(fs.lstat) +const copyFile = promisify(fs.copyFile) +const rmdir = promisify(fs.rmdir) +const access = promisify(fs.access) +const mkdir = promisify(fs.mkdir) type EventCallback = { 'extract': (filename: string) => void @@ -47,7 +46,7 @@ export class FileExtractor { this.libraryFolder = getSettings().libraryPath const files = await readdir(this.sourceFolder) if (this.isArchive) { - this.extract(files[0]) + this.extract(files[0], extname(files[0]) == '.rar') } else { this.transfer() } @@ -56,19 +55,24 @@ export class FileExtractor { /** * Extracts the file at `filename` to `this.sourceFolder`. */ - private extract(filename: string) { + private async extract(filename: string, useRarExtractor: boolean) { + await new Promise(resolve => setTimeout(() => resolve(), 100)) // Wait for filesystem to process downloaded file... if (this.wasCanceled) { return } // CANCEL POINT this.callbacks.extract(filename) const source = join(this.sourceFolder, filename) - if (extname(filename) == '.rar') { + if (useRarExtractor) { // Use node-unrar-js to extract the archive try { - let extractor = unrarjs.createExtractorFromFile(source, this.sourceFolder) - extractor.extractAll() + await extractRar(source, this.sourceFolder) } catch (err) { - this.callbacks.error({ header: 'Extract Failed.', body: `Unable to extract [${filename}]: ${err.name}` }, () => this.extract(filename)) + this.callbacks.error({ + header: 'Extract Failed.', + body: `Unable to extract [${filename}]: ${err}` + }, + () => this.extract(filename, extname(filename) == '.rar') + ) return } this.transfer(source) @@ -82,12 +86,17 @@ export class FileExtractor { this.callbacks.extractProgress(progress.percent, progress.fileCount) }) - stream.on('error', (err: Error) => { - this.callbacks.error({ header: 'Extract Failed.', body: `Unable to extract [${filename}]: ${err.name}` }, () => this.extract(filename)) + let extractErrorOccured = false + stream.on('error', () => { + extractErrorOccured = true + console.log(`Failed to extract [${filename}], retrying with .rar extractor...`) + this.extract(filename, true) }) stream.on('end', () => { - this.transfer(source) + if (!extractErrorOccured) { + this.transfer(source) + } }) } @@ -104,7 +113,7 @@ export class FileExtractor { const destinationFolder = join(this.libraryFolder, this.destinationFolderName) this.callbacks.transfer(destinationFolder) try { - await access(destinationFolder, constants.F_OK) + await access(destinationFolder, fs.constants.F_OK) } catch (e) { await mkdir(destinationFolder) } diff --git a/src/electron/ipc/download/RarExtractor.ts b/src/electron/ipc/download/RarExtractor.ts new file mode 100644 index 0000000..1dcc8d3 --- /dev/null +++ b/src/electron/ipc/download/RarExtractor.ts @@ -0,0 +1,39 @@ +import * as fs from 'fs' +import { join } from 'path' +import * as unrarjs from 'node-unrar-js' +import { promisify } from 'util' + +const mkdir = promisify(fs.mkdir) + +/** + * Extracts the archive at `sourceFile` to a new folder in `destinationFolder`. Throws an error when this fails. + */ +export async function extractRar(sourceFile: string, destinationFolder: string) { + const extractor = unrarjs.createExtractorFromFile(sourceFile, destinationFolder) + + const fileList = extractor.getFileList() + + 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(destinationFolder, header.name), { recursive: true }) + } catch (e) { + throw new Error(`Failed to extract directory: ${e}`) + } + } + } + } else { + console.log('Warning: failed to read .rar files: ', fileList[0].reason, fileList[0].msg) + } + + // Extract archive + const extractResult = extractor.extractAll() + + if (extractResult[0].state == 'FAIL') { + throw new Error(`${extractResult[0].reason}: ${extractResult[0].msg}`) + } +} \ No newline at end of file