Restructure

This commit is contained in:
Geomitron
2023-11-27 18:53:09 -06:00
parent 558d76f582
commit 49c3f38f99
758 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
import { basename, parse } from 'path'
import { getSettingsHandler } from '../ipc/SettingsHandler.ipc'
import { emitIPCEvent } from '../main'
import { lower } from './UtilFunctions'
/**
* @returns The relative filepath from the library folder to `absoluteFilepath`.
*/
export function getRelativeFilepath(absoluteFilepath: string) {
const settings = getSettingsHandler.getSettings()
return basename(settings.libraryPath) + absoluteFilepath.substring(settings.libraryPath.length)
}
/**
* @returns `true` if `name` has a valid video file extension.
*/
export function hasVideoExtension(name: string) {
return (['.mp4', '.avi', '.webm', '.ogv', '.mpeg'].includes(parse(lower(name)).ext))
}
/**
* 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: unknown[]) {
emitIPCEvent('log', messages)
}

123
src-shared/IPCHandler.ts Normal file
View File

@@ -0,0 +1,123 @@
import { UpdateInfo } from 'electron-updater'
import { albumArtHandler } from '../ipc/browse/AlbumArtHandler.ipc'
import { batchSongDetailsHandler } from '../ipc/browse/BatchSongDetailsHandler.ipc'
import { searchHandler } from '../ipc/browse/SearchHandler.ipc'
import { songDetailsHandler } from '../ipc/browse/SongDetailsHandler.ipc'
import { clearCacheHandler } from '../ipc/CacheHandler.ipc'
import { downloadHandler } from '../ipc/download/DownloadHandler'
import { openURLHandler } from '../ipc/OpenURLHandler.ipc'
import { getSettingsHandler, setSettingsHandler } from '../ipc/SettingsHandler.ipc'
import { downloadUpdateHandler, getCurrentVersionHandler, getUpdateAvailableHandler, quitAndInstallHandler, updateChecker, UpdateProgress } from '../ipc/UpdateHandler.ipc'
import { Download, DownloadProgress } from './interfaces/download.interface'
import { SongResult, SongSearch } from './interfaces/search.interface'
import { AlbumArtResult, VersionResult } from './interfaces/songDetails.interface'
import { Settings } from './Settings'
/**
* To add a new IPC listener:
* 1.) Write input/output interfaces
* 2.) Add the event to IPCEvents
* 3.) Write a class that implements IPCHandler
* 4.) Add the class to getIPCHandlers
*/
export function getIPCInvokeHandlers(): IPCInvokeHandler<keyof IPCInvokeEvents>[] {
return [
getSettingsHandler,
clearCacheHandler,
searchHandler,
songDetailsHandler,
batchSongDetailsHandler,
albumArtHandler,
getCurrentVersionHandler,
getUpdateAvailableHandler,
]
}
/**
* The list of possible async IPC events that return values, mapped to their input and output types.
*/
export interface IPCInvokeEvents {
'get-settings': {
input: undefined
output: Settings
}
'clear-cache': {
input: undefined
output: void
}
'song-search': {
input: SongSearch
output: SongResult[]
}
'album-art': {
input: SongResult['id']
output: AlbumArtResult
}
'song-details': {
input: SongResult['id']
output: VersionResult[]
}
'batch-song-details': {
input: number[]
output: VersionResult[]
}
'get-current-version': {
input: undefined
output: string
}
'get-update-available': {
input: undefined
output: boolean
}
}
/**
* Describes an object that handles the `E` async IPC event that will return a value.
*/
export interface IPCInvokeHandler<E extends keyof IPCInvokeEvents> {
event: E
handler(data: IPCInvokeEvents[E]['input']): Promise<IPCInvokeEvents[E]['output']> | IPCInvokeEvents[E]['output']
}
export function getIPCEmitHandlers(): IPCEmitHandler<keyof IPCEmitEvents>[] {
return [
downloadHandler,
setSettingsHandler,
downloadUpdateHandler,
updateChecker,
quitAndInstallHandler,
openURLHandler,
]
}
/**
* The list of possible async IPC events that don't return values, mapped to their input types.
*/
export interface IPCEmitEvents {
'log': any[]
'download': Download
'download-updated': DownloadProgress
'set-settings': Settings
'queue-updated': number[]
'update-error': Error
'update-available': UpdateInfo
'update-progress': UpdateProgress
'update-downloaded': undefined
'download-update': undefined
'retry-update': undefined
'quit-and-install': undefined
'open-url': string
}
/**
* Describes an object that handles the `E` async IPC event that will not return a value.
*/
export interface IPCEmitHandler<E extends keyof IPCEmitEvents> {
event: E
handler(data: IPCEmitEvents[E]): void
}

18
src-shared/Paths.ts Normal file
View File

@@ -0,0 +1,18 @@
import { app } from 'electron'
import { join } from 'path'
// Data paths
export const dataPath = join(app.getPath('userData'), 'bridge_data')
export const libraryPath = join(dataPath, 'library.db')
export const settingsPath = join(dataPath, 'settings.json')
export const tempPath = join(dataPath, 'temp')
export const themesPath = join(dataPath, 'themes')
// URL
export const serverURL = 'bridge-db.net'
// OAuth callback server
export const SERVER_PORT = 42813
export const REDIRECT_BASE = `http://127.0.0.1:${SERVER_PORT}`
export const REDIRECT_PATH = `/oauth2callback`
export const REDIRECT_URI = `${REDIRECT_BASE}${REDIRECT_PATH}`

19
src-shared/Settings.ts Normal file
View File

@@ -0,0 +1,19 @@
/**
* Represents Bridge's user settings.
*/
export interface Settings {
rateLimitDelay: number // Number of seconds to wait between each file download from Google servers
downloadVideos: boolean // If background videos should be downloaded
theme: string // The name of the currently enabled UI theme
libraryPath: string // The path to the user's library
}
/**
* Bridge's default user settings.
*/
export const defaultSettings: Settings = {
rateLimitDelay: 31,
downloadVideos: true,
theme: 'Default',
libraryPath: undefined,
}

View File

@@ -0,0 +1,61 @@
import randomBytes from 'randombytes'
import sanitize from 'sanitize-filename'
// WARNING: do not import anything related to Electron; the code will not compile correctly.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AnyFunction = (...args: any) => any
/**
* @returns `filename` with all invalid filename characters replaced.
*/
export function sanitizeFilename(filename: string): string {
const newFilename = sanitize(filename, {
replacement: ((invalidChar: string) => {
switch (invalidChar) {
case '<': return ''
case '>': return ''
case ':': return ''
case '"': return "'"
case '/': return ''
case '\\': return ''
case '|': return '⏐'
case '?': return ''
case '*': return ''
default: return '_'
}
}),
})
return (newFilename == '' ? randomBytes(5).toString('hex') : newFilename)
}
/**
* @returns `text` converted to lower case.
*/
export function lower(text: string) {
return text.toLowerCase()
}
/**
* Converts `val` from the range (`fromStart`, `fromEnd`) to the range (`toStart`, `toEnd`).
*/
export function interpolate(val: number, fromStart: number, fromEnd: number, toStart: number, toEnd: number) {
return ((val - fromStart) / (fromEnd - fromStart)) * (toEnd - toStart) + toStart
}
/**
* @returns `objectList` split into multiple groups, where each group contains objects where every one of its values in `keys` matches.
*/
export function groupBy<T>(objectList: T[], ...keys: (keyof T)[]) {
const results: T[][] = []
for (const object of objectList) {
const matchingGroup = results.find(result => keys.every(key => result[0][key] == object[key]))
if (matchingGroup != undefined) {
matchingGroup.push(object)
} else {
results.push([object])
}
}
return results
}

View File

@@ -0,0 +1,38 @@
import { DriveChart } from './songDetails.interface'
/**
* Represents a user's request to interact with the download system.
*/
export interface Download {
action: 'add' | 'retry' | 'cancel'
versionID: number
data?: NewDownload // Should be defined if action == 'add'
}
/**
* Contains the data required to start downloading a single chart.
*/
export interface NewDownload {
chartName: string
artist: string
charter: string
driveData: DriveChart & { inChartPack: boolean }
}
/**
* Represents the download progress of a single chart.
*/
export interface DownloadProgress {
versionID: number
title: string
header: string
description: string
percent: number
type: ProgressType
/** If `description` contains a filepath that can be clicked */
isLink: boolean
/** If the download should not appear in the total download progress */
stale?: boolean
}
export type ProgressType = 'good' | 'error' | 'cancel' | 'done'

View File

@@ -0,0 +1,91 @@
/**
* Represents a user's song search query.
*/
export interface SongSearch { // TODO: make limit a setting that's not always 51
query: string
quantity: 'all' | 'any'
similarity: 'similar' | 'exact'
fields: SearchFields
tags: SearchTags
instruments: SearchInstruments
difficulties: SearchDifficulties
minDiff: number
maxDiff: number
limit: number
offset: number
}
export function getDefaultSearch(): SongSearch {
return {
query: '',
quantity: 'all',
similarity: 'similar',
fields: { name: true, artist: true, album: true, genre: true, year: true, charter: true, tag: true },
tags: {
'sections': false, 'star power': false, 'forcing': false, 'taps': false, 'lyrics': false,
'video': false, 'stems': false, 'solo sections': false, 'open notes': false
},
instruments: {
guitar: false, bass: false, rhythm: false, keys: false,
drums: false, guitarghl: false, bassghl: false, vocals: false
},
difficulties: { expert: false, hard: false, medium: false, easy: false },
minDiff: 0,
maxDiff: 6,
limit: 50 + 1,
offset: 0,
}
}
export interface SearchFields {
name: boolean
artist: boolean
album: boolean
genre: boolean
year: boolean
charter: boolean
tag: boolean
}
export interface SearchTags {
'sections': boolean // Tag inverted
'star power': boolean // Tag inverted
'forcing': boolean // Tag inverted
'taps': boolean
'lyrics': boolean
'video': boolean
'stems': boolean
'solo sections': boolean
'open notes': boolean
}
export interface SearchInstruments {
guitar: boolean
bass: boolean
rhythm: boolean
keys: boolean
drums: boolean
guitarghl: boolean
bassghl: boolean
vocals: boolean
}
export interface SearchDifficulties {
expert: boolean
hard: boolean
medium: boolean
easy: boolean
}
/**
* Represents a single song search result.
*/
export interface SongResult {
id: number
chartCount: number
name: string
artist: string
album: string
genre: string
year: string
}

View File

@@ -0,0 +1,101 @@
/**
* The image data for a song's album art.
*/
export interface AlbumArtResult {
base64Art: string
}
/**
* Represents a single chart version.
*/
export interface VersionResult {
versionID: number
chartID: number
songID: number
latestVersionID: number
latestSetlistVersionID: number
name: string
chartName: string
artist: string
album: string
genre: string
year: string
songDataIncorrect: boolean
driveData: DriveChart & { inChartPack: boolean }
md5: string
lastModified: string
icon: string
charters: string
charterIDs: string
tags: string | null
songLength: number
diff_band: number
diff_guitar: number
diff_bass: number
diff_rhythm: number
diff_drums: number
diff_keys: number
diff_guitarghl: number
diff_bassghl: number
chartData: ChartData
}
export interface DriveChart {
source: DriveSource
isArchive: boolean
downloadPath: string
filesHash: string
folderName: string
folderID: string
files: DriveFile[]
}
export interface DriveSource {
isSetlistSource: boolean
isDriveFileSource?: boolean
setlistIcon?: string
sourceUserIDs: number[]
sourceName: string
sourceDriveID: string
proxyLink?: string
}
export interface DriveFile {
id: string
name: string
mimeType: string
webContentLink: string
modifiedTime: string
md5Checksum: string
size: string
}
export interface ChartData {
hasSections: boolean
hasStarPower: boolean
hasForced: boolean
hasTap: boolean
hasOpen: {
[instrument: string]: boolean
}
hasSoloSections: boolean
hasLyrics: boolean
is120: boolean
hasBrokenNotes: boolean
noteCounts: {
[instrument in Instrument]: {
[difficulty in ChartedDifficulty]: number
}
}
/** number of seconds */
length: number
/** number of seconds */
effectiveLength: number
}
export type Instrument = 'guitar' | 'bass' | 'rhythm' | 'keys' | 'drums' | 'guitarghl' | 'bassghl' | 'vocals' | 'undefined'
export type ChartedDifficulty = 'x' | 'h' | 'm' | 'e'
export function getInstrumentIcon(instrument: Instrument) {
return `${instrument}.png`
}