Skip to content

Commit

Permalink
Merge branch 'master' into language-fix-1
Browse files Browse the repository at this point in the history
  • Loading branch information
digimezzo authored Aug 25, 2024
2 parents b9bfe76 + 1846847 commit 78a4f04
Show file tree
Hide file tree
Showing 63 changed files with 1,241 additions and 200 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.0.0-preview.32] - 2024-08-22
## [3.0.0-preview.32] - 2024-08-25

### Added

- It is now possible to split multiple artists by customizable symbols like "ft." or "feat."

### Fixed

Expand Down
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ import { AlbumArtworkCacheService } from './services/album-artwork-cache/album-a
import { AlbumArtworkCacheServiceBase } from './services/album-artwork-cache/album-artwork-cache.service.base';
import { QueuedTrackRepositoryBase } from './data/repositories/queued-track-repository.base';
import { QueuedTrackRepository } from './data/repositories/queued-track-repository';
import { TextIconSecondaryButtonComponent } from './ui/components/controls/text-icon-secondary-button/text-icon-secondary-button.component';

export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
Expand Down Expand Up @@ -453,6 +454,7 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
BigIconButtonComponent,
ToggleSwitchComponent,
IconButtonComponent,
TextIconSecondaryButtonComponent,
NotificationBarComponent,
],
imports: [
Expand Down
2 changes: 2 additions & 0 deletions src/app/common/settings/settings.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,6 @@ export abstract class SettingsBase {
public abstract playbackControlsLoop: number;
public abstract playbackControlsShuffle: number;
public abstract rememberPlaybackStateAfterRestart: boolean;
public abstract artistSplitSeparators: string;
public abstract artistSplitExceptions: string;
}
26 changes: 26 additions & 0 deletions src/app/common/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,24 @@ export class Settings implements SettingsBase {
this.settings.set('rememberPlaybackStateAfterRestart', v);
}

// artistSplitSeparators
public get artistSplitSeparators(): string {
return <string>this.settings.get('artistSplitSeparators');
}

public set artistSplitSeparators(v: string) {
this.settings.set('artistSplitSeparators', v);
}

// artistSplitExceptions
public get artistSplitExceptions(): string {
return <string>this.settings.get('artistSplitExceptions');
}

public set artistSplitExceptions(v: string) {
this.settings.set('artistSplitExceptions', v);
}

// Initialize
private initialize(): void {
if (!this.settings.has('language')) {
Expand Down Expand Up @@ -1041,5 +1059,13 @@ export class Settings implements SettingsBase {
if (!this.settings.has('rememberPlaybackStateAfterRestart')) {
this.settings.set('rememberPlaybackStateAfterRestart', true);
}

if (!this.settings.has('artistSplitSeparators')) {
this.settings.set('artistSplitSeparators', '[feat.][ft.]');
}

if (!this.settings.has('artistSplitExceptions')) {
this.settings.set('artistSplitExceptions', '');
}
}
}
20 changes: 10 additions & 10 deletions src/app/common/sorting/artist-sorter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ describe('ArtistSorter', () => {
beforeEach(() => {
translatorServiceMock = Mock.ofType<TranslatorServiceBase>();

artistModel1 = new ArtistModel('Artist1', translatorServiceMock.object);
artistModel2 = new ArtistModel('Artist2', translatorServiceMock.object);
artistModel3 = new ArtistModel('Artist3', translatorServiceMock.object);
artistModel4 = new ArtistModel('Artist4', translatorServiceMock.object);
artistModel5 = new ArtistModel('Artist5', translatorServiceMock.object);
artistModel6 = new ArtistModel('Artist6', translatorServiceMock.object);
artistModel7 = new ArtistModel('Artist7', translatorServiceMock.object);
artistModel8 = new ArtistModel('Artist8', translatorServiceMock.object);
artistModel9 = new ArtistModel('Artist9', translatorServiceMock.object);
artistModel10 = new ArtistModel('Artist10', translatorServiceMock.object);
artistModel1 = new ArtistModel('Artist1', 'Artist1', translatorServiceMock.object);
artistModel2 = new ArtistModel('Artist2', 'Artist2', translatorServiceMock.object);
artistModel3 = new ArtistModel('Artist3', 'Artist3', translatorServiceMock.object);
artistModel4 = new ArtistModel('Artist4', 'Artist4', translatorServiceMock.object);
artistModel5 = new ArtistModel('Artist5', 'Artist5', translatorServiceMock.object);
artistModel6 = new ArtistModel('Artist6', 'Artist6', translatorServiceMock.object);
artistModel7 = new ArtistModel('Artist7', 'Artist7', translatorServiceMock.object);
artistModel8 = new ArtistModel('Artist8', 'Artist8', translatorServiceMock.object);
artistModel9 = new ArtistModel('Artist9', 'Artist9', translatorServiceMock.object);
artistModel10 = new ArtistModel('Artist10', 'Artist10', translatorServiceMock.object);

artists = [
artistModel2,
Expand Down
68 changes: 68 additions & 0 deletions src/app/common/utils/collection-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { StringUtils } from './string-utils';
import { CollectionUtils } from './collections-utils';

describe('CollectionUtils', () => {
describe('fromString', () => {
it('should return an empty collection if the string is undefined', () => {
// Assert
expect(CollectionUtils.fromString(undefined)).toEqual([]);
});

it('should return an empty collection if the string is empty', () => {
// Assert
expect(CollectionUtils.fromString('')).toEqual([]);
});

it('should return an empty collection if the string is whitespace', () => {
// Assert
expect(CollectionUtils.fromString(' ')).toEqual([]);
});

it('should return an empty collection if the string is whitespace', () => {
// Assert
expect(CollectionUtils.fromString(' ')).toEqual([]);
});

it('should return a collection with the given string as first item if it does not start with [', () => {
// Assert
expect(CollectionUtils.fromString('item1][item2]')).toEqual(['item1][item2]']);
});

it('should return a collection with the given string as first item if it does not end with ]', () => {
// Assert
expect(CollectionUtils.fromString('[item1][item2')).toEqual(['[item1][item2']);
});

it('should return a collection with the given string as first item if it does not start with [ and end with ]', () => {
// Assert
expect(CollectionUtils.fromString('item1][item2')).toEqual(['item1][item2']);
});

it('should return a collection with the given string as first item if the string only has 1 item', () => {
// Assert
expect(CollectionUtils.fromString('[item1]')).toEqual(['item1']);
});

it('should return a collection with all items if the string only has multiple items', () => {
// Assert
expect(CollectionUtils.fromString('[item1][item2][item3]')).toEqual(['item1', 'item2', 'item3']);
});
});

describe('toString', () => {
it('should return an empty string if the collection is undefined', () => {
// Assert
expect(CollectionUtils.toString(undefined)).toEqual('');
});

it('should return an empty string if the collection is empty', () => {
// Assert
expect(CollectionUtils.toString([])).toEqual('');
});

it('should return a string with the items joined by ][', () => {
// Assert
expect(CollectionUtils.toString(['item1', 'item2', 'item3'])).toEqual('[item1][item2][item3]');
});
});
});
30 changes: 30 additions & 0 deletions src/app/common/utils/collections-utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { StringUtils } from './string-utils';

export class CollectionUtils {
public static groupBy<S, T>(list: T[], keyGetter: (T) => S): Map<S, T[]> {
const map: Map<S, T[]> = new Map();
Expand Down Expand Up @@ -35,4 +37,32 @@ export class CollectionUtils {

return nextItem;
}

public static includesIgnoreCase(array: string[], value: string): boolean {
return array.some((item) => item.toLowerCase() === value.toLowerCase());
}

public static toString(itemsAsCollection: string[] | undefined): string {
if (!itemsAsCollection) {
return '';
}

if (itemsAsCollection.length === 0) {
return '';
}

return `[${itemsAsCollection.join('][')}]`;
}

public static fromString(itemsAsString: string | undefined): string[] {
if (StringUtils.isNullOrWhiteSpace(itemsAsString)) {
return [];
}

if (!itemsAsString!.startsWith('[') || !itemsAsString!.endsWith(']')) {
return [itemsAsString!];
}

return itemsAsString!.slice(1, -1).split('][');
}
}
16 changes: 16 additions & 0 deletions src/app/common/utils/string-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ export class StringUtils {
return string1.toLowerCase() === string2.toLowerCase();
}

public static includesIgnoreCase(sourceString: string | undefined, stringToCheck: string | undefined): boolean {
if (sourceString == undefined && stringToCheck == undefined) {
return false;
}

if (sourceString == undefined) {
return false;
}

if (stringToCheck == undefined) {
return false;
}

return sourceString.toLowerCase().includes(stringToCheck.toLowerCase());
}

public static isNullOrWhiteSpace(stringToCheck: string | undefined): boolean {
if (stringToCheck == undefined) {
return true;
Expand Down
3 changes: 2 additions & 1 deletion src/app/services/album/album-service.base.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ArtistType } from '../artist/artist-type';
import { AlbumModel } from './album-model';
import { ArtistModel } from '../artist/artist-model';

export abstract class AlbumServiceBase {
public abstract getAllAlbums(): AlbumModel[];
public abstract getAlbumsForArtists(artists: string[], artistType: ArtistType): AlbumModel[];
public abstract getAlbumsForArtists(artists: ArtistModel[], artistType: ArtistType): AlbumModel[];
public abstract getAlbumsForGenres(genres: string[]): AlbumModel[];
}
40 changes: 29 additions & 11 deletions src/app/services/album/album-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AlbumData } from '../../data/entities/album-data';
import { ApplicationPaths } from '../../common/application/application-paths';
import { SettingsBase } from '../../common/settings/settings.base';
import { SettingsMock } from '../../testing/settings-mock';
import { ArtistModel } from '../artist/artist-model';

describe('AlbumService', () => {
let trackRepositoryMock: IMock<TrackRepositoryBase>;
Expand Down Expand Up @@ -78,13 +79,18 @@ describe('AlbumService', () => {
albumData2.albumKey = 'Album key 2';
const albumDatas: AlbumData[] = [albumData1, albumData2];

trackRepositoryMock.setup((x) => x.getAlbumDataForTrackArtists('', ['Artist 1', 'Artist 2'])).returns(() => albumDatas);
trackRepositoryMock
.setup((x) => x.getAlbumDataForTrackArtists('', ['Source artist 1', 'Source artist 2']))
.returns(() => albumDatas);

const artist1: ArtistModel = new ArtistModel('Source artist 1', 'Artist 1', translatorServiceMock.object);
const artist2: ArtistModel = new ArtistModel('Source artist 2', 'Artist 2', translatorServiceMock.object);

// Act
const returnedAlbums: AlbumModel[] = service.getAlbumsForArtists(['Artist 1', 'Artist 2'], ArtistType.trackArtists);
const returnedAlbums: AlbumModel[] = service.getAlbumsForArtists([artist1, artist2], ArtistType.trackArtists);

// Assert
trackRepositoryMock.verify((x) => x.getAlbumDataForTrackArtists('', ['Artist 1', 'Artist 2']), Times.exactly(1));
trackRepositoryMock.verify((x) => x.getAlbumDataForTrackArtists('', ['Source artist 1', 'Source artist 2']), Times.exactly(1));
expect(returnedAlbums.length).toEqual(2);
expect(returnedAlbums[0].albumKey).toEqual('Album key 1');
expect(returnedAlbums[1].albumKey).toEqual('Album key 2');
Expand All @@ -98,13 +104,18 @@ describe('AlbumService', () => {
albumData2.albumKey = 'Album key 2';
const albumDatas: AlbumData[] = [albumData1, albumData2];

trackRepositoryMock.setup((x) => x.getAlbumDataForAlbumArtists('', ['Artist 1', 'Artist 2'])).returns(() => albumDatas);
trackRepositoryMock
.setup((x) => x.getAlbumDataForAlbumArtists('', ['Source artist 1', 'Source artist 2']))
.returns(() => albumDatas);

const artist1: ArtistModel = new ArtistModel('Source artist 1', 'Artist 1', translatorServiceMock.object);
const artist2: ArtistModel = new ArtistModel('Source artist 2', 'Artist 2', translatorServiceMock.object);

// Act
const returnedAlbums: AlbumModel[] = service.getAlbumsForArtists(['Artist 1', 'Artist 2'], ArtistType.albumArtists);
const returnedAlbums: AlbumModel[] = service.getAlbumsForArtists([artist1, artist2], ArtistType.albumArtists);

// Assert
trackRepositoryMock.verify((x) => x.getAlbumDataForAlbumArtists('', ['Artist 1', 'Artist 2']), Times.exactly(1));
trackRepositoryMock.verify((x) => x.getAlbumDataForAlbumArtists('', ['Source artist 1', 'Source artist 2']), Times.exactly(1));
expect(returnedAlbums.length).toEqual(2);
expect(returnedAlbums[0].albumKey).toEqual('Album key 1');
expect(returnedAlbums[1].albumKey).toEqual('Album key 2');
Expand All @@ -117,15 +128,22 @@ describe('AlbumService', () => {
const albumData2: AlbumData = new AlbumData();
albumData2.albumKey = 'Album key 2';

trackRepositoryMock.setup((x) => x.getAlbumDataForTrackArtists('', ['Artist 1', 'Artist 2'])).returns(() => [albumData1]);
trackRepositoryMock.setup((x) => x.getAlbumDataForAlbumArtists('', ['Artist 1', 'Artist 2'])).returns(() => [albumData2]);
trackRepositoryMock
.setup((x) => x.getAlbumDataForTrackArtists('', ['Source artist 1', 'Source artist 2']))
.returns(() => [albumData1]);
trackRepositoryMock
.setup((x) => x.getAlbumDataForAlbumArtists('', ['Source artist 1', 'Source artist 2']))
.returns(() => [albumData2]);

const artist1: ArtistModel = new ArtistModel('Source artist 1', 'Artist 1', translatorServiceMock.object);
const artist2: ArtistModel = new ArtistModel('Source artist 2', 'Artist 2', translatorServiceMock.object);

// Act
const returnedAlbums: AlbumModel[] = service.getAlbumsForArtists(['Artist 1', 'Artist 2'], ArtistType.allArtists);
const returnedAlbums: AlbumModel[] = service.getAlbumsForArtists([artist1, artist2], ArtistType.allArtists);

// Assert
trackRepositoryMock.verify((x) => x.getAlbumDataForTrackArtists('', ['Artist 1', 'Artist 2']), Times.exactly(1));
trackRepositoryMock.verify((x) => x.getAlbumDataForAlbumArtists('', ['Artist 1', 'Artist 2']), Times.exactly(1));
trackRepositoryMock.verify((x) => x.getAlbumDataForTrackArtists('', ['Source artist 1', 'Source artist 2']), Times.exactly(1));
trackRepositoryMock.verify((x) => x.getAlbumDataForAlbumArtists('', ['Source artist 1', 'Source artist 2']), Times.exactly(1));
expect(returnedAlbums.length).toEqual(2);
expect(returnedAlbums[0].albumKey).toEqual('Album key 1');
expect(returnedAlbums[1].albumKey).toEqual('Album key 2');
Expand Down
48 changes: 31 additions & 17 deletions src/app/services/album/album-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AlbumServiceBase } from './album-service.base';
import { TrackRepositoryBase } from '../../data/repositories/track-repository.base';
import { ApplicationPaths } from '../../common/application/application-paths';
import { SettingsBase } from '../../common/settings/settings.base';
import { ArtistModel } from '../artist/artist-model';

@Injectable()
export class AlbumService implements AlbumServiceBase {
Expand All @@ -23,34 +24,47 @@ export class AlbumService implements AlbumServiceBase {
return this.createAlbumsFromAlbumData(albumDatas);
}

public getAlbumsForArtists(artists: string[], artistType: ArtistType): AlbumModel[] {
public getAlbumsForArtists(artists: ArtistModel[], artistType: ArtistType): AlbumModel[] {
const albumDatas: AlbumData[] = [];

if (artistType === ArtistType.trackArtists || artistType === ArtistType.allArtists) {
const trackArtistsAlbumDatas: AlbumData[] =
this.trackRepository.getAlbumDataForTrackArtists(this.settings.albumKeyIndex, artists) ?? [];
const sourceArtists: string[] = artists.reduce<string[]>(
(acc, artist) => (artist.sourceNames ? acc.concat(artist.sourceNames) : acc),
[],
);

for (const albumData of trackArtistsAlbumDatas) {
albumDatas.push(albumData);
}
if (artistType === ArtistType.trackArtists || artistType === ArtistType.allArtists) {
this.addAlbumsForTrackOrAllArtists(sourceArtists, albumDatas);
}

if (artistType === ArtistType.albumArtists || artistType === ArtistType.allArtists) {
const albumArtistsAlbumDatas: AlbumData[] =
this.trackRepository.getAlbumDataForAlbumArtists(this.settings.albumKeyIndex, artists) ?? [];

for (const albumData of albumArtistsAlbumDatas) {
// Avoid adding a track twice
// TODO: can this be done better?
if (!albumDatas.map((x) => x.albumKey).includes(albumData.albumKey)) {
albumDatas.push(albumData);
}
}
this.addAlbumsForAlbumOrAllArtists(sourceArtists, albumDatas);
}

return this.createAlbumsFromAlbumData(albumDatas);
}

private addAlbumsForTrackOrAllArtists(artists: string[], albumDatas: AlbumData[]): void {
const trackArtistsAlbumDatas: AlbumData[] =
this.trackRepository.getAlbumDataForTrackArtists(this.settings.albumKeyIndex, artists) ?? [];

for (const albumData of trackArtistsAlbumDatas) {
albumDatas.push(albumData);
}
}

private addAlbumsForAlbumOrAllArtists(artists: string[], albumDatas: AlbumData[]): void {
const albumArtistsAlbumDatas: AlbumData[] =
this.trackRepository.getAlbumDataForAlbumArtists(this.settings.albumKeyIndex, artists) ?? [];

for (const albumData of albumArtistsAlbumDatas) {
// Avoid adding a track twice
// TODO: can this be done better?
if (!albumDatas.map((x) => x.albumKey).includes(albumData.albumKey)) {
albumDatas.push(albumData);
}
}
}

public getAlbumsForGenres(genres: string[]): AlbumModel[] {
const albumDatas: AlbumData[] = this.trackRepository.getAlbumDataForGenres(this.settings.albumKeyIndex, genres) ?? [];

Expand Down
Loading

0 comments on commit 78a4f04

Please sign in to comment.