Skip to content

Commit

Permalink
[Issue comixed#20] Add the consolidation feature to the library state.
Browse files Browse the repository at this point in the history
  • Loading branch information
mcpierce committed Mar 23, 2020
1 parent 4627c98 commit c031241
Show file tree
Hide file tree
Showing 13 changed files with 360 additions and 10 deletions.
28 changes: 26 additions & 2 deletions comixed-frontend/src/app/library/actions/library.actions.ts
Expand Up @@ -33,7 +33,10 @@ export enum LibraryActionTypes {
DeleteMultipleComicsFailed = '[LIBRARY] Failed to delete multiple comics',
ConvertComics = '[LIBRARY] Convert comics to a new archive type',
ComicsConverting = '[LIBRARY] Comics converting to a new archive type',
ConvertComicsFailed = '[LIBRARY] Failed to convert comics'
ConvertComicsFailed = '[LIBRARY] Failed to convert comics',
Consolidate = '[LIBRARY] Consolidate the library',
Consolidated = '[LIBRARY] Library is consolidated',
ConsolidateFailed = '[LIBRARY] Failed to consolidate libary'
}

export class LibraryReset implements Action {
Expand Down Expand Up @@ -137,6 +140,24 @@ export class LibraryConvertComicsFailed implements Action {
constructor() {}
}

export class LibraryConsolidate implements Action {
readonly type = LibraryActionTypes.Consolidate;

constructor(public payload: { deletePhysicalFiles: boolean }) {}
}

export class LibraryConsolidated implements Action {
readonly type = LibraryActionTypes.Consolidated;

constructor(public payload: { deletedComics: Comic[] }) {}
}

export class LibraryConsolidateFailed implements Action {
readonly type = LibraryActionTypes.ConsolidateFailed;

constructor() {}
}

export type LibraryActions =
| LibraryReset
| LibraryGetUpdates
Expand All @@ -150,4 +171,7 @@ export type LibraryActions =
| LibraryDeleteMultipleComicsFailed
| LibraryConvertComics
| LibraryComicsConverting
| LibraryConvertComicsFailed;
| LibraryConvertComicsFailed
| LibraryConsolidate
| LibraryConsolidated
| LibraryConsolidateFailed;
68 changes: 68 additions & 0 deletions comixed-frontend/src/app/library/adaptors/library.adaptor.spec.ts
Expand Up @@ -45,6 +45,9 @@ import { MessageService } from 'primeng/api';
import * as LibraryActions from '../actions/library.actions';
import {
LibraryComicsConverting,
LibraryConsolidate,
LibraryConsolidated,
LibraryConsolidateFailed,
LibraryConvertComics,
LibraryConvertComicsFailed,
LibraryGetUpdates,
Expand Down Expand Up @@ -311,4 +314,69 @@ describe('LibraryAdaptor', () => {
});
});
});

describe('consolidating the library', () => {
beforeEach(() => {
adaptor.consolidate(true);
});

it('fires an action', () => {
expect(store.dispatch).toHaveBeenCalledWith(
new LibraryConsolidate({ deletePhysicalFiles: true })
);
});

it('provides updates on consolidating', () => {
adaptor.consolidating$.subscribe(response =>
expect(response).toBeTruthy()
);
});

describe('success', () => {
const DELETED_COMICS = [COMICS[2]];

beforeEach(() => {
// preload the library
store.dispatch(
new LibraryUpdatesReceived({
lastComicId: LAST_COMIC_ID,
mostRecentUpdate: MOST_RECENT_UPDATE,
moreUpdates: MORE_UPDATES,
processingCount: PROCESSING_COUNT,
comics: COMICS,
lastReadDates: []
})
);
store.dispatch(
new LibraryConsolidated({ deletedComics: DELETED_COMICS })
);
});

it('provides updates on consolidating', () => {
adaptor.consolidating$.subscribe(response =>
expect(response).toBeFalsy()
);
});

it('provides updates on comics', () => {
DELETED_COMICS.forEach(comic => {
adaptor.comic$.subscribe(comics =>
expect(comics).not.toContain(comic)
);
});
});
});

describe('failure', () => {
beforeEach(() => {
store.dispatch(new LibraryConsolidateFailed());
});

it('provides updates on consolidating', () => {
adaptor.consolidating$.subscribe(response =>
expect(response).toBeFalsy()
);
});
});
});
});
18 changes: 18 additions & 0 deletions comixed-frontend/src/app/library/adaptors/library.adaptor.ts
Expand Up @@ -30,6 +30,7 @@ import { filter } from 'rxjs/operators';
import { extractField } from 'app/library/utility.functions';
import { LastReadDate } from 'app/library/models/last-read-date';
import {
LibraryConsolidate,
LibraryConvertComics,
LibraryDeleteMultipleComics,
LibraryGetUpdates,
Expand Down Expand Up @@ -60,6 +61,7 @@ export class LibraryAdaptor {
private _timeout = 60;
private _maximum = 100;
private _converting$ = new BehaviorSubject<boolean>(false);
private _consolidating$ = new BehaviorSubject<boolean>(false);

constructor(
private store: Store<AppState>,
Expand Down Expand Up @@ -118,6 +120,9 @@ export class LibraryAdaptor {
if (state.convertingComics !== this._converting$.getValue()) {
this._converting$.next(state.convertingComics);
}
if (state.consolidating !== this._consolidating$.getValue()) {
this._consolidating$.next(state.consolidating);
}
});
}

Expand Down Expand Up @@ -241,4 +246,17 @@ export class LibraryAdaptor {
get converting$(): Observable<boolean> {
return this._converting$.asObservable();
}

consolidate(deletePhysicalFiles: boolean): void {
this.logger.debug(
`firing action to consolidate library: deletePhysicalFiles=${deletePhysicalFiles}`
);
this.store.dispatch(
new LibraryConsolidate({ deletePhysicalFiles: deletePhysicalFiles })
);
}

get consolidating$(): Observable<boolean> {
return this._consolidating$.asObservable();
}
}
52 changes: 51 additions & 1 deletion comixed-frontend/src/app/library/effects/library.effects.spec.ts
Expand Up @@ -23,6 +23,9 @@ import { TranslateModule } from '@ngx-translate/core';
import { COMIC_1, COMIC_3, COMIC_5 } from 'app/comics/models/comic.fixtures';
import {
LibraryComicsConverting,
LibraryConsolidate,
LibraryConsolidated,
LibraryConsolidateFailed,
LibraryConvertComics,
LibraryConvertComicsFailed,
LibraryDeleteMultipleComics,
Expand Down Expand Up @@ -77,7 +80,8 @@ describe('LibraryEffects', () => {
deleteMultipleComics: jasmine.createSpy(
'LibraryService.deleteMultipleComics()'
),
convertComics: jasmine.createSpy('LibraryService.convertComics()')
convertComics: jasmine.createSpy('LibraryService.convertComics()'),
consolidate: jasmine.createSpy('LibraryService.consolidate()')
}
},
MessageService
Expand Down Expand Up @@ -330,4 +334,50 @@ describe('LibraryEffects', () => {
);
});
});

describe('consolidating the library', () => {
it('fires an action on success', () => {
const serviceResponse = COMICS;
const action = new LibraryConsolidate({ deletePhysicalFiles: true });
const outcome = new LibraryConsolidated({ deletedComics: COMICS });

actions$ = hot('-a', { a: action });
libraryService.consolidate.and.returnValue(of(serviceResponse));

const expected = hot('-b', { b: outcome });
expect(effects.consolidate$).toBeObservable(expected);
expect(messageService.add).toHaveBeenCalledWith(
objectContaining({ severity: 'info' })
);
});

it('fires an action on service failure', () => {
const serviceResponse = new HttpErrorResponse({});
const action = new LibraryConsolidate({ deletePhysicalFiles: true });
const outcome = new LibraryConsolidateFailed();

actions$ = hot('-a', { a: action });
libraryService.consolidate.and.returnValue(throwError(serviceResponse));

const expected = hot('-b', { b: outcome });
expect(effects.consolidate$).toBeObservable(expected);
expect(messageService.add).toHaveBeenCalledWith(
objectContaining({ severity: 'error' })
);
});

it('fires an action on general failure', () => {
const action = new LibraryConsolidate({ deletePhysicalFiles: true });
const outcome = new LibraryConsolidateFailed();

actions$ = hot('-a', { a: action });
libraryService.consolidate.and.throwError('expected');

const expected = hot('-(b|)', { b: outcome });
expect(effects.consolidate$).toBeObservable(expected);
expect(messageService.add).toHaveBeenCalledWith(
objectContaining({ severity: 'error' })
);
});
});
});
50 changes: 49 additions & 1 deletion comixed-frontend/src/app/library/effects/library.effects.ts
Expand Up @@ -31,6 +31,9 @@ import { catchError, map, switchMap, tap } from 'rxjs/operators';
import {
LibraryActionTypes,
LibraryComicsConverting,
LibraryConsolidate,
LibraryConsolidated,
LibraryConsolidateFailed,
LibraryConvertComics,
LibraryConvertComicsFailed,
LibraryDeleteMultipleComics,
Expand All @@ -42,6 +45,7 @@ import {
LibraryStartRescanFailed,
LibraryUpdatesReceived
} from '../actions/library.actions';
import { Comic } from 'app/comics';

@Injectable()
export class LibraryEffects {
Expand Down Expand Up @@ -240,10 +244,54 @@ export class LibraryEffects {
this.messageService.add({
severity: 'error',
detail: this.translateService.instant(
'library-effects.convert-comics.error.details'
'general-message.error.general-service-failure'
)
});
return of(new LibraryConvertComicsFailed());
})
);

@Effect()
consolidate$: Observable<Action> = this.actions$.pipe(
ofType(LibraryActionTypes.Consolidate),
tap(action => this.logger.debug('effect: consolidate library:', action)),
map((action: LibraryConsolidate) => action.payload),
switchMap(action =>
this.libraryService.consolidate(action.deletePhysicalFiles).pipe(
tap(response => this.logger.debug('received response:', response)),
tap(() =>
this.messageService.add({
severity: 'info',
detail: this.translateService.instant(
'library-effects.consolidate.success.detail'
)
})
),
map(
(response: Comic[]) =>
new LibraryConsolidated({ deletedComics: response })
),
catchError(error => {
this.logger.error('service failure conslidating library:', error);
this.messageService.add({
severity: 'error',
detail: this.translateService.instant(
'library-effects.consolidate.error.detail'
)
});
return of(new LibraryConsolidateFailed());
})
)
),
catchError(error => {
this.logger.error('general failure conslidating library:', error);
this.messageService.add({
severity: 'error',
detail: this.translateService.instant(
'general-message.error.general-service-failure'
)
});
return of(new LibraryConsolidateFailed());
})
);
}
1 change: 1 addition & 0 deletions comixed-frontend/src/app/library/library.constants.ts
Expand Up @@ -27,6 +27,7 @@ export const SET_DELETED_STATE_URL = `${API_ROOT_URL}/pages/hashes/deleted`;
export const START_RESCAN_URL = `${COMIXED_API_ROOT}/comics/rescan`;
export const DELETE_MULTIPLE_COMICS_URL = `${COMIXED_API_ROOT}/comics/multiple/delete`;
export const CONVERT_COMICS_URL = `${COMIXED_API_ROOT}/library/convert`;
export const CONSOLIDATE_LIBRARY_URL = `${COMIXED_API_ROOT}/library/consolidate`;

export const GET_COLLECTION_ENTRIES_URL = `${API_ROOT_URL}/collections/\${type}`;
export const GET_PAGE_FOR_ENTRY_URL = `${API_ROOT_URL}/collections/\${type}/\${name}`;
Expand Down
@@ -0,0 +1,21 @@
/*
* ComiXed - A digital comic book library management application.
* Copyright (C) 2019, The ComiXed Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

export interface ConsolidateLibraryRequest {
deletePhysicalFiles: boolean;
}

0 comments on commit c031241

Please sign in to comment.