mirror of
https://github.com/Myxelium/Bridge-Multi.git
synced 2026-04-11 14:19:38 +00:00
Add database for library
This commit is contained in:
@@ -22,8 +22,10 @@
|
||||
"build:windows": "ng build -c production && tsc -p src-electron/tsconfig.electron.json && node src-electron/rename-to-mjs.js && electron-builder build --windows",
|
||||
"build:mac": "ng build -c production && tsc -p src-electron/tsconfig.electron.json && node src-electron/rename-to-mjs.js && electron-builder build --mac",
|
||||
"build:linux": "ng build -c production && tsc -p src-electron/tsconfig.electron.json && node src-electron/rename-to-mjs.js && electron-builder build --linux",
|
||||
"release": "ng build -c production && tsc -p src-electron/tsconfig.electron.json && node src-electron/rename-to-mjs.js && electron-builder build"
|
||||
},
|
||||
"release": "ng build -c production && tsc -p src-electron/tsconfig.electron.json && node src-electron/rename-to-mjs.js && electron-builder build",
|
||||
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
|
||||
"migration:add": "npx typeorm-ts-node-esm migration:generate ./src-electron/database/migrations/%npm_config_name% -d ./src-electron/database/dataSource.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "19.2.1",
|
||||
"@angular/common": "19.2.1",
|
||||
@@ -46,11 +48,14 @@
|
||||
"fs-extra": "11.2.0",
|
||||
"lodash": "4.17.21",
|
||||
"parse-sng": "4.0.3",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "7.8.1",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"scan-chart": "6.1.0",
|
||||
"sqlite3": "5.1.6",
|
||||
"three": "0.166.1",
|
||||
"tslib": "2.6.3",
|
||||
"typeorm": "^0.3.21",
|
||||
"zod": "3.23.8",
|
||||
"zone.js": "0.15.0"
|
||||
},
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<div class="border-t border-t-neutral p-2 flex gap-2 items-center max-w-full justify-between">
|
||||
<div class="flex items-center gap-[8px]">
|
||||
{{ (this.libraryService.tracks$ | async)?.length ?? 0 }} Songs
|
||||
|
||||
<button class="btn btn-sm btn-primary" (click)="fileInput.click()">Import Setlist</button>
|
||||
@if ((this.libraryService.selectedSongs$ | async)!.length > 0) {
|
||||
<button class="btn btn-sm btn-primary" (click)="exportSelected()">Export Selected</button>
|
||||
<button class="btn btn-sm btn-primary" (click)="exportSelected()">Export Selected Setlist</button>
|
||||
} @else {
|
||||
<button class="btn btn-sm btn-primary" (click)="exportPlaylist()">Export</button>
|
||||
<button class="btn btn-sm btn-primary" (click)="exportLibrary()">Export Setlist</button>
|
||||
}
|
||||
<button class="btn btn-sm btn-primary" (click)="importPlaylist()">Import</button>
|
||||
|
||||
<input type="file" #fileInput accept=".library" class="hidden" (change)="onFileSelected($event)" />
|
||||
|
||||
@if ((this.libraryService.selectedSongs$ | async)!.length === 0) {
|
||||
<button type="button" class="btn btn-sm min-w-[108px] hover:btn-error" (click)="this.libraryService.clearPlaylist()">
|
||||
<button type="button" class="btn btn-sm min-w-[108px] hover:btn-error" (click)="this.libraryService.clearLibrary()">
|
||||
<i class="bi bi-trash"></i>
|
||||
Delete all
|
||||
</button>
|
||||
} @else {
|
||||
<button type="button" class="btn btn-sm min-w-[108px] hover:btn-error" (click)="this.libraryService.removeFromPlaylist()">
|
||||
<button type="button" class="btn btn-sm min-w-[108px] hover:btn-error" (click)="this.libraryService.removeFromLibrary()">
|
||||
<i class="bi bi-trash"></i>
|
||||
Delete selected
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core'
|
||||
import { DownloadService } from '../../../core/services/download.service'
|
||||
import { LibraryService } from 'src-angular/app/core/services/library.service'
|
||||
import { ChartData } from 'src-shared/interfaces/search.interface'
|
||||
|
||||
@Component({
|
||||
selector: 'app-library-bar',
|
||||
@@ -12,7 +13,7 @@ export class LibraryBarComponent {
|
||||
|
||||
constructor(public libraryService: LibraryService, public downloadService: DownloadService) { }
|
||||
|
||||
exportPlaylist() {
|
||||
exportLibrary() {
|
||||
this.libraryService.storeLibrary()
|
||||
}
|
||||
|
||||
@@ -20,29 +21,24 @@ export class LibraryBarComponent {
|
||||
this.libraryService.storeSelectedSongs()
|
||||
}
|
||||
|
||||
importPlaylist() {
|
||||
this.libraryfileInput.nativeElement.click()
|
||||
}
|
||||
|
||||
onFileSelected(event: Event) {
|
||||
async onFileSelected(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
|
||||
if (input.files && input.files.length > 0) {
|
||||
const file = input.files[0]
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const importedTracks = JSON.parse(reader.result as string)
|
||||
if (Array.isArray(importedTracks)) {
|
||||
this.libraryService.downloadLibrary(importedTracks)
|
||||
} else {
|
||||
console.error('Invalid file format')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing file:', error)
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
if (!input.files?.length)
|
||||
return
|
||||
|
||||
const file = input.files[0]
|
||||
|
||||
try {
|
||||
const fileContent = await file.text()
|
||||
const json = JSON.parse(fileContent)
|
||||
const chartData = json as ChartData[]
|
||||
this.libraryService.downloadLibrary(chartData)
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error reading or parsing the file:', error)
|
||||
}
|
||||
|
||||
this.libraryfileInput.nativeElement.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
(keyup)="filterSongs()" />
|
||||
</div>
|
||||
<div
|
||||
*ngIf="filteredSongs.length > 0"
|
||||
*ngIf="songs.length > 0"
|
||||
class="basis-2/3 flex-1 overflow-y-auto scrollbar scrollbar-w-2 scrollbar-h-2 scrollbar-track-base-300 scrollbar-thumb-neutral scrollbar-thumb-rounded-full h-[calc(100vh-164px)]">
|
||||
<table id="resultTable" class="table table-zebra table-pin-rows" [class.table-xs]="settingsService.isCompactTable">
|
||||
<thead>
|
||||
@@ -36,7 +36,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let song of filteredSongs; trackBy: trackByFn">
|
||||
<tr *ngFor="let song of songs; trackBy: trackByFn">
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -55,13 +55,6 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="align-center h-[calc(100vh-169.5px)]"
|
||||
*ngIf="songs.length > 1 && filteredSongs.length < 1"
|
||||
style="display: flex; justify-content: center; align-items: center">
|
||||
<p class="text-center" style="font-size: 1.5rem">No songs found!</p>
|
||||
</div>
|
||||
|
||||
<div class="flex align-center justify-center items-center h-[calc(100vh-169.5px)]" *ngIf="songs.length < 1 && filteredSongs.length === 0">
|
||||
<div class="flex align-center justify-center items-center h-[calc(100vh-169.5px)]" *ngIf="songs.length < 1">
|
||||
<p class="text-center" style="font-size: 1.5rem">No songs added!</p>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,6 @@ export class LibraryTableComponent implements OnInit, OnDestroy {
|
||||
songs: ChartData[] = []
|
||||
sortDirection: 'asc' | 'desc' = 'asc'
|
||||
sortColumn: SortColumn = null
|
||||
filteredSongs: ChartData[] = []
|
||||
searchTerm: string = ''
|
||||
allRowsSelected: boolean = false
|
||||
subscriptions: Subscription[] = []
|
||||
@@ -45,10 +44,9 @@ export class LibraryTableComponent implements OnInit, OnDestroy {
|
||||
this.libraryService.tracks$
|
||||
.subscribe(tracks => {
|
||||
this.songs = tracks
|
||||
this.filterSongs()
|
||||
})
|
||||
)
|
||||
this.filteredSongs = [...this.songs]
|
||||
|
||||
this.subscriptions.push(
|
||||
this.libraryService.selectedSongs$
|
||||
.subscribe(songs =>
|
||||
@@ -58,20 +56,11 @@ export class LibraryTableComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
filterSongs(): void {
|
||||
const term = this.searchTerm.toLowerCase()
|
||||
this.filteredSongs = this.songs.filter(
|
||||
song =>
|
||||
song.name?.toLowerCase().includes(term) ||
|
||||
song.artist?.toLowerCase().includes(term) ||
|
||||
song.album?.toLowerCase().includes(term) ||
|
||||
song.genre?.toLowerCase().includes(term) ||
|
||||
song.year?.toLowerCase().includes(term) ||
|
||||
song.charter?.toLowerCase().includes(term)
|
||||
)
|
||||
this.libraryService.getChartsBySearchTerm(this.searchTerm)
|
||||
}
|
||||
|
||||
onColClicked(column: SortColumn) {
|
||||
if (this.filteredSongs.length === 0) { return }
|
||||
if (this.songs.length === 0) { return }
|
||||
|
||||
if (this.sortColumn !== column) {
|
||||
this.sortColumn = column
|
||||
@@ -84,7 +73,7 @@ export class LibraryTableComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (this.sortColumn) {
|
||||
this.filteredSongs.sort((a, b) => {
|
||||
this.songs.sort((a, b) => {
|
||||
const valueA = a[this.sortColumn! as keyof ChartData]
|
||||
const valueB = b[this.sortColumn! as keyof ChartData]
|
||||
|
||||
@@ -116,7 +105,7 @@ export class LibraryTableComponent implements OnInit, OnDestroy {
|
||||
this.allRowsSelected = !this.allRowsSelected
|
||||
|
||||
if (this.allRowsSelected) {
|
||||
this.filteredSongs.forEach(song => this.libraryService.addToSelectedSongs(song))
|
||||
this.songs.forEach(song => this.libraryService.addToSelectedSongs(song))
|
||||
} else {
|
||||
this.libraryService.clearSelectedSongs()
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ import { Injectable, Injector } from '@angular/core'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { ChartData } from 'src-shared/interfaces/search.interface'
|
||||
import { DownloadService } from './download.service'
|
||||
|
||||
const LibraryStorageIdentifyer: string = "library"
|
||||
import { StorageService } from './storage.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -17,42 +16,39 @@ export class LibraryService {
|
||||
|
||||
private _downloadService: DownloadService | null = null
|
||||
|
||||
constructor(private injector: Injector) {
|
||||
const library = localStorage.getItem(LibraryStorageIdentifyer)
|
||||
|
||||
if (library) {
|
||||
this._tracks.next(JSON.parse(library))
|
||||
}
|
||||
constructor(private injector: Injector, private storageService: StorageService) {
|
||||
this.storageService.getChartsBySearchTerm().then(library => {
|
||||
if (library) {
|
||||
this._tracks.next(library)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private get downloadService(): DownloadService {
|
||||
if (!this._downloadService) {
|
||||
this._downloadService = this.injector.get(DownloadService)
|
||||
}
|
||||
return this._downloadService
|
||||
}
|
||||
|
||||
getPlaylist() {
|
||||
return this._tracks.value
|
||||
return this._downloadService
|
||||
}
|
||||
|
||||
libraryAdd(chart: ChartData) {
|
||||
const updatedTracks = [...this._tracks.value, chart]
|
||||
this._tracks.next(updatedTracks)
|
||||
localStorage.setItem(LibraryStorageIdentifyer, JSON.stringify(updatedTracks))
|
||||
|
||||
this.storageService.addChart(chart)
|
||||
}
|
||||
|
||||
downloadLibrary(songs: ChartData[]) {
|
||||
songs.forEach(track => {
|
||||
if (!this._tracks.value.includes(track)) {
|
||||
this.downloadService.addDownload(track)
|
||||
}
|
||||
this.downloadService.addDownload(track)
|
||||
})
|
||||
}
|
||||
|
||||
storeLibrary() {
|
||||
const fakeLink = document.createElement('a')
|
||||
const file = new Blob([JSON.stringify(this._tracks.value)], { type: 'application/json' })
|
||||
|
||||
fakeLink.href = URL.createObjectURL(file)
|
||||
fakeLink.download = 'songs.library'
|
||||
fakeLink.click()
|
||||
@@ -61,6 +57,7 @@ export class LibraryService {
|
||||
storeSelectedSongs() {
|
||||
const fakeLink = document.createElement('a')
|
||||
const file = new Blob([JSON.stringify(this._selectedSongs.value)], { type: 'application/json' })
|
||||
|
||||
fakeLink.href = URL.createObjectURL(file)
|
||||
fakeLink.download = 'selected.library'
|
||||
fakeLink.click()
|
||||
@@ -80,16 +77,28 @@ export class LibraryService {
|
||||
this._selectedSongs.next([])
|
||||
}
|
||||
|
||||
removeFromPlaylist() {
|
||||
this._selectedSongs.value.forEach(selectedSong => {
|
||||
const updatedTracks = this._tracks.value.filter(track => track !== selectedSong)
|
||||
removeFromLibrary() {
|
||||
this._selectedSongs.value.forEach((selectedSong: ChartData) => {
|
||||
const updatedTracks = this._tracks.value.filter(track => track !== selectedSong) as ChartData[]
|
||||
this._tracks.next(updatedTracks)
|
||||
localStorage.setItem(LibraryStorageIdentifyer, JSON.stringify(updatedTracks))
|
||||
this.storageService.removeChart(selectedSong.md5)
|
||||
})
|
||||
|
||||
this.clearSelectedSongs()
|
||||
}
|
||||
|
||||
clearPlaylist() {
|
||||
this._tracks.next([])
|
||||
localStorage.removeItem(LibraryStorageIdentifyer)
|
||||
async clearLibrary() {
|
||||
this.storageService.removeAllCharts()
|
||||
this.clearSelectedSongs()
|
||||
|
||||
const library = await this.storageService.getChartsBySearchTerm()
|
||||
this._tracks.next(library)
|
||||
}
|
||||
|
||||
async getChartsBySearchTerm(searchTerm?: string): Promise<ChartData[]> {
|
||||
const library = await this.storageService.getChartsBySearchTerm(searchTerm)
|
||||
|
||||
this._tracks.next(library)
|
||||
return library
|
||||
}
|
||||
}
|
||||
|
||||
27
src-angular/app/core/services/storage.service.ts
Normal file
27
src-angular/app/core/services/storage.service.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ChartData } from 'src-shared/interfaces/search.interface'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StorageService {
|
||||
async addChart(chartData: ChartData): Promise<ChartData> {
|
||||
return window.electron.invoke.addChart(chartData)
|
||||
}
|
||||
|
||||
async removeChart(md5: string): Promise<void> {
|
||||
return window.electron.invoke.removeChart(md5)
|
||||
}
|
||||
|
||||
async removeCharts(charts: ChartData[]): Promise<void> {
|
||||
return window.electron.invoke.removeCharts(charts)
|
||||
}
|
||||
|
||||
async getChartsBySearchTerm(searchTerm?: string): Promise<ChartData[]> {
|
||||
return window.electron.invoke.getChartsBySearchTerm(searchTerm)
|
||||
}
|
||||
|
||||
async removeAllCharts(): Promise<void> {
|
||||
return window.electron.emit.removeAllCharts()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IpcInvokeHandlers, IpcToMainEmitHandlers } from '../src-shared/interfaces/ipc.interface.js'
|
||||
import { download } from './ipc/DownloadHandler.ipc.js'
|
||||
import { scanIssues } from './ipc/issue-scan/IssueScanHandler.ipc.js'
|
||||
import { addChart, getChartsBySearchTerm, removeAllCharts, removeChart, removeCharts } from './ipc/LibraryHandler.ipc.js'
|
||||
import { getSettings, setSettings } from './ipc/SettingsHandler.ipc.js'
|
||||
import { downloadUpdate, getCurrentVersion, getUpdateAvailable, quitAndInstall, retryUpdate } from './ipc/UpdateHandler.ipc.js'
|
||||
import { getPlatform, getThemeColors, isMaximized, maximize, minimize, openUrl, quit, restore, showFile, showFolder, showOpenDialog, toggleDevTools } from './ipc/UtilHandlers.ipc.js'
|
||||
@@ -14,6 +15,10 @@ export function getIpcInvokeHandlers(): IpcInvokeHandlers {
|
||||
isMaximized,
|
||||
showOpenDialog,
|
||||
getThemeColors,
|
||||
addChart,
|
||||
removeChart,
|
||||
removeCharts,
|
||||
getChartsBySearchTerm,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,5 +38,6 @@ export function getIpcToMainEmitHandlers(): IpcToMainEmitHandlers {
|
||||
showFile,
|
||||
showFolder,
|
||||
scanIssues,
|
||||
removeAllCharts,
|
||||
}
|
||||
}
|
||||
|
||||
18
src-electron/database/dataSource.ts
Normal file
18
src-electron/database/dataSource.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { DataSource } from 'typeorm'
|
||||
import { Chart } from './entities/Chart.js'
|
||||
import { Init1743124434920 } from './migrations/1743124434920-init.js'
|
||||
|
||||
const migrations = [Init1743124434920]
|
||||
const entities = [Chart]
|
||||
|
||||
export const dataSource = new DataSource({
|
||||
type: "sqlite",
|
||||
database: "library.sqlite",
|
||||
entities: entities,
|
||||
// Configure migrations to use a folder that contains your migration files:
|
||||
migrations: migrations,
|
||||
// Keep synchronize off when using migrations in production
|
||||
synchronize: false,
|
||||
logging: true,
|
||||
migrationsRun: true,
|
||||
})
|
||||
125
src-electron/database/databaseService.ts
Normal file
125
src-electron/database/databaseService.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { ChartData } from 'src-shared/interfaces/search.interface.js'
|
||||
import { dataSource } from './dataSource.js'
|
||||
import { Chart } from './entities/Chart.js'
|
||||
import { Like } from 'typeorm'
|
||||
|
||||
export class DatabaseService {
|
||||
async insertChart(chartData: ChartData): Promise<ChartData> {
|
||||
try {
|
||||
if (!dataSource.isInitialized) {
|
||||
await dataSource.initialize()
|
||||
}
|
||||
|
||||
const chartRepository = dataSource.getRepository(Chart)
|
||||
|
||||
// if one already exist dont create
|
||||
const existingChart = await chartRepository.findOneBy({ md5: chartData.md5 })
|
||||
|
||||
if (existingChart) {
|
||||
return existingChart as unknown as ChartData
|
||||
}
|
||||
|
||||
const newChart = chartRepository.create({
|
||||
name: chartData.name!,
|
||||
album: chartData.album!,
|
||||
artist: chartData.artist!,
|
||||
genre: chartData.genre!,
|
||||
year: chartData.year!,
|
||||
charter: chartData.charter!,
|
||||
md5: chartData.md5,
|
||||
hasVideoBackground: chartData.hasVideoBackground,
|
||||
})
|
||||
|
||||
return await chartRepository.save(newChart) as unknown as ChartData
|
||||
} catch (error) {
|
||||
console.error('Error inserting chart:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async removeChart(md5: string): Promise<void> {
|
||||
try {
|
||||
if (!dataSource.isInitialized) {
|
||||
await dataSource.initialize()
|
||||
}
|
||||
|
||||
const chartRepository = dataSource.getRepository(Chart)
|
||||
|
||||
await chartRepository.delete({ md5 })
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error removing chart:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async removeCharts(charts: ChartData[]): Promise<void> {
|
||||
try {
|
||||
if (!dataSource.isInitialized) {
|
||||
await dataSource.initialize()
|
||||
}
|
||||
|
||||
const chartRepository = dataSource.getRepository(Chart)
|
||||
|
||||
// delete the array of charts provided using querybulilder
|
||||
charts.forEach(async chart => {
|
||||
console.log('removing chart:', chart.name)
|
||||
await chartRepository.delete({ md5: chart.md5 })
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error removing charts:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async getChartsBySearchTerm(searchTerm?: string): Promise<ChartData[]> {
|
||||
try {
|
||||
if (!dataSource.isInitialized) {
|
||||
await dataSource.initialize()
|
||||
}
|
||||
|
||||
const chartRepository = dataSource.getRepository(Chart)
|
||||
|
||||
let charts: Chart[]
|
||||
|
||||
if (searchTerm) {
|
||||
const likeSearchTerm = `%${searchTerm}%`
|
||||
charts = await chartRepository.find({
|
||||
where: [
|
||||
{ name: Like(likeSearchTerm) },
|
||||
{ album: Like(likeSearchTerm) },
|
||||
{ artist: Like(likeSearchTerm) },
|
||||
{ genre: Like(likeSearchTerm) },
|
||||
{ year: Like(likeSearchTerm) },
|
||||
{ charter: Like(likeSearchTerm) },
|
||||
],
|
||||
})
|
||||
} else {
|
||||
charts = await chartRepository.find()
|
||||
}
|
||||
|
||||
return charts as unknown as ChartData[]
|
||||
} catch (error) {
|
||||
console.error('Error fetching charts by search term:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async removeAllCharts(): Promise<void> {
|
||||
try {
|
||||
if (!dataSource.isInitialized) {
|
||||
await dataSource.initialize()
|
||||
}
|
||||
|
||||
const chartRepository = dataSource.getRepository(Chart)
|
||||
|
||||
await chartRepository.clear()
|
||||
} catch (error) {
|
||||
console.error('Error removing all charts:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const databaseService = new DatabaseService()
|
||||
31
src-electron/database/entities/Chart.ts
Normal file
31
src-electron/database/entities/Chart.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity()
|
||||
export class Chart {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string
|
||||
|
||||
@Column()
|
||||
md5: string
|
||||
|
||||
@Column()
|
||||
hasVideoBackground: boolean
|
||||
|
||||
@Column()
|
||||
charter: string
|
||||
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@Column()
|
||||
artist: string
|
||||
|
||||
@Column()
|
||||
album: string
|
||||
|
||||
@Column()
|
||||
genre: string
|
||||
|
||||
@Column()
|
||||
year: string
|
||||
}
|
||||
14
src-electron/database/migrations/1743124434920-init.ts
Normal file
14
src-electron/database/migrations/1743124434920-init.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Init1743124434920 implements MigrationInterface {
|
||||
name = 'Init1743124434920'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "chart" ("id" varchar PRIMARY KEY NOT NULL, "md5" varchar NOT NULL, "hasVideoBackground" boolean NOT NULL, "charter" varchar NOT NULL, "name" varchar NOT NULL, "artist" varchar NOT NULL, "album" varchar NOT NULL, "genre" varchar NOT NULL, "year" varchar NOT NULL)`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "chart"`);
|
||||
}
|
||||
|
||||
}
|
||||
10
src-electron/database/readme.md
Normal file
10
src-electron/database/readme.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## Migrations
|
||||
In order to create a new migration, there is a some steps to go through.
|
||||
|
||||
1. Run ``npm run migration:add --name <migration name>`` This currently work for Windows machines. If using Linux or Mac run this instead ``npx typeorm-ts-node-esm migration:generate ./src-electron/database/migrations/<migration name> -d ./src-electron/database/dataSource.ts``
|
||||
|
||||
2. Go to ``./src-electron/database/dataSource.ts`` and add the newly
|
||||
generated migration to the migrations array and entity variables. In that way it will automatically apply the latest changes to the database on startup.
|
||||
|
||||
## The database
|
||||
A Sqlite database file is automatically created on startup named Library, it will be placed in the same directory as the executable.
|
||||
47
src-electron/ipc/LibraryHandler.ipc.ts
Normal file
47
src-electron/ipc/LibraryHandler.ipc.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { databaseService } from '../database/databaseService.js'
|
||||
import { ChartData } from 'src-shared/interfaces/search.interface.js'
|
||||
|
||||
export async function addChart(chartData: ChartData): Promise<ChartData> {
|
||||
try {
|
||||
return await databaseService.insertChart(chartData)
|
||||
} catch (error) {
|
||||
console.error('Error in addChartHandler:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeChart(md5: string): Promise<void> {
|
||||
try {
|
||||
await databaseService.removeChart(md5)
|
||||
} catch (error) {
|
||||
console.error('Error in removeChartHandler:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeCharts(charts: ChartData[]): Promise<void> {
|
||||
try {
|
||||
await databaseService.removeCharts(charts)
|
||||
} catch (error) {
|
||||
console.error('Error in removeChartsHandler:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function removeAllCharts(): Promise<void> {
|
||||
try {
|
||||
await databaseService.removeAllCharts()
|
||||
} catch (error) {
|
||||
console.error('Error in removeAllChartsHandler:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function getChartsBySearchTerm(searchTerm?: string): Promise<ChartData[]> {
|
||||
try {
|
||||
return await databaseService.getChartsBySearchTerm(searchTerm)
|
||||
} catch (error) {
|
||||
console.error('Error in getChartsHandler:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,13 @@ import electronUnhandled from 'electron-unhandled'
|
||||
import windowStateKeeper from 'electron-window-state'
|
||||
import * as path from 'path'
|
||||
import * as url from 'url'
|
||||
|
||||
import "reflect-metadata"
|
||||
import { IpcFromMainEmitEvents } from '../src-shared/interfaces/ipc.interface.js'
|
||||
import { dataPath } from '../src-shared/Paths.js'
|
||||
import { settings } from './ipc/SettingsHandler.ipc.js'
|
||||
import { retryUpdate } from './ipc/UpdateHandler.ipc.js'
|
||||
import { getIpcInvokeHandlers, getIpcToMainEmitHandlers } from './IpcHandler.js'
|
||||
import { dataSource } from './database/dataSource.js'
|
||||
|
||||
electronUnhandled({ showDialog: true, logger: err => console.log('Error: Unhandled Rejection:', err) })
|
||||
|
||||
@@ -26,6 +27,14 @@ app.on('ready', async () => {
|
||||
if (!isDevBuild) {
|
||||
retryUpdate()
|
||||
}
|
||||
|
||||
// Initialize the database
|
||||
dataSource.initialize().then(() => {
|
||||
console.log('Database initialized')
|
||||
}
|
||||
).catch(error => {
|
||||
console.error('Error initializing database:', error)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,10 @@ const electronApi: ContextBridgeApi = {
|
||||
isMaximized: getInvoker('isMaximized'),
|
||||
showOpenDialog: getInvoker('showOpenDialog'),
|
||||
getThemeColors: getInvoker('getThemeColors'),
|
||||
addChart: getInvoker('addChart'),
|
||||
removeChart: getInvoker('removeChart'),
|
||||
removeCharts: getInvoker('removeCharts'),
|
||||
getChartsBySearchTerm: getInvoker('getChartsBySearchTerm'),
|
||||
},
|
||||
emit: {
|
||||
download: getEmitter('download'),
|
||||
@@ -41,6 +45,7 @@ const electronApi: ContextBridgeApi = {
|
||||
showFolder: getEmitter('showFolder'),
|
||||
showFile: getEmitter('showFile'),
|
||||
scanIssues: getEmitter('scanIssues'),
|
||||
removeAllCharts: getEmitter('removeAllCharts'),
|
||||
},
|
||||
on: {
|
||||
errorLog: getListenerAdder('errorLog'),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Settings } from '../Settings.js'
|
||||
import { Download, DownloadProgress } from './download.interface.js'
|
||||
import { ThemeColors } from './theme.interface.js'
|
||||
import { UpdateProgress } from './update.interface.js'
|
||||
import { ChartData } from './search.interface.js'
|
||||
|
||||
export interface ContextBridgeApi {
|
||||
invoke: IpcInvokeHandlers
|
||||
@@ -50,6 +51,22 @@ export interface IpcInvokeEvents {
|
||||
input: string
|
||||
output: ThemeColors | null
|
||||
}
|
||||
addChart: {
|
||||
input: ChartData
|
||||
output: ChartData
|
||||
}
|
||||
removeChart: {
|
||||
input: string
|
||||
output: void
|
||||
}
|
||||
removeCharts: {
|
||||
input: ChartData[]
|
||||
output: void
|
||||
}
|
||||
getChartsBySearchTerm: {
|
||||
input?: string
|
||||
output: ChartData[]
|
||||
}
|
||||
}
|
||||
|
||||
export type IpcInvokeHandlers = {
|
||||
@@ -75,6 +92,7 @@ export interface IpcToMainEmitEvents {
|
||||
showFolder: string
|
||||
showFile: string
|
||||
scanIssues: void
|
||||
removeAllCharts: void
|
||||
}
|
||||
|
||||
export type IpcToMainEmitHandlers = {
|
||||
|
||||
Reference in New Issue
Block a user