diff --git a/src/app/core/data/request.actions.ts b/src/app/core/data/request.actions.ts index 28149c2ead..2b2de13504 100644 --- a/src/app/core/data/request.actions.ts +++ b/src/app/core/data/request.actions.ts @@ -10,7 +10,8 @@ export const RequestActionTypes = { CONFIGURE: type('dspace/core/data/request/CONFIGURE'), EXECUTE: type('dspace/core/data/request/EXECUTE'), COMPLETE: type('dspace/core/data/request/COMPLETE'), - RESET_TIMESTAMPS: type('dspace/core/data/request/RESET_TIMESTAMPS') + RESET_TIMESTAMPS: type('dspace/core/data/request/RESET_TIMESTAMPS'), + REMOVE: type('dspace/core/data/request/REMOVE') }; /* tslint:disable:max-classes-per-file */ @@ -82,6 +83,24 @@ export class ResetResponseTimestampsAction implements Action { } } +/** + * An ngrx action to remove a cached request + */ +export class RequestRemoveAction implements Action { + type = RequestActionTypes.REMOVE; + uuid: string; + + /** + * Create a new RequestRemoveAction + * + * @param uuid + * the request's uuid + */ + constructor(uuid: string) { + this.uuid = uuid + } +} + /* tslint:enable:max-classes-per-file */ /** @@ -91,4 +110,5 @@ export type RequestAction = RequestConfigureAction | RequestExecuteAction | RequestCompleteAction - | ResetResponseTimestampsAction; + | ResetResponseTimestampsAction + | RequestRemoveAction; diff --git a/src/app/core/data/request.reducer.ts b/src/app/core/data/request.reducer.ts index a680de2d6b..322ac46727 100644 --- a/src/app/core/data/request.reducer.ts +++ b/src/app/core/data/request.reducer.ts @@ -1,6 +1,6 @@ import { RequestActionTypes, RequestAction, RequestConfigureAction, - RequestExecuteAction, RequestCompleteAction, ResetResponseTimestampsAction + RequestExecuteAction, RequestCompleteAction, ResetResponseTimestampsAction, RequestRemoveAction } from './request.actions'; import { RestRequest } from './request.models'; import { RestResponse } from '../cache/response.models'; @@ -38,6 +38,10 @@ export function requestReducer(state = initialState, action: RequestAction): Req return resetResponseTimestamps(state, action as ResetResponseTimestampsAction); } + case RequestActionTypes.REMOVE: { + return removeRequest(state, action as RequestRemoveAction); + } + default: { return state; } @@ -95,3 +99,13 @@ function resetResponseTimestamps(state: RequestState, action: ResetResponseTimes }); return newState; } + +function removeRequest(state: RequestState, action: RequestRemoveAction): RequestState { + const newState = Object.create(null); + for (const value in state) { + if (value !== action.uuid) { + newState[value] = state[value]; + } + } + return newState; +} diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 285ed06545..922f035139 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -15,23 +15,23 @@ import { import { race as observableRace } from 'rxjs'; import { Injectable } from '@angular/core'; -import { MemoizedSelector, select, Store } from '@ngrx/store'; -import { hasNoValue, hasValue, isNotUndefined } from '../../shared/empty.util'; +import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; +import { hasNoValue, hasValue, isNotEmpty, isNotUndefined } from '../../shared/empty.util'; import { CacheableObject } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; import { DSOSuccessResponse, RestResponse } from '../cache/response.models'; import { coreSelector, CoreState } from '../core.reducers'; -import { IndexName } from '../index/index.reducer'; +import { IndexName, IndexState } from '../index/index.reducer'; import { pathSelector } from '../shared/selectors'; import { UUIDService } from '../shared/uuid.service'; -import { RequestConfigureAction, RequestExecuteAction } from './request.actions'; +import { RequestConfigureAction, RequestExecuteAction, RequestRemoveAction } from './request.actions'; import { GetRequest, RestRequest } from './request.models'; import { RequestEntry } from './request.reducer'; import { CommitSSBAction } from '../cache/server-sync-buffer.actions'; import { RestRequestMethod } from './rest-request-method'; import { getResponseFromEntry } from '../shared/operators'; -import { AddToIndexAction } from '../index/index.actions'; +import { AddToIndexAction, RemoveFromIndexBySubstringAction } from '../index/index.actions'; @Injectable() export class RequestService { @@ -39,7 +39,8 @@ export class RequestService { constructor(private objectCache: ObjectCacheService, private uuidService: UUIDService, - private store: Store) { + private store: Store, + private indexStore: Store) { } private entryFromUUIDSelector(uuid: string): MemoizedSelector { @@ -54,6 +55,25 @@ export class RequestService { return pathSelector(coreSelector, 'index', IndexName.UUID_MAPPING, uuid); } + private uuidsFromHrefSubstringSelector(selector: MemoizedSelector, name: string, href: string): MemoizedSelector { + return createSelector(selector, (state: IndexState) => this.getUuidsFromHrefSubstring(state, name, href)); + } + + private getUuidsFromHrefSubstring(state: IndexState, name: string, href: string): string[] { + let result = []; + if (isNotEmpty(state)) { + const subState = state[name]; + if (isNotEmpty(subState)) { + for (const value in subState) { + if (value.indexOf(href) > -1) { + result = [...result, subState[value]]; + } + } + } + } + return result; + } + generateRequestId(): string { return `client/${this.uuidService.generate()}`; } @@ -119,6 +139,32 @@ export class RequestService { } } + /** + * Remove all request cache providing (part of) the href + * This also includes href-to-uuid index cache + * @param href A substring of the request(s) href + */ + removeByHrefSubstring(href: string) { + this.store.pipe( + select(this.uuidsFromHrefSubstringSelector(pathSelector(coreSelector, 'index'), IndexName.REQUEST, href)), + take(1) + ).subscribe((uuids: string[]) => { + for (const uuid of uuids) { + this.removeByUuid(uuid); + } + }); + this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((reqHref: string) => reqHref.indexOf(href) < 0); + this.indexStore.dispatch(new RemoveFromIndexBySubstringAction(IndexName.REQUEST, href)); + } + + /** + * Remove request cache using the request's UUID + * @param uuid + */ + removeByUuid(uuid: string) { + this.store.dispatch(new RequestRemoveAction(uuid)); + } + /** * Check if a request is in the cache or if it's still pending * @param {GetRequest} request The request to check diff --git a/src/app/core/index/index.actions.ts b/src/app/core/index/index.actions.ts index 014b6561a3..98d07d59d5 100644 --- a/src/app/core/index/index.actions.ts +++ b/src/app/core/index/index.actions.ts @@ -8,7 +8,8 @@ import { IndexName } from './index.reducer'; */ export const IndexActionTypes = { ADD: type('dspace/core/index/ADD'), - REMOVE_BY_VALUE: type('dspace/core/index/REMOVE_BY_VALUE') + REMOVE_BY_VALUE: type('dspace/core/index/REMOVE_BY_VALUE'), + REMOVE_BY_SUBSTRING: type('dspace/core/index/REMOVE_BY_SUBSTRING') }; /* tslint:disable:max-classes-per-file */ @@ -60,6 +61,30 @@ export class RemoveFromIndexByValueAction implements Action { this.payload = { name, value }; } +} + +/** + * An ngrx action to remove multiple values from the index by substring + */ +export class RemoveFromIndexBySubstringAction implements Action { + type = IndexActionTypes.REMOVE_BY_SUBSTRING; + payload: { + name: IndexName, + value: string + }; + + /** + * Create a new RemoveFromIndexByValueAction + * + * @param name + * the name of the index to remove from + * @param value + * the value to remove the UUID for + */ + constructor(name: IndexName, value: string) { + this.payload = { name, value }; + } + } /* tslint:enable:max-classes-per-file */ diff --git a/src/app/core/index/index.reducer.ts b/src/app/core/index/index.reducer.ts index c179182509..64b41e2111 100644 --- a/src/app/core/index/index.reducer.ts +++ b/src/app/core/index/index.reducer.ts @@ -2,7 +2,7 @@ import { IndexAction, IndexActionTypes, AddToIndexAction, - RemoveFromIndexByValueAction + RemoveFromIndexByValueAction, RemoveFromIndexBySubstringAction } from './index.actions'; export enum IndexName { @@ -31,6 +31,10 @@ export function indexReducer(state = initialState, action: IndexAction): IndexSt return removeFromIndexByValue(state, action as RemoveFromIndexByValueAction) } + case IndexActionTypes.REMOVE_BY_SUBSTRING: { + return removeFromIndexBySubstring(state, action as RemoveFromIndexBySubstringAction) + } + default: { return state; } @@ -60,3 +64,16 @@ function removeFromIndexByValue(state: IndexState, action: RemoveFromIndexByValu [action.payload.name]: newSubState }); } + +function removeFromIndexBySubstring(state: IndexState, action: RemoveFromIndexByValueAction): IndexState { + const subState = state[action.payload.name]; + const newSubState = Object.create(null); + for (const value in subState) { + if (value.indexOf(action.payload.value) < 0) { + newSubState[value] = subState[value]; + } + } + return Object.assign({}, state, { + [action.payload.name]: newSubState + }); +}