diff --git a/angular.webpack.js b/angular.webpack.js index 74294d332..95262ef7e 100644 --- a/angular.webpack.js +++ b/angular.webpack.js @@ -20,6 +20,5 @@ module.exports = { net: 'commonjs net', querystring: 'commonjs querystring', url: 'commonjs url', - sharp: 'commonjs sharp', }, }; diff --git a/electron-builder.config.js b/electron-builder.config.js index 47775a13c..5870c3a49 100644 --- a/electron-builder.config.js +++ b/electron-builder.config.js @@ -73,7 +73,6 @@ const config = { Name: 'Dopamine 3', Terminal: 'false', }, - asarUnpack: ['**/node_modules/sharp/**'], // Because ElectronBuilder ignores Linux. See: https://github.com/electron-userland/electron-builder/issues/6200 }, }; diff --git a/main.js b/main.js index 7668b149a..390cacb82 100644 --- a/main.js +++ b/main.js @@ -18,7 +18,6 @@ const os = require("os"); const path = require("path"); const url = require("url"); const worker_threads_1 = require("worker_threads"); -const sharp = require('sharp'); // required to use sharp in worker threads /** * Command line parameters */ diff --git a/main.js.map b/main.js.map index 26e4b78d8..3d833c90b 100644 --- a/main.js.map +++ b/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/main.ts b/main.ts index b4a22efbc..190a194f1 100644 --- a/main.ts +++ b/main.ts @@ -8,16 +8,14 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-return */ -import {app, BrowserWindow, ipcMain, Menu, nativeTheme, protocol, Tray} from 'electron'; +import { app, BrowserWindow, ipcMain, Menu, nativeTheme, protocol, Tray } from 'electron'; import log from 'electron-log'; import * as Store from 'electron-store'; import * as windowStateKeeper from 'electron-window-state'; import * as os from 'os'; import * as path from 'path'; import * as url from 'url'; -import {Worker} from 'worker_threads'; - -const sharp = require('sharp'); // required to use sharp in worker threads +import { Worker } from 'worker_threads'; /** * Command line parameters @@ -332,7 +330,7 @@ try { ipcMain.on('indexing-worker', (event: any, arg: any) => { const workerThread = new Worker(path.join(__dirname, 'main/workers/indexing-worker.js'), { - workerData: {arg}, + workerData: { arg }, }); workerThread.on('message', (message): void => { diff --git a/main/common/image-processor.js b/main/common/image-processor.js deleted file mode 100644 index 5d34660f4..000000000 --- a/main/common/image-processor.js +++ /dev/null @@ -1,28 +0,0 @@ -const fs = require('fs-extra'); -const sharp = require('sharp'); - -class ImageProcessor { - constructor(fileAccess) { - this.fileAccess = fileAccess; - } - async convertOnlineImageToBufferAsync(imageUrl) { - const response = await fetch(imageUrl); - const imageArrayBuffer = await response.arrayBuffer(); - return Buffer.from(imageArrayBuffer); - } - - async convertLocalImageToBufferAsync(imagePath) { - return await this.fileAccess.getFileContentAsBufferAsync(imagePath); - } - - async toResizedJpegFileAsync(imageBuffer, imagePath, maxWidth, maxHeight, jpegQuality) { - await sharp(imageBuffer) - .resize(maxWidth, maxHeight) - .jpeg({ - quality: jpegQuality, - }) - .toFile(imagePath); - } -} - -exports.ImageProcessor = ImageProcessor; diff --git a/main/data/album-artwork-repository.js b/main/data/album-artwork-repository.js deleted file mode 100644 index d7c654e19..000000000 --- a/main/data/album-artwork-repository.js +++ /dev/null @@ -1,79 +0,0 @@ -class AlbumArtworkRepository { - constructor(databaseFactory) { - this.databaseFactory = databaseFactory; - } - - addAlbumArtwork(albumArtwork) { - const database = this.databaseFactory.create(); - - const statement = database.prepare('INSERT INTO AlbumArtwork (AlbumKey, ArtworkID) VALUES (?, ?);'); - statement.run(albumArtwork.albumKey, albumArtwork.artworkId); - } - - getAllAlbumArtwork() { - const database = this.databaseFactory.create(); - - const statement = database.prepare( - `SELECT AlbumArtworkID as albumArtworkId, AlbumKey as albumKey, ArtworkID as artworkId - FROM AlbumArtwork;`, - ); - - return statement.all(); - } - - getNumberOfAlbumArtwork() { - const database = this.databaseFactory.create(); - const statement = database.prepare(`SELECT COUNT(*) AS numberOfAlbumArtwork FROM AlbumArtwork;`); - const result = statement.get(); - - return result.numberOfAlbumArtwork; - } - - getNumberOfAlbumArtworkThatHasNoTrack() { - const database = this.databaseFactory.create(); - - const statement = database.prepare( - `SELECT COUNT(*) AS numberOfAlbumArtwork - FROM AlbumArtwork - WHERE AlbumKey NOT IN (SELECT AlbumKey FROM Track);`, - ); - - const result = statement.get(); - - return result.numberOfAlbumArtwork; - } - - deleteAlbumArtworkThatHasNoTrack() { - const database = this.databaseFactory.create(); - - const statement = database.prepare('DELETE FROM AlbumArtwork WHERE AlbumKey NOT IN (SELECT AlbumKey FROM Track);'); - - const info = statement.run(); - - return info.changes; - } - - getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing() { - const database = this.databaseFactory.create(); - - const statement = database.prepare(`SELECT COUNT(*) AS numberOfAlbumArtwork FROM AlbumArtwork - WHERE AlbumKey IN (SELECT AlbumKey FROM Track WHERE NeedsAlbumArtworkIndexing = 1);`); - - const result = statement.get(); - - return result.numberOfAlbumArtwork; - } - - deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexing() { - const database = this.databaseFactory.create(); - - const statement = database.prepare(`DELETE FROM AlbumArtwork - WHERE AlbumKey IN (SELECT AlbumKey FROM Track WHERE NeedsAlbumArtworkIndexing = 1);`); - - const info = statement.run(); - - return info.changes; - } -} - -exports.AlbumArtworkRepository = AlbumArtworkRepository; diff --git a/main/data/track-repository.js b/main/data/track-repository.js index 9fe89f769..7d37c65a5 100644 --- a/main/data/track-repository.js +++ b/main/data/track-repository.js @@ -275,46 +275,6 @@ class TrackRepository { const statement = database.prepare(`${QueryParts.selectTracksQueryPart(false)} WHERE t.Path=?;`); return statement.get(path); } - - disableNeedsAlbumArtworkIndexing(albumKey) { - const database = this.databaseFactory.create(); - const statement = database.prepare(`UPDATE Track SET NeedsAlbumArtworkIndexing=0 WHERE AlbumKey=?;`); - statement.run(albumKey); - } - - getLastModifiedTrackForAlbumKeyAsync(albumKeyIndex, albumKey) { - const database = this.databaseFactory.create(); - const statement = database.prepare(`${QueryParts.selectTracksQueryPart(false)} WHERE t.AlbumKey${albumKeyIndex}=?;`); - return statement.get(albumKey); - } - - getAlbumDataThatNeedsIndexing(albumKeyIndex) { - const database = this.databaseFactory.create(); - - const statement = database.prepare( - `${QueryParts.selectAlbumDataQueryPart(albumKeyIndex, false)} - WHERE (t.AlbumKey${albumKeyIndex} IS NOT NULL AND t.AlbumKey${albumKeyIndex} <> '' - AND t.AlbumKey${albumKeyIndex} NOT IN (SELECT AlbumKey FROM AlbumArtwork)) OR NeedsAlbumArtworkIndexing=1 - GROUP BY t.AlbumKey${albumKeyIndex};`, - ); - - return statement.all(); - } - - enableNeedsAlbumArtworkIndexingForAllTracks(onlyWhenHasNoCover) { - const database = this.databaseFactory.create(); - let statement; - - if (onlyWhenHasNoCover) { - statement = database.prepare( - `UPDATE Track SET NeedsAlbumArtworkIndexing=1 WHERE AlbumKey NOT IN (SELECT AlbumKey FROM AlbumArtwork);`, - ); - } else { - statement = database.prepare(`UPDATE Track SET NeedsAlbumArtworkIndexing=1;`); - } - - statement.run(); - } } exports.TrackRepository = TrackRepository; diff --git a/main/indexing/album-artwork-adder.js b/main/indexing/album-artwork-adder.js deleted file mode 100644 index 858161c13..000000000 --- a/main/indexing/album-artwork-adder.js +++ /dev/null @@ -1,115 +0,0 @@ -const { AlbumArtwork } = require('../data/entities/album-artwork'); -const { UpdatingAlbumArtworkMessage } = require('./messages/updating-album-artwork-message'); -const { MathUtils } = require('../common/utils/math-utils'); - -class AlbumArtworkAdder { - constructor(trackRepository, albumArtworkRepository, albumArtworkGetter, fileMetadataFactory, albumArtworkCache, workerProxy, logger) { - this.trackRepository = trackRepository; - this.albumArtworkRepository = albumArtworkRepository; - this.albumArtworkGetter = albumArtworkGetter; - this.fileMetadataFactory = fileMetadataFactory; - this.albumArtworkCache = albumArtworkCache; - this.workerProxy = workerProxy; - this.logger = logger; - } - async addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync() { - try { - this.logger.info( - `Adding album artwork for albumKeyIndex: '${this.workerProxy.albumKeyIndex()}'`, - 'AlbumArtworkAdder', - 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', - ); - - const albumDataThatNeedsIndexing = this.trackRepository.getAlbumDataThatNeedsIndexing(this.workerProxy.albumKeyIndex()) ?? []; - - if (albumDataThatNeedsIndexing.length === 0) { - this.logger.info( - `Found no album data that needs indexing`, - 'AlbumArtworkAdder', - 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', - ); - - return; - } - - this.logger.info( - `Found ${albumDataThatNeedsIndexing.length} album data that needs indexing`, - 'AlbumArtworkAdder', - 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', - ); - - const numberOfAlbumArtwork = this.albumArtworkRepository.getNumberOfAlbumArtwork(); - - // Only show message the first time that album artwork is added or if there are more than 20 albums that need indexing - if (numberOfAlbumArtwork === 0 || albumDataThatNeedsIndexing.length > 20) { - this.workerProxy.postMessage(new UpdatingAlbumArtworkMessage()); - } - - const loggedPercentages = new Set(); - - for (let i = 0; i < albumDataThatNeedsIndexing.length; i++) { - try { - await this.#addAlbumArtworkAsync(this.workerProxy.albumKeyIndex(), albumDataThatNeedsIndexing[i].albumKey); - - const percentage = MathUtils.calculatePercentage(i + 1, albumDataThatNeedsIndexing.length); - - if ((percentage % 10 === 0 || percentage === 100) && !loggedPercentages.has(percentage)) { - this.logger.info( - `Added ${i + 1} album artwork`, - 'AlbumArtworkAdder', - 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', - ); - loggedPercentages.add(percentage); - } - } catch (e) { - this.logger.error( - e, - `Could not add album artwork for albumKey=${albumDataThatNeedsIndexing[i].albumKey}`, - 'AlbumArtworkAdder', - 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', - ); - } - } - } catch (e) { - this.logger.error( - e, - 'Could not add album artwork for tracks that need album artwork indexing', - 'AlbumArtworkAdder', - 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', - ); - } - } - - async #addAlbumArtworkAsync(albumKeyIndex, albumKey) { - const track = this.trackRepository.getLastModifiedTrackForAlbumKeyAsync(albumKeyIndex, albumKey); - - if (track === undefined || track === null) { - return; - } - - let albumArtwork; - - try { - const fileMetadata = this.fileMetadataFactory.create(track.path); - albumArtwork = await this.albumArtworkGetter.getAlbumArtworkAsync(fileMetadata, true); - } catch (e) { - this.logger.error(e, `Could not create file metadata for path='${track.path}'`, 'AlbumArtworkAdder', 'addAlbumArtworkAsync'); - } - - if (albumArtwork === undefined || albumArtwork === null) { - return; - } - - const albumArtworkCacheId = await this.albumArtworkCache.addArtworkDataToCacheAsync(albumArtwork); - - if (albumArtworkCacheId === undefined || albumArtworkCacheId === null) { - return; - } - - this.trackRepository.disableNeedsAlbumArtworkIndexing(albumKey); - const newAlbumArtwork = new AlbumArtwork(albumKey, albumArtworkCacheId.id); - this.albumArtworkRepository.addAlbumArtwork(newAlbumArtwork); - } -} - -exports.AlbumArtworkAdder = AlbumArtworkAdder; diff --git a/main/indexing/album-artwork-adder.spec.js b/main/indexing/album-artwork-adder.spec.js deleted file mode 100644 index cd76160d0..000000000 --- a/main/indexing/album-artwork-adder.spec.js +++ /dev/null @@ -1,305 +0,0 @@ -const { LoggerMock } = require('../mocks/logger-mock'); -const { TrackRepositoryMock } = require('../mocks/track-repository-mock'); -const { AlbumArtworkRepositoryMock } = require('../mocks/album-artwork-repository-mock'); -const { AlbumArtworkGetterMock } = require('../mocks/album-artwork-getter-mock'); -const { FileMetadataFactoryMock } = require('../mocks/file-metadata.factory-mock'); -const { AlbumArtworkCacheMock } = require('../mocks/album-artwork-cache-mock'); -const { WorkerProxyMock } = require('../mocks/worker-proxy-mock'); -const { AlbumArtworkAdder } = require('./album-artwork-adder'); -const { Track } = require('../data/entities/track'); -const { AlbumArtworkCacheId } = require('./album-artwork-cache-id'); -const { GuidFactoryMock } = require('../mocks/guid-factory-mock'); -const { AlbumArtwork } = require('../data/entities/album-artwork'); - -describe('AlbumArtworkAdder', () => { - let trackRepositoryMock; - let albumArtworkRepositoryMock; - let albumArtworkGetterMock; - let fileMetadataFactoryMock; - let albumArtworkCacheMock; - let workerProxyMock; - let loggerMock; - - beforeEach(() => { - trackRepositoryMock = new TrackRepositoryMock(); - albumArtworkRepositoryMock = new AlbumArtworkRepositoryMock(); - albumArtworkGetterMock = new AlbumArtworkGetterMock(); - fileMetadataFactoryMock = new FileMetadataFactoryMock(); - albumArtworkCacheMock = new AlbumArtworkCacheMock(); - workerProxyMock = new WorkerProxyMock(); - loggerMock = new LoggerMock(); - }); - - function createSut() { - return new AlbumArtworkAdder( - trackRepositoryMock, - albumArtworkRepositoryMock, - albumArtworkGetterMock, - fileMetadataFactoryMock, - albumArtworkCacheMock, - workerProxyMock, - loggerMock, - ); - } - - describe('addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', () => { - it('should get album data that needs indexing', async () => { - // Arrange - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(trackRepositoryMock.getAlbumDataThatNeedsIndexingCalls.length).toBe(1); - }); - - it('should notify that album artwork is being updated if it is the first time that indexing runs', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - albumArtworkRepositoryMock.getNumberOfAlbumArtworkReturnValue = 0; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(workerProxyMock.postMessageCalls.length).toBe(1); - }); - - it('should not notify that album artwork is being updated if it is not the first time that indexing runs', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - albumArtworkRepositoryMock.getNumberOfAlbumArtworkReturnValue = 10; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(workerProxyMock.postMessageCalls.length).toBe(0); - }); - - it('should not get the last modified track for an album key if there is no album data that needs indexing', async () => { - // Arrange - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = []; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncCalls.length).toEqual(0); - }); - - it('should get the last modified track for an album key if there is album data that needs indexing', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncCalls.length).toEqual(1); - }); - - it('should not create a read-only file metadata if there is no last modified track for the given album key', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncReturnValues = { AlbumKey1: undefined }; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(fileMetadataFactoryMock.createCalls.length).toBe(0); - }); - - it('should create a read-only file metadata if there is a last modified track for the given album key', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - const track1 = new Track('/home/user/Music/track1.mp3'); - - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncReturnValues = { AlbumKey1: track1 }; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(fileMetadataFactoryMock.createCalls.filter((x) => x === '/home/user/Music/track1.mp3').length).toBe(1); - }); - - it('should get album artwork if a read-only file metadata was created', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - const track1 = new Track('/home/user/Music/track1.mp3'); - const fileMetadataStub = {}; - - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncReturnValues = { AlbumKey1: track1 }; - fileMetadataFactoryMock.createReturnValues = { '/home/user/Music/track1.mp3': fileMetadataStub }; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(albumArtworkGetterMock.getAlbumArtworkAsyncCalls.length).toEqual(1); - }); - - it('should not add album artwork to the cache if no album artwork data was found', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - const track1 = new Track('/home/user/Music/track1.mp3'); - const fileMetadataStub = {}; - - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncReturnValues = { AlbumKey1: track1 }; - fileMetadataFactoryMock.createReturnValues = { '/home/user/Music/track1.mp3': fileMetadataStub }; - albumArtworkGetterMock.getAlbumArtworkAsyncReturnValues = { '/home/user/Music/track1.mp3;true': undefined }; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(albumArtworkCacheMock.addArtworkDataToCacheAsyncCalls.length).toEqual(0); - }); - - it('should add album artwork to the cache if album artwork data was found', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - const track1 = new Track('/home/user/Music/track1.mp3'); - const fileMetadataStub = { path: '/home/user/Music/track1.mp3' }; - const albumArtworkData1 = Buffer.from([1, 2, 3]); - - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncReturnValues = { AlbumKey1: track1 }; - fileMetadataFactoryMock.createReturnValues = { '/home/user/Music/track1.mp3': fileMetadataStub }; - albumArtworkGetterMock.getAlbumArtworkAsyncReturnValues = { '/home/user/Music/track1.mp3;true': albumArtworkData1 }; - albumArtworkRepositoryMock.getNumberOfAlbumArtworkReturnValue = 1; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(albumArtworkCacheMock.addArtworkDataToCacheAsyncCalls.filter((x) => x === albumArtworkData1).length).toBe(1); - }); - - it('should not disable album artwork indexing for the given album key if the artwork was not added to the cache', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - const track1 = new Track('/home/user/Music/track1.mp3'); - const fileMetadataStub = { path: '/home/user/Music/track1.mp3' }; - const albumArtworkData1 = Buffer.from([1, 2, 3]); - - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncReturnValues = { AlbumKey1: track1 }; - fileMetadataFactoryMock.createReturnValues = { '/home/user/Music/track1.mp3': fileMetadataStub }; - albumArtworkGetterMock.getAlbumArtworkAsyncReturnValues = { '/home/user/Music/track1.mp3;true': albumArtworkData1 }; - albumArtworkCacheMock.addArtworkDataToCacheAsyncReturnValues = { '1,2,3': undefined }; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(trackRepositoryMock.disableNeedsAlbumArtworkIndexingCalls.length).toEqual(0); - }); - - it('should disable album artwork indexing for the given album key if the artwork was added to the cache', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - const track1 = new Track('/home/user/Music/track1.mp3'); - const fileMetadataStub = { path: '/home/user/Music/track1.mp3' }; - const albumArtworkData1 = Buffer.from([1, 2, 3]); - - const guidFactoryMock = new GuidFactoryMock(); - guidFactoryMock.createReturnValue = '123456'; - const albumArtworkCacheId1 = new AlbumArtworkCacheId(guidFactoryMock); - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncReturnValues = { AlbumKey1: track1 }; - fileMetadataFactoryMock.createReturnValues = { '/home/user/Music/track1.mp3': fileMetadataStub }; - albumArtworkGetterMock.getAlbumArtworkAsyncReturnValues = { '/home/user/Music/track1.mp3;true': albumArtworkData1 }; - albumArtworkCacheMock.addArtworkDataToCacheAsyncReturnValues = { '1,2,3': albumArtworkCacheId1 }; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(trackRepositoryMock.disableNeedsAlbumArtworkIndexingCalls.filter((x) => x === 'AlbumKey1').length).toEqual(1); - }); - - it('should not add album artwork to the database if the artwork was not added to the cache', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - const track1 = new Track('/home/user/Music/track1.mp3'); - const fileMetadataStub = { path: '/home/user/Music/track1.mp3' }; - const albumArtworkData1 = Buffer.from([1, 2, 3]); - - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncReturnValues = { AlbumKey1: track1 }; - fileMetadataFactoryMock.createReturnValues = { '/home/user/Music/track1.mp3': fileMetadataStub }; - albumArtworkGetterMock.getAlbumArtworkAsyncReturnValues = { '/home/user/Music/track1.mp3;true': albumArtworkData1 }; - albumArtworkCacheMock.addArtworkDataToCacheAsyncReturnValues = { '1,2,3': undefined }; - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(albumArtworkRepositoryMock.addAlbumArtworkCalls.length).toEqual(0); - }); - - it('should add album artwork to the database if the artwork was added to the cache', async () => { - // Arrange - const albumData1 = { albumKey: 'AlbumKey1' }; - const track1 = new Track('/home/user/Music/track1.mp3'); - const fileMetadataStub = { path: '/home/user/Music/track1.mp3' }; - const albumArtworkData1 = Buffer.from([1, 2, 3]); - const guidFactoryMock = new GuidFactoryMock(); - guidFactoryMock.createReturnValue = '123456'; - const albumArtworkCacheId1 = new AlbumArtworkCacheId(guidFactoryMock); - - trackRepositoryMock.getAlbumDataThatNeedsIndexingReturnValue = [albumData1]; - trackRepositoryMock.getLastModifiedTrackForAlbumKeyAsyncReturnValues = { AlbumKey1: track1 }; - fileMetadataFactoryMock.createReturnValues = { '/home/user/Music/track1.mp3': fileMetadataStub }; - albumArtworkGetterMock.getAlbumArtworkAsyncReturnValues = { '/home/user/Music/track1.mp3;true': albumArtworkData1 }; - albumArtworkCacheMock.addArtworkDataToCacheAsyncReturnValues = { '1,2,3': albumArtworkCacheId1 }; - - const newAlbumArtwork1 = new AlbumArtwork('AlbumKey1', albumArtworkCacheId1.id); - - const sut = createSut(); - - // Act - await sut.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(albumArtworkRepositoryMock.addAlbumArtworkCalls.filter((x) => x === 'album-123456').length).toEqual(1); - }); - }); -}); diff --git a/main/indexing/album-artwork-cache-id-factory.js b/main/indexing/album-artwork-cache-id-factory.js deleted file mode 100644 index eb68b677e..000000000 --- a/main/indexing/album-artwork-cache-id-factory.js +++ /dev/null @@ -1,14 +0,0 @@ -const { AlbumArtworkCacheId } = require('./album-artwork-cache-id'); - -class AlbumArtworkCacheIdFactory { - constructor(guidFactory) { - this.guidFactory = guidFactory; - this.id = `album-${guidFactory.create()}`; - } - - create() { - return new AlbumArtworkCacheId(this.guidFactory); - } -} - -exports.AlbumArtworkCacheIdFactory = AlbumArtworkCacheIdFactory; diff --git a/main/indexing/album-artwork-cache-id.js b/main/indexing/album-artwork-cache-id.js deleted file mode 100644 index d925ae097..000000000 --- a/main/indexing/album-artwork-cache-id.js +++ /dev/null @@ -1,7 +0,0 @@ -class AlbumArtworkCacheId { - constructor(guidFactory) { - this.id = `album-${guidFactory.create()}`; - } -} - -exports.AlbumArtworkCacheId = AlbumArtworkCacheId; diff --git a/main/indexing/album-artwork-cache.js b/main/indexing/album-artwork-cache.js deleted file mode 100644 index c5cc484f2..000000000 --- a/main/indexing/album-artwork-cache.js +++ /dev/null @@ -1,58 +0,0 @@ -const { Constants } = require('../common/application/constants'); - -class AlbumArtworkCache { - constructor(albumArtworkCacheIdFactory, imageProcessor, applicationPaths, fileAccess, logger) { - this.albumArtworkCacheIdFactory = albumArtworkCacheIdFactory; - this.imageProcessor = imageProcessor; - this.applicationPaths = applicationPaths; - this.fileAccess = fileAccess; - this.logger = logger; - - this.#createCoverArtCacheOnDisk(); - } - - async addArtworkDataToCacheAsync(imageBuffer) { - if (imageBuffer === undefined || imageBuffer === null) { - return undefined; - } - - if (imageBuffer.length === 0) { - return undefined; - } - - try { - const albumArtworkCacheId = this.albumArtworkCacheIdFactory.create(); - const cachedArtworkFilePath = this.#coverArtFullPath(albumArtworkCacheId.id); - await this.imageProcessor.toResizedJpegFileAsync( - imageBuffer, - cachedArtworkFilePath, - Constants.cachedCoverArtMaximumSize, - Constants.cachedCoverArtMaximumSize, - Constants.cachedCoverArtJpegQuality, - ); - - return albumArtworkCacheId; - } catch (e) { - this.logger.error(e, 'Could not add artwork data to cache', 'AlbumArtworkCache', 'addArtworkDataToCacheAsync'); - } - - return undefined; - } - - #coverArtFullPath(artworkId) { - return this.fileAccess.combinePath([this.applicationPaths.getCoverArtCacheFullPath(), `${artworkId}.jpg`]); - } - - #createCoverArtCacheOnDisk() { - try { - this.fileAccess.createFullDirectoryPathIfDoesNotExist(this.applicationPaths.getCoverArtCacheFullPath()); - } catch (e) { - this.logger.error(e, 'Could not create artwork cache directory', 'AlbumArtworkCache', 'createCoverArtCacheOnDisk'); - - // We cannot proceed if the above fails - throw e; - } - } -} - -exports.AlbumArtworkCache = AlbumArtworkCache; diff --git a/main/indexing/album-artwork-getter.js b/main/indexing/album-artwork-getter.js deleted file mode 100644 index 2ef7e4255..000000000 --- a/main/indexing/album-artwork-getter.js +++ /dev/null @@ -1,34 +0,0 @@ -class AlbumArtworkGetter { - constructor(embeddedAlbumArtworkGetter, externalAlbumArtworkGetter, onlineAlbumArtworkGetter, workerProxy) { - this.embeddedAlbumArtworkGetter = embeddedAlbumArtworkGetter; - this.externalAlbumArtworkGetter = externalAlbumArtworkGetter; - this.onlineAlbumArtworkGetter = onlineAlbumArtworkGetter; - this.workerProxy = workerProxy; - } - - async getAlbumArtworkAsync(fileMetadata, getOnlineArtwork) { - const embeddedArtwork = this.embeddedAlbumArtworkGetter.getEmbeddedArtwork(fileMetadata); - - if (embeddedArtwork !== undefined && embeddedArtwork !== null) { - return embeddedArtwork; - } - - const externalArtwork = await this.externalAlbumArtworkGetter.getExternalArtworkAsync(fileMetadata); - - if (externalArtwork !== undefined && externalArtwork !== null) { - return externalArtwork; - } - - if (getOnlineArtwork && this.workerProxy.downloadMissingAlbumCovers()) { - const onlineArtwork = await this.onlineAlbumArtworkGetter.getOnlineArtworkAsync(fileMetadata); - - if (onlineArtwork !== undefined && onlineArtwork !== null) { - return onlineArtwork; - } - } - - return undefined; - } -} - -exports.AlbumArtworkGetter = AlbumArtworkGetter; diff --git a/main/indexing/album-artwork-getter.spec.js b/main/indexing/album-artwork-getter.spec.js deleted file mode 100644 index 25f59d35d..000000000 --- a/main/indexing/album-artwork-getter.spec.js +++ /dev/null @@ -1,164 +0,0 @@ -const { EmbeddedAlbumArtworkGetterMock } = require('../mocks/embedded-album-artwork-getter-mock'); -const { ExternalAlbumArtworkGetterMock } = require('../mocks/external-album-artwork-getter-mock'); -const { OnlineAlbumArtworkGetterMock } = require('../mocks/online-album-artwork-getter-mock'); -const { AlbumArtworkGetter } = require('./album-artwork-getter'); -const { FileMetadataMock } = require('../mocks/file-metadata-mock'); -const { WorkerProxyMock } = require('../mocks/worker-proxy-mock'); - -describe('AlbumArtworkGetter', () => { - let embeddedAlbumArtworkGetterMock; - let externalAlbumArtworkGetterMock; - let onlineAlbumArtworkGetterMock; - let workerProxyMock; - - beforeEach(() => { - embeddedAlbumArtworkGetterMock = new EmbeddedAlbumArtworkGetterMock(); - externalAlbumArtworkGetterMock = new ExternalAlbumArtworkGetterMock(); - onlineAlbumArtworkGetterMock = new OnlineAlbumArtworkGetterMock(); - workerProxyMock = new WorkerProxyMock(); - }); - - function createSut() { - return new AlbumArtworkGetter( - embeddedAlbumArtworkGetterMock, - externalAlbumArtworkGetterMock, - onlineAlbumArtworkGetterMock, - workerProxyMock, - ); - } - - describe('getAlbumArtwork', () => { - it('should return embedded artwork when there is embedded artwork', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - const metaDataMock = new FileMetadataMock('/path/to/file'); - - embeddedAlbumArtworkGetterMock.getEmbeddedArtworkReturnValues = { '/path/to/file': expectedAlbumArtwork }; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getAlbumArtworkAsync(metaDataMock, true); - - // Assert - expect(actualAlbumArtwork).toBe(expectedAlbumArtwork); - }); - - it('should return external artwork when there is no embedded artwork but there is external artwork', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - const metaDataMock = new FileMetadataMock('/path/to/file'); - - embeddedAlbumArtworkGetterMock.getEmbeddedArtworkReturnValues = { '/path/to/file': undefined }; - externalAlbumArtworkGetterMock.getExternalArtworkAsyncReturnValues = { '/path/to/file': expectedAlbumArtwork }; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getAlbumArtworkAsync(metaDataMock, true); - - // Assert - expect(actualAlbumArtwork).toBe(expectedAlbumArtwork); - }); - - it('should return online artwork when settings require downloading missing covers when there is no embedded and no external artwork but there is online artwork and getOnlineArtwork is true', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - const metaDataMock = new FileMetadataMock('/path/to/file'); - - embeddedAlbumArtworkGetterMock.getEmbeddedArtworkReturnValues = { '/path/to/file': undefined }; - externalAlbumArtworkGetterMock.getExternalArtworkAsyncReturnValues = { '/path/to/file': undefined }; - onlineAlbumArtworkGetterMock.getOnlineArtworkAsyncReturnValues = { '/path/to/file': expectedAlbumArtwork }; - - workerProxyMock.downloadMissingAlbumCoversReturnValue = true; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getAlbumArtworkAsync(metaDataMock, true); - - // Assert - expect(actualAlbumArtwork).toBe(expectedAlbumArtwork); - }); - - it('should return undefined when settings require downloading missing covers when there is no embedded and no external artwork and getOnlineArtwork is false', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - const metaDataMock = new FileMetadataMock('/path/to/file'); - - embeddedAlbumArtworkGetterMock.getEmbeddedArtworkReturnValues = { '/path/to/file': undefined }; - externalAlbumArtworkGetterMock.getExternalArtworkAsyncReturnValues = { '/path/to/file': undefined }; - onlineAlbumArtworkGetterMock.getOnlineArtworkAsyncReturnValues = { '/path/to/file': expectedAlbumArtwork }; - - workerProxyMock.downloadMissingAlbumCoversReturnValue = true; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getAlbumArtworkAsync(metaDataMock, false); - - // Assert - expect(actualAlbumArtwork).toBeUndefined(); - }); - - it('should return undefined when settings do not require downloading missing covers when there is no embedded and no external artwork and getOnlineArtwork is true', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - const metaDataMock = new FileMetadataMock('/path/to/file'); - - embeddedAlbumArtworkGetterMock.getEmbeddedArtworkReturnValues = { '/path/to/file': undefined }; - externalAlbumArtworkGetterMock.getExternalArtworkAsyncReturnValues = { '/path/to/file': undefined }; - onlineAlbumArtworkGetterMock.getOnlineArtworkAsyncReturnValues = { '/path/to/file': expectedAlbumArtwork }; - - workerProxyMock.downloadMissingAlbumCoversReturnValue = false; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getAlbumArtworkAsync(metaDataMock, true); - - // Assert - expect(actualAlbumArtwork).toBeUndefined(); - }); - - it('should return undefined when settings do not require downloading missing covers when there is no embedded and no external artwork but there is online artwork and getOnlineArtwork is false', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - const metaDataMock = new FileMetadataMock('/path/to/file'); - - embeddedAlbumArtworkGetterMock.getEmbeddedArtworkReturnValues = { '/path/to/file': undefined }; - externalAlbumArtworkGetterMock.getExternalArtworkAsyncReturnValues = { '/path/to/file': undefined }; - onlineAlbumArtworkGetterMock.getOnlineArtworkAsyncReturnValues = { '/path/to/file': expectedAlbumArtwork }; - - workerProxyMock.downloadMissingAlbumCoversReturnValue = false; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getAlbumArtworkAsync(metaDataMock, false); - - // Assert - expect(actualAlbumArtwork).toBeUndefined(); - }); - - it('should return undefined when there is no embedded and no external and no online artwork', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - const metaDataMock = new FileMetadataMock('/path/to/file'); - - embeddedAlbumArtworkGetterMock.getEmbeddedArtworkReturnValues = { '/path/to/file': undefined }; - externalAlbumArtworkGetterMock.getExternalArtworkAsyncReturnValues = { '/path/to/file': undefined }; - onlineAlbumArtworkGetterMock.getOnlineArtworkAsyncReturnValues = { '/path/to/file': undefined }; - - workerProxyMock.downloadMissingAlbumCoversReturnValue = false; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getAlbumArtworkAsync(metaDataMock, true); - - // Assert - expect(actualAlbumArtwork).toBeUndefined(); - }); - }); -}); diff --git a/main/indexing/album-artwork-indexer.js b/main/indexing/album-artwork-indexer.js deleted file mode 100644 index fbc3d511c..000000000 --- a/main/indexing/album-artwork-indexer.js +++ /dev/null @@ -1,31 +0,0 @@ -const { Timer } = require('../common/scheduling/timer'); - -class AlbumArtworkIndexer { - constructor(albumArtworkRemover, albumArtworkAdder, logger) { - this.albumArtworkRemover = albumArtworkRemover; - this.albumArtworkAdder = albumArtworkAdder; - this.logger = logger; - } - - async indexAlbumArtworkAsync() { - this.logger.info('+++ STARTED INDEXING ALBUM ARTWORK +++', 'AlbumArtworkIndexer', 'indexAlbumArtworkAsync'); - - const timer = new Timer(); - timer.start(); - - await this.albumArtworkRemover.removeAlbumArtworkThatHasNoTrackAsync(); - await this.albumArtworkRemover.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - await this.albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - await this.albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); - - timer.stop(); - - this.logger.info( - `+++ FINISHED INDEXING ALBUM ARTWORK (Time required: ${timer.getElapsedMilliseconds()} ms) +++`, - 'AlbumArtworkIndexer', - 'indexAlbumArtworkAsync', - ); - } -} - -exports.AlbumArtworkIndexer = AlbumArtworkIndexer; diff --git a/main/indexing/album-artwork-indexer.spec.js b/main/indexing/album-artwork-indexer.spec.js deleted file mode 100644 index b9ab09fac..000000000 --- a/main/indexing/album-artwork-indexer.spec.js +++ /dev/null @@ -1,66 +0,0 @@ -const { AlbumArtworkIndexer } = require('./album-artwork-indexer'); -const { AlbumArtworkRemoverMock } = require('../mocks/album-artwork-remover-mock'); -const { AlbumArtworkAdderMock } = require('../mocks/album-artwork-adder-mock'); -const { LoggerMock } = require('../mocks/logger-mock'); - -describe('AlbumArtworkIndexer', () => { - let albumArtworkRemoverMock; - let albumArtworkAdderMock; - let loggerMock; - - beforeEach(() => { - albumArtworkRemoverMock = new AlbumArtworkRemoverMock(); - albumArtworkAdderMock = new AlbumArtworkAdderMock(); - loggerMock = new LoggerMock(); - }); - - function createSut() { - return new AlbumArtworkIndexer(albumArtworkRemoverMock, albumArtworkAdderMock, loggerMock); - } - - describe('indexAlbumArtworkAsync', () => { - it('should remove artwork that has no track', async () => { - // Arrange - const sut = createSut(); - - // Act - await sut.indexAlbumArtworkAsync(); - - // Assert - expect(albumArtworkRemoverMock.removeAlbumArtworkThatHasNoTrackAsyncCalls.length).toEqual(1); - }); - - it('should remove artwork for tracks that need album artwork indexing', async () => { - // Arrange - const sut = createSut(); - - // Act - await sut.indexAlbumArtworkAsync(); - - // Assert - expect(albumArtworkRemoverMock.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsyncCalls.length).toEqual(1); - }); - - it('should add artwork for tracks that need album artwork indexing', async () => { - // Arrange - const sut = createSut(); - - // Act - await sut.indexAlbumArtworkAsync(); - - // Assert - expect(albumArtworkAdderMock.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsyncCalls.length).toEqual(1); - }); - - it('should remove artwork that is not in the database from disk', async () => { - // Arrange - const sut = createSut(); - - // Act - await sut.indexAlbumArtworkAsync(); - - // Assert - expect(albumArtworkRemoverMock.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsyncCalls.length).toEqual(1); - }); - }); -}); diff --git a/main/indexing/album-artwork-remover.spec.js b/main/indexing/album-artwork-remover.spec.js deleted file mode 100644 index 3fdf73b48..000000000 --- a/main/indexing/album-artwork-remover.spec.js +++ /dev/null @@ -1,334 +0,0 @@ -const { FileAccessMock } = require('../mocks/file-access-mock'); -const { LoggerMock } = require('../mocks/logger-mock'); -const { WorkerProxyMock } = require('../mocks/worker-proxy-mock'); -const { AlbumArtworkRepositoryMock } = require('../mocks/album-artwork-repository-mock'); -const { ApplicationPathsMock } = require('../mocks/application-paths-mock'); -const { AlbumArtworkRemover } = require('./album-artwork-remover'); -const { AlbumArtwork } = require('../data/entities/album-artwork'); - -describe('AlbumArtworkRemover', () => { - let albumArtworkRepositoryMock; - let applicationPathsMock; - let workerProxyMock; - let fileAccessMock; - let loggerMock; - - beforeEach(() => { - albumArtworkRepositoryMock = new AlbumArtworkRepositoryMock(); - applicationPathsMock = new ApplicationPathsMock(); - workerProxyMock = new WorkerProxyMock(); - fileAccessMock = new FileAccessMock(); - loggerMock = new LoggerMock(); - }); - - function createSut() { - return new AlbumArtworkRemover(albumArtworkRepositoryMock, applicationPathsMock, workerProxyMock, fileAccessMock, loggerMock); - } - - describe('removeAlbumArtworkThatHasNoTrackAsync', () => { - it('should get the number of album artwork that has no track from the database', async () => { - // Arrange - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatHasNoTrackAsync(); - - // Assert - expect(albumArtworkRepositoryMock.getNumberOfAlbumArtworkThatHasNoTrackCalls).toBe(1); - }); - - it('should notify that album artwork is being updated, if there is album artwork that has no track.', async () => { - // Arrange - albumArtworkRepositoryMock.getNumberOfAlbumArtworkThatHasNoTrackReturnValue = 2; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatHasNoTrackAsync(); - - // Assert - expect(workerProxyMock.postMessageCalls.length).toBe(1); - }); - - it('should not notify that album artwork is being updated, if there is no album artwork that has no track.', async () => { - // Arrange - albumArtworkRepositoryMock.getNumberOfAlbumArtworkThatHasNoTrackReturnValue = 0; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatHasNoTrackAsync(); - - // Assert - expect(workerProxyMock.postMessageCalls.length).toBe(0); - }); - - it('should delete album artwork that has no track from the database', async () => { - // Arrange - albumArtworkRepositoryMock.getNumberOfAlbumArtworkThatHasNoTrackReturnValue = 2; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatHasNoTrackAsync(); - - // Assert - expect(albumArtworkRepositoryMock.deleteAlbumArtworkThatHasNoTrackCalls).toBe(1); - }); - - it('should not delete album artwork that has no track from the database if there is none', async () => { - // Arrange - albumArtworkRepositoryMock.getNumberOfAlbumArtworkThatHasNoTrackReturnValue = 0; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatHasNoTrackAsync(); - - // Assert - expect(albumArtworkRepositoryMock.deleteAlbumArtworkThatHasNoTrackCalls).toBe(0); - }); - }); - - describe('removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', () => { - it('should get the number of album artwork for tracks that need album artwork indexing from the database', async () => { - // Arrange - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(albumArtworkRepositoryMock.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexingCalls).toBe(1); - }); - - it('should notify that album artwork is being updated, if there are tracks that need album artwork indexing.', async () => { - // Arrange - albumArtworkRepositoryMock.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexingReturnValue = 2; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(workerProxyMock.postMessageCalls.length).toBe(1); - }); - - it('should not notify that album artwork is being updated, if there are no tracks that need album artwork indexing.', async () => { - // Arrange - albumArtworkRepositoryMock.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexingReturnValue = 0; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(workerProxyMock.postMessageCalls.length).toBe(0); - }); - - it('should delete album artwork for tracks that need album artwork indexing from the database', async () => { - // Arrange - albumArtworkRepositoryMock.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexingReturnValue = 2; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(albumArtworkRepositoryMock.deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexingCalls).toBe(1); - }); - - it('should not delete album artwork if there are no tracks that need album artwork indexing from the database', async () => { - // Arrange - albumArtworkRepositoryMock.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexingReturnValue = 0; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); - - // Assert - expect(albumArtworkRepositoryMock.deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexingCalls).toBe(0); - }); - }); - - describe('removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync', () => { - it('should get all album artwork in the database', async () => { - // Arrange - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); - - // Assert - expect(albumArtworkRepositoryMock.getAllAlbumArtworkCalls).toBe(1); - }); - - it('should get all artwork files which are in the cover art cache path', async () => { - // Arrange - const albumArtwork1 = new AlbumArtwork('albumKey1', 'album-artworkId1'); - const albumArtwork2 = new AlbumArtwork('albumKey2', 'album-artworkId2'); - albumArtworkRepositoryMock.getAllAlbumArtworkReturnValue = [albumArtwork1, albumArtwork2]; - applicationPathsMock.getCoverArtCacheFullPathReturnValue = '/home/user/.config/Dopamine/Cache/CoverArt'; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); - - // Assert - expect( - fileAccessMock.getFilesInDirectoryAsyncCalls.filter((x) => x === '/home/user/.config/Dopamine/Cache/CoverArt').length, - ).toBe(1); - }); - - it('should not notify that artwork is being updated if there are no artwork files on disk', async () => { - // Arrange - const albumArtwork1 = new AlbumArtwork('albumKey1', 'album-artworkId1'); - const albumArtwork2 = new AlbumArtwork('albumKey2', 'album-artworkId2'); - albumArtworkRepositoryMock.getAllAlbumArtworkReturnValue = [albumArtwork1, albumArtwork2]; - applicationPathsMock.getCoverArtCacheFullPathReturnValue = '/home/user/.config/Dopamine/Cache/CoverArt'; - fileAccessMock.getFilesInDirectoryAsyncReturnValues = { '/home/user/.config/Dopamine/Cache/CoverArt': [] }; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); - - // Assert - expect(workerProxyMock.postMessageCalls.length).toBe(0); - }); - - it('should not notify that artwork is being updated if there are artwork files on disk but they are all found in the database', async () => { - // Arrange - const albumArtwork1 = new AlbumArtwork('albumKey1', 'album-artworkId1'); - const albumArtwork2 = new AlbumArtwork('albumKey2', 'album-artworkId2'); - albumArtworkRepositoryMock.getAllAlbumArtworkReturnValue = [albumArtwork1, albumArtwork2]; - applicationPathsMock.getCoverArtCacheFullPathReturnValue = '/home/user/.config/Dopamine/Cache/CoverArt'; - fileAccessMock.getFilesInDirectoryAsyncReturnValues = { - '/home/user/.config/Dopamine/Cache/CoverArt': [ - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg', - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg', - ], - }; - - fileAccessMock.getFileNameWithoutExtensionReturnValues = { - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg': 'album-artworkId1', - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg': 'album-artworkId2', - }; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); - - // Assert - expect(workerProxyMock.postMessageCalls.length).toBe(0); - }); - - it('should notify exactly once that artwork is being updated if there are artwork files on disk which are not found in the database', async () => { - // Arrange - albumArtworkRepositoryMock.getAllAlbumArtworkReturnValue = []; - applicationPathsMock.getCoverArtCacheFullPathReturnValue = '/home/user/.config/Dopamine/Cache/CoverArt'; - fileAccessMock.getFilesInDirectoryAsyncReturnValues = { - '/home/user/.config/Dopamine/Cache/CoverArt': [ - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg', - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg', - ], - }; - - fileAccessMock.getFileNameWithoutExtensionReturnValues = { - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg': 'album-artworkId1', - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg': 'album-artworkId2', - }; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); - - // Assert - expect(workerProxyMock.postMessageCalls.length).toBe(1); - }); - - it('should not delete any artwork files if none are found on disk', async () => { - // Arrange - albumArtworkRepositoryMock.getAllAlbumArtworkReturnValue = []; - applicationPathsMock.getCoverArtCacheFullPathReturnValue = '/home/user/.config/Dopamine/Cache/CoverArt'; - fileAccessMock.getFilesInDirectoryAsyncReturnValues = { - '/home/user/.config/Dopamine/Cache/CoverArt': [], - }; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); - - // Assert - expect(fileAccessMock.deleteFileIfExistsAsyncCalls.length).toBe(0); - }); - - it('should delete artwork files if artwork is not found in the database', async () => { - // Arrange - const albumArtwork1 = new AlbumArtwork('albumKey1', 'album-artworkId1'); - albumArtworkRepositoryMock.getAllAlbumArtworkReturnValue = [albumArtwork1]; - applicationPathsMock.getCoverArtCacheFullPathReturnValue = '/home/user/.config/Dopamine/Cache/CoverArt'; - fileAccessMock.getFilesInDirectoryAsyncReturnValues = { - '/home/user/.config/Dopamine/Cache/CoverArt': [ - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg', - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg', - ], - }; - - fileAccessMock.getFileNameWithoutExtensionReturnValues = { - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg': 'album-artworkId1', - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg': 'album-artworkId2', - }; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); - - // Assert - expect( - fileAccessMock.deleteFileIfExistsAsyncCalls.filter( - (x) => x === '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg', - ).length, - ).toBe(1); - }); - - it('should not delete artwork files if artwork is found in the database', async () => { - // Arrange - const albumArtwork1 = new AlbumArtwork('albumKey1', 'album-artworkId1'); - albumArtworkRepositoryMock.getAllAlbumArtworkReturnValue = [albumArtwork1]; - applicationPathsMock.getCoverArtCacheFullPathReturnValue = '/home/user/.config/Dopamine/Cache/CoverArt'; - fileAccessMock.getFilesInDirectoryAsyncReturnValues = { - '/home/user/.config/Dopamine/Cache/CoverArt': [ - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg', - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg', - ], - }; - - fileAccessMock.getFileNameWithoutExtensionReturnValues = { - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg': 'album-artworkId1', - '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg': 'album-artworkId2', - }; - - const sut = createSut(); - - // Act - await sut.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); - - // Assert - expect( - fileAccessMock.deleteFileIfExistsAsyncCalls.filter( - (x) => x === '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg', - ).length, - ).toBe(0); - }); - }); -}); diff --git a/main/indexing/embedded-album-artwork-getter.js b/main/indexing/embedded-album-artwork-getter.js deleted file mode 100644 index 993839212..000000000 --- a/main/indexing/embedded-album-artwork-getter.js +++ /dev/null @@ -1,28 +0,0 @@ -class EmbeddedAlbumArtworkGetter { - constructor(logger) { - this.logger = logger; - } - - getEmbeddedArtwork(fileMetadata) { - if (fileMetadata === undefined || fileMetadata === null) { - return undefined; - } - - let artworkData; - - try { - artworkData = fileMetadata.picture; - } catch (e) { - this.logger.error( - e, - `Could not get embedded artwork for track with path='${fileMetadata.path}'`, - 'EmbeddedAlbumArtworkGetter', - 'getEmbeddedArtwork', - ); - } - - return artworkData; - } -} - -exports.EmbeddedAlbumArtworkGetter = EmbeddedAlbumArtworkGetter; diff --git a/main/indexing/embedded-album-artwork-getter.spec.js b/main/indexing/embedded-album-artwork-getter.spec.js deleted file mode 100644 index 9ca458eea..000000000 --- a/main/indexing/embedded-album-artwork-getter.spec.js +++ /dev/null @@ -1,35 +0,0 @@ -const { LoggerMock } = require('../mocks/logger-mock'); -const { EmbeddedAlbumArtworkGetter } = require('./embedded-album-artwork-getter'); -const { FileMetadataMock } = require('../mocks/file-metadata-mock'); - -describe('EmbeddedAlbumArtworkGetter', () => { - describe('getEmbeddedArtwork', () => { - it('should return null if fileMetaData is undefined', () => { - // Arrange - const loggerMock = new LoggerMock(); - const embeddedAlbumArtworkGetter = new EmbeddedAlbumArtworkGetter(loggerMock); - - // Act - const artwork = embeddedAlbumArtworkGetter.getEmbeddedArtwork(undefined); - - // Assert - expect(artwork).toBeUndefined(); - }); - - it('should return embedded artwork if fileMetaData is not undefined', () => { - // Arrange - const loggerMock = new LoggerMock(); - const fileMetaDataMock = new FileMetadataMock(); - const embeddedAlbumArtworkGetter = new EmbeddedAlbumArtworkGetter(loggerMock); - - const artwork = Buffer.from([1, 2, 3]); - fileMetaDataMock.picture = artwork; - - // Act - const actualArtwork = embeddedAlbumArtworkGetter.getEmbeddedArtwork(fileMetaDataMock); - - // Assert - expect(actualArtwork).toEqual(artwork); - }); - }); -}); diff --git a/main/indexing/external-album-artwork-getter.js b/main/indexing/external-album-artwork-getter.js deleted file mode 100644 index 259a7e03c..000000000 --- a/main/indexing/external-album-artwork-getter.js +++ /dev/null @@ -1,36 +0,0 @@ -const { StringUtils } = require('../common/utils/string-utils'); - -class ExternalAlbumArtworkGetter { - constructor(externalArtworkPathGetter, imageProcessor, logger) { - this.externalArtworkPathGetter = externalArtworkPathGetter; - this.imageProcessor = imageProcessor; - this.logger = logger; - } - - async getExternalArtworkAsync(fileMetadata) { - if (fileMetadata === undefined || fileMetadata === null) { - return undefined; - } - - let artworkData; - - try { - const externalArtworkPath = await this.externalArtworkPathGetter.getExternalArtworkPathAsync(fileMetadata.path); - - if (!StringUtils.isNullOrWhiteSpace(externalArtworkPath)) { - artworkData = await this.imageProcessor.convertLocalImageToBufferAsync(externalArtworkPath); - } - } catch (e) { - this.logger.error( - e, - `Could not get external artwork for track with path='${fileMetadata.path}'`, - 'ExternalAlbumArtworkGetter', - 'getExternalArtwork', - ); - } - - return artworkData; - } -} - -exports.ExternalAlbumArtworkGetter = ExternalAlbumArtworkGetter; diff --git a/main/indexing/external-album-artwork-getter.spec.js b/main/indexing/external-album-artwork-getter.spec.js deleted file mode 100644 index a92b05503..000000000 --- a/main/indexing/external-album-artwork-getter.spec.js +++ /dev/null @@ -1,84 +0,0 @@ -const { ExternalArtworkPathGetterMock } = require('../mocks/external-artwork-path-getter-mock'); -const { LoggerMock } = require('../mocks/logger-mock'); -const { ImageProcessorMock } = require('../mocks/image-processor-mock'); -const { ExternalAlbumArtworkGetter } = require('./external-album-artwork-getter'); -const { FileMetadataMock } = require('../mocks/file-metadata-mock'); - -describe('ExternalAlbumArtworkGetter', () => { - let externalArtworkPathGetterMock; - let imageProcessorMock; - let loggerMock; - - beforeEach(() => { - externalArtworkPathGetterMock = new ExternalArtworkPathGetterMock(); - imageProcessorMock = new ImageProcessorMock(); - loggerMock = new LoggerMock(); - }); - - function createSut() { - return new ExternalAlbumArtworkGetter(externalArtworkPathGetterMock, imageProcessorMock, loggerMock); - } - - describe('getExternalArtworkAsync', () => { - it('should return undefined if fileMetaData is undefined', async () => { - // Arrange - const sut = createSut(); - - // Act - const artwork = await sut.getExternalArtworkAsync(undefined); - - // Assert - expect(artwork).toBeUndefined(); - }); - - it('should return undefined if fileMetaData is not undefined and external artwork path is empty', async () => { - // Arrange - const fileMetadataMock = new FileMetadataMock('/home/MyUser/Music/track.mp3'); - externalArtworkPathGetterMock.getExternalArtworkPathAsyncReturnValues = { '/home/MyUser/Music/track.mp3': '' }; - - const sut = createSut(); - - // Act - const artwork = await sut.getExternalArtworkAsync(fileMetadataMock); - - // Assert - expect(artwork).toBeUndefined(); - }); - - it('should return undefined if fileMetaData is not undefined and external artwork path is space', async () => { - // Arrange - const fileMetadataMock = new FileMetadataMock('/home/MyUser/Music/track.mp3'); - externalArtworkPathGetterMock.getExternalArtworkPathAsyncReturnValues = { '/home/MyUser/Music/track.mp3': ' ' }; - - const sut = createSut(); - - // Act - const actualArtwork = await sut.getExternalArtworkAsync(fileMetadataMock); - - // Assert - expect(actualArtwork).toBeUndefined(); - }); - - it('should return external artwork if fileMetaData is not undefined and an external artwork path was found', async () => { - // Arrange - const fileMetadataMock = new FileMetadataMock('/home/MyUser/Music/track.mp3'); - const expectedArtwork = Buffer.from([1, 2, 3]); - - externalArtworkPathGetterMock.getExternalArtworkPathAsyncReturnValues = { - '/home/MyUser/Music/track.mp3': '/home/MyUser/Music/front.png', - }; - - imageProcessorMock.convertLocalImageToBufferAsyncReturnValues = { - '/home/MyUser/Music/front.png': expectedArtwork, - }; - - const sut = createSut(); - - // Act - const actualArtwork = await sut.getExternalArtworkAsync(fileMetadataMock); - - // Assert - expect(actualArtwork).toBe(expectedArtwork); - }); - }); -}); diff --git a/main/indexing/external-artwork-path-getter.js b/main/indexing/external-artwork-path-getter.js deleted file mode 100644 index 5e8cc8473..000000000 --- a/main/indexing/external-artwork-path-getter.js +++ /dev/null @@ -1,43 +0,0 @@ -const { StringUtils } = require('../common/utils/string-utils'); -const { Constants } = require('../common/application/constants'); - -class ExternalArtworkPathGetter { - constructor(fileAccess) { - this.fileAccess = fileAccess; - } - - async getExternalArtworkPathAsync(audioFilePath) { - if (StringUtils.isNullOrWhiteSpace(audioFilePath)) { - return ''; - } - - const directory = this.fileAccess.getDirectoryPath(audioFilePath); - const filesInDirectory = await this.fileAccess.getFilesInDirectoryAsync(directory); - - for (const filePath of filesInDirectory) { - const fileName = this.fileAccess.getFileName(filePath); - - if (Constants.externalCoverArtPatterns.includes(fileName.toLowerCase())) { - return filePath; - } - - const fileNameWithoutExtension = this.fileAccess.getFileNameWithoutExtension(filePath); - - for (const externalCoverArtPattern of Constants.externalCoverArtPatterns) { - const externalCoverArtPatternReplacedByFileName = StringUtils.replaceAll( - externalCoverArtPattern, - '%filename%', - fileNameWithoutExtension, - ); - - if (fileName.toLowerCase() === externalCoverArtPatternReplacedByFileName.toLowerCase()) { - return filePath; - } - } - } - - return ''; - } -} - -exports.ExternalArtworkPathGetter = ExternalArtworkPathGetter; diff --git a/main/indexing/external-artwork-path-getter.spec.js b/main/indexing/external-artwork-path-getter.spec.js deleted file mode 100644 index 739e99eb6..000000000 --- a/main/indexing/external-artwork-path-getter.spec.js +++ /dev/null @@ -1,197 +0,0 @@ -const { FileAccessMock } = require('../mocks/file-access-mock'); -const { ExternalArtworkPathGetter } = require('./external-artwork-path-getter'); - -describe('ExternalArtworkPathGetter', () => { - let fileAccessMock; - - beforeEach(() => { - fileAccessMock = new FileAccessMock(); - }); - - function createSut() { - return new ExternalArtworkPathGetter(fileAccessMock); - } - - describe('getExternalArtworkPath', () => { - it('should return empty if audio file path is undefined', async () => { - // Arrange - fileAccessMock.getDirectoryPathReturnValues = { '/home/MyUser/Music/MyMusicFile.mp3': '/home/MyUser/Music' }; - fileAccessMock.getFileNameReturnValues = { '/home/MyUser/Music/MyMusicFile.mp3': 'MyMusicFile.mp3' }; - - const sut = createSut(); - - // Act - const externalArtworkPath = await sut.getExternalArtworkPathAsync(undefined); - - // Assert - expect(externalArtworkPath).toEqual(''); - }); - - it('should return empty if there is no file that matches an external artwork pattern in the same directory', async () => { - // Arrange - fileAccessMock.getDirectoryPathReturnValues = { '/home/MyUser/Music/MyMusicFile.mp3': '/home/MyUser/Music' }; - fileAccessMock.getFileNameReturnValues = { '/home/MyUser/Music/MyMusicFile.mp3': 'MyMusicFile.mp3' }; - fileAccessMock.getFilesInDirectoryAsyncReturnValues = { '/home/MyUser/Music': ['/home/MyUser/Music/MyMusicFile.mp3'] }; - - const sut = createSut(); - - // Act - const externalArtworkPath = await sut.getExternalArtworkPathAsync('/home/MyUser/Music/MyMusicFile.mp3'); - - // Assert - expect(externalArtworkPath).toEqual(''); - }); - - async function getExternalArtworkPathAsync(artworkFileName) { - fileAccessMock.reset(); - - fileAccessMock.getFileNameReturnValues = { - '/home/MyUser/Music/front.png': 'front.png', - '/home/MyUser/Music/Front.png': 'Front.png', - '/home/MyUser/Music/Front.PNG': 'Front.PNG', - '/home/MyUser/Music/front.jpg': 'front.jpg', - '/home/MyUser/Music/Front.jpg': 'Front.jpg', - '/home/MyUser/Music/Front.JPG': 'Front.JPG', - '/home/MyUser/Music/front.jpeg': 'front.jpeg', - '/home/MyUser/Music/Front.jpeg': 'Front.jpeg', - '/home/MyUser/Music/Front.JPEG': 'Front.JPEG', - '/home/MyUser/Music/cover.png': 'cover.png', - '/home/MyUser/Music/Cover.png': 'Cover.png', - '/home/MyUser/Music/Cover.PNG': 'Cover.PNG', - '/home/MyUser/Music/cover.jpg': 'cover.jpg', - '/home/MyUser/Music/Cover.jpg': 'Cover.jpg', - '/home/MyUser/Music/Cover.JPG': 'Cover.JPG', - '/home/MyUser/Music/cover.jpeg': 'cover.jpeg', - '/home/MyUser/Music/Cover.jpeg': 'Cover.jpeg', - '/home/MyUser/Music/Cover.JPEG': 'Cover.JPEG', - '/home/MyUser/Music/folder.png': 'folder.png', - '/home/MyUser/Music/Folder.png': 'Folder.png', - '/home/MyUser/Music/Folder.PNG': 'Folder.PNG', - '/home/MyUser/Music/folder.jpg': 'folder.jpg', - '/home/MyUser/Music/Folder.jpg': 'Folder.jpg', - '/home/MyUser/Music/Folder.JPG': 'Folder.JPG', - '/home/MyUser/Music/folder.jpeg': 'folder.jpeg', - '/home/MyUser/Music/Folder.jpeg': 'Folder.jpeg', - '/home/MyUser/Music/Folder.JPEG': 'Folder.JPEG', - '/home/MyUser/Music/MyMusicFile.mp3': 'MyMusicFile.mp3', - '/home/MyUser/Music/mymusicfile.png': 'mymusicfile.png', - '/home/MyUser/Music/MyMusicFile.png': 'MyMusicFile.png', - '/home/MyUser/Music/MyMusicFile.PNG': 'MyMusicFile.PNG', - '/home/MyUser/Music/mymusicfile.jpg': 'mymusicfile.jpg', - '/home/MyUser/Music/MyMusicFile.jpg': 'MyMusicFile.jpg', - '/home/MyUser/Music/MyMusicFile.JPG': 'MyMusicFile.JPG', - '/home/MyUser/Music/mymusicfile.jpeg': 'mymusicfile.jpeg', - '/home/MyUser/Music/MyMusicFile.jpeg': 'MyMusicFile.jpeg', - '/home/MyUser/Music/MyMusicFile.JPEG': 'MyMusicFile.JPEG', - }; - - fileAccessMock.getDirectoryPathReturnValues = { '/home/MyUser/Music/MyMusicFile.mp3': '/home/MyUser/Music' }; - - fileAccessMock.getFileNameWithoutExtensionReturnValues = { - '/home/MyUser/Music/MyMusicFile.mp3': 'MyMusicFile', - '/home/MyUser/Music/mymusicfile.png': 'mymusicfile', - '/home/MyUser/Music/MyMusicFile.png': 'MyMusicFile', - '/home/MyUser/Music/MyMusicFile.PNG': 'MyMusicFile', - '/home/MyUser/Music/mymusicfile.jpg': 'mymusicfile', - '/home/MyUser/Music/MyMusicFile.jpg': 'MyMusicFile', - '/home/MyUser/Music/MyMusicFile.JPG': 'MyMusicFile', - '/home/MyUser/Music/mymusicfile.jpeg': 'mymusicfile', - '/home/MyUser/Music/MyMusicFile.jpeg': 'MyMusicFile', - '/home/MyUser/Music/MyMusicFile.JPEG': 'MyMusicFile', - }; - - fileAccessMock.getFilesInDirectoryAsyncReturnValues = { - '/home/MyUser/Music': ['/home/MyUser/Music/MyMusicFile.mp3', '/home/MyUser/Music/' + artworkFileName], - }; - - const sut = createSut(); - return await sut.getExternalArtworkPathAsync('/home/MyUser/Music/MyMusicFile.mp3'); - } - - it('should return a external artwork path if there is an artwork file in the same directory', async () => { - // Arrange - - // Act - const frontPng1ArtworkPath = await getExternalArtworkPathAsync('front.png'); - const frontPng2ArtworkPath = await getExternalArtworkPathAsync('Front.png'); - const frontPng3ArtworkPath = await getExternalArtworkPathAsync('Front.PNG'); - const frontJpg1ArtworkPath = await getExternalArtworkPathAsync('front.jpg'); - const frontJpg2ArtworkPath = await getExternalArtworkPathAsync('Front.jpg'); - const frontJpg3ArtworkPath = await getExternalArtworkPathAsync('Front.JPG'); - const frontJpeg1ArtworkPath = await getExternalArtworkPathAsync('front.jpeg'); - const frontJpeg2ArtworkPath = await getExternalArtworkPathAsync('Front.jpeg'); - const frontJpeg3ArtworkPath = await getExternalArtworkPathAsync('Front.JPEG'); - - const coverPng1ArtworkPath = await getExternalArtworkPathAsync('cover.png'); - const coverPng2ArtworkPath = await getExternalArtworkPathAsync('Cover.png'); - const coverPng3ArtworkPath = await getExternalArtworkPathAsync('Cover.PNG'); - const coverJpg1ArtworkPath = await getExternalArtworkPathAsync('cover.jpg'); - const coverJpg2ArtworkPath = await getExternalArtworkPathAsync('Cover.jpg'); - const coverJpg3ArtworkPath = await getExternalArtworkPathAsync('Cover.JPG'); - const coverJpeg1ArtworkPath = await getExternalArtworkPathAsync('cover.jpeg'); - const coverJpeg2ArtworkPath = await getExternalArtworkPathAsync('Cover.jpeg'); - const coverJpeg3ArtworkPath = await getExternalArtworkPathAsync('Cover.JPEG'); - - const folderPng1ArtworkPath = await getExternalArtworkPathAsync('folder.png'); - const folderPng2ArtworkPath = await getExternalArtworkPathAsync('Folder.png'); - const folderPng3ArtworkPath = await getExternalArtworkPathAsync('Folder.PNG'); - const folderJpg1ArtworkPath = await getExternalArtworkPathAsync('folder.jpg'); - const folderJpg2ArtworkPath = await getExternalArtworkPathAsync('Folder.jpg'); - const folderJpg3ArtworkPath = await getExternalArtworkPathAsync('Folder.JPG'); - const folderJpeg1ArtworkPath = await getExternalArtworkPathAsync('folder.jpeg'); - const folderJpeg2ArtworkPath = await getExternalArtworkPathAsync('Folder.jpeg'); - const folderJpeg3ArtworkPath = await getExternalArtworkPathAsync('Folder.JPEG'); - - const fileNamePng1ArtworkPath = await getExternalArtworkPathAsync('mymusicfile.png'); - const fileNamePng2ArtworkPath = await getExternalArtworkPathAsync('MyMusicFile.png'); - const fileNamePng3ArtworkPath = await getExternalArtworkPathAsync('MyMusicFile.PNG'); - const fileNameJpg1ArtworkPath = await getExternalArtworkPathAsync('mymusicfile.jpg'); - const fileNameJpg2ArtworkPath = await getExternalArtworkPathAsync('MyMusicFile.jpg'); - const fileNameJpg3ArtworkPath = await getExternalArtworkPathAsync('MyMusicFile.JPG'); - const fileNameJpeg1ArtworkPath = await getExternalArtworkPathAsync('mymusicfile.jpeg'); - const fileNameJpeg2ArtworkPath = await getExternalArtworkPathAsync('MyMusicFile.jpeg'); - const fileNameJpeg3ArtworkPath = await getExternalArtworkPathAsync('MyMusicFile.JPEG'); - - // Assert - expect(frontPng1ArtworkPath).toEqual('/home/MyUser/Music/front.png'); - expect(frontPng2ArtworkPath).toEqual('/home/MyUser/Music/Front.png'); - expect(frontPng3ArtworkPath).toEqual('/home/MyUser/Music/Front.PNG'); - expect(frontJpg1ArtworkPath).toEqual('/home/MyUser/Music/front.jpg'); - expect(frontJpg2ArtworkPath).toEqual('/home/MyUser/Music/Front.jpg'); - expect(frontJpg3ArtworkPath).toEqual('/home/MyUser/Music/Front.JPG'); - expect(frontJpeg1ArtworkPath).toEqual('/home/MyUser/Music/front.jpeg'); - expect(frontJpeg2ArtworkPath).toEqual('/home/MyUser/Music/Front.jpeg'); - expect(frontJpeg3ArtworkPath).toEqual('/home/MyUser/Music/Front.JPEG'); - - expect(coverPng1ArtworkPath).toEqual('/home/MyUser/Music/cover.png'); - expect(coverPng2ArtworkPath).toEqual('/home/MyUser/Music/Cover.png'); - expect(coverPng3ArtworkPath).toEqual('/home/MyUser/Music/Cover.PNG'); - expect(coverJpg1ArtworkPath).toEqual('/home/MyUser/Music/cover.jpg'); - expect(coverJpg2ArtworkPath).toEqual('/home/MyUser/Music/Cover.jpg'); - expect(coverJpg3ArtworkPath).toEqual('/home/MyUser/Music/Cover.JPG'); - expect(coverJpeg1ArtworkPath).toEqual('/home/MyUser/Music/cover.jpeg'); - expect(coverJpeg2ArtworkPath).toEqual('/home/MyUser/Music/Cover.jpeg'); - expect(coverJpeg3ArtworkPath).toEqual('/home/MyUser/Music/Cover.JPEG'); - - expect(folderPng1ArtworkPath).toEqual('/home/MyUser/Music/folder.png'); - expect(folderPng2ArtworkPath).toEqual('/home/MyUser/Music/Folder.png'); - expect(folderPng3ArtworkPath).toEqual('/home/MyUser/Music/Folder.PNG'); - expect(folderJpg1ArtworkPath).toEqual('/home/MyUser/Music/folder.jpg'); - expect(folderJpg2ArtworkPath).toEqual('/home/MyUser/Music/Folder.jpg'); - expect(folderJpg3ArtworkPath).toEqual('/home/MyUser/Music/Folder.JPG'); - expect(folderJpeg1ArtworkPath).toEqual('/home/MyUser/Music/folder.jpeg'); - expect(folderJpeg2ArtworkPath).toEqual('/home/MyUser/Music/Folder.jpeg'); - expect(folderJpeg3ArtworkPath).toEqual('/home/MyUser/Music/Folder.JPEG'); - - expect(fileNamePng1ArtworkPath).toEqual('/home/MyUser/Music/mymusicfile.png'); - expect(fileNamePng2ArtworkPath).toEqual('/home/MyUser/Music/MyMusicFile.png'); - expect(fileNamePng3ArtworkPath).toEqual('/home/MyUser/Music/MyMusicFile.PNG'); - expect(fileNameJpg1ArtworkPath).toEqual('/home/MyUser/Music/mymusicfile.jpg'); - expect(fileNameJpg2ArtworkPath).toEqual('/home/MyUser/Music/MyMusicFile.jpg'); - expect(fileNameJpg3ArtworkPath).toEqual('/home/MyUser/Music/MyMusicFile.JPG'); - expect(fileNameJpeg1ArtworkPath).toEqual('/home/MyUser/Music/mymusicfile.jpeg'); - expect(fileNameJpeg2ArtworkPath).toEqual('/home/MyUser/Music/MyMusicFile.jpeg'); - expect(fileNameJpeg3ArtworkPath).toEqual('/home/MyUser/Music/MyMusicFile.JPEG'); - }); - }); -}); diff --git a/main/indexing/indexer.js b/main/indexing/indexer.js index abe190f35..e1c4b0438 100644 --- a/main/indexing/indexer.js +++ b/main/indexing/indexer.js @@ -1,9 +1,8 @@ const { DismissMessage } = require('./messages/dismiss-message'); class Indexer { - constructor(collectionChecker, albumArtworkIndexer, trackIndexer, trackRepository, workerProxy, logger) { + constructor(collectionChecker, trackIndexer, trackRepository, workerProxy, logger) { this.collectionChecker = collectionChecker; - this.albumArtworkIndexer = albumArtworkIndexer; this.trackIndexer = trackIndexer; this.trackRepository = trackRepository; this.workerProxy = workerProxy; @@ -22,8 +21,6 @@ class Indexer { this.logger.info('Collection is not outdated.', 'Indexer', 'indexCollectionIfOutdatedAsync'); } - await this.albumArtworkIndexer.indexAlbumArtworkAsync(); - this.workerProxy.postMessage(new DismissMessage()); } @@ -31,16 +28,6 @@ class Indexer { this.logger.info('Indexing collection.', 'Indexer', 'indexCollectionAlwaysAsync'); await this.trackIndexer.indexTracksAsync(); - await this.albumArtworkIndexer.indexAlbumArtworkAsync(); - - this.workerProxy.postMessage(new DismissMessage()); - } - - async indexAlbumArtworkOnlyAsync(onlyWhenHasNoCover) { - this.logger.info('Indexing collection.', 'IndexingService', 'indexAlbumArtworkOnlyAsync'); - - this.trackRepository.enableNeedsAlbumArtworkIndexingForAllTracks(onlyWhenHasNoCover); - await this.albumArtworkIndexer.indexAlbumArtworkAsync(); this.workerProxy.postMessage(new DismissMessage()); } diff --git a/main/indexing/online-album-artwork-getter.js b/main/indexing/online-album-artwork-getter.js deleted file mode 100644 index 2510a5b14..000000000 --- a/main/indexing/online-album-artwork-getter.js +++ /dev/null @@ -1,83 +0,0 @@ -const { StringUtils } = require('../common/utils/string-utils'); - -class OnlineAlbumArtworkGetter { - constructor(imageProcessor, lastfmApi, logger) { - this.imageProcessor = imageProcessor; - this.lastfmApi = lastfmApi; - this.logger = logger; - } - async getOnlineArtworkAsync(fileMetadata) { - if (fileMetadata === undefined || fileMetadata === null) { - return undefined; - } - - let title = ''; - const artists = []; - - // Title - if (!StringUtils.isNullOrWhiteSpace(fileMetadata.album)) { - title = fileMetadata.album; - } else if (!StringUtils.isNullOrWhiteSpace(fileMetadata.title)) { - title = fileMetadata.title; - } - - // Artist - if (fileMetadata.albumArtists !== undefined && fileMetadata.albumArtists !== null && fileMetadata.albumArtists.length > 0) { - const nonWhiteSpaceAlbumArtists = fileMetadata.albumArtists.filter((x) => !StringUtils.isNullOrWhiteSpace(x)); - artists.push(...nonWhiteSpaceAlbumArtists); - } - - if (fileMetadata.artists !== undefined && fileMetadata.artists !== null && fileMetadata.artists.length > 0) { - const nonWhiteSpaceTrackArtists = fileMetadata.artists.filter((x) => !StringUtils.isNullOrWhiteSpace(x)); - artists.push(...nonWhiteSpaceTrackArtists); - } - - if (StringUtils.isNullOrWhiteSpace(title) || artists.length === 0) { - return undefined; - } - - for (const artist of artists) { - let lastfmAlbum; - - try { - lastfmAlbum = await this.lastfmApi.getAlbumInfoAsync(artist, title, false, 'EN'); - } catch (e) { - this.logger.error( - e, - `Could not get album info for artist='${artist}' and title='${title}'`, - 'OnlineAlbumArtworkGetter', - 'getOnlineArtworkAsync', - ); - } - - if (lastfmAlbum !== undefined && lastfmAlbum !== null) { - if (!StringUtils.isNullOrWhiteSpace(lastfmAlbum.largestImage())) { - let artworkData; - - try { - artworkData = await this.imageProcessor.convertOnlineImageToBufferAsync(lastfmAlbum.largestImage()); - - this.logger.info( - `Downloaded online artwork for artist='${artist}' and title='${title}'`, - 'OnlineAlbumArtworkGetter', - 'getOnlineArtworkAsync', - ); - - return artworkData; - } catch (e) { - this.logger.error( - e, - `Could not convert file '${lastfmAlbum.largestImage()}' to data`, - 'OnlineAlbumArtworkGetter', - 'getOnlineArtworkAsync', - ); - } - } - } - } - - return undefined; - } -} - -exports.OnlineAlbumArtworkGetter = OnlineAlbumArtworkGetter; diff --git a/main/indexing/online-album-artwork-getter.spec.js b/main/indexing/online-album-artwork-getter.spec.js deleted file mode 100644 index 40be72e4f..000000000 --- a/main/indexing/online-album-artwork-getter.spec.js +++ /dev/null @@ -1,207 +0,0 @@ -const { ImageProcessorMock } = require('../mocks/image-processor-mock'); -const { LoggerMock } = require('../mocks/logger-mock'); -const { OnlineAlbumArtworkGetter } = require('./online-album-artwork-getter'); -const { LastfmApiMock } = require('../mocks/lastfm.api-mock'); -const { LastfmAlbum } = require('../common/api/lastfm-album'); -const { FileMetadataMock } = require('../mocks/file-metadata-mock'); - -describe('OnlineAlbumArtworkGetter', () => { - let imageProcessorMock; - let lastfmApiMock; - let loggerMock; - - let lastfmAlbum; - - beforeEach(() => { - imageProcessorMock = new ImageProcessorMock(); - lastfmApiMock = new LastfmApiMock(); - loggerMock = new LoggerMock(); - - lastfmAlbum = new LastfmAlbum(); - lastfmAlbum.imageMega = 'http://images.com/image.png'; - }); - - function createSut() { - return new OnlineAlbumArtworkGetter(imageProcessorMock, lastfmApiMock, loggerMock); - } - - describe('getOnlineArtworkAsync', () => { - it('should return undefined if fileMetaData is undefined', async () => { - // Arrange - // const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - // lastfmApiMock.getAlbumInfoAsyncReturnValues = { 'Artist;Album;false;EN': lastfmAlbum }; - // imageProcessorMock.convertOnlineImageToBufferAsyncReturnValues = { 'http://images.com/image.png': expectedAlbumArtwork }; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getOnlineArtworkAsync(undefined); - - // Assert - expect(actualAlbumArtwork).toBeUndefined(); - }); - - it('should return undefined if fileMetaData has artists but no titles', async () => { - // Arrange - const metaDataMock = new FileMetadataMock('Path'); - metaDataMock.album = ''; - metaDataMock.title = ''; - metaDataMock.artists = ['Artist 1', 'Artist 2']; - metaDataMock.albumArtists = ['Album artist 1', 'Album artist 2']; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getOnlineArtworkAsync(metaDataMock); - - // Assert - expect(actualAlbumArtwork).toBeUndefined(); - }); - - it('should return artwork if fileMetaData has artists and titles', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - lastfmApiMock.getAlbumInfoAsyncReturnValues = { 'Artist 1;My album title;false;EN': lastfmAlbum }; - imageProcessorMock.convertOnlineImageToBufferAsyncReturnValues = { 'http://images.com/image.png': expectedAlbumArtwork }; - - const metaDataMock = new FileMetadataMock('Path'); - metaDataMock.album = 'My album title'; - metaDataMock.title = 'My track title'; - metaDataMock.artists = ['Artist 1', 'Artist 2']; - metaDataMock.albumArtists = ['Album artist 1', 'Album artist 2']; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getOnlineArtworkAsync(metaDataMock); - - // Assert - expect(actualAlbumArtwork).toBe(expectedAlbumArtwork); - }); - - it('should return artwork if fileMetaData has artists and only a track title', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - lastfmApiMock.getAlbumInfoAsyncReturnValues = { 'Artist 1;My track title;false;EN': lastfmAlbum }; - imageProcessorMock.convertOnlineImageToBufferAsyncReturnValues = { 'http://images.com/image.png': expectedAlbumArtwork }; - - const metaDataMock = new FileMetadataMock('Path'); - metaDataMock.album = ''; - metaDataMock.title = 'My track title'; - metaDataMock.artists = ['Artist 1', 'Artist 2']; - metaDataMock.albumArtists = ['Album artist 1', 'Album artist 2']; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getOnlineArtworkAsync(metaDataMock); - - // Assert - expect(actualAlbumArtwork).toBe(expectedAlbumArtwork); - }); - - it('should return artwork if fileMetaData has artists and only an album title', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - lastfmApiMock.getAlbumInfoAsyncReturnValues = { 'Artist 1;My album title;false;EN': lastfmAlbum }; - imageProcessorMock.convertOnlineImageToBufferAsyncReturnValues = { 'http://images.com/image.png': expectedAlbumArtwork }; - - const metaDataMock = new FileMetadataMock('Path'); - metaDataMock.album = 'My album title'; - metaDataMock.title = ''; - metaDataMock.artists = ['Artist 1', 'Artist 2']; - metaDataMock.albumArtists = ['Album artist 1', 'Album artist 2']; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getOnlineArtworkAsync(metaDataMock); - - // Assert - expect(actualAlbumArtwork).toBe(expectedAlbumArtwork); - }); - - it('should return artwork if fileMetaData has titles and only album artists', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - lastfmApiMock.getAlbumInfoAsyncReturnValues = { 'Album artist 1;My album title;false;EN': lastfmAlbum }; - imageProcessorMock.convertOnlineImageToBufferAsyncReturnValues = { 'http://images.com/image.png': expectedAlbumArtwork }; - - const metaDataMock = new FileMetadataMock('Path'); - metaDataMock.album = 'My album title'; - metaDataMock.title = 'My track title'; - metaDataMock.artists = []; - metaDataMock.albumArtists = ['Album artist 1', 'Album artist 2']; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getOnlineArtworkAsync(metaDataMock); - - // Assert - expect(actualAlbumArtwork).toBe(expectedAlbumArtwork); - }); - - it('should return artwork if fileMetaData has titles and only track artists', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - lastfmApiMock.getAlbumInfoAsyncReturnValues = { 'Artist 1;My album title;false;EN': lastfmAlbum }; - imageProcessorMock.convertOnlineImageToBufferAsyncReturnValues = { 'http://images.com/image.png': expectedAlbumArtwork }; - - const metaDataMock = new FileMetadataMock('Path'); - metaDataMock.album = 'My album title'; - metaDataMock.title = 'My track title'; - metaDataMock.artists = ['Artist 1', 'Artist 2']; - metaDataMock.albumArtists = []; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getOnlineArtworkAsync(metaDataMock); - - // Assert - expect(actualAlbumArtwork).toBe(expectedAlbumArtwork); - }); - - it('should return undefined if converting file to data throws error', async () => { - // Arrange - lastfmApiMock.getAlbumInfoAsyncReturnValues = { 'Artist 1;My album title;false;EN': lastfmAlbum }; - imageProcessorMock.convertImageBufferToFileAsyncShouldThrowError = true; - - const metaDataMock = new FileMetadataMock('Path'); - metaDataMock.album = 'My album title'; - metaDataMock.title = 'My track title'; - metaDataMock.artists = ['Artist 1', 'Artist 2']; - metaDataMock.albumArtists = ['Album artist 1', 'Album artist 2']; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getOnlineArtworkAsync(metaDataMock); - - // Assert - expect(actualAlbumArtwork).toBeUndefined(); - }); - - it('should return undefined if getting online album info throws error', async () => { - // Arrange - const expectedAlbumArtwork = Buffer.from([1, 2, 3]); - lastfmApiMock.getAlbumInfoAsyncThrowsError = true; - imageProcessorMock.convertOnlineImageToBufferAsyncReturnValues = { 'http://images.com/image.png': expectedAlbumArtwork }; - - const metaDataMock = new FileMetadataMock('Path'); - metaDataMock.album = 'My album title'; - metaDataMock.title = 'My track title'; - metaDataMock.artists = ['Artist 1', 'Artist 2']; - metaDataMock.albumArtists = ['Album artist 1', 'Album artist 2']; - - const sut = createSut(); - - // Act - const actualAlbumArtwork = await sut.getOnlineArtworkAsync(metaDataMock); - - // Assert - expect(actualAlbumArtwork).toBeUndefined(); - }); - }); -}); diff --git a/main/ioc/ioc.js b/main/ioc/ioc.js index 186fa285f..18b3b961c 100644 --- a/main/ioc/ioc.js +++ b/main/ioc/ioc.js @@ -2,36 +2,23 @@ const { DateTime } = require('../common/date-time'); const { FileAccess } = require('../common/io/file-access'); const { WorkerProxy } = require('../workers/worker-proxy'); const { DatabaseFactory } = require('../data/database.factory'); -const { AlbumArtworkRepository } = require('../data/album-artwork-repository'); const { FolderRepository } = require('../data/folder-repository'); const { FolderTrackRepository } = require('../data/folder-track-repository'); const { RemovedTrackRepository } = require('../data/removed-track-repository'); const { LastfmApi } = require('../common/api/lastfm.api'); const { ApplicationPaths } = require('../common/application/application-paths'); const { GuidFactory } = require('../common/guid.factory'); -const { ImageProcessor } = require('../common/image-processor'); const { Logger } = require('../common/logger'); -const { AlbumArtworkCacheId } = require('../indexing/album-artwork-cache-id'); -const { AlbumArtworkCacheIdFactory } = require('../indexing/album-artwork-cache-id-factory'); const { TrackVerifier } = require('../indexing/track-verifier'); const { MimeTypes } = require('../indexing/mime-types'); const { DirectoryWalker } = require('../indexing/directory-walker'); const { MetadataPatcher } = require('../indexing/metadata-patcher'); const { TrackFieldCreator } = require('../indexing/track-field-creator'); -const { AlbumArtworkAdder } = require('../indexing/album-artwork-adder'); const { TrackRepository } = require('../data/track-repository'); -const { ExternalArtworkPathGetter } = require('../indexing/external-artwork-path-getter'); -const { ExternalAlbumArtworkGetter } = require('../indexing/external-album-artwork-getter'); -const { EmbeddedAlbumArtworkGetter } = require('../indexing/embedded-album-artwork-getter'); -const { OnlineAlbumArtworkGetter } = require('../indexing/online-album-artwork-getter'); -const { AlbumArtworkGetter } = require('../indexing/album-artwork-getter'); const { FileMetadataFactory } = require('../indexing/file-metadata.factory'); -const { AlbumArtworkCache } = require('../indexing/album-artwork-cache'); const { IndexablePathFetcher } = require('../indexing/indexable-path-fetcher'); const { AlbumKeyGenerator } = require('../indexing/album-key-generator'); const { TrackFiller } = require('../indexing/track-filler'); -const { AlbumArtworkRemover } = require('../indexing/album-artwork-remover'); -const { AlbumArtworkIndexer } = require('../indexing/album-artwork-indexer'); const { CollectionChecker } = require('../indexing/collection-checker'); const { TrackUpdater } = require('../indexing/track-updater'); const { TrackRemover } = require('../indexing/track-remover'); @@ -53,90 +40,21 @@ class Ioc { global.iocContainer.set('FileMetadataFactory', new FileMetadataFactory()); global.iocContainer.set('AlbumKeyGenerator', new AlbumKeyGenerator()); - global.iocContainer.set('AlbumArtworkCacheIdFactory', new AlbumArtworkCacheIdFactory(Ioc.get('GuidFactory'))); - global.iocContainer.set('FileAccess', new FileAccess(Ioc.get('DateTime'))); - global.iocContainer.set('ImageProcessor', new ImageProcessor(Ioc.get('FileAccess'))); global.iocContainer.set('TrackVerifier', new TrackVerifier(Ioc.get('FileAccess'))); global.iocContainer.set('DirectoryWalker', new DirectoryWalker(Ioc.get('FileAccess'))); - global.iocContainer.set('ExternalArtworkPathGetter', new ExternalArtworkPathGetter(Ioc.get('FileAccess'))); global.iocContainer.set('ApplicationPaths', new ApplicationPaths(Ioc.get('FileAccess'), Ioc.get('WorkerProxy'))); global.iocContainer.set('DatabaseFactory', new DatabaseFactory(Ioc.get('FileAccess'), Ioc.get('WorkerProxy'))); - global.iocContainer.set('AlbumArtworkRepository', new AlbumArtworkRepository(Ioc.get('DatabaseFactory'))); global.iocContainer.set('FolderRepository', new FolderRepository(Ioc.get('DatabaseFactory'))); global.iocContainer.set('FolderTrackRepository', new FolderTrackRepository(Ioc.get('DatabaseFactory'))); global.iocContainer.set('RemovedTrackRepository', new RemovedTrackRepository(Ioc.get('DatabaseFactory'))); global.iocContainer.set('TrackRepository', new TrackRepository(Ioc.get('DatabaseFactory'))); - global.iocContainer.set('AlbumArtworkCacheId', new AlbumArtworkCacheId(Ioc.get('GuidFactory'))); - global.iocContainer.set('TrackFieldCreator', new TrackFieldCreator(Ioc.get('MetadataPatcher'))); - global.iocContainer.set( - 'ExternalAlbumArtworkGetter', - new ExternalAlbumArtworkGetter(Ioc.get('ExternalArtworkPathGetter'), Ioc.get('ImageProcessor'), Ioc.get('Logger')), - ); - - global.iocContainer.set('EmbeddedAlbumArtworkGetter', new EmbeddedAlbumArtworkGetter(Ioc.get('Logger'))); - - global.iocContainer.set( - 'OnlineAlbumArtworkGetter', - new OnlineAlbumArtworkGetter(Ioc.get('ImageProcessor'), Ioc.get('LastfmApi'), Ioc.get('Logger')), - ); - - global.iocContainer.set( - 'AlbumArtworkGetter', - new AlbumArtworkGetter( - Ioc.get('EmbeddedAlbumArtworkGetter'), - Ioc.get('ExternalAlbumArtworkGetter'), - Ioc.get('OnlineAlbumArtworkGetter'), - Ioc.get('WorkerProxy'), - ), - ); - - global.iocContainer.set( - 'AlbumArtworkCache', - new AlbumArtworkCache( - Ioc.get('AlbumArtworkCacheIdFactory'), - Ioc.get('ImageProcessor'), - Ioc.get('ApplicationPaths'), - Ioc.get('FileAccess'), - Ioc.get('Logger'), - ), - ); - - global.iocContainer.set( - 'AlbumArtworkAdder', - new AlbumArtworkAdder( - Ioc.get('TrackRepository'), - Ioc.get('AlbumArtworkRepository'), - Ioc.get('AlbumArtworkGetter'), - Ioc.get('FileMetadataFactory'), - Ioc.get('AlbumArtworkCache'), - Ioc.get('WorkerProxy'), - Ioc.get('Logger'), - ), - ); - - global.iocContainer.set( - 'AlbumArtworkRemover', - new AlbumArtworkRemover( - Ioc.get('AlbumArtworkRepository'), - Ioc.get('ApplicationPaths'), - Ioc.get('WorkerProxy'), - Ioc.get('FileAccess'), - Ioc.get('Logger'), - ), - ); - - global.iocContainer.set( - 'AlbumArtworkIndexer', - new AlbumArtworkIndexer(Ioc.get('AlbumArtworkRemover'), Ioc.get('AlbumArtworkAdder'), Ioc.get('Logger')), - ); - global.iocContainer.set( 'IndexablePathFetcher', new IndexablePathFetcher(Ioc.get('FolderRepository'), Ioc.get('DirectoryWalker'), Ioc.get('FileAccess'), Ioc.get('Logger')), @@ -210,7 +128,6 @@ class Ioc { 'Indexer', new Indexer( Ioc.get('CollectionChecker'), - Ioc.get('AlbumArtworkIndexer'), Ioc.get('TrackIndexer'), Ioc.get('TrackRepository'), Ioc.get('WorkerProxy'), diff --git a/main/mocks/track-repository-mock.js b/main/mocks/track-repository-mock.js index 70c799342..34e6fac2b 100644 --- a/main/mocks/track-repository-mock.js +++ b/main/mocks/track-repository-mock.js @@ -1,9 +1,4 @@ class TrackRepositoryMock { - getAlbumDataThatNeedsIndexingCalls = []; - getAlbumDataThatNeedsIndexingReturnValue = []; - getLastModifiedTrackForAlbumKeyAsyncCalls = []; - getLastModifiedTrackForAlbumKeyAsyncReturnValues = {}; - disableNeedsAlbumArtworkIndexingCalls = []; getNumberOfTracksThatDoNotBelongFoldersCalls = 0; getNumberOfTracksThatDoNotBelongFoldersReturnValue = 0; deleteTracksThatDoNotBelongFoldersCalls = 0; @@ -62,22 +57,6 @@ class TrackRepositoryMock { this.getTrackByPathReturnCalls.push(path); return this.getTrackByPathReturnValues[path]; } - - disableNeedsAlbumArtworkIndexing(albumKey) { - this.disableNeedsAlbumArtworkIndexingCalls.push(albumKey); - } - - getLastModifiedTrackForAlbumKeyAsync(albumKeyIndex, albumKey) { - this.getLastModifiedTrackForAlbumKeyAsyncCalls.push({}); - return this.getLastModifiedTrackForAlbumKeyAsyncReturnValues[albumKey]; - } - - getAlbumDataThatNeedsIndexing(albumKeyIndex) { - this.getAlbumDataThatNeedsIndexingCalls.push({}); - return this.getAlbumDataThatNeedsIndexingReturnValue; - } - - enableNeedsAlbumArtworkIndexingForAllTracks(onlyWhenHasNoCover) {} } exports.TrackRepositoryMock = TrackRepositoryMock; diff --git a/main/workers/indexing-worker.js b/main/workers/indexing-worker.js index 4598f3b02..b92021e0b 100644 --- a/main/workers/indexing-worker.js +++ b/main/workers/indexing-worker.js @@ -16,8 +16,6 @@ async function performTaskAsync() { await indexer.indexCollectionIfOutdatedAsync(); } else if (workerProxy.task() === 'always') { await indexer.indexCollectionAlwaysAsync(); - } else if (workerProxy.task() === 'albumArtwork') { - await indexer.indexAlbumArtworkOnlyAsync(); } } catch (e) { logger.error(e, 'Unexpected error', 'IndexingWorker', 'performTaskAsync'); diff --git a/main/workers/worker-proxy.js b/main/workers/worker-proxy.js index 0f5f250f8..c4b680050 100644 --- a/main/workers/worker-proxy.js +++ b/main/workers/worker-proxy.js @@ -13,17 +13,9 @@ class WorkerProxy { return workerData.arg.applicationDataDirectory; } - downloadMissingAlbumCovers() { - return workerData.arg.downloadMissingAlbumCovers; - } - skipRemovedFilesDuringRefresh() { return workerData.arg.skipRemovedFilesDuringRefresh; } - - albumKeyIndex() { - return workerData.arg.albumKeyIndex; - } } exports.WorkerProxy = WorkerProxy; diff --git a/package-lock.json b/package-lock.json index a67e22d1b..6d0da7c57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,6 @@ "node-taglib-sharp": "5.2.3", "node-vibrant": "^3.2.1-alpha.1", "sanitize-filename": "1.6.3", - "sharp": "^0.33.4", "tinycolor2": "1.6.0", "tslib": "2.0.0", "uuid": "9.0.0" @@ -4572,21 +4571,6 @@ "node": "*" } }, - "node_modules/@emnapi/runtime": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", - "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime/node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "optional": true - }, "node_modules/@esbuild/android-arm": { "version": "0.17.8", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.8.tgz", @@ -5162,437 +5146,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz", - "integrity": "sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.0.2" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz", - "integrity": "sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.0.2" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", - "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "macos": ">=11", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", - "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "macos": ">=10.13", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", - "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.28", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", - "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.26", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", - "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.28", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", - "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.26", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", - "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "musl": ">=1.2.2", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", - "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "musl": ">=1.2.2", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz", - "integrity": "sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.28", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.0.2" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz", - "integrity": "sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.0.2" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz", - "integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.31", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.0.2" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz", - "integrity": "sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "glibc": ">=2.26", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.0.2" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz", - "integrity": "sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "musl": ">=1.2.2", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz", - "integrity": "sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "musl": ">=1.2.2", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.2" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz", - "integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==", - "cpu": [ - "wasm32" - ], - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.1.1" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz", - "integrity": "sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz", - "integrity": "sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0", - "npm": ">=9.6.5", - "pnpm": ">=7.1.0", - "yarn": ">=3.2.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -11493,18 +11046,6 @@ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -11517,16 +11058,8 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/color-support": { "version": "1.1.3", @@ -11537,22 +11070,6 @@ "color-support": "bin.js" } }, - "node_modules/color/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "node_modules/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", @@ -22749,75 +22266,6 @@ "node": ">=8" } }, - "node_modules/sharp": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz", - "integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==", - "hasInstallScript": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.0" - }, - "engines": { - "libvips": ">=8.15.2", - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.33.4", - "@img/sharp-darwin-x64": "0.33.4", - "@img/sharp-libvips-darwin-arm64": "1.0.2", - "@img/sharp-libvips-darwin-x64": "1.0.2", - "@img/sharp-libvips-linux-arm": "1.0.2", - "@img/sharp-libvips-linux-arm64": "1.0.2", - "@img/sharp-libvips-linux-s390x": "1.0.2", - "@img/sharp-libvips-linux-x64": "1.0.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", - "@img/sharp-libvips-linuxmusl-x64": "1.0.2", - "@img/sharp-linux-arm": "0.33.4", - "@img/sharp-linux-arm64": "0.33.4", - "@img/sharp-linux-s390x": "0.33.4", - "@img/sharp-linux-x64": "0.33.4", - "@img/sharp-linuxmusl-arm64": "0.33.4", - "@img/sharp-linuxmusl-x64": "0.33.4", - "@img/sharp-wasm32": "0.33.4", - "@img/sharp-win32-ia32": "0.33.4", - "@img/sharp-win32-x64": "0.33.4" - } - }, - "node_modules/sharp/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -22932,19 +22380,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -29141,23 +28576,6 @@ } } }, - "@emnapi/runtime": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", - "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", - "optional": true, - "requires": { - "tslib": "^2.4.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "optional": true - } - } - }, "@esbuild/android-arm": { "version": "0.17.8", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.8.tgz", @@ -29483,147 +28901,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@img/sharp-darwin-arm64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.4.tgz", - "integrity": "sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==", - "optional": true, - "requires": { - "@img/sharp-libvips-darwin-arm64": "1.0.2" - } - }, - "@img/sharp-darwin-x64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.4.tgz", - "integrity": "sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==", - "optional": true, - "requires": { - "@img/sharp-libvips-darwin-x64": "1.0.2" - } - }, - "@img/sharp-libvips-darwin-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.2.tgz", - "integrity": "sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==", - "optional": true - }, - "@img/sharp-libvips-darwin-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.2.tgz", - "integrity": "sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==", - "optional": true - }, - "@img/sharp-libvips-linux-arm": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.2.tgz", - "integrity": "sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==", - "optional": true - }, - "@img/sharp-libvips-linux-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.2.tgz", - "integrity": "sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==", - "optional": true - }, - "@img/sharp-libvips-linux-s390x": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.2.tgz", - "integrity": "sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==", - "optional": true - }, - "@img/sharp-libvips-linux-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.2.tgz", - "integrity": "sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==", - "optional": true - }, - "@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.2.tgz", - "integrity": "sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==", - "optional": true - }, - "@img/sharp-libvips-linuxmusl-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.2.tgz", - "integrity": "sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==", - "optional": true - }, - "@img/sharp-linux-arm": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.4.tgz", - "integrity": "sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==", - "optional": true, - "requires": { - "@img/sharp-libvips-linux-arm": "1.0.2" - } - }, - "@img/sharp-linux-arm64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.4.tgz", - "integrity": "sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==", - "optional": true, - "requires": { - "@img/sharp-libvips-linux-arm64": "1.0.2" - } - }, - "@img/sharp-linux-s390x": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.4.tgz", - "integrity": "sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==", - "optional": true, - "requires": { - "@img/sharp-libvips-linux-s390x": "1.0.2" - } - }, - "@img/sharp-linux-x64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.4.tgz", - "integrity": "sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==", - "optional": true, - "requires": { - "@img/sharp-libvips-linux-x64": "1.0.2" - } - }, - "@img/sharp-linuxmusl-arm64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.4.tgz", - "integrity": "sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==", - "optional": true, - "requires": { - "@img/sharp-libvips-linuxmusl-arm64": "1.0.2" - } - }, - "@img/sharp-linuxmusl-x64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.4.tgz", - "integrity": "sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==", - "optional": true, - "requires": { - "@img/sharp-libvips-linuxmusl-x64": "1.0.2" - } - }, - "@img/sharp-wasm32": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.4.tgz", - "integrity": "sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==", - "optional": true, - "requires": { - "@emnapi/runtime": "^1.1.1" - } - }, - "@img/sharp-win32-ia32": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.4.tgz", - "integrity": "sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==", - "optional": true - }, - "@img/sharp-win32-x64": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.4.tgz", - "integrity": "sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==", - "optional": true - }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -34511,30 +33788,6 @@ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, - "color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "requires": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "dependencies": { - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - } - } - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -34547,16 +33800,8 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "color-support": { "version": "1.1.3", @@ -42911,58 +42156,6 @@ "kind-of": "^6.0.2" } }, - "sharp": { - "version": "0.33.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.4.tgz", - "integrity": "sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==", - "requires": { - "@img/sharp-darwin-arm64": "0.33.4", - "@img/sharp-darwin-x64": "0.33.4", - "@img/sharp-libvips-darwin-arm64": "1.0.2", - "@img/sharp-libvips-darwin-x64": "1.0.2", - "@img/sharp-libvips-linux-arm": "1.0.2", - "@img/sharp-libvips-linux-arm64": "1.0.2", - "@img/sharp-libvips-linux-s390x": "1.0.2", - "@img/sharp-libvips-linux-x64": "1.0.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.0.2", - "@img/sharp-libvips-linuxmusl-x64": "1.0.2", - "@img/sharp-linux-arm": "0.33.4", - "@img/sharp-linux-arm64": "0.33.4", - "@img/sharp-linux-s390x": "0.33.4", - "@img/sharp-linux-x64": "0.33.4", - "@img/sharp-linuxmusl-arm64": "0.33.4", - "@img/sharp-linuxmusl-x64": "0.33.4", - "@img/sharp-wasm32": "0.33.4", - "@img/sharp-win32-ia32": "0.33.4", - "@img/sharp-win32-x64": "0.33.4", - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.6.0" - }, - "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -43028,21 +42221,6 @@ "simple-concat": "^1.0.0" } }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } - } - }, "simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", diff --git a/package.json b/package.json index 2ea7e7f4c..275e86ffb 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,6 @@ "node-taglib-sharp": "5.2.3", "node-vibrant": "^3.2.1-alpha.1", "sanitize-filename": "1.6.3", - "sharp": "^0.33.4", "tinycolor2": "1.6.0", "tslib": "2.0.0", "uuid": "9.0.0" diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ebfd666f0..01436f746 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -299,6 +299,10 @@ import { EmbeddedAlbumArtworkGetter } from './services/indexing/embedded-album-a import { OnlineAlbumArtworkGetter } from './services/indexing/online-album-artwork-getter'; import { ExternalAlbumArtworkGetter } from './services/indexing/external-album-artwork-getter'; import { ExternalArtworkPathGetter } from './services/indexing/external-artwork-path-getter'; +import { AlbumArtworkRepositoryBase } from './data/repositories/album-artwork-repository.base'; +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'; export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { return new TranslateHttpLoader(http, './assets/i18n/', '.json'); @@ -561,9 +565,11 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj { provide: FileAccessBase, useClass: FileAccess }, { provide: TrackRepositoryBase, useClass: TrackRepository }, { provide: FolderRepositoryBase, useClass: FolderRepository }, + { provide: AlbumArtworkRepositoryBase, useClass: AlbumArtworkRepository }, { provide: ApplicationServiceBase, useClass: ApplicationService }, { provide: NavigationServiceBase, useClass: NavigationService }, { provide: IndexingServiceBase, useClass: IndexingService }, + { provide: AlbumArtworkCacheServiceBase, useClass: AlbumArtworkCacheService }, { provide: TranslatorServiceBase, useClass: TranslatorService }, { provide: UpdateServiceBase, useClass: UpdateService }, { provide: NotificationServiceBase, useClass: NotificationService }, diff --git a/src/app/common/image-processor.ts b/src/app/common/image-processor.ts index d7df4afa6..e7ad3ca40 100644 --- a/src/app/common/image-processor.ts +++ b/src/app/common/image-processor.ts @@ -1,12 +1,17 @@ import { Injectable } from '@angular/core'; import fetch from 'node-fetch'; import { FileAccessBase } from './io/file-access.base'; -import sharp from 'sharp'; +import { nativeImage, NativeImage, Size } from 'electron'; +import * as fs from 'fs-extra'; @Injectable() export class ImageProcessor { public constructor(private fileAccess: FileAccessBase) {} + public async convertImageBufferToFileAsync(imageBuffer: Buffer, imagePath: string): Promise { + await fs.writeFile(imagePath, imageBuffer); + } + public async convertLocalImageToBufferAsync(imagePath: string): Promise { return await this.fileAccess.getFileContentAsBufferAsync(imagePath); } @@ -23,19 +28,19 @@ export class ImageProcessor { } public async toResizedJpegBufferAsync(imageBuffer: Buffer, maxWidth: number, maxHeight: number, jpegQuality: number): Promise { - return await sharp(imageBuffer) - .resize(maxWidth, maxHeight) - .jpeg({ - quality: jpegQuality, - }) - .toBuffer(); + let image: NativeImage = nativeImage.createFromBuffer(imageBuffer); + const imageSize: Size = image.getSize(); + + if (imageSize.width > maxWidth || imageSize.height > maxHeight) { + image = image.resize({ width: maxWidth, height: maxHeight, quality: 'best' }); + } + + return image.toJPEG(jpegQuality); } public async toJpegBufferAsync(imageBuffer: Buffer, jpegQuality: number): Promise { - return await sharp(imageBuffer) - .jpeg({ - quality: jpegQuality, - }) - .toBuffer(); + let image: NativeImage = nativeImage.createFromBuffer(imageBuffer); + + return image.toJPEG(jpegQuality); } } diff --git a/src/app/common/io/ipc-proxy.base.ts b/src/app/common/io/ipc-proxy.base.ts index 737ba03f0..014b9b199 100644 --- a/src/app/common/io/ipc-proxy.base.ts +++ b/src/app/common/io/ipc-proxy.base.ts @@ -1,5 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import {Observable} from "rxjs"; +import {IIndexingMessage} from "../../services/indexing/messages/i-indexing-message"; + export abstract class IpcProxyBase { + public abstract onIndexingWorkerMessage$: Observable; + public abstract onIndexingWorkerExit$: Observable; + public abstract sendToMainProcess(channel: string, arg: unknown): void; - public abstract sendToMainProcessAsync(channel: string, arg: unknown): Promise; } diff --git a/src/app/common/io/ipc-proxy.ts b/src/app/common/io/ipc-proxy.ts index 72cef7f31..3640bdd0b 100644 --- a/src/app/common/io/ipc-proxy.ts +++ b/src/app/common/io/ipc-proxy.ts @@ -1,30 +1,31 @@ /* 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 { SchedulerBase } from '../scheduling/scheduler.base'; +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 { - public constructor(private scheduler: SchedulerBase) {} + private onIndexingWorkerMessage: Subject = new Subject(); + private onIndexingWorkerExit: Subject = new Subject(); - public sendToMainProcess(channel: string, arg: unknown): void { - ipcRenderer.send(channel, arg); - } - - public async sendToMainProcessAsync(channel: string, arg: unknown): Promise { - ipcRenderer.send(`${channel}-request`, arg); - - let hasExited: boolean = false; + public constructor() { + ipcRenderer.on('indexing-worker-message', (_: Electron.IpcRendererEvent, message: IIndexingMessage): void => { + this.onIndexingWorkerMessage.next(message); + }); - ipcRenderer.on(`${channel}-exit`, (_: Electron.IpcRendererEvent, message: any): void => { - hasExited = true; + ipcRenderer.on('indexing-worker-exit', async () => { + this.onIndexingWorkerExit.next(); }); + } + + public onIndexingWorkerMessage$: Observable = this.onIndexingWorkerMessage.asObservable(); + public onIndexingWorkerExit$: Observable = this.onIndexingWorkerExit.asObservable(); - while (!hasExited) { - await this.scheduler.sleepAsync(100); - } + public sendToMainProcess(channel: string, arg: unknown): void { + ipcRenderer.send(channel, arg); } } diff --git a/src/app/data/repositories/album-artwork-repository.base.ts b/src/app/data/repositories/album-artwork-repository.base.ts new file mode 100644 index 000000000..1241b8e4f --- /dev/null +++ b/src/app/data/repositories/album-artwork-repository.base.ts @@ -0,0 +1,11 @@ +import { AlbumArtwork } from '../entities/album-artwork'; + +export abstract class AlbumArtworkRepositoryBase { + public abstract getNumberOfAlbumArtwork(): number; + public abstract getNumberOfAlbumArtworkThatHasNoTrack(albumKeyIndex: string): number; + public abstract deleteAlbumArtworkThatHasNoTrack(albumKeyIndex: string): number; + public abstract addAlbumArtwork(albumArtwork: AlbumArtwork): void; + public abstract getAllAlbumArtwork(): AlbumArtwork[] | undefined; + public abstract getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(albumKeyIndex: string): number; + public abstract deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(albumKeyIndex: string): number; +} diff --git a/src/app/data/repositories/album-artwork-repository.ts b/src/app/data/repositories/album-artwork-repository.ts new file mode 100644 index 000000000..653df3142 --- /dev/null +++ b/src/app/data/repositories/album-artwork-repository.ts @@ -0,0 +1,90 @@ +/* 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 { DatabaseFactory } from '../database-factory'; +import { AlbumArtwork } from '../entities/album-artwork'; +import { AlbumArtworkRepositoryBase } from './album-artwork-repository.base'; + +@Injectable() +export class AlbumArtworkRepository implements AlbumArtworkRepositoryBase { + public constructor(private databaseFactory: DatabaseFactory) {} + + public addAlbumArtwork(albumArtwork: AlbumArtwork): void { + const database: any = this.databaseFactory.create(); + + const statement = database.prepare('INSERT INTO AlbumArtwork (AlbumKey, ArtworkID) VALUES (?, ?);'); + statement.run(albumArtwork.albumKey, albumArtwork.artworkId); + } + + public getAllAlbumArtwork(): AlbumArtwork[] | undefined { + const database: any = this.databaseFactory.create(); + + const statement = database.prepare( + `SELECT AlbumArtworkID as albumArtworkId, AlbumKey as albumKey, ArtworkID as artworkId + FROM AlbumArtwork;`, + ); + + const albumArtwork: AlbumArtwork[] | undefined = statement.all(); + + return albumArtwork; + } + + public getNumberOfAlbumArtwork(): number { + const database: any = this.databaseFactory.create(); + + const statement = database.prepare(`SELECT COUNT(*) AS numberOfAlbumArtwork FROM AlbumArtwork;`); + + const result: any = statement.get(); + + return result.numberOfAlbumArtwork; + } + + public getNumberOfAlbumArtworkThatHasNoTrack(albumKeyIndex: string): number { + const database: any = this.databaseFactory.create(); + + const statement = database.prepare( + `SELECT COUNT(*) AS numberOfAlbumArtwork + FROM AlbumArtwork + WHERE AlbumKey NOT IN (SELECT AlbumKey${albumKeyIndex} FROM Track);`, + ); + + const result: any = statement.get(); + + return result.numberOfAlbumArtwork; + } + + public deleteAlbumArtworkThatHasNoTrack(albumKeyIndex: string): number { + const database: any = this.databaseFactory.create(); + + const statement = database.prepare(`DELETE FROM AlbumArtwork WHERE AlbumKey NOT IN (SELECT AlbumKey${albumKeyIndex} FROM Track);`); + + const info = statement.run(); + + return info.changes; + } + + public getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(albumKeyIndex: string): number { + const database: any = this.databaseFactory.create(); + + const statement = database.prepare(`SELECT COUNT(*) AS numberOfAlbumArtwork FROM AlbumArtwork + WHERE AlbumKey IN (SELECT AlbumKey${albumKeyIndex} FROM Track WHERE NeedsAlbumArtworkIndexing = 1);`); + + const result: any = statement.get(); + + return result.numberOfAlbumArtwork; + } + + public deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(albumKeyIndex: string): number { + const database: any = this.databaseFactory.create(); + + const statement = database.prepare(`DELETE FROM AlbumArtwork + WHERE AlbumKey IN (SELECT AlbumKey${albumKeyIndex} FROM Track WHERE NeedsAlbumArtworkIndexing = 1);`); + + const info = statement.run(); + + return info.changes; + } +} diff --git a/src/app/data/repositories/track-repository.base.ts b/src/app/data/repositories/track-repository.base.ts index fe4889981..73c6213f0 100644 --- a/src/app/data/repositories/track-repository.base.ts +++ b/src/app/data/repositories/track-repository.base.ts @@ -16,6 +16,7 @@ export abstract class TrackRepositoryBase { public abstract getAlbumDataForTrackArtists(albumKeyIndex: string, trackArtists: string[]): AlbumData[] | undefined; public abstract getAlbumDataForAlbumArtists(albumKeyIndex: string, albumArtists: string[]): AlbumData[] | undefined; public abstract getAlbumDataForGenres(albumKeyIndex: string, genres: string[]): AlbumData[] | undefined; + public abstract getAlbumDataThatNeedsIndexing(albumKeyIndex: string): AlbumData[] | undefined; public abstract getTrackArtistData(): ArtistData[] | undefined; public abstract getAlbumArtistData(): ArtistData[] | undefined; public abstract getGenreData(): GenreData[] | undefined; @@ -23,4 +24,6 @@ export abstract class TrackRepositoryBase { public abstract updateSkipCount(trackId: number, skipCount: number): void; public abstract updateRating(trackId: number, rating: number): void; public abstract updateLove(trackId: number, love: number): void; + public abstract getLastModifiedTrackForAlbumKeyAsync(albumKeyIndex: string, albumKey: string): Track | undefined; + public abstract disableNeedsAlbumArtworkIndexing(albumKey: string): void; } diff --git a/src/app/data/repositories/track-repository.ts b/src/app/data/repositories/track-repository.ts index 7e3252cfd..3abc09753 100644 --- a/src/app/data/repositories/track-repository.ts +++ b/src/app/data/repositories/track-repository.ts @@ -107,6 +107,19 @@ export class TrackRepository implements TrackRepositoryBase { return tracks; } + public getAlbumDataThatNeedsIndexing(albumKeyIndex: string): AlbumData[] | undefined { + const database = this.databaseFactory.create(); + + const statement = database.prepare( + `${QueryParts.selectAlbumDataQueryPart(albumKeyIndex, false)} + WHERE (t.AlbumKey${albumKeyIndex} IS NOT NULL AND t.AlbumKey${albumKeyIndex} <> '' + AND t.AlbumKey${albumKeyIndex} NOT IN (SELECT AlbumKey FROM AlbumArtwork)) OR NeedsAlbumArtworkIndexing=1 + GROUP BY t.AlbumKey${albumKeyIndex};`, + ); + + return statement.all(); + } + public getAlbumDataForAlbumKey(albumKeyIndex: string, albumKey: string): AlbumData[] | undefined { const database: any = this.databaseFactory.create(); @@ -265,4 +278,17 @@ export class TrackRepository implements TrackRepositoryBase { love: love, }); } + + public getLastModifiedTrackForAlbumKeyAsync(albumKeyIndex: string, albumKey: string): Track | undefined { + const database: any = this.databaseFactory.create(); + const statement = database.prepare(`${QueryParts.selectTracksQueryPart(false)} WHERE t.AlbumKey${albumKeyIndex}=?;`); + return statement.get(albumKey); + } + + public disableNeedsAlbumArtworkIndexing(albumKey: string): void { + const database: any = this.databaseFactory.create(); + const statement: any = database.prepare(`UPDATE Track SET NeedsAlbumArtworkIndexing=0 WHERE AlbumKey=?;`); + + statement.run(albumKey); + } } diff --git a/src/app/services/album-artwork-cache/album-artwork-cache-id-factory.ts b/src/app/services/album-artwork-cache/album-artwork-cache-id-factory.ts new file mode 100644 index 000000000..3d04d0ae7 --- /dev/null +++ b/src/app/services/album-artwork-cache/album-artwork-cache-id-factory.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; +import { GuidFactory } from '../../common/guid.factory'; +import { AlbumArtworkCacheId } from './album-artwork-cache-id'; + +@Injectable({ providedIn: 'root' }) +export class AlbumArtworkCacheIdFactory { + public constructor(private guidFactory: GuidFactory) {} + + public create(): AlbumArtworkCacheId { + return new AlbumArtworkCacheId(this.guidFactory); + } +} diff --git a/src/app/services/album-artwork-cache/album-artwork-cache-id.spec.ts b/src/app/services/album-artwork-cache/album-artwork-cache-id.spec.ts new file mode 100644 index 000000000..83bf58e84 --- /dev/null +++ b/src/app/services/album-artwork-cache/album-artwork-cache-id.spec.ts @@ -0,0 +1,50 @@ +import { IMock, Mock } from 'typemoq'; +import { GuidFactory } from '../../common/guid.factory'; +import { AlbumArtworkCacheId } from './album-artwork-cache-id'; + +describe('AlbumArtworkCacheId', () => { + let guidFactoryMock: IMock; + + beforeEach(() => { + guidFactoryMock = Mock.ofType(); + guidFactoryMock.setup((x) => x.create()).returns(() => '688af0b5-8c41-4a10-9d3e-2ba13a0d918d'); + }); + + function createInstance(): AlbumArtworkCacheId { + return new AlbumArtworkCacheId(guidFactoryMock.object); + } + + describe('constructor', () => { + it('should create', () => { + // Arrange, Act + const instance: AlbumArtworkCacheId = createInstance(); + + // Assert + expect(instance).toBeDefined(); + }); + + it('should define id', () => { + // Arrange, Act + const instance: AlbumArtworkCacheId = createInstance(); + + // Assert + expect(instance.id).toBeDefined(); + }); + + it('should create id that starts with album-', () => { + // Arrange, Act + const instance: AlbumArtworkCacheId = createInstance(); + + // Assert + expect(instance.id.startsWith('album-')).toBeTruthy(); + }); + + it('should create id that has a length of 42 characters', () => { + // Arrange, Act + const instance: AlbumArtworkCacheId = createInstance(); + + // Assert + expect(instance.id.length).toEqual(42); + }); + }); +}); \ No newline at end of file diff --git a/src/app/services/album-artwork-cache/album-artwork-cache-id.ts b/src/app/services/album-artwork-cache/album-artwork-cache-id.ts new file mode 100644 index 000000000..c64d27c81 --- /dev/null +++ b/src/app/services/album-artwork-cache/album-artwork-cache-id.ts @@ -0,0 +1,9 @@ +import { GuidFactory } from '../../common/guid.factory'; + +export class AlbumArtworkCacheId { + public constructor(guidFactory: GuidFactory) { + this.id = `album-${guidFactory.create()}`; + } + + public readonly id: string; +} \ No newline at end of file diff --git a/src/app/services/album-artwork-cache/album-artwork-cache.service.base.ts b/src/app/services/album-artwork-cache/album-artwork-cache.service.base.ts new file mode 100644 index 000000000..d76076156 --- /dev/null +++ b/src/app/services/album-artwork-cache/album-artwork-cache.service.base.ts @@ -0,0 +1,6 @@ +import { AlbumArtworkCacheId } from './album-artwork-cache-id'; + +export abstract class AlbumArtworkCacheServiceBase { + public abstract addArtworkDataToCacheAsync(data: Buffer): Promise; + public abstract removeArtworkDataFromCacheAsync(albumKey: string): Promise; +} \ No newline at end of file diff --git a/src/app/services/album-artwork-cache/album-artwork-cache.service.spec.ts b/src/app/services/album-artwork-cache/album-artwork-cache.service.spec.ts new file mode 100644 index 000000000..cba6ef506 --- /dev/null +++ b/src/app/services/album-artwork-cache/album-artwork-cache.service.spec.ts @@ -0,0 +1,142 @@ +import { IMock, Mock, Times } from 'typemoq'; +import { Constants } from '../../common/application/constants'; +import { GuidFactory } from '../../common/guid.factory'; +import { ImageProcessor } from '../../common/image-processor'; +import { Logger } from '../../common/logger'; +import { AlbumArtworkCacheId } from './album-artwork-cache-id'; +import { AlbumArtworkCacheIdFactory } from './album-artwork-cache-id-factory'; +import { AlbumArtworkCacheService } from './album-artwork-cache.service'; +import { FileAccessBase } from '../../common/io/file-access.base'; +import { ApplicationPaths } from '../../common/application/application-paths'; + +describe('AlbumArtworkCacheService', () => { + let albumArtworkCacheIdFactoryMock: IMock; + let imageProcessorMock: IMock; + let fileAccessMock: IMock; + let applicationPathsMock: IMock; + let loggerMock: IMock; + let service: AlbumArtworkCacheService; + let guidFactoryMock: IMock; + + beforeEach(() => { + albumArtworkCacheIdFactoryMock = Mock.ofType(); + imageProcessorMock = Mock.ofType(); + fileAccessMock = Mock.ofType(); + applicationPathsMock = Mock.ofType(); + loggerMock = Mock.ofType(); + guidFactoryMock = Mock.ofType(); + + service = new AlbumArtworkCacheService( + albumArtworkCacheIdFactoryMock.object, + imageProcessorMock.object, + fileAccessMock.object, + applicationPathsMock.object, + loggerMock.object, + ); + }); + + describe('constructor', () => { + it('should create', () => { + expect(service).toBeDefined(); + }); + + it('should create the full directory path to the artwork cache if it does not exist', () => { + // Arrange + applicationPathsMock.setup((x) => x.coverArtCacheFullPath()).returns(() => '/home/user/.config/Dopamine/Cache/CoverArt'); + + // Act + service = new AlbumArtworkCacheService( + albumArtworkCacheIdFactoryMock.object, + imageProcessorMock.object, + fileAccessMock.object, + applicationPathsMock.object, + loggerMock.object, + ); + + // Assert + fileAccessMock.verify( + (x) => x.createFullDirectoryPathIfDoesNotExist('/home/user/.config/Dopamine/Cache/CoverArt'), + Times.exactly(1), + ); + }); + }); + + describe('addArtworkDataToCacheAsync', () => { + it('should return undefined when the data is undefined', async () => { + // Arrange + + // Act + const albumArtworkCacheId: AlbumArtworkCacheId | undefined = await service.addArtworkDataToCacheAsync(undefined); + + // Assert + expect(albumArtworkCacheId).toBeUndefined(); + }); + it('should return undefined when the data is empty', async () => { + // Arrange + const imageBuffer = Buffer.alloc(0); + + // Act + const albumArtworkCacheId: AlbumArtworkCacheId | undefined = await service.addArtworkDataToCacheAsync(imageBuffer); + + // Assert + expect(albumArtworkCacheId).toBeUndefined(); + }); + + it('should return a valid AlbumArtworkCacheId when the data is not empty', async () => { + // Arrange + const albumArtworkCacheIdToCreate: AlbumArtworkCacheId = new AlbumArtworkCacheId(guidFactoryMock.object); + albumArtworkCacheIdFactoryMock.setup((x) => x.create()).returns(() => albumArtworkCacheIdToCreate); + applicationPathsMock.setup((x) => x.coverArtCacheFullPath()).returns(() => '/home/user/Dopamine/Cache/CoverArt'); + + const imageBuffer = Buffer.from([1, 2, 3]); + + // Act + const albumArtworkCacheIdToReturn: AlbumArtworkCacheId | undefined = await service.addArtworkDataToCacheAsync(imageBuffer); + + // Assert + expect(albumArtworkCacheIdToCreate.id).toEqual(albumArtworkCacheIdToReturn!.id); + }); + + it('should save thumbnail to file when the data is not empty', async () => { + // Arrange + const imageBuffer = Buffer.from([1, 2, 3]); + const resizedImageBuffer = Buffer.from([4, 5, 6]); + const albumArtworkCacheIdToCreate: AlbumArtworkCacheId = new AlbumArtworkCacheId(guidFactoryMock.object); + const cachedArtworkFilePath: string = '/home/user/Dopamine/Cache/CoverArt/Dummy.jpg'; + albumArtworkCacheIdFactoryMock.setup((x) => x.create()).returns(() => albumArtworkCacheIdToCreate); + applicationPathsMock.setup((x) => x.coverArtFullPath(albumArtworkCacheIdToCreate.id)).returns(() => cachedArtworkFilePath); + imageProcessorMock + .setup((x) => + x.toResizedJpegBufferAsync( + imageBuffer, + Constants.cachedCoverArtMaximumSize, + Constants.cachedCoverArtMaximumSize, + Constants.cachedCoverArtJpegQuality, + ), + ) + .returns(() => Promise.resolve(resizedImageBuffer)); + + // Act + await service.addArtworkDataToCacheAsync(imageBuffer); + + // Assert + imageProcessorMock.verify((x) => x.convertImageBufferToFileAsync(resizedImageBuffer, cachedArtworkFilePath), Times.once()); + }); + }); + + describe('removeArtworkDataFromCache', () => { + it('should delete cached artwork file if it exists', async () => { + // Arrange + const albumArtworkCacheIdToCreate: AlbumArtworkCacheId = new AlbumArtworkCacheId(guidFactoryMock.object); + const cachedArtworkFilePath: string = '/home/user/Dopamine/Cache/CoverArt/Dummy.jpg'; + albumArtworkCacheIdFactoryMock.setup((x) => x.create()).returns(() => albumArtworkCacheIdToCreate); + applicationPathsMock.setup((x) => x.coverArtFullPath(albumArtworkCacheIdToCreate.id)).returns(() => cachedArtworkFilePath); + + // Act + await service.removeArtworkDataFromCacheAsync(albumArtworkCacheIdToCreate.id); + + // Assert + fileAccessMock.verify((x) => x.deleteFileIfExistsAsync(cachedArtworkFilePath), Times.exactly(1)); + }); + }); +}); diff --git a/src/app/services/album-artwork-cache/album-artwork-cache.service.ts b/src/app/services/album-artwork-cache/album-artwork-cache.service.ts new file mode 100644 index 000000000..2d6b8ea24 --- /dev/null +++ b/src/app/services/album-artwork-cache/album-artwork-cache.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@angular/core'; +import { Constants } from '../../common/application/constants'; +import { ImageProcessor } from '../../common/image-processor'; +import { Logger } from '../../common/logger'; +import { AlbumArtworkCacheId } from './album-artwork-cache-id'; +import { AlbumArtworkCacheIdFactory } from './album-artwork-cache-id-factory'; +import { AlbumArtworkCacheServiceBase } from './album-artwork-cache.service.base'; +import { FileAccessBase } from '../../common/io/file-access.base'; +import { ApplicationPaths } from '../../common/application/application-paths'; + +@Injectable() +export class AlbumArtworkCacheService implements AlbumArtworkCacheServiceBase { + public constructor( + private albumArtworkCacheIdFactory: AlbumArtworkCacheIdFactory, + private imageProcessor: ImageProcessor, + private fileAccess: FileAccessBase, + private applicationPaths: ApplicationPaths, + private logger: Logger, + ) { + this.createCoverArtCacheOnDisk(); + } + + public async removeArtworkDataFromCacheAsync(artworkId: string): Promise { + try { + const cachedArtworkFilePath: string = this.applicationPaths.coverArtFullPath(artworkId); + await this.fileAccess.deleteFileIfExistsAsync(cachedArtworkFilePath); + } catch (e: unknown) { + this.logger.error(e, 'Could not remove artwork data from cache', 'AlbumArtworkCacheService', 'removeArtworkDataFromCacheAsync'); + } + } + + public async addArtworkDataToCacheAsync(imageBuffer: Buffer | undefined): Promise { + if (imageBuffer == undefined) { + return undefined; + } + + if (imageBuffer.length === 0) { + return undefined; + } + + try { + const albumArtworkCacheId: AlbumArtworkCacheId = this.albumArtworkCacheIdFactory.create(); + const cachedArtworkFilePath: string = this.applicationPaths.coverArtFullPath(albumArtworkCacheId.id); + const resizedImageBuffer: Buffer = await this.imageProcessor.toResizedJpegBufferAsync( + imageBuffer, + Constants.cachedCoverArtMaximumSize, + Constants.cachedCoverArtMaximumSize, + Constants.cachedCoverArtJpegQuality, + ); + await this.imageProcessor.convertImageBufferToFileAsync(resizedImageBuffer, cachedArtworkFilePath); + + return albumArtworkCacheId; + } catch (e: unknown) { + this.logger.error(e, 'Could not add artwork data to cache', 'AlbumArtworkCacheService', 'addArtworkDataToCacheAsync'); + } + + return undefined; + } + + private createCoverArtCacheOnDisk(): void { + try { + this.fileAccess.createFullDirectoryPathIfDoesNotExist(this.applicationPaths.coverArtCacheFullPath()); + } catch (e: unknown) { + this.logger.error(e, 'Could not create artwork cache directory', 'AlbumArtworkCacheService', 'createDirectories'); + + // We cannot proceed if the above fails + throw e; + } + } +} diff --git a/src/app/services/indexing/album-artwork-adder.spec.ts b/src/app/services/indexing/album-artwork-adder.spec.ts new file mode 100644 index 000000000..610dea301 --- /dev/null +++ b/src/app/services/indexing/album-artwork-adder.spec.ts @@ -0,0 +1,361 @@ +import { IMock, It, Mock, Times } from 'typemoq'; +import { GuidFactory } from '../../common/guid.factory'; +import { Logger } from '../../common/logger'; +import { IFileMetadata } from '../../common/metadata/i-file-metadata'; +import { AlbumArtworkCacheId } from '../album-artwork-cache/album-artwork-cache-id'; +import { AlbumArtworkAdder } from './album-artwork-adder'; +import { AlbumArtworkGetter } from './album-artwork-getter'; +import { AlbumArtworkCacheServiceBase } from '../album-artwork-cache/album-artwork-cache.service.base'; +import { AlbumArtworkRepositoryBase } from '../../data/repositories/album-artwork-repository.base'; +import { TrackRepositoryBase } from '../../data/repositories/track-repository.base'; +import { FileMetadataFactory } from '../../common/metadata/file-metadata.factory'; +import { AlbumData } from '../../data/entities/album-data'; +import { Track } from '../../data/entities/track'; +import { AlbumArtwork } from '../../data/entities/album-artwork'; +import { NotificationServiceBase } from '../notification/notification.service.base'; +import { SettingsBase } from '../../common/settings/settings.base'; +import { SettingsMock } from '../../testing/settings-mock'; + +class FileMetadataImplementation implements IFileMetadata { + public path: string; + public bitRate: number; + public sampleRate: number; + public durationInMilliseconds: number; + public title: string; + public album: string; + public albumArtists: string[]; + public artists: string[]; + public genres: string[]; + public comment: string; + public grouping: string; + public year: number; + public trackNumber: number; + public trackCount: number; + public discNumber: number; + public discCount: number; + public lyrics: string; + public picture: Buffer; + public rating: number; + public save(): void {} + public async loadAsync(): Promise {} +} + +describe('AlbumArtworkAdder', () => { + let albumArtworkCacheServiceMock: IMock; + let albumArtworkRepositoryMock: IMock; + let trackRepositoryMock: IMock; + let fileMetadataFactoryMock: IMock; + let notificationServiceMock: IMock; + let loggerMock: IMock; + let albumArtworkGetterMock: IMock; + let guidFactoryMock: IMock; + let settingsMock: SettingsBase; + + let albumArtworkAdder: AlbumArtworkAdder; + + beforeEach(() => { + albumArtworkCacheServiceMock = Mock.ofType(); + albumArtworkRepositoryMock = Mock.ofType(); + trackRepositoryMock = Mock.ofType(); + fileMetadataFactoryMock = Mock.ofType(); + notificationServiceMock = Mock.ofType(); + loggerMock = Mock.ofType(); + albumArtworkGetterMock = Mock.ofType(); + guidFactoryMock = Mock.ofType(); + settingsMock = new SettingsMock(); + + albumArtworkAdder = new AlbumArtworkAdder( + albumArtworkCacheServiceMock.object, + albumArtworkRepositoryMock.object, + trackRepositoryMock.object, + fileMetadataFactoryMock.object, + notificationServiceMock.object, + loggerMock.object, + albumArtworkGetterMock.object, + settingsMock, + ); + }); + + describe('addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', () => { + it('should get album data that needs indexing', async () => { + // Arrange + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + trackRepositoryMock.verify((x) => x.getAlbumDataThatNeedsIndexing(''), Times.exactly(1)); + }); + + it('should notify that album artwork is being updated if it is the first time that indexing runs', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtwork()).returns(() => 0); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + notificationServiceMock.verify((x) => x.updatingAlbumArtworkAsync(), Times.exactly(1)); + }); + + it('should not notify that album artwork is being updated if it is not the first time that indexing runs', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtwork()).returns(() => 10); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + notificationServiceMock.verify((x) => x.updatingAlbumArtworkAsync(), Times.never()); + }); + + it('should not get the last modified track for an album key if there is no album data that needs indexing', async () => { + // Arrange + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => []); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + trackRepositoryMock.verify((x) => x.getLastModifiedTrackForAlbumKeyAsync('', It.isAny()), Times.never()); + }); + + it('should get the last modified track for an album key if there is album data that needs indexing', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + trackRepositoryMock.verify((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1'), Times.exactly(1)); + }); + + it('should not create a read-only file metadata if there is no last modified track for the given album key', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + trackRepositoryMock.setup((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1')).returns(() => undefined); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + fileMetadataFactoryMock.verify((x) => x.createAsync(It.isAny()), Times.never()); + }); + + it('should create a read-only file metadata if there is a last modified track for the given album key', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + const track1: Track = new Track('/home/user/Music/track1.mp3'); + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + trackRepositoryMock.setup((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1')).returns(() => track1); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + fileMetadataFactoryMock.verify((x) => x.createAsync('/home/user/Music/track1.mp3'), Times.exactly(1)); + }); + + it('should get album artwork if a read-only file metadata was created', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + const track1: Track = new Track('/home/user/Music/track1.mp3'); + const fileMetadataStub = new FileMetadataImplementation(); + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + trackRepositoryMock.setup((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1')).returns(() => track1); + fileMetadataFactoryMock + .setup((x) => x.createAsync('/home/user/Music/track1.mp3')) + .returns(() => Promise.resolve(fileMetadataStub)); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + albumArtworkGetterMock.verify((x) => x.getAlbumArtworkAsync(It.isAny(), true), Times.exactly(1)); + }); + + it('should not add album artwork to the cache if no album artwork data was found', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + const track1: Track = new Track('/home/user/Music/track1.mp3'); + const fileMetadataStub = new FileMetadataImplementation(); + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + trackRepositoryMock.setup((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1')).returns(() => track1); + fileMetadataFactoryMock + .setup((x) => x.createAsync('/home/user/Music/track1.mp3')) + .returns(() => Promise.resolve(fileMetadataStub)); + albumArtworkGetterMock.setup((x) => x.getAlbumArtworkAsync(fileMetadataStub, true)).returns(() => Promise.resolve(undefined)); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + albumArtworkCacheServiceMock.verify((x) => x.addArtworkDataToCacheAsync(It.isAny()), Times.never()); + }); + + it('should add album artwork to the cache if album artwork data was found', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + const track1: Track = new Track('/home/user/Music/track1.mp3'); + const fileMetadataStub = new FileMetadataImplementation(); + const albumArtworkData1: Buffer = Buffer.from([1, 2, 3]); + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + trackRepositoryMock.setup((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1')).returns(() => track1); + fileMetadataFactoryMock + .setup((x) => x.createAsync('/home/user/Music/track1.mp3')) + .returns(() => Promise.resolve(fileMetadataStub)); + albumArtworkGetterMock + .setup((x) => x.getAlbumArtworkAsync(fileMetadataStub, true)) + .returns(() => Promise.resolve(albumArtworkData1)); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + albumArtworkCacheServiceMock.verify((x) => x.addArtworkDataToCacheAsync(albumArtworkData1), Times.exactly(1)); + }); + + it('should not disable album artwork indexing for the given album key if the artwork was not added to the cache', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + const track1: Track = new Track('/home/user/Music/track1.mp3'); + const fileMetadataStub = new FileMetadataImplementation(); + const albumArtworkData1: Buffer = Buffer.from([1, 2, 3]); + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + trackRepositoryMock.setup((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1')).returns(() => track1); + fileMetadataFactoryMock + .setup((x) => x.createAsync('/home/user/Music/track1.mp3')) + .returns(() => Promise.resolve(fileMetadataStub)); + albumArtworkGetterMock + .setup((x) => x.getAlbumArtworkAsync(fileMetadataStub, true)) + .returns(() => Promise.resolve(albumArtworkData1)); + albumArtworkCacheServiceMock + .setup((x) => x.addArtworkDataToCacheAsync(albumArtworkData1)) + .returns(() => Promise.resolve(undefined)); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + trackRepositoryMock.verify((x) => x.disableNeedsAlbumArtworkIndexing('AlbumKey1'), Times.never()); + }); + + it('should disable album artwork indexing for the given album key if the artwork was added to the cache', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + const track1: Track = new Track('/home/user/Music/track1.mp3'); + const fileMetadataStub = new FileMetadataImplementation(); + const albumArtworkData1: Buffer = Buffer.from([1, 2, 3]); + const albumArtworkCacheId1: AlbumArtworkCacheId = new AlbumArtworkCacheId(guidFactoryMock.object); + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + trackRepositoryMock.setup((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1')).returns(() => track1); + fileMetadataFactoryMock + .setup((x) => x.createAsync('/home/user/Music/track1.mp3')) + .returns(() => Promise.resolve(fileMetadataStub)); + albumArtworkGetterMock + .setup((x) => x.getAlbumArtworkAsync(fileMetadataStub, true)) + .returns(() => Promise.resolve(albumArtworkData1)); + albumArtworkCacheServiceMock + .setup((x) => x.addArtworkDataToCacheAsync(albumArtworkData1)) + .returns(() => Promise.resolve(albumArtworkCacheId1)); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + trackRepositoryMock.verify((x) => x.disableNeedsAlbumArtworkIndexing('AlbumKey1'), Times.exactly(1)); + }); + + it('should not add album artwork to the database if the artwork was not added to the cache', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + const track1: Track = new Track('/home/user/Music/track1.mp3'); + const fileMetadataStub = new FileMetadataImplementation(); + const albumArtworkData1: Buffer = Buffer.from([1, 2, 3]); + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + trackRepositoryMock.setup((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1')).returns(() => track1); + fileMetadataFactoryMock + .setup((x) => x.createAsync('/home/user/Music/track1.mp3')) + .returns(() => Promise.resolve(fileMetadataStub)); + albumArtworkGetterMock + .setup((x) => x.getAlbumArtworkAsync(fileMetadataStub, true)) + .returns(() => Promise.resolve(albumArtworkData1)); + albumArtworkCacheServiceMock + .setup((x) => x.addArtworkDataToCacheAsync(albumArtworkData1)) + .returns(() => Promise.resolve(undefined)); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + albumArtworkRepositoryMock.verify((x) => x.addAlbumArtwork(It.isAny()), Times.never()); + }); + + it('should add album artwork to the database if the artwork was added to the cache', async () => { + // Arrange + const albumData1: AlbumData = new AlbumData(); + albumData1.albumKey = 'AlbumKey1'; + + const track1: Track = new Track('/home/user/Music/track1.mp3'); + const fileMetadataStub = new FileMetadataImplementation(); + const albumArtworkData1: Buffer = Buffer.from([1, 2, 3]); + const albumArtworkCacheId1: AlbumArtworkCacheId = new AlbumArtworkCacheId(guidFactoryMock.object); + + trackRepositoryMock.setup((x) => x.getAlbumDataThatNeedsIndexing('')).returns(() => [albumData1]); + trackRepositoryMock.setup((x) => x.getLastModifiedTrackForAlbumKeyAsync('', 'AlbumKey1')).returns(() => track1); + fileMetadataFactoryMock + .setup((x) => x.createAsync('/home/user/Music/track1.mp3')) + .returns(() => Promise.resolve(fileMetadataStub)); + albumArtworkGetterMock + .setup((x) => x.getAlbumArtworkAsync(fileMetadataStub, true)) + .returns(() => Promise.resolve(albumArtworkData1)); + albumArtworkCacheServiceMock + .setup((x) => x.addArtworkDataToCacheAsync(albumArtworkData1)) + .returns(() => Promise.resolve(albumArtworkCacheId1)); + + const newAlbumArtwork1: AlbumArtwork = new AlbumArtwork('AlbumKey1', albumArtworkCacheId1.id); + + // Act + await albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + albumArtworkRepositoryMock.verify((x) => x.addAlbumArtwork(newAlbumArtwork1), Times.exactly(1)); + }); + }); +}); diff --git a/src/app/services/indexing/album-artwork-adder.ts b/src/app/services/indexing/album-artwork-adder.ts new file mode 100644 index 000000000..687d3b28e --- /dev/null +++ b/src/app/services/indexing/album-artwork-adder.ts @@ -0,0 +1,110 @@ +import { Injectable } from '@angular/core'; +import { AlbumArtwork } from '../../data/entities/album-artwork'; +import { AlbumData } from '../../data/entities/album-data'; +import { Track } from '../../data/entities/track'; +import { Logger } from '../../common/logger'; +import { IFileMetadata } from '../../common/metadata/i-file-metadata'; +import { AlbumArtworkCacheId } from '../album-artwork-cache/album-artwork-cache-id'; +import { AlbumArtworkGetter } from './album-artwork-getter'; +import { AlbumArtworkCacheServiceBase } from '../album-artwork-cache/album-artwork-cache.service.base'; +import { AlbumArtworkRepositoryBase } from '../../data/repositories/album-artwork-repository.base'; +import { TrackRepositoryBase } from '../../data/repositories/track-repository.base'; +import { FileMetadataFactoryBase } from '../../common/metadata/file-metadata.factory.base'; +import { NotificationServiceBase } from '../notification/notification.service.base'; +import { SettingsBase } from '../../common/settings/settings.base'; + +@Injectable({ providedIn: 'root' }) +export class AlbumArtworkAdder { + public constructor( + private albumArtworkCacheService: AlbumArtworkCacheServiceBase, + private albumArtworkRepository: AlbumArtworkRepositoryBase, + private trackRepository: TrackRepositoryBase, + private fileMetadataFactory: FileMetadataFactoryBase, + private notificationService: NotificationServiceBase, + private logger: Logger, + private albumArtworkGetter: AlbumArtworkGetter, + private settings: SettingsBase, + ) {} + + public async addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(): Promise { + try { + const albumDataThatNeedsIndexing: AlbumData[] = + this.trackRepository.getAlbumDataThatNeedsIndexing(this.settings.albumKeyIndex) ?? []; + + if (albumDataThatNeedsIndexing.length === 0) { + this.logger.info( + `Found no album data that needs indexing`, + 'AlbumArtworkAdder', + 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', + ); + + return; + } + + this.logger.info( + `Found ${albumDataThatNeedsIndexing.length} album data that needs indexing`, + 'AlbumArtworkAdder', + 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', + ); + + const numberOfAlbumArtwork: number = this.albumArtworkRepository.getNumberOfAlbumArtwork(); + + // Only show this notification the 1st time indexing runs. + if (numberOfAlbumArtwork === 0) { + await this.notificationService.updatingAlbumArtworkAsync(); + } + + for (const albumData of albumDataThatNeedsIndexing) { + try { + await this.addAlbumArtworkAsync(albumData.albumKey); + } catch (e: unknown) { + this.logger.error( + e, + `Could not add album artwork for albumKey=${albumData.albumKey}`, + 'AlbumArtworkAdder', + 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', + ); + } + } + } catch (e: unknown) { + this.logger.error( + e, + 'Could not add album artwork for tracks that need album artwork indexing', + 'AlbumArtworkAdder', + 'addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', + ); + } + } + + private async addAlbumArtworkAsync(albumKey: string): Promise { + const track: Track | undefined = this.trackRepository.getLastModifiedTrackForAlbumKeyAsync(this.settings.albumKeyIndex, albumKey); + + if (track == undefined) { + return; + } + + let albumArtwork: Buffer | undefined; + + try { + const fileMetadata: IFileMetadata = await this.fileMetadataFactory.createAsync(track.path); + albumArtwork = await this.albumArtworkGetter.getAlbumArtworkAsync(fileMetadata, true); + } catch (e: unknown) { + this.logger.error(e, `Could not create file metadata for path='${track.path}'`, 'AlbumArtworkAdder', 'addAlbumArtworkAsync'); + } + + if (albumArtwork == undefined) { + return; + } + + const albumArtworkCacheId: AlbumArtworkCacheId | undefined = + await this.albumArtworkCacheService.addArtworkDataToCacheAsync(albumArtwork); + + if (albumArtworkCacheId == undefined) { + return; + } + + this.trackRepository.disableNeedsAlbumArtworkIndexing(albumKey); + const newAlbumArtwork: AlbumArtwork = new AlbumArtwork(albumKey, albumArtworkCacheId.id); + this.albumArtworkRepository.addAlbumArtwork(newAlbumArtwork); + } +} diff --git a/src/app/services/indexing/album-artwork-indexer.spec.ts b/src/app/services/indexing/album-artwork-indexer.spec.ts new file mode 100644 index 000000000..beaeedb5c --- /dev/null +++ b/src/app/services/indexing/album-artwork-indexer.spec.ts @@ -0,0 +1,69 @@ +import { IMock, Mock, Times } from 'typemoq'; +import { Logger } from '../../common/logger'; +import { AlbumArtworkAdder } from './album-artwork-adder'; +import { AlbumArtworkIndexer } from './album-artwork-indexer'; +import { AlbumArtworkRemover } from './album-artwork-remover'; +import { NotificationServiceBase } from '../notification/notification.service.base'; + +describe('AlbumArtworkIndexer', () => { + let albumArtworkRemoverMock: IMock; + let albumArtworkAdderMock: IMock; + let notificationServiceMock: IMock; + let loggerMock: IMock; + let albumArtworkIndexer: AlbumArtworkIndexer; + + beforeEach(() => { + albumArtworkRemoverMock = Mock.ofType(); + albumArtworkAdderMock = Mock.ofType(); + notificationServiceMock = Mock.ofType(); + loggerMock = Mock.ofType(); + albumArtworkIndexer = new AlbumArtworkIndexer( + albumArtworkRemoverMock.object, + albumArtworkAdderMock.object, + notificationServiceMock.object, + loggerMock.object, + ); + }); + + describe('indexAlbumArtworkAsync', () => { + it('should remove artwork that has no track', async () => { + // Arrange, Act + await albumArtworkIndexer.indexAlbumArtworkAsync(); + + // Assert + albumArtworkRemoverMock.verify((x) => x.removeAlbumArtworkThatHasNoTrackAsync(), Times.exactly(1)); + }); + + it('should remove artwork for tracks that need album artwork indexing', async () => { + // Arrange, Act + await albumArtworkIndexer.indexAlbumArtworkAsync(); + + // Assert + albumArtworkRemoverMock.verify((x) => x.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(), Times.exactly(1)); + }); + + it('should add artwork for tracks that need album artwork indexing', async () => { + // Arrange, Act + await albumArtworkIndexer.indexAlbumArtworkAsync(); + + // Assert + albumArtworkAdderMock.verify((x) => x.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(), Times.exactly(1)); + }); + + it('should remove artwork that is not in the database from disk', async () => { + // Arrange, Act + await albumArtworkIndexer.indexAlbumArtworkAsync(); + + // Assert + albumArtworkRemoverMock.verify((x) => x.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(), Times.exactly(1)); + }); + + it('should dismiss the indexing notification', async () => { + // Arrange, Act + await albumArtworkIndexer.indexAlbumArtworkAsync(); + + // Assert + notificationServiceMock.verify((x) => x.dismiss(), Times.exactly(1)); + }); + }); +}); diff --git a/src/app/services/indexing/album-artwork-indexer.ts b/src/app/services/indexing/album-artwork-indexer.ts new file mode 100644 index 000000000..4ef5864fa --- /dev/null +++ b/src/app/services/indexing/album-artwork-indexer.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { Logger } from '../../common/logger'; +import { Timer } from '../../common/scheduling/timer'; +import { AlbumArtworkAdder } from './album-artwork-adder'; +import { AlbumArtworkRemover } from './album-artwork-remover'; +import { NotificationServiceBase } from '../notification/notification.service.base'; + +@Injectable({ providedIn: 'root' }) +export class AlbumArtworkIndexer { + public constructor( + private albumArtworkRemover: AlbumArtworkRemover, + private albumArtworkAdder: AlbumArtworkAdder, + private notificationService: NotificationServiceBase, + private logger: Logger, + ) {} + + public async indexAlbumArtworkAsync(): Promise { + this.logger.info('+++ STARTED INDEXING ALBUM ARTWORK +++', 'AlbumArtworkIndexer', 'indexAlbumArtworkAsync'); + + const timer: Timer = new Timer(); + timer.start(); + + await this.albumArtworkRemover.removeAlbumArtworkThatHasNoTrackAsync(); + await this.albumArtworkRemover.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + await this.albumArtworkAdder.addAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + await this.albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); + + timer.stop(); + + this.logger.info( + `+++ FINISHED INDEXING ALBUM ARTWORK (Time required: ${timer.elapsedMilliseconds} ms) +++`, + 'AlbumArtworkIndexer', + 'indexAlbumArtworkAsync', + ); + + this.notificationService.dismiss(); + } +} diff --git a/src/app/services/indexing/album-artwork-remover.spec.ts b/src/app/services/indexing/album-artwork-remover.spec.ts new file mode 100644 index 000000000..384f01733 --- /dev/null +++ b/src/app/services/indexing/album-artwork-remover.spec.ts @@ -0,0 +1,321 @@ +import { IMock, It, Mock, Times } from 'typemoq'; +import { Logger } from '../../common/logger'; +import { AlbumArtworkRemover } from './album-artwork-remover'; +import { AlbumArtworkRepositoryBase } from '../../data/repositories/album-artwork-repository.base'; +import { FileAccessBase } from '../../common/io/file-access.base'; +import { AlbumArtwork } from '../../data/entities/album-artwork'; +import { NotificationServiceBase } from '../notification/notification.service.base'; +import { ApplicationPaths } from '../../common/application/application-paths'; +import { SettingsMock } from '../../testing/settings-mock'; + +describe('AlbumArtworkRemover', () => { + let albumArtworkRepositoryMock: IMock; + let fileAccessMock: IMock; + let applicationPathsMock: IMock; + let loggerMock: IMock; + let notificationServiceMock: IMock; + let albumArtworkRemover: AlbumArtworkRemover; + let settingsMock: any; + + beforeEach(() => { + albumArtworkRepositoryMock = Mock.ofType(); + fileAccessMock = Mock.ofType(); + loggerMock = Mock.ofType(); + notificationServiceMock = Mock.ofType(); + settingsMock = new SettingsMock(); + applicationPathsMock = Mock.ofType(); + + albumArtworkRemover = new AlbumArtworkRemover( + albumArtworkRepositoryMock.object, + fileAccessMock.object, + applicationPathsMock.object, + notificationServiceMock.object, + settingsMock, + loggerMock.object, + ); + }); + + describe('removeAlbumArtworkThatHasNoTrackAsync', () => { + it('should get the number of album artwork that has no track from the database', async () => { + // Arrange + + // Act + await albumArtworkRemover.removeAlbumArtworkThatHasNoTrackAsync(); + + // Assert + albumArtworkRepositoryMock.verify((x) => x.getNumberOfAlbumArtworkThatHasNoTrack(''), Times.exactly(1)); + }); + + it('should notify that album artwork is being updated, if there is album artwork that has no track.', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtworkThatHasNoTrack('')).returns(() => 2); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatHasNoTrackAsync(); + + // Assert + notificationServiceMock.verify((x) => x.updatingAlbumArtworkAsync(), Times.exactly(1)); + }); + + it('should not notify that album artwork is being updated, if there is no album artwork that has no track.', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtworkThatHasNoTrack('')).returns(() => 0); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatHasNoTrackAsync(); + + // Assert + notificationServiceMock.verify((x) => x.updatingAlbumArtworkAsync(), Times.never()); + }); + + it('should delete album artwork that has no track from the database', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtworkThatHasNoTrack('')).returns(() => 2); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatHasNoTrackAsync(); + + // Assert + albumArtworkRepositoryMock.verify((x) => x.deleteAlbumArtworkThatHasNoTrack(''), Times.exactly(1)); + }); + + it('should not delete album artwork that has no track from the database if there is none', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtworkThatHasNoTrack('')).returns(() => 0); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatHasNoTrackAsync(); + + // Assert + albumArtworkRepositoryMock.verify((x) => x.deleteAlbumArtworkThatHasNoTrack(''), Times.never()); + }); + }); + + describe('removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', () => { + it('should get the number of album artwork for tracks that need album artwork indexing from the database', async () => { + // Arrange + + // Act + await albumArtworkRemover.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + albumArtworkRepositoryMock.verify((x) => x.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(''), Times.exactly(1)); + }); + + it('should notify that album artwork is being updated, if there are tracks that need album artwork indexing.', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing('')).returns(() => 2); + + // Act + await albumArtworkRemover.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + notificationServiceMock.verify((x) => x.updatingAlbumArtworkAsync(), Times.exactly(1)); + }); + + it('should not notify that album artwork is being updated, if there are no tracks that need album artwork indexing.', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing('')).returns(() => 0); + + // Act + await albumArtworkRemover.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + notificationServiceMock.verify((x) => x.updatingAlbumArtworkAsync(), Times.never()); + }); + + it('should delete album artwork for tracks that need album artwork indexing from the database', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing('')).returns(() => 2); + + // Act + await albumArtworkRemover.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + albumArtworkRepositoryMock.verify((x) => x.deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(''), Times.exactly(1)); + }); + + it('should not delete album artwork if there are no tracks that need album artwork indexing from the database', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing('')).returns(() => 0); + + // Act + await albumArtworkRemover.removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(); + + // Assert + albumArtworkRepositoryMock.verify((x) => x.deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(''), Times.never()); + }); + }); + + describe('removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync', () => { + it('should get all album artwork in the database', async () => { + // Arrange + + // Act + await albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); + + // Assert + albumArtworkRepositoryMock.verify((x) => x.getAllAlbumArtwork(), Times.exactly(1)); + }); + + it('should get all artwork files which are in the cover art cache path', async () => { + // Arrange + const albumArtwork1: AlbumArtwork = new AlbumArtwork('albumKey1', 'artworkId1'); + const albumArtwork2: AlbumArtwork = new AlbumArtwork('albumKey2', 'artworkId2'); + albumArtworkRepositoryMock.setup((x) => x.getAllAlbumArtwork()).returns(() => [albumArtwork1, albumArtwork2]); + applicationPathsMock.setup((x) => x.coverArtCacheFullPath()).returns(() => '/home/user/.config/Dopamine/Cache/CoverArt'); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); + + // Assert + fileAccessMock.verify((x) => x.getFilesInDirectoryAsync('/home/user/.config/Dopamine/Cache/CoverArt'), Times.exactly(1)); + }); + + it('should not notify that artwork is being updated if there are no artwork files on disk', async () => { + // Arrange + const albumArtwork1: AlbumArtwork = new AlbumArtwork('albumKey1', 'artworkId1'); + const albumArtwork2: AlbumArtwork = new AlbumArtwork('albumKey2', 'artworkId2'); + albumArtworkRepositoryMock.setup((x) => x.getAllAlbumArtwork()).returns(() => [albumArtwork1, albumArtwork2]); + applicationPathsMock.setup((x) => x.coverArtCacheFullPath()).returns(() => '/home/user/.config/Dopamine/Cache/CoverArt'); + fileAccessMock + .setup((x) => x.getFilesInDirectoryAsync('/home/user/.config/Dopamine/Cache/CoverArt')) + .returns(() => Promise.resolve([])); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); + + // Assert + notificationServiceMock.verify((x) => x.updatingAlbumArtworkAsync(), Times.never()); + }); + + it('should not notify that artwork is being updated if there are artwork files on disk but they are all found in the database', async () => { + // Arrange + const albumArtwork1: AlbumArtwork = new AlbumArtwork('albumKey1', 'album-artworkId1'); + const albumArtwork2: AlbumArtwork = new AlbumArtwork('albumKey2', 'album-artworkId2'); + albumArtworkRepositoryMock.setup((x) => x.getAllAlbumArtwork()).returns(() => [albumArtwork1, albumArtwork2]); + applicationPathsMock.setup((x) => x.coverArtCacheFullPath()).returns(() => '/home/user/.config/Dopamine/Cache/CoverArt'); + fileAccessMock + .setup((x) => x.getFilesInDirectoryAsync('/home/user/.config/Dopamine/Cache/CoverArt')) + .returns(() => + Promise.resolve([ + '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg', + '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg', + ]), + ); + fileAccessMock + .setup((x) => x.getFileNameWithoutExtension('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg')) + .returns(() => 'album-artworkId1'); + fileAccessMock + .setup((x) => x.getFileNameWithoutExtension('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg')) + .returns(() => 'album-artworkId2'); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); + + // Assert + notificationServiceMock.verify((x) => x.updatingAlbumArtworkAsync(), Times.never()); + }); + + it('should notify exactly once that artwork is being updated if there are artwork files on disk which are not found in the database', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getAllAlbumArtwork()).returns(() => []); + applicationPathsMock.setup((x) => x.coverArtCacheFullPath()).returns(() => '/home/user/.config/Dopamine/Cache/CoverArt'); + fileAccessMock + .setup((x) => x.getFilesInDirectoryAsync('/home/user/.config/Dopamine/Cache/CoverArt')) + .returns(() => + Promise.resolve([ + '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg', + '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg', + ]), + ); + fileAccessMock + .setup((x) => x.getFileNameWithoutExtension('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg')) + .returns(() => 'album-artworkId1'); + fileAccessMock + .setup((x) => x.getFileNameWithoutExtension('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg')) + .returns(() => 'album-artworkId2'); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); + + // Assert + notificationServiceMock.verify((x) => x.updatingAlbumArtworkAsync(), Times.exactly(1)); + }); + + it('should not delete any artwork files if none are found on disk', async () => { + // Arrange + albumArtworkRepositoryMock.setup((x) => x.getAllAlbumArtwork()).returns(() => []); + applicationPathsMock.setup((x) => x.coverArtCacheFullPath()).returns(() => '/home/user/.config/Dopamine/Cache/CoverArt'); + fileAccessMock + .setup((x) => x.getFilesInDirectoryAsync('/home/user/.config/Dopamine/Cache/CoverArt')) + .returns(() => Promise.resolve([])); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); + + // Assert + fileAccessMock.verify((x) => x.deleteFileIfExistsAsync(It.isAny()), Times.never()); + }); + + it('should delete artwork files if artwork is not found in the database', async () => { + // Arrange + const albumArtwork1: AlbumArtwork = new AlbumArtwork('albumKey1', 'album-artworkId1'); + albumArtworkRepositoryMock.setup((x) => x.getAllAlbumArtwork()).returns(() => [albumArtwork1]); + applicationPathsMock.setup((x) => x.coverArtCacheFullPath()).returns(() => '/home/user/.config/Dopamine/Cache/CoverArt'); + fileAccessMock + .setup((x) => x.getFilesInDirectoryAsync('/home/user/.config/Dopamine/Cache/CoverArt')) + .returns(() => + Promise.resolve([ + '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg', + '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg', + ]), + ); + fileAccessMock + .setup((x) => x.getFileNameWithoutExtension('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg')) + .returns(() => 'album-artworkId1'); + fileAccessMock + .setup((x) => x.getFileNameWithoutExtension('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg')) + .returns(() => 'album-artworkId2'); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); + + // Assert + fileAccessMock.verify( + (x) => x.deleteFileIfExistsAsync('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg'), + Times.exactly(1), + ); + }); + + it('should not delete artwork files if artwork is found in the database', async () => { + // Arrange + const albumArtwork1: AlbumArtwork = new AlbumArtwork('albumKey1', 'album-artworkId1'); + albumArtworkRepositoryMock.setup((x) => x.getAllAlbumArtwork()).returns(() => [albumArtwork1]); + applicationPathsMock.setup((x) => x.coverArtCacheFullPath()).returns(() => '/home/user/.config/Dopamine/Cache/CoverArt'); + fileAccessMock + .setup((x) => x.getFilesInDirectoryAsync('/home/user/.config/Dopamine/Cache/CoverArt')) + .returns(async () => + Promise.resolve([ + '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg', + '/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg', + ]), + ); + fileAccessMock + .setup((x) => x.getFileNameWithoutExtension('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg')) + .returns(() => 'album-artworkId1'); + fileAccessMock + .setup((x) => x.getFileNameWithoutExtension('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId2.jpg')) + .returns(() => 'album-artworkId2'); + + // Act + await albumArtworkRemover.removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(); + + // Assert + fileAccessMock.verify( + (x) => x.deleteFileIfExistsAsync('/home/user/.config/Dopamine/Cache/CoverArt/album-artworkId1.jpg'), + Times.never(), + ); + }); + }); +}); diff --git a/main/indexing/album-artwork-remover.js b/src/app/services/indexing/album-artwork-remover.ts similarity index 52% rename from main/indexing/album-artwork-remover.js rename to src/app/services/indexing/album-artwork-remover.ts index 5b88c5b7c..3c5eb08f4 100644 --- a/main/indexing/album-artwork-remover.js +++ b/src/app/services/indexing/album-artwork-remover.ts @@ -1,27 +1,38 @@ -const { Timer } = require('../common/scheduling/timer'); -const { UpdatingAlbumArtworkMessage } = require('./messages/updating-album-artwork-message'); - -class AlbumArtworkRemover { - constructor(albumArtworkRepository, applicationPaths, workerProxy, fileAccess, logger) { - this.albumArtworkRepository = albumArtworkRepository; - this.applicationPaths = applicationPaths; - this.workerProxy = workerProxy; - this.fileAccess = fileAccess; - this.logger = logger; - } - - async removeAlbumArtworkThatHasNoTrackAsync() { - const timer = new Timer(); +import { Injectable } from '@angular/core'; +import { AlbumArtwork } from '../../data/entities/album-artwork'; +import { Logger } from '../../common/logger'; +import { Timer } from '../../common/scheduling/timer'; +import { AlbumArtworkRepositoryBase } from '../../data/repositories/album-artwork-repository.base'; +import { FileAccessBase } from '../../common/io/file-access.base'; +import { NotificationServiceBase } from '../notification/notification.service.base'; +import { ApplicationPaths } from '../../common/application/application-paths'; +import { SettingsBase } from '../../common/settings/settings.base'; + +@Injectable({ providedIn: 'root' }) +export class AlbumArtworkRemover { + public constructor( + private albumArtworkRepository: AlbumArtworkRepositoryBase, + private fileAccess: FileAccessBase, + private applicationPaths: ApplicationPaths, + private notificationService: NotificationServiceBase, + private settings: SettingsBase, + private logger: Logger, + ) {} + + public async removeAlbumArtworkThatHasNoTrackAsync(): Promise { + const timer: Timer = new Timer(); timer.start(); try { - const numberOfAlbumArtworkToRemove = this.albumArtworkRepository.getNumberOfAlbumArtworkThatHasNoTrack(); + const numberOfAlbumArtworkToRemove: number = this.albumArtworkRepository.getNumberOfAlbumArtworkThatHasNoTrack( + this.settings.albumKeyIndex, + ); if (numberOfAlbumArtworkToRemove === 0) { timer.stop(); this.logger.info( - `There is no album artwork to remove. Time required: ${timer.getElapsedMilliseconds()} ms.`, + `There is no album artwork to remove. Time required: ${timer.elapsedMilliseconds} ms.`, 'AlbumArtworkRemover', 'removeAlbumArtworkThatHasNoTrackAsync', ); @@ -35,36 +46,39 @@ class AlbumArtworkRemover { 'removeAlbumArtworkThatHasNoTrackAsync', ); - this.workerProxy.postMessage(new UpdatingAlbumArtworkMessage()); + await this.notificationService.updatingAlbumArtworkAsync(); - const numberOfRemovedAlbumArtwork = this.albumArtworkRepository.deleteAlbumArtworkThatHasNoTrack(); + const numberOfRemovedAlbumArtwork: number = this.albumArtworkRepository.deleteAlbumArtworkThatHasNoTrack( + this.settings.albumKeyIndex, + ); timer.stop(); this.logger.info( - `Removed ${numberOfRemovedAlbumArtwork} album artwork. Time required: ${timer.getElapsedMilliseconds()} ms.`, + `Removed ${numberOfRemovedAlbumArtwork} album artwork. Time required: ${timer.elapsedMilliseconds} ms.`, 'AlbumArtworkRemover', 'removeAlbumArtworkThatHasNoTrackAsync', ); - } catch (e) { + } catch (e: unknown) { timer.stop(); this.logger.error(e, 'Could not remove album artwork', 'AlbumArtworkRemover', 'removeAlbumArtworkThatHasNoTrackAsync'); } } - async removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync() { - const timer = new Timer(); + public async removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync(): Promise { + const timer: Timer = new Timer(); timer.start(); try { - const numberOfAlbumArtworkToRemove = this.albumArtworkRepository.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(); + const numberOfAlbumArtworkToRemove: number = + this.albumArtworkRepository.getNumberOfAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(this.settings.albumKeyIndex); if (numberOfAlbumArtworkToRemove === 0) { timer.stop(); this.logger.info( - `There is no album artwork to remove. Time required: ${timer.getElapsedMilliseconds()} ms.`, + `There is no album artwork to remove. Time required: ${timer.elapsedMilliseconds} ms.`, 'AlbumArtworkRemover', 'removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', ); @@ -78,18 +92,20 @@ class AlbumArtworkRemover { 'removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', ); - this.workerProxy.postMessage(new UpdatingAlbumArtworkMessage()); + await this.notificationService.updatingAlbumArtworkAsync(); - const numberOfRemovedAlbumArtwork = this.albumArtworkRepository.deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexing(); + const numberOfRemovedAlbumArtwork: number = this.albumArtworkRepository.deleteAlbumArtworkForTracksThatNeedAlbumArtworkIndexing( + this.settings.albumKeyIndex, + ); timer.stop(); this.logger.info( - `Removed ${numberOfRemovedAlbumArtwork} album artwork. Time required: ${timer.getElapsedMilliseconds()} ms.`, + `Removed ${numberOfRemovedAlbumArtwork} album artwork. Time required: ${timer.elapsedMilliseconds} ms.`, 'AlbumArtworkRemover', 'removeAlbumArtworkForTracksThatNeedAlbumArtworkIndexingAsync', ); - } catch (e) { + } catch (e: unknown) { timer.stop(); this.logger.error( @@ -101,9 +117,9 @@ class AlbumArtworkRemover { } } - async removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync() { + public async removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync(): Promise { try { - const allAlbumArtworkInDatabase = this.albumArtworkRepository.getAllAlbumArtwork() ?? []; + const allAlbumArtworkInDatabase: AlbumArtwork[] = this.albumArtworkRepository.getAllAlbumArtwork() ?? []; this.logger.info( `Found ${allAlbumArtworkInDatabase.length} album artwork in the database`, @@ -111,7 +127,7 @@ class AlbumArtworkRemover { 'removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync', ); - const allArtworkIdsInDatabase = allAlbumArtworkInDatabase.map((x) => x.artworkId); + const allArtworkIdsInDatabase: string[] = allAlbumArtworkInDatabase.map((x) => x.artworkId); this.logger.info( `Found ${allArtworkIdsInDatabase.length} artworkIds in the database`, @@ -119,8 +135,8 @@ class AlbumArtworkRemover { 'removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync', ); - const coverArtCacheFullPath = this.applicationPaths.getCoverArtCacheFullPath(); - const allAlbumArtworkFilePaths = await this.fileAccess.getFilesInDirectoryAsync(coverArtCacheFullPath); + const coverArtCacheFullPath: string = this.applicationPaths.coverArtCacheFullPath(); + const allAlbumArtworkFilePaths: string[] = await this.fileAccess.getFilesInDirectoryAsync(coverArtCacheFullPath); this.logger.info( `Found ${allAlbumArtworkFilePaths.length} artwork files on disk`, @@ -128,22 +144,22 @@ class AlbumArtworkRemover { 'removeAlbumArtworkThatIsNotInTheDatabaseFromDiskAsync', ); - let numberOfRemovedAlbumArtwork = 0; + let numberOfRemovedAlbumArtwork: number = 0; for (const albumArtworkFilePath of allAlbumArtworkFilePaths) { - const albumArtworkFileNameWithoutExtension = this.fileAccess.getFileNameWithoutExtension(albumArtworkFilePath); + const albumArtworkFileNameWithoutExtension: string = this.fileAccess.getFileNameWithoutExtension(albumArtworkFilePath); if (!allArtworkIdsInDatabase.includes(albumArtworkFileNameWithoutExtension)) { await this.fileAccess.deleteFileIfExistsAsync(albumArtworkFilePath); numberOfRemovedAlbumArtwork++; } - // Only send message once if (numberOfRemovedAlbumArtwork === 1) { - this.workerProxy.postMessage(new UpdatingAlbumArtworkMessage()); + // Only trigger the notification once + await this.notificationService.updatingAlbumArtworkAsync(); } } - } catch (e) { + } catch (e: unknown) { this.logger.error( e, 'Could not remove album artwork from disk', @@ -153,5 +169,3 @@ class AlbumArtworkRemover { } } } - -exports.AlbumArtworkRemover = AlbumArtworkRemover; diff --git a/src/app/services/indexing/indexing.service.base.ts b/src/app/services/indexing/indexing.service.base.ts index e410a66c0..eb83553f9 100644 --- a/src/app/services/indexing/indexing.service.base.ts +++ b/src/app/services/indexing/indexing.service.base.ts @@ -5,7 +5,7 @@ export abstract class IndexingServiceBase { public abstract isIndexingCollection: boolean; public abstract indexCollectionIfOutdated(): void; public abstract indexCollectionAlways(): void; - public abstract indexAlbumArtworkOnly(onlyWhenHasNoCover: boolean): void; + public abstract indexAlbumArtworkOnlyAsync(onlyWhenHasNoCover: boolean): Promise; public abstract onAlbumGroupingChanged(): void; - public abstract indexCollectionIfOptionsHaveChanged(): void; + public abstract indexCollectionIfOptionsHaveChangedAsync(): Promise; } diff --git a/src/app/services/indexing/indexing.service.spec.ts b/src/app/services/indexing/indexing.service.spec.ts index f4660e360..c360e4df7 100644 --- a/src/app/services/indexing/indexing.service.spec.ts +++ b/src/app/services/indexing/indexing.service.spec.ts @@ -7,34 +7,54 @@ import { IndexingService } from './indexing.service'; import { IndexingServiceBase } from './indexing.service.base'; import { Observable, Subject } from 'rxjs'; import { DesktopBase } from '../../common/io/desktop.base'; +import { AlbumArtworkIndexer } from './album-artwork-indexer'; +import {IpcProxyBase} from "../../common/io/ipc-proxy.base"; +import {IIndexingMessage} from "./messages/i-indexing-message"; describe('IndexingService', () => { let notificationServiceMock: IMock; let folderServiceMock: IMock; + let albumArtworkIndexerMock: IMock; let desktopMock: IMock; let settingsMock: IMock; + let ipcProxyMock: IMock; let loggerMock: IMock; let folderService_foldersChanged: Subject; + let onIndexingWorkerMessage: Subject; + let onIndexingWorkerExit: Subject; + beforeEach(() => { notificationServiceMock = Mock.ofType(); folderServiceMock = Mock.ofType(); desktopMock = Mock.ofType(); + albumArtworkIndexerMock = Mock.ofType(); settingsMock = Mock.ofType(); + ipcProxyMock = Mock.ofType(); loggerMock = Mock.ofType(); folderService_foldersChanged = new Subject(); const folderService_foldersChanged$: Observable = folderService_foldersChanged.asObservable(); - folderServiceMock.setup((x) => x.foldersChanged$).returns(() => folderService_foldersChanged); + folderServiceMock.setup((x) => x.foldersChanged$).returns(() => folderService_foldersChanged$); + + onIndexingWorkerMessage = new Subject(); + const onIndexingWorkerMessage$: Observable = onIndexingWorkerMessage.asObservable(); + ipcProxyMock.setup((x) => x.onIndexingWorkerMessage$).returns(() => onIndexingWorkerMessage$); + + onIndexingWorkerExit = new Subject(); + const onIndexingWorkerExit$: Observable = onIndexingWorkerExit.asObservable(); + ipcProxyMock.setup((x) => x.onIndexingWorkerExit$).returns(() => onIndexingWorkerExit$); }); function createSut(): IndexingServiceBase { return new IndexingService( notificationServiceMock.object, folderServiceMock.object, + albumArtworkIndexerMock.object, desktopMock.object, settingsMock.object, + ipcProxyMock.object, loggerMock.object, ); } diff --git a/src/app/services/indexing/indexing.service.ts b/src/app/services/indexing/indexing.service.ts index 3bb6c1ceb..1916c3a45 100644 --- a/src/app/services/indexing/indexing.service.ts +++ b/src/app/services/indexing/indexing.service.ts @@ -4,12 +4,13 @@ import { Logger } from '../../common/logger'; import { IndexingServiceBase } from './indexing.service.base'; import { FolderServiceBase } from '../folder/folder.service.base'; import { SettingsBase } from '../../common/settings/settings.base'; -import { ipcRenderer } from 'electron'; import { PromiseUtils } from '../../common/utils/promise-utils'; import { NotificationServiceBase } from '../notification/notification.service.base'; import { DesktopBase } from '../../common/io/desktop.base'; import { IIndexingMessage } from './messages/i-indexing-message'; import { AddingTracksMessage } from './messages/adding-tracks-message'; +import { AlbumArtworkIndexer } from './album-artwork-indexer'; +import {IpcProxyBase} from "../../common/io/ipc-proxy.base"; @Injectable() export class IndexingService implements IndexingServiceBase, OnDestroy { @@ -21,15 +22,13 @@ export class IndexingService implements IndexingServiceBase, OnDestroy { public constructor( private notificationService: NotificationServiceBase, private folderService: FolderServiceBase, + private albumArtworkIndexer: AlbumArtworkIndexer, private desktop: DesktopBase, private settings: SettingsBase, + private ipcProxy: IpcProxyBase, private logger: Logger, ) { - this.subscription.add( - this.folderService.foldersChanged$.subscribe(() => { - this.foldersHaveChanged = true; - }), - ); + this.initializeSubscriptions(); } public indexingFinished$: Observable = this.indexingFinished.asObservable(); @@ -48,7 +47,25 @@ export class IndexingService implements IndexingServiceBase, OnDestroy { this.indexCollection('always'); } - public indexCollectionIfOptionsHaveChanged(): void { + public initializeSubscriptions(): void { + this.subscription.add( + this.folderService.foldersChanged$.subscribe(() => { + this.foldersHaveChanged = true; + }), + ); + + this.ipcProxy.onIndexingWorkerMessage$.subscribe((message: IIndexingMessage) => { + PromiseUtils.noAwait(this.showNotification(message)); + }); + + this.ipcProxy.onIndexingWorkerExit$.subscribe(async () => { + await this.albumArtworkIndexer.indexAlbumArtworkAsync(); + this.isIndexingCollection = false; + this.indexingFinished.next(); + }); + } + + public async indexCollectionIfOptionsHaveChangedAsync(): Promise { if (this.foldersHaveChanged) { this.logger.info('Folders have changed. Indexing collection.', 'IndexingService', 'indexCollectionIfOptionsHaveChanged'); this.indexCollection('always'); @@ -58,11 +75,11 @@ export class IndexingService implements IndexingServiceBase, OnDestroy { 'IndexingService', 'indexCollectionIfOptionsHaveChanged', ); - this.indexAlbumArtworkOnly(false); + await this.indexAlbumArtworkOnlyAsync(false); } } - public indexAlbumArtworkOnly(onlyWhenHasNoCover: boolean): void { + public async indexAlbumArtworkOnlyAsync(onlyWhenHasNoCover: boolean): Promise { if (this.isIndexingCollection) { this.logger.info('Already indexing.', 'IndexingService', 'indexAlbumArtworkOnlyAsync'); @@ -74,16 +91,7 @@ export class IndexingService implements IndexingServiceBase, OnDestroy { this.logger.info('Indexing collection.', 'IndexingService', 'indexAlbumArtworkOnlyAsync'); - ipcRenderer.send('indexing-worker', this.createWorkerArgs('albumArtwork', onlyWhenHasNoCover)); - - ipcRenderer.on('indexing-worker-message', (_: Electron.IpcRendererEvent, message: IIndexingMessage): void => { - PromiseUtils.noAwait(this.showSnackBarMessage(message)); - }); - - ipcRenderer.on('indexing-worker-exit', (): void => { - this.isIndexingCollection = false; - this.indexingFinished.next(); - }); + await this.albumArtworkIndexer.indexAlbumArtworkAsync(); } public onAlbumGroupingChanged(): void { @@ -94,14 +102,11 @@ export class IndexingService implements IndexingServiceBase, OnDestroy { return { task: task, skipRemovedFilesDuringRefresh: this.settings.skipRemovedFilesDuringRefresh, - downloadMissingAlbumCovers: this.settings.downloadMissingAlbumCovers, applicationDataDirectory: this.desktop.getApplicationDataDirectory(), - albumKeyIndex: this.settings.albumKeyIndex, - onlyWhenHasNoCover: onlyWhenHasNoCover, }; } - private async showSnackBarMessage(message: IIndexingMessage): Promise { + private async showNotification(message: IIndexingMessage): Promise { switch (message.type) { case 'refreshing': { await this.notificationService.refreshing(); @@ -149,15 +154,6 @@ export class IndexingService implements IndexingServiceBase, OnDestroy { this.logger.info('Indexing collection.', 'IndexingService', 'indexCollection'); - ipcRenderer.send('indexing-worker', this.createWorkerArgs(task, false)); - - ipcRenderer.on('indexing-worker-message', (_: Electron.IpcRendererEvent, message: IIndexingMessage): void => { - PromiseUtils.noAwait(this.showSnackBarMessage(message)); - }); - - ipcRenderer.on('indexing-worker-exit', (): void => { - this.isIndexingCollection = false; - this.indexingFinished.next(); - }); + this.ipcProxy.sendToMainProcess('indexing-worker', this.createWorkerArgs(task, false)); } } diff --git a/src/app/ui/components/back-button/back-button.component.spec.ts b/src/app/ui/components/back-button/back-button.component.spec.ts index 4ba1599e1..0ea44a269 100644 --- a/src/app/ui/components/back-button/back-button.component.spec.ts +++ b/src/app/ui/components/back-button/back-button.component.spec.ts @@ -37,7 +37,7 @@ describe('BackButtonComponent', () => { await component.goBackToCollectionAsync(); // Assert - indexingServiceMock.verify((x) => x.indexCollectionIfOptionsHaveChanged(), Times.exactly(1)); + indexingServiceMock.verify((x) => x.indexCollectionIfOptionsHaveChangedAsync(), Times.exactly(1)); }); }); }); diff --git a/src/app/ui/components/back-button/back-button.component.ts b/src/app/ui/components/back-button/back-button.component.ts index 63ffabdf8..1c0b4563f 100644 --- a/src/app/ui/components/back-button/back-button.component.ts +++ b/src/app/ui/components/back-button/back-button.component.ts @@ -17,6 +17,6 @@ export class BackButtonComponent { public async goBackToCollectionAsync(): Promise { await this.navigationService.navigateToCollectionAsync(); - this.indexingService.indexCollectionIfOptionsHaveChanged(); + await this.indexingService.indexCollectionIfOptionsHaveChangedAsync(); } } diff --git a/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.html b/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.html index d0efb8c08..40823cd0e 100644 --- a/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.html +++ b/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.html @@ -8,11 +8,11 @@
{{ 'refresh' | translate }}
{{ 'refresh-all-covers' | translate }}
- + {{ 'refresh-all' | translate }}
{{ 'refresh-missing-covers' | translate }}
- + {{ 'refresh-missing' | translate }} diff --git a/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.spec.ts b/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.spec.ts index 1f01cd571..ac44e1934 100644 --- a/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.spec.ts +++ b/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.spec.ts @@ -41,26 +41,22 @@ describe('ManageAlbumsComponent', () => { }); describe('refreshAllCoversAsync', () => { - it('should index artwork only, for all albums', () => { - // Arrange - + it('should index artwork only, for all albums', async () => { // Act - component.refreshAllCovers(); + await component.refreshAllCoversAsync(); // Assert - indexingServiceMock.verify((x) => x.indexAlbumArtworkOnly(false), Times.exactly(1)); + indexingServiceMock.verify((x) => x.indexAlbumArtworkOnlyAsync(false), Times.exactly(1)); }); }); describe('refreshMissingCoversAsync', () => { - it('should index artwork only, for albums with missing covers', () => { - // Arrange - + it('should index artwork only, for albums with missing covers', async () => { // Act - component.refreshMissingCovers(); + await component.refreshMissingCoversAsync(); // Assert - indexingServiceMock.verify((x) => x.indexAlbumArtworkOnly(true), Times.exactly(1)); + indexingServiceMock.verify((x) => x.indexAlbumArtworkOnlyAsync(true), Times.exactly(1)); }); }); }); diff --git a/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.ts b/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.ts index 5c97363f6..b4189e9b2 100644 --- a/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.ts +++ b/src/app/ui/components/manage-collection/manage-albums/manage-albums.component.ts @@ -78,12 +78,12 @@ export class ManageAlbumsComponent { }); } - public refreshAllCovers(): void { - this.indexingService.indexAlbumArtworkOnly(false); + public async refreshAllCoversAsync(): Promise { + await this.indexingService.indexAlbumArtworkOnlyAsync(false); } - public refreshMissingCovers(): void { - this.indexingService.indexAlbumArtworkOnly(true); + public async refreshMissingCoversAsync(): Promise { + await this.indexingService.indexAlbumArtworkOnlyAsync(true); } private allAlbumGroupingSettingsAreDisabled(): boolean {