Skip to content

Commit

Permalink
Feature/530 queue retention (#644)
Browse files Browse the repository at this point in the history
* Preparing for QueuePersister

* Adds hook to perform closing tasks

* Prepares PlaybackService for saving and restoring of queue

* Saving of queue on close works

* Restoring works
  • Loading branch information
digimezzo authored Aug 4, 2024
1 parent 0e67f0b commit 3e7396e
Show file tree
Hide file tree
Showing 24 changed files with 319 additions and 67 deletions.
21 changes: 15 additions & 6 deletions main.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion main.js.map

Large diffs are not rendered by default.

23 changes: 15 additions & 8 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,16 +205,15 @@ function createMainWindow(): void {
});

mainWindow.on('close', (event: any) => {
if (shouldCloseToNotificationArea()) {
if (!isQuitting) {
event.preventDefault();

if (mainWindow) {
if (!isQuitting) {
event.preventDefault();
if (mainWindow) {
if (shouldCloseToNotificationArea()) {
mainWindow.hide();
} else {
mainWindow.webContents.send('application-close');
}
}

return false;
}
});
}
Expand Down Expand Up @@ -312,7 +311,9 @@ try {
{
label: arg.exitLabel,
click(): void {
app.quit();
if (process.platform !== 'darwin') {
app.quit();
}
},
},
]);
Expand Down Expand Up @@ -345,6 +346,12 @@ try {
}
});
});

ipcMain.on('closing-tasks-performed', (_) => {
if (process.platform !== 'darwin') {
app.quit();
}
});
}
} catch (e) {
log.error(`[Main] [Main] Could not start. Error: ${e.message}`);
Expand Down
17 changes: 16 additions & 1 deletion src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { AddToPlaylistMenu } from './ui/components/add-to-playlist-menu';
import { DesktopBase } from './common/io/desktop.base';
import { AudioVisualizer } from './services/playback/audio-visualizer';
import { PlaybackServiceBase } from './services/playback/playback.service.base';
import { LifetimeService } from './services/lifetime/lifetime.service';

describe('AppComponent', () => {
let playbackServiceMock: IMock<PlaybackServiceBase>;
Expand All @@ -27,6 +28,7 @@ describe('AppComponent', () => {
let trayServiceMock: IMock<TrayServiceBase>;
let mediaSessionServiceMock: IMock<MediaSessionServiceBase>;
let eventListenerServiceMock: IMock<EventListenerServiceBase>;
let lifetimeServiceMock: IMock<LifetimeService>;
let audioVisualizerMock: IMock<AudioVisualizer>;

let addToPlaylistMenuMock: IMock<AddToPlaylistMenu>;
Expand All @@ -50,6 +52,7 @@ describe('AppComponent', () => {
trayServiceMock.object,
mediaSessionServiceMock.object,
eventListenerServiceMock.object,
lifetimeServiceMock.object,
addToPlaylistMenuMock.object,
desktopMock.object,
loggerMock.object,
Expand All @@ -68,6 +71,7 @@ describe('AppComponent', () => {
trayServiceMock = Mock.ofType<TrayServiceBase>();
mediaSessionServiceMock = Mock.ofType<MediaSessionServiceBase>();
eventListenerServiceMock = Mock.ofType<EventListenerServiceBase>();
lifetimeServiceMock = Mock.ofType<LifetimeService>();
addToPlaylistMenuMock = Mock.ofType<AddToPlaylistMenu>();
desktopMock = Mock.ofType<DesktopBase>();
loggerMock = Mock.ofType<Logger>();
Expand Down Expand Up @@ -213,7 +217,18 @@ describe('AppComponent', () => {
await app.ngOnInit();

// Assert
playbackServiceMock.verify((x) => x.initialize(), Times.once());
playbackServiceMock.verify((x) => x.initializeAsync(), Times.once());
});

it('should initialize LifetimeService', async () => {
// Arrange
const app: AppComponent = createComponent();

// Act
await app.ngOnInit();

// Assert
lifetimeServiceMock.verify((x) => x.initialize(), Times.once());
});

it('should connect audio visualizer audio element', async () => {
Expand Down
6 changes: 4 additions & 2 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { EventListenerServiceBase } from './services/event-listener/event-listen
import { AddToPlaylistMenu } from './ui/components/add-to-playlist-menu';
import { DesktopBase } from './common/io/desktop.base';
import { AudioVisualizer } from './services/playback/audio-visualizer';
import { PlaybackService } from './services/playback/playback.service';
import { PlaybackServiceBase } from './services/playback/playback.service.base';
import { LifetimeService } from './services/lifetime/lifetime.service';

@Component({
selector: 'app-root',
Expand All @@ -40,6 +40,7 @@ export class AppComponent implements OnInit {
private trayService: TrayServiceBase,
private mediaSessionService: MediaSessionServiceBase,
private eventListenerService: EventListenerServiceBase,
private lifetimeService: LifetimeService,
private addToPlaylistMenu: AddToPlaylistMenu,
private desktop: DesktopBase,
private logger: Logger,
Expand Down Expand Up @@ -91,6 +92,7 @@ export class AppComponent implements OnInit {
this.scrobblingService.initialize();
this.eventListenerService.listenToEvents();
await this.navigationService.navigateToLoadingAsync();
this.playbackService.initialize();
await this.playbackService.initializeAsync();
this.lifetimeService.initialize();
}
}
4 changes: 3 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ import { AlbumArtworkRepositoryBase } from './data/repositories/album-artwork-re
import { AlbumArtworkRepository } from './data/repositories/album-artwork-repository';
import { AlbumArtworkCacheService } from './services/album-artwork-cache/album-artwork-cache.service';
import { AlbumArtworkCacheServiceBase } from './services/album-artwork-cache/album-artwork-cache.service.base';
import { QueuedTrackRepositoryBase } from './data/repositories/queued-track-repository.base';
import { QueuedTrackRepository } from './data/repositories/queued-track-repository';

export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
Expand Down Expand Up @@ -502,7 +504,6 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
Hacks,
Shuffler,
ProgressUpdater,
Queue,
MathExtensions,
PathValidator,
AlbumRowsGetter,
Expand Down Expand Up @@ -566,6 +567,7 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
{ provide: TrackRepositoryBase, useClass: TrackRepository },
{ provide: FolderRepositoryBase, useClass: FolderRepository },
{ provide: AlbumArtworkRepositoryBase, useClass: AlbumArtworkRepository },
{ provide: QueuedTrackRepositoryBase, useClass: QueuedTrackRepository },
{ provide: ApplicationServiceBase, useClass: ApplicationService },
{ provide: NavigationServiceBase, useClass: NavigationService },
{ provide: IndexingServiceBase, useClass: IndexingService },
Expand Down
5 changes: 3 additions & 2 deletions src/app/common/io/ipc-proxy.base.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {Observable} from "rxjs";
import {IIndexingMessage} from "../../services/indexing/messages/i-indexing-message";
import { Observable } from 'rxjs';
import { IIndexingMessage } from '../../services/indexing/messages/i-indexing-message';

export abstract class IpcProxyBase {
public abstract onIndexingWorkerMessage$: Observable<IIndexingMessage>;
public abstract onIndexingWorkerExit$: Observable<void>;
public abstract onApplicationClose$: Observable<void>;

public abstract sendToMainProcess(channel: string, arg: unknown): void;
}
16 changes: 11 additions & 5 deletions src/app/common/io/ipc-proxy.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import {Injectable} from '@angular/core';
import {ipcRenderer} from 'electron';
import {IpcProxyBase} from './ipc-proxy.base';
import {Observable, Subject} from "rxjs";
import {IIndexingMessage} from "../../services/indexing/messages/i-indexing-message";
import { Injectable } from '@angular/core';
import { ipcRenderer } from 'electron';
import { IpcProxyBase } from './ipc-proxy.base';
import { Observable, Subject } from 'rxjs';
import { IIndexingMessage } from '../../services/indexing/messages/i-indexing-message';

@Injectable()
export class IpcProxy implements IpcProxyBase {
private onIndexingWorkerMessage: Subject<IIndexingMessage> = new Subject();
private onIndexingWorkerExit: Subject<void> = new Subject();
private onApplicationClose: Subject<void> = new Subject();

public constructor() {
ipcRenderer.on('indexing-worker-message', (_: Electron.IpcRendererEvent, message: IIndexingMessage): void => {
Expand All @@ -20,10 +21,15 @@ export class IpcProxy implements IpcProxyBase {
ipcRenderer.on('indexing-worker-exit', async () => {
this.onIndexingWorkerExit.next();
});

ipcRenderer.on('application-close', (_) => {
this.onApplicationClose.next();
});
}

public onIndexingWorkerMessage$: Observable<IIndexingMessage> = this.onIndexingWorkerMessage.asObservable();
public onIndexingWorkerExit$: Observable<void> = this.onIndexingWorkerExit.asObservable();
public onApplicationClose$: Observable<void> = this.onApplicationClose.asObservable();

public sendToMainProcess(channel: string, arg: unknown): void {
ipcRenderer.send(channel, arg);
Expand Down
2 changes: 1 addition & 1 deletion src/app/common/settings/settings.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,5 @@ export abstract class SettingsBase {
public abstract albumsDefinedByFolders: boolean;
public abstract playbackControlsLoop: number;
public abstract playbackControlsShuffle: number;
public abstract rememberPlaybackControlsAfterRestart: boolean;
public abstract rememberPlaybackStateAfterRestart: boolean;
}
14 changes: 7 additions & 7 deletions src/app/common/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,13 +723,13 @@ export class Settings implements SettingsBase {
this.settings.set('playbackControlsShuffle', v);
}

// rememberPlaybackControlsAfterRestart
public get rememberPlaybackControlsAfterRestart(): boolean {
return <boolean>this.settings.get('rememberPlaybackControlsAfterRestart');
// rememberPlaybackStateAfterRestart
public get rememberPlaybackStateAfterRestart(): boolean {
return <boolean>this.settings.get('rememberPlaybackStateAfterRestart');
}

public set rememberPlaybackControlsAfterRestart(v: boolean) {
this.settings.set('rememberPlaybackControlsAfterRestart', v);
public set rememberPlaybackStateAfterRestart(v: boolean) {
this.settings.set('rememberPlaybackStateAfterRestart', v);
}

// Initialize
Expand Down Expand Up @@ -1038,8 +1038,8 @@ export class Settings implements SettingsBase {
this.settings.set('playbackControlsShuffle', false);
}

if (!this.settings.has('rememberPlaybackControlsAfterRestart')) {
this.settings.set('rememberPlaybackControlsAfterRestart', true);
if (!this.settings.has('rememberPlaybackStateAfterRestart')) {
this.settings.set('rememberPlaybackStateAfterRestart', true);
}
}
}
15 changes: 0 additions & 15 deletions src/app/data/entities/folder.spec.ts

This file was deleted.

8 changes: 8 additions & 0 deletions src/app/data/entities/queued-track.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class QueuedTrack {
public constructor(public path: string) {}

public queuedTrackId: number;
public isPlaying: number;
public progressSeconds: number;
public orderId: number;
}
6 changes: 6 additions & 0 deletions src/app/data/repositories/queued-track-repository.base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { QueuedTrack } from '../entities/queued-track';

export abstract class QueuedTrackRepositoryBase {
public abstract getSavedQueuedTracks(): QueuedTrack[] | undefined;
public abstract saveQueuedTracks(tracks: QueuedTrack[]): void;
}
51 changes: 51 additions & 0 deletions src/app/data/repositories/queued-track-repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Injectable } from '@angular/core';
import { QueuedTrackRepositoryBase } from './queued-track-repository.base';
import { DatabaseFactory } from '../database-factory';
import { QueuedTrack } from '../entities/queued-track';

@Injectable()
export class QueuedTrackRepository implements QueuedTrackRepositoryBase {
public constructor(private databaseFactory: DatabaseFactory) {}

public getSavedQueuedTracks(): QueuedTrack[] | undefined {
const database: any = this.databaseFactory.create();

const statement = database.prepare(
`SELECT QueuedTrackID as queuedTrackId,
Path as path,
IsPlaying as isPlaying,
ProgressSeconds as progressSeconds,
OrderID as orderId
FROM QueuedTrack
ORDER BY QueuedTrackID;`,
);

const queuedTracks: QueuedTrack[] | undefined = statement.all();

return queuedTracks;
}

public saveQueuedTracks(tracks: QueuedTrack[]): void {
const database: any = this.databaseFactory.create();

database.exec('BEGIN TRANSACTION;');

// First, clear old queued tracks.
database.exec('DELETE FROM QueuedTrack;');

// Then, insert new queued tracks.
for (const track of tracks) {
const statement = database.prepare(
'INSERT INTO QueuedTrack (Path, SafePath, IsPlaying, ProgressSeconds, OrderID) VALUES (?, ?, ?, ?, ?);',
);
statement.run(track.path, track.path.toLowerCase(), track.isPlaying, track.progressSeconds, track.orderId);
}

database.exec('COMMIT;');
}
}
23 changes: 23 additions & 0 deletions src/app/services/lifetime/lifetime.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { IpcProxyBase } from '../../common/io/ipc-proxy.base';
import { Injectable } from '@angular/core';
import { PlaybackServiceBase } from '../playback/playback.service.base';

@Injectable({ providedIn: 'root' })
export class LifetimeService {
public constructor(
private playbackService: PlaybackServiceBase,
private ipcProxy: IpcProxyBase,
) {}

public initialize(): void {
this.ipcProxy.onApplicationClose$.subscribe(() => {
this.performClosingTasks();
});
}

private performClosingTasks(): void {
this.playbackService.saveQueue();

this.ipcProxy.sendToMainProcess('closing-tasks-performed', undefined);
}
}
Loading

0 comments on commit 3e7396e

Please sign in to comment.