Skip to content
Snippets Groups Projects
Commit a535b86e authored by Art Lowel's avatar Art Lowel
Browse files

fix timestamps on rehydrate

parent 0e516bea
No related merge requests found
...@@ -4,7 +4,8 @@ import { CacheableObject } from "./object-cache.reducer"; ...@@ -4,7 +4,8 @@ import { CacheableObject } from "./object-cache.reducer";
export const ObjectCacheActionTypes = { export const ObjectCacheActionTypes = {
ADD: type('dspace/core/cache/object/ADD'), ADD: type('dspace/core/cache/object/ADD'),
REMOVE: type('dspace/core/cache/object/REMOVE') REMOVE: type('dspace/core/cache/object/REMOVE'),
RESET_TIMESTAMPS: type('dspace/core/cache/object/RESET_TIMESTAMPS')
}; };
export class AddToObjectCacheAction implements Action { export class AddToObjectCacheAction implements Action {
...@@ -29,6 +30,16 @@ export class RemoveFromObjectCacheAction implements Action { ...@@ -29,6 +30,16 @@ export class RemoveFromObjectCacheAction implements Action {
} }
} }
export class ResetObjectCacheTimestampsAction implements Action {
type = ObjectCacheActionTypes.RESET_TIMESTAMPS;
payload: number;
constructor(newTimestamp: number) {
this.payload = newTimestamp;
}
}
export type ObjectCacheAction export type ObjectCacheAction
= AddToObjectCacheAction = AddToObjectCacheAction
| RemoveFromObjectCacheAction | RemoveFromObjectCacheAction
| ResetObjectCacheTimestampsAction;
...@@ -2,7 +2,7 @@ import * as deepFreeze from "deep-freeze"; ...@@ -2,7 +2,7 @@ import * as deepFreeze from "deep-freeze";
import { objectCacheReducer } from "./object-cache.reducer"; import { objectCacheReducer } from "./object-cache.reducer";
import { import {
AddToObjectCacheAction, AddToObjectCacheAction,
RemoveFromObjectCacheAction RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
} from "./object-cache.actions"; } from "./object-cache.actions";
class NullAction extends RemoveFromObjectCacheAction { class NullAction extends RemoveFromObjectCacheAction {
...@@ -15,15 +15,24 @@ class NullAction extends RemoveFromObjectCacheAction { ...@@ -15,15 +15,24 @@ class NullAction extends RemoveFromObjectCacheAction {
} }
describe("objectCacheReducer", () => { describe("objectCacheReducer", () => {
const uuid = '1698f1d3-be98-4c51-9fd8-6bfedcbd59b7'; const uuid1 = '1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
const uuid2 = '28b04544-1766-4e82-9728-c4e93544ecd3';
const testState = { const testState = {
[uuid]: { [uuid1]: {
data: { data: {
uuid: uuid, uuid: uuid1,
foo: "bar" foo: "bar"
}, },
timeAdded: new Date().getTime(), timeAdded: new Date().getTime(),
msToLive: 900000 msToLive: 900000
},
[uuid2]: {
data: {
uuid: uuid2,
foo: "baz"
},
timeAdded: new Date().getTime(),
msToLive: 900000
} }
}; };
deepFreeze(testState); deepFreeze(testState);
...@@ -44,31 +53,31 @@ describe("objectCacheReducer", () => { ...@@ -44,31 +53,31 @@ describe("objectCacheReducer", () => {
it("should add the payload to the cache in response to an ADD action", () => { it("should add the payload to the cache in response to an ADD action", () => {
const state = Object.create(null); const state = Object.create(null);
const objectToCache = {uuid: uuid}; const objectToCache = {uuid: uuid1};
const timeAdded = new Date().getTime(); const timeAdded = new Date().getTime();
const msToLive = 900000; const msToLive = 900000;
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive); const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
const newState = objectCacheReducer(state, action); const newState = objectCacheReducer(state, action);
expect(newState[uuid].data).toEqual(objectToCache); expect(newState[uuid1].data).toEqual(objectToCache);
expect(newState[uuid].timeAdded).toEqual(timeAdded); expect(newState[uuid1].timeAdded).toEqual(timeAdded);
expect(newState[uuid].msToLive).toEqual(msToLive); expect(newState[uuid1].msToLive).toEqual(msToLive);
}); });
it("should overwrite an object in the cache in response to an ADD action if it already exists", () => { it("should overwrite an object in the cache in response to an ADD action if it already exists", () => {
const objectToCache = {uuid: uuid, foo: "baz", somethingElse: true}; const objectToCache = {uuid: uuid1, foo: "baz", somethingElse: true};
const timeAdded = new Date().getTime(); const timeAdded = new Date().getTime();
const msToLive = 900000; const msToLive = 900000;
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive); const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
const newState = objectCacheReducer(testState, action); const newState = objectCacheReducer(testState, action);
expect(newState[uuid].data['foo']).toBe("baz"); expect(newState[uuid1].data['foo']).toBe("baz");
expect(newState[uuid].data['somethingElse']).toBe(true); expect(newState[uuid1].data['somethingElse']).toBe(true);
}); });
it("should perform the ADD action without affecting the previous state", () => { it("should perform the ADD action without affecting the previous state", () => {
const state = Object.create(null); const state = Object.create(null);
const objectToCache = {uuid: uuid}; const objectToCache = {uuid: uuid1};
const timeAdded = new Date().getTime(); const timeAdded = new Date().getTime();
const msToLive = 900000; const msToLive = 900000;
const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive); const action = new AddToObjectCacheAction(objectToCache, timeAdded, msToLive);
...@@ -78,11 +87,11 @@ describe("objectCacheReducer", () => { ...@@ -78,11 +87,11 @@ describe("objectCacheReducer", () => {
}); });
it("should remove the specified object from the cache in response to the REMOVE action", () => { it("should remove the specified object from the cache in response to the REMOVE action", () => {
const action = new RemoveFromObjectCacheAction(uuid); const action = new RemoveFromObjectCacheAction(uuid1);
const newState = objectCacheReducer(testState, action); const newState = objectCacheReducer(testState, action);
expect(testState[uuid]).not.toBeUndefined(); expect(testState[uuid1]).not.toBeUndefined();
expect(newState[uuid]).toBeUndefined(); expect(newState[uuid1]).toBeUndefined();
}); });
it("shouldn't do anything in response to the REMOVE action for an object that isn't cached", () => { it("shouldn't do anything in response to the REMOVE action for an object that isn't cached", () => {
...@@ -93,7 +102,22 @@ describe("objectCacheReducer", () => { ...@@ -93,7 +102,22 @@ describe("objectCacheReducer", () => {
}); });
it("should perform the REMOVE action without affecting the previous state", () => { it("should perform the REMOVE action without affecting the previous state", () => {
const action = new RemoveFromObjectCacheAction(uuid); const action = new RemoveFromObjectCacheAction(uuid1);
//testState has already been frozen above
objectCacheReducer(testState, action);
});
it("should set the timestamp of all objects in the cache in response to a RESET_TIMESTAMPS action", () => {
const newTimestamp = new Date().getTime();
const action = new ResetObjectCacheTimestampsAction(newTimestamp);
const newState = objectCacheReducer(testState, action);
Object.keys(newState).forEach((key) => {
expect(newState[key].timeAdded).toEqual(newTimestamp);
});
});
it("should perform the RESET_TIMESTAMPS action without affecting the previous state", () => {
const action = new ResetObjectCacheTimestampsAction(new Date().getTime());
//testState has already been frozen above //testState has already been frozen above
objectCacheReducer(testState, action); objectCacheReducer(testState, action);
}); });
......
import { ObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction, RemoveFromObjectCacheAction } from "./object-cache.actions"; import {
ObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction,
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction
} from "./object-cache.actions";
import { hasValue } from "../../shared/empty.util"; import { hasValue } from "../../shared/empty.util";
import { CacheEntry } from "./cache-entry"; import { CacheEntry } from "./cache-entry";
...@@ -54,6 +57,10 @@ export const objectCacheReducer = (state = initialState, action: ObjectCacheActi ...@@ -54,6 +57,10 @@ export const objectCacheReducer = (state = initialState, action: ObjectCacheActi
return removeFromObjectCache(state, <RemoveFromObjectCacheAction>action) return removeFromObjectCache(state, <RemoveFromObjectCacheAction>action)
} }
case ObjectCacheActionTypes.RESET_TIMESTAMPS: {
return resetObjectCacheTimestamps(state, <ResetObjectCacheTimestampsAction>action)
}
default: { default: {
return state; return state;
} }
...@@ -101,3 +108,23 @@ function removeFromObjectCache(state: ObjectCacheState, action: RemoveFromObject ...@@ -101,3 +108,23 @@ function removeFromObjectCache(state: ObjectCacheState, action: RemoveFromObject
return state; return state;
} }
} }
/**
* Set the timeAdded timestamp of every cached object to the specified value
*
* @param state
* the current state
* @param action
* a ResetObjectCacheTimestampsAction
* @return ObjectCacheState
* the new state, with all timeAdded timestamps set to the specified value
*/
function resetObjectCacheTimestamps(state: ObjectCacheState, action: ResetObjectCacheTimestampsAction): ObjectCacheState {
let newState = Object.create(null);
Object.keys(state).forEach(key => {
newState[key] = Object.assign({}, state[key], {
timeAdded: action.payload
});
});
return newState;
}
...@@ -9,7 +9,8 @@ export const RequestCacheActionTypes = { ...@@ -9,7 +9,8 @@ export const RequestCacheActionTypes = {
FIND_ALL: type('dspace/core/cache/request/FIND_ALL'), FIND_ALL: type('dspace/core/cache/request/FIND_ALL'),
SUCCESS: type('dspace/core/cache/request/SUCCESS'), SUCCESS: type('dspace/core/cache/request/SUCCESS'),
ERROR: type('dspace/core/cache/request/ERROR'), ERROR: type('dspace/core/cache/request/ERROR'),
REMOVE: type('dspace/core/cache/request/REMOVE') REMOVE: type('dspace/core/cache/request/REMOVE'),
RESET_TIMESTAMPS: type('dspace/core/cache/request/RESET_TIMESTAMPS')
}; };
export class RequestCacheFindAllAction implements Action { export class RequestCacheFindAllAction implements Action {
...@@ -103,9 +104,19 @@ export class RequestCacheRemoveAction implements Action { ...@@ -103,9 +104,19 @@ export class RequestCacheRemoveAction implements Action {
} }
} }
export class ResetRequestCacheTimestampsAction implements Action {
type = RequestCacheActionTypes.RESET_TIMESTAMPS;
payload: number;
constructor(newTimestamp: number) {
this.payload = newTimestamp;
}
}
export type RequestCacheAction export type RequestCacheAction
= RequestCacheFindAllAction = RequestCacheFindAllAction
| RequestCacheFindByIDAction | RequestCacheFindByIDAction
| RequestCacheSuccessAction | RequestCacheSuccessAction
| RequestCacheErrorAction | RequestCacheErrorAction
| RequestCacheRemoveAction; | RequestCacheRemoveAction
| ResetRequestCacheTimestampsAction;
...@@ -3,7 +3,7 @@ import { SortOptions } from "../shared/sort-options.model"; ...@@ -3,7 +3,7 @@ import { SortOptions } from "../shared/sort-options.model";
import { import {
RequestCacheAction, RequestCacheActionTypes, RequestCacheFindAllAction, RequestCacheAction, RequestCacheActionTypes, RequestCacheFindAllAction,
RequestCacheSuccessAction, RequestCacheErrorAction, RequestCacheFindByIDAction, RequestCacheSuccessAction, RequestCacheErrorAction, RequestCacheFindByIDAction,
RequestCacheRemoveAction RequestCacheRemoveAction, ResetRequestCacheTimestampsAction
} from "./request-cache.actions"; } from "./request-cache.actions";
import { OpaqueToken } from "@angular/core"; import { OpaqueToken } from "@angular/core";
import { CacheEntry } from "./cache-entry"; import { CacheEntry } from "./cache-entry";
...@@ -54,6 +54,10 @@ export const requestCacheReducer = (state = initialState, action: RequestCacheAc ...@@ -54,6 +54,10 @@ export const requestCacheReducer = (state = initialState, action: RequestCacheAc
return removeFromCache(state, <RequestCacheRemoveAction> action); return removeFromCache(state, <RequestCacheRemoveAction> action);
} }
case RequestCacheActionTypes.RESET_TIMESTAMPS: {
return resetRequestCacheTimestamps(state, <ResetRequestCacheTimestampsAction>action)
}
default: { default: {
return state; return state;
} }
...@@ -121,5 +125,12 @@ function removeFromCache(state: RequestCacheState, action: RequestCacheRemoveAct ...@@ -121,5 +125,12 @@ function removeFromCache(state: RequestCacheState, action: RequestCacheRemoveAct
} }
} }
function resetRequestCacheTimestamps(state: RequestCacheState, action: ResetRequestCacheTimestampsAction): RequestCacheState {
let newState = Object.create(null);
Object.keys(state).forEach(key => {
newState[key] = Object.assign({}, state[key], {
timeAdded: action.payload
});
});
return newState;
}
import { EffectsModule } from "@ngrx/effects"; import { EffectsModule } from "@ngrx/effects";
import { CollectionDataEffects } from "./data-services/collection-data.effects"; import { CollectionDataEffects } from "./data-services/collection-data.effects";
import { ItemDataEffects } from "./data-services/item-data.effects"; import { ItemDataEffects } from "./data-services/item-data.effects";
import { ObjectCacheEffects } from "./data-services/object-cache.effects";
import { RequestCacheEffects } from "./data-services/request-cache.effects";
export const coreEffects = [ export const coreEffects = [
EffectsModule.run(CollectionDataEffects), EffectsModule.run(CollectionDataEffects),
EffectsModule.run(ItemDataEffects) EffectsModule.run(ItemDataEffects),
EffectsModule.run(RequestCacheEffects),
EffectsModule.run(ObjectCacheEffects),
]; ];
import { Injectable } from "@angular/core";
import { Actions, Effect } from "@ngrx/effects";
import { StoreActionTypes } from "../../store.actions";
import { ResetObjectCacheTimestampsAction } from "../cache/object-cache.actions";
import { Store } from "@ngrx/store";
import { ObjectCacheState } from "../cache/object-cache.reducer";
@Injectable()
export class ObjectCacheEffects {
constructor(
private actions$: Actions,
private store: Store<ObjectCacheState>
) { }
/**
* When the store is rehydrated in the browser, set all cache
* timestamps to "now", because the time zone of the server can
* differ from the client.
*
* This assumes that the server cached everything a negligible
* time ago, and will likely need to be revisited later
*/
@Effect() fixTimestampsOnRehydrate = this.actions$
.ofType(StoreActionTypes.REHYDRATE)
.map(() => new ResetObjectCacheTimestampsAction(new Date().getTime()));
}
import { Injectable } from "@angular/core";
import { Actions, Effect } from "@ngrx/effects";
import { ResetRequestCacheTimestampsAction } from "../cache/request-cache.actions";
import { Store } from "@ngrx/store";
import { RequestCacheState } from "../cache/request-cache.reducer";
import { ObjectCacheActionTypes } from "../cache/object-cache.actions";
@Injectable()
export class RequestCacheEffects {
constructor(
private actions$: Actions,
private store: Store<RequestCacheState>
) { }
/**
* When the store is rehydrated in the browser, set all cache
* timestamps to "now", because the time zone of the server can
* differ from the client.
*
* This assumes that the server cached everything a negligible
* time ago, and will likely need to be revisited later
*
* This effect should listen for StoreActionTypes.REHYDRATE,
* but can't because you can only have one effect listen to
* an action atm. Github issue:
* https://github.com/ngrx/effects/issues/87
*
* It's listening for ObjectCacheActionTypes.RESET_TIMESTAMPS
* instead, until there's a solution.
*/
@Effect() fixTimestampsOnRehydrate = this.actions$
.ofType(ObjectCacheActionTypes.RESET_TIMESTAMPS)
.map(() => new ResetRequestCacheTimestampsAction(new Date().getTime()));
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment