mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
implementation of server synchronization
This commit is contained in:
@@ -20,7 +20,12 @@ module.exports = {
|
|||||||
// NOTE: how long should objects be cached for by default
|
// NOTE: how long should objects be cached for by default
|
||||||
msToLive: 15 * 60 * 1000, // 15 minutes
|
msToLive: 15 * 60 * 1000, // 15 minutes
|
||||||
// msToLive: 1000, // 15 minutes
|
// msToLive: 1000, // 15 minutes
|
||||||
control: 'max-age=60' // revalidate browser
|
control: 'max-age=60', // revalidate browser
|
||||||
|
autoSync: {
|
||||||
|
defaultTime: 0,
|
||||||
|
maxBufferSize: 100,
|
||||||
|
timePerMethod: {'Patch': 30} //time in seconds
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// Form settings
|
// Form settings
|
||||||
form: {
|
form: {
|
||||||
|
50
src/app/core/cache/object-cache.actions.ts
vendored
50
src/app/core/cache/object-cache.actions.ts
vendored
@@ -11,7 +11,8 @@ 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'),
|
RESET_TIMESTAMPS: type('dspace/core/cache/object/RESET_TIMESTAMPS'),
|
||||||
PATCH: type('dspace/core/cache/object/PATCH')
|
ADD_PATCH: type('dspace/core/cache/object/ADD_PATCH'),
|
||||||
|
APPLY_PATCH: type('dspace/core/cache/object/APPLY_PATCH')
|
||||||
};
|
};
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
@@ -56,11 +57,11 @@ export class RemoveFromObjectCacheAction implements Action {
|
|||||||
/**
|
/**
|
||||||
* Create a new RemoveFromObjectCacheAction
|
* Create a new RemoveFromObjectCacheAction
|
||||||
*
|
*
|
||||||
* @param uuid
|
* @param href
|
||||||
* the UUID of the object to remove
|
* the unique href of the object to remove
|
||||||
*/
|
*/
|
||||||
constructor(uuid: string) {
|
constructor(href: string) {
|
||||||
this.payload = uuid;
|
this.payload = href;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,25 +84,43 @@ export class ResetObjectCacheTimestampsAction implements Action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An ngrx action to add new operations to a specified cached objects
|
* An ngrx action to add new operations to a specified cached object
|
||||||
*/
|
*/
|
||||||
export class PatchObjectCacheAction implements Action {
|
export class AddPatchObjectCacheAction implements Action {
|
||||||
type = ObjectCacheActionTypes.PATCH;
|
type = ObjectCacheActionTypes.ADD_PATCH;
|
||||||
payload: {
|
payload: {
|
||||||
uuid: string,
|
href: string,
|
||||||
operations: Operation[]
|
operations: Operation[]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new PatchObjectCacheAction
|
* Create a new AddPatchObjectCacheAction
|
||||||
*
|
*
|
||||||
* @param uuid
|
* @param href
|
||||||
* the uuid of the object that should be updated
|
* the unique href of the object that should be updated
|
||||||
* @param operations
|
* @param operations
|
||||||
* the list of operations to add
|
* the list of operations to add
|
||||||
*/
|
*/
|
||||||
constructor(uuid: string, operations: Operation[]) {
|
constructor(href: string, operations: Operation[]) {
|
||||||
this.payload = { uuid, operations };
|
this.payload = { href, operations };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ngrx action to apply all existing operations to a specified cached object
|
||||||
|
*/
|
||||||
|
export class ApplyPatchObjectCacheAction implements Action {
|
||||||
|
type = ObjectCacheActionTypes.APPLY_PATCH;
|
||||||
|
payload: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ApplyPatchObjectCacheAction
|
||||||
|
*
|
||||||
|
* @param href
|
||||||
|
* the unique href of the object that should be updated
|
||||||
|
*/
|
||||||
|
constructor(href: string) {
|
||||||
|
this.payload = href;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,4 +133,5 @@ export type ObjectCacheAction
|
|||||||
= AddToObjectCacheAction
|
= AddToObjectCacheAction
|
||||||
| RemoveFromObjectCacheAction
|
| RemoveFromObjectCacheAction
|
||||||
| ResetObjectCacheTimestampsAction
|
| ResetObjectCacheTimestampsAction
|
||||||
| PatchObjectCacheAction;
|
| AddPatchObjectCacheAction
|
||||||
|
| ApplyPatchObjectCacheAction;
|
||||||
|
63
src/app/core/cache/object-cache.reducer.ts
vendored
63
src/app/core/cache/object-cache.reducer.ts
vendored
@@ -1,11 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
ObjectCacheAction, ObjectCacheActionTypes, AddToObjectCacheAction,
|
ObjectCacheAction,
|
||||||
RemoveFromObjectCacheAction, ResetObjectCacheTimestampsAction, PatchObjectCacheAction
|
ObjectCacheActionTypes,
|
||||||
|
AddToObjectCacheAction,
|
||||||
|
RemoveFromObjectCacheAction,
|
||||||
|
ResetObjectCacheTimestampsAction,
|
||||||
|
AddPatchObjectCacheAction, ApplyPatchObjectCacheAction
|
||||||
} from './object-cache.actions';
|
} from './object-cache.actions';
|
||||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { CacheEntry } from './cache-entry';
|
import { CacheEntry } from './cache-entry';
|
||||||
import { ResourceType } from '../shared/resource-type';
|
import { ResourceType } from '../shared/resource-type';
|
||||||
import { Operation } from 'fast-json-patch';
|
import { applyPatch, Operation } from 'fast-json-patch';
|
||||||
|
|
||||||
export enum DirtyType {
|
export enum DirtyType {
|
||||||
Created = 'Created',
|
Created = 'Created',
|
||||||
@@ -13,7 +17,11 @@ export enum DirtyType {
|
|||||||
Deleted = 'Deleted'
|
Deleted = 'Deleted'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface Patch {
|
||||||
|
uuid?: string;
|
||||||
|
operations: Operation[];
|
||||||
|
}
|
||||||
|
/**conca
|
||||||
* An interface to represent objects that can be cached
|
* An interface to represent objects that can be cached
|
||||||
*
|
*
|
||||||
* A cacheable object should have a self link
|
* A cacheable object should have a self link
|
||||||
@@ -37,7 +45,8 @@ export class ObjectCacheEntry implements CacheEntry {
|
|||||||
timeAdded: number;
|
timeAdded: number;
|
||||||
msToLive: number;
|
msToLive: number;
|
||||||
requestHref: string;
|
requestHref: string;
|
||||||
operations: Operation[];
|
patches: Patch[] = [];
|
||||||
|
isDirty: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,9 +87,14 @@ export function objectCacheReducer(state = initialState, action: ObjectCacheActi
|
|||||||
return resetObjectCacheTimestamps(state, action as ResetObjectCacheTimestampsAction)
|
return resetObjectCacheTimestamps(state, action as ResetObjectCacheTimestampsAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
case ObjectCacheActionTypes.PATCH: {
|
case ObjectCacheActionTypes.ADD_PATCH: {
|
||||||
return patchObjectCache(state, action as PatchObjectCacheAction);
|
return addPatchObjectCache(state, action as AddPatchObjectCacheAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case ObjectCacheActionTypes.APPLY_PATCH: {
|
||||||
|
return applyPatchObjectCache(state, action as ApplyPatchObjectCacheAction);
|
||||||
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -104,7 +118,7 @@ function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheActio
|
|||||||
timeAdded: action.payload.timeAdded,
|
timeAdded: action.payload.timeAdded,
|
||||||
msToLive: action.payload.msToLive,
|
msToLive: action.payload.msToLive,
|
||||||
requestHref: action.payload.requestHref,
|
requestHref: action.payload.requestHref,
|
||||||
operations: []
|
isDirty: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -156,16 +170,41 @@ function resetObjectCacheTimestamps(state: ObjectCacheState, action: ResetObject
|
|||||||
* @param state
|
* @param state
|
||||||
* the current state
|
* the current state
|
||||||
* @param action
|
* @param action
|
||||||
* a PatchObjectCacheAction
|
* an AddPatchObjectCacheAction
|
||||||
* @return ObjectCacheState
|
* @return ObjectCacheState
|
||||||
* the new state, with the new operations added to the state of the specified ObjectCacheEntry
|
* the new state, with the new operations added to the state of the specified ObjectCacheEntry
|
||||||
*/
|
*/
|
||||||
function patchObjectCache(state: ObjectCacheState, action: PatchObjectCacheAction): ObjectCacheState {
|
function addPatchObjectCache(state: ObjectCacheState, action: AddPatchObjectCacheAction): ObjectCacheState {
|
||||||
const uuid = action.payload.uuid;
|
const uuid = action.payload.href;
|
||||||
const operations = action.payload.operations;
|
const operations = action.payload.operations;
|
||||||
const newState = Object.assign({}, state);
|
const newState = Object.assign({}, state);
|
||||||
if (hasValue(newState[uuid])) {
|
if (hasValue(newState[uuid])) {
|
||||||
newState[uuid].operations = state[uuid].operations.concat(operations);
|
const patches = newState[uuid].patches;
|
||||||
|
newState[uuid].patches = [...patches, {operations} as Patch];
|
||||||
|
newState[uuid].isDirty = true;
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the list of patch operations to a cached object
|
||||||
|
*
|
||||||
|
* @param state
|
||||||
|
* the current state
|
||||||
|
* @param action
|
||||||
|
* an ApplyPatchObjectCacheAction
|
||||||
|
* @return ObjectCacheState
|
||||||
|
* the new state, with the new operations applied to the state of the specified ObjectCacheEntry
|
||||||
|
*/
|
||||||
|
function applyPatchObjectCache(state: ObjectCacheState, action: ApplyPatchObjectCacheAction): ObjectCacheState {
|
||||||
|
const uuid = action.payload;
|
||||||
|
const newState = Object.assign({}, state);
|
||||||
|
if (hasValue(newState[uuid])) {
|
||||||
|
// flatten two dimensional array
|
||||||
|
const flatPatch: Operation[] = [].concat(...newState[uuid].patches);
|
||||||
|
const newData = applyPatch( newState[uuid].data, flatPatch);
|
||||||
|
newState[uuid].data = newData.newDocument;
|
||||||
|
newState[uuid].patches = [];
|
||||||
}
|
}
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
47
src/app/core/cache/object-cache.service.ts
vendored
47
src/app/core/cache/object-cache.service.ts
vendored
@@ -7,8 +7,8 @@ import { IndexName } from '../index/index.reducer';
|
|||||||
|
|
||||||
import { CacheableObject, ObjectCacheEntry } from './object-cache.reducer';
|
import { CacheableObject, ObjectCacheEntry } from './object-cache.reducer';
|
||||||
import {
|
import {
|
||||||
AddToObjectCacheAction,
|
AddPatchObjectCacheAction,
|
||||||
PatchObjectCacheAction,
|
AddToObjectCacheAction, ApplyPatchObjectCacheAction,
|
||||||
RemoveFromObjectCacheAction
|
RemoveFromObjectCacheAction
|
||||||
} from './object-cache.actions';
|
} from './object-cache.actions';
|
||||||
import { hasNoValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasNoValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
@@ -18,13 +18,15 @@ import { pathSelector } from '../shared/selectors';
|
|||||||
import { NormalizedObjectFactory } from './models/normalized-object-factory';
|
import { NormalizedObjectFactory } from './models/normalized-object-factory';
|
||||||
import { NormalizedObject } from './models/normalized-object.model';
|
import { NormalizedObject } from './models/normalized-object.model';
|
||||||
import { applyPatch, Operation } from 'fast-json-patch';
|
import { applyPatch, Operation } from 'fast-json-patch';
|
||||||
|
import { RestRequestMethod } from '../data/request.models';
|
||||||
|
import { AddToSSBAction } from './server-sync-buffer.actions';
|
||||||
|
|
||||||
function selfLinkFromUuidSelector(uuid: string): MemoizedSelector<CoreState, string> {
|
function selfLinkFromUuidSelector(uuid: string): MemoizedSelector<CoreState, string> {
|
||||||
return pathSelector<CoreState, string>(coreSelector, 'index', IndexName.OBJECT, uuid);
|
return pathSelector<CoreState, string>(coreSelector, 'index', IndexName.OBJECT, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
function entryFromSelfLinkSelector(selfLink: string): MemoizedSelector<CoreState, ObjectCacheEntry> {
|
function entryFromSelfLinkSelector(selfLink: string): MemoizedSelector<CoreState, ObjectCacheEntry> {
|
||||||
return pathSelector<CoreState, ObjectCacheEntry>(coreSelector, 'data/object', selfLink);
|
return pathSelector<CoreState, ObjectCacheEntry>(coreSelector, 'cache/object', selfLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,10 +54,10 @@ export class ObjectCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the object with the supplied UUID from the cache
|
* Remove the object with the supplied href from the cache
|
||||||
*
|
*
|
||||||
* @param uuid
|
* @param href
|
||||||
* The UUID of the object to be removed
|
* The unique href of the object to be removed
|
||||||
*/
|
*/
|
||||||
remove(uuid: string): void {
|
remove(uuid: string): void {
|
||||||
this.store.dispatch(new RemoveFromObjectCacheAction(uuid));
|
this.store.dispatch(new RemoveFromObjectCacheAction(uuid));
|
||||||
@@ -87,13 +89,17 @@ export class ObjectCacheService {
|
|||||||
|
|
||||||
getBySelfLink<T extends NormalizedObject>(selfLink: string): Observable<T> {
|
getBySelfLink<T extends NormalizedObject>(selfLink: string): Observable<T> {
|
||||||
return this.getEntry(selfLink).pipe(
|
return this.getEntry(selfLink).pipe(
|
||||||
|
map((entry: ObjectCacheEntry) => {
|
||||||
|
// flatten two dimensional array
|
||||||
|
const flatPatch: Operation[] = [].concat(...entry.patches);
|
||||||
|
const patchedData = applyPatch(entry.data, flatPatch).newDocument;
|
||||||
|
return Object.assign({}, entry, { data: patchedData });
|
||||||
|
}
|
||||||
|
),
|
||||||
map((entry: ObjectCacheEntry) => {
|
map((entry: ObjectCacheEntry) => {
|
||||||
const type: GenericConstructor<NormalizedObject> = NormalizedObjectFactory.getConstructor(entry.data.type);
|
const type: GenericConstructor<NormalizedObject> = NormalizedObjectFactory.getConstructor(entry.data.type);
|
||||||
return Object.assign(new type(), entry.data) as T
|
return Object.assign(new type(), entry.data) as T
|
||||||
}),
|
})
|
||||||
// map((entry: ObjectCacheEntry) =>
|
|
||||||
// applyPatch(entry.data, entry.operations).newDocument
|
|
||||||
// )
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,14 +211,16 @@ export class ObjectCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add operations to a the existing list of operations for an ObjectCacheEntry
|
* Add operations to the existing list of operations for an ObjectCacheEntry
|
||||||
|
* Makes sure the ServerSyncBuffer for this ObjectCacheEntry is updated
|
||||||
* @param {string} uuid
|
* @param {string} uuid
|
||||||
* the uuid of the ObjectCacheEntry
|
* the uuid of the ObjectCacheEntry
|
||||||
* @param {Operation[]} patch
|
* @param {Operation[]} patch
|
||||||
* list of operations to perform
|
* list of operations to perform
|
||||||
*/
|
*/
|
||||||
private addOperations(uuid: string, patch: Operation[]) {
|
private addPatch(uuid: string, patch: Operation[]) {
|
||||||
this.store.dispatch(new PatchObjectCacheAction(uuid, patch));
|
this.store.dispatch(new AddPatchObjectCacheAction(uuid, patch));
|
||||||
|
this.store.dispatch(new AddToSSBAction(uuid, RestRequestMethod.Patch));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -224,6 +232,17 @@ export class ObjectCacheService {
|
|||||||
* false if the entry is there are no operations left in the ObjectCacheEntry, true otherwise
|
* false if the entry is there are no operations left in the ObjectCacheEntry, true otherwise
|
||||||
*/
|
*/
|
||||||
private isDirty(entry: ObjectCacheEntry): boolean {
|
private isDirty(entry: ObjectCacheEntry): boolean {
|
||||||
return isNotEmpty(entry.operations);
|
return isNotEmpty(entry.patches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the existing operations on an ObjectCacheEntry in the store
|
||||||
|
* NB: this does not make any server side changes
|
||||||
|
* @param {string} uuid
|
||||||
|
* the uuid of the ObjectCacheEntry
|
||||||
|
*/
|
||||||
|
private applyPatchesToCachedObject(uuid: string) {
|
||||||
|
this.store.dispatch(new ApplyPatchObjectCacheAction(uuid));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
2
src/app/core/cache/response-cache.service.ts
vendored
2
src/app/core/cache/response-cache.service.ts
vendored
@@ -12,7 +12,7 @@ import { coreSelector, CoreState } from '../core.reducers';
|
|||||||
import { pathSelector } from '../shared/selectors';
|
import { pathSelector } from '../shared/selectors';
|
||||||
|
|
||||||
function entryFromKeySelector(key: string): MemoizedSelector<CoreState, ResponseCacheEntry> {
|
function entryFromKeySelector(key: string): MemoizedSelector<CoreState, ResponseCacheEntry> {
|
||||||
return pathSelector<CoreState, ResponseCacheEntry>(coreSelector, 'data/response', key);
|
return pathSelector<CoreState, ResponseCacheEntry>(coreSelector, 'cache/response', key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
83
src/app/core/cache/server-sync-buffer.actions.ts
vendored
Normal file
83
src/app/core/cache/server-sync-buffer.actions.ts
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { type } from '../../shared/ngrx/type';
|
||||||
|
import { CacheableObject } from './object-cache.reducer';
|
||||||
|
import { Operation } from 'fast-json-patch';
|
||||||
|
import { RestRequest, RestRequestMethod } from '../data/request.models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of ServerSyncBufferAction type definitions
|
||||||
|
*/
|
||||||
|
export const ServerSyncBufferActionTypes = {
|
||||||
|
ADD: type('dspace/core/cache/syncbuffer/ADD'),
|
||||||
|
COMMIT: type('dspace/core/cache/syncbuffer/COMMIT'),
|
||||||
|
EMPTY: type('dspace/core/cache/syncbuffer/EMPTY'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ngrx action to add a new cached object to the server's sync buffer
|
||||||
|
*/
|
||||||
|
export class AddToSSBAction implements Action {
|
||||||
|
type = ServerSyncBufferActionTypes.ADD;
|
||||||
|
payload: {
|
||||||
|
href: string,
|
||||||
|
method: RestRequestMethod
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new AddToSSBAction
|
||||||
|
*
|
||||||
|
* @param href
|
||||||
|
* the unique href of the cached object entry that should be updated
|
||||||
|
*/
|
||||||
|
constructor(href: string, method: RestRequestMethod) {
|
||||||
|
this.payload = { href, method };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ngrx action to commit everything (for a certain method, when specified) in the ServerSyncBuffer to the server
|
||||||
|
*/
|
||||||
|
export class CommitSSBAction implements Action {
|
||||||
|
type = ServerSyncBufferActionTypes.COMMIT;
|
||||||
|
payload?: RestRequestMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new CommitSSBAction
|
||||||
|
*
|
||||||
|
* @param method
|
||||||
|
* an optional method for which the ServerSyncBuffer should send its entries to the server
|
||||||
|
*/
|
||||||
|
constructor(method?: RestRequestMethod) {
|
||||||
|
this.payload = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* An ngrx action to remove everything (for a certain method, when specified) from the ServerSyncBuffer to the server
|
||||||
|
*/
|
||||||
|
export class EmptySSBAction implements Action {
|
||||||
|
type = ServerSyncBufferActionTypes.EMPTY;
|
||||||
|
payload?: RestRequestMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new EmptySSBAction
|
||||||
|
*
|
||||||
|
* @param method
|
||||||
|
* an optional method for which the ServerSyncBuffer should remove its entries
|
||||||
|
*/
|
||||||
|
constructor(method?: RestRequestMethod) {
|
||||||
|
this.payload = method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type to encompass all ServerSyncBufferActions
|
||||||
|
*/
|
||||||
|
export type ServerSyncBufferAction
|
||||||
|
= AddToSSBAction
|
||||||
|
| CommitSSBAction
|
||||||
|
| EmptySSBAction
|
55
src/app/core/cache/server-sync-buffer.effects.ts
vendored
Normal file
55
src/app/core/cache/server-sync-buffer.effects.ts
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { delay, exhaustMap, filter, first, map } from 'rxjs/operators';
|
||||||
|
import { Inject, Injectable } from '@angular/core';
|
||||||
|
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||||
|
import {
|
||||||
|
AddToSSBAction,
|
||||||
|
CommitSSBAction,
|
||||||
|
EmptySSBAction,
|
||||||
|
ServerSyncBufferActionTypes
|
||||||
|
} from './server-sync-buffer.actions';
|
||||||
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { select, Store } from '@ngrx/store';
|
||||||
|
import { ServerSyncBufferEntry, ServerSyncBufferState } from './server-sync-buffer.reducer';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { PutRequest } from '../data/request.models';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ObjectCacheEffects {
|
||||||
|
@Effect() setTimeoutForServerSync = this.actions$
|
||||||
|
.pipe(ofType(ServerSyncBufferActionTypes.ADD),
|
||||||
|
exhaustMap((action: AddToSSBAction) => {
|
||||||
|
const autoSyncConfig = this.EnvConfig.cache.autoSync;
|
||||||
|
const timeoutInSeconds = autoSyncConfig.timePerMethod[action.type] || autoSyncConfig.defaultTime;
|
||||||
|
return observableOf(new CommitSSBAction(action.payload.method)).pipe(delay( timeoutInSeconds * 1000))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
@Effect() commitServerSyncBuffer = this.actions$
|
||||||
|
.pipe(ofType(ServerSyncBufferActionTypes.COMMIT),
|
||||||
|
map((action: CommitSSBAction) => {
|
||||||
|
this.store.pipe(
|
||||||
|
select(serverSyncBufferSelector),
|
||||||
|
first()
|
||||||
|
).subscribe((bufferState: ServerSyncBufferState) => {
|
||||||
|
bufferState.buffer
|
||||||
|
.filter((entry: ServerSyncBufferEntry) => entry.method === action.payload)
|
||||||
|
.forEach((entry: ServerSyncBufferEntry) => {
|
||||||
|
this.requestService.configure(new PutRequest(this.requestService.generateRequestId(), ,))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return new EmptySSBAction(action.payload);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor(private actions$: Actions,
|
||||||
|
private store: Store<CoreState>,
|
||||||
|
private requestService: RequestService,
|
||||||
|
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const serverSyncBufferSelector = (state: CoreState) => state['cache/syncbuffer'];
|
92
src/app/core/cache/server-sync-buffer.reducer.ts
vendored
Normal file
92
src/app/core/cache/server-sync-buffer.reducer.ts
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { RestRequestMethod } from '../data/request.models';
|
||||||
|
import { hasNoValue, hasValue } from '../../shared/empty.util';
|
||||||
|
import {
|
||||||
|
AddToSSBAction,
|
||||||
|
EmptySSBAction,
|
||||||
|
ServerSyncBufferAction,
|
||||||
|
ServerSyncBufferActionTypes
|
||||||
|
} from './server-sync-buffer.actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An entry in the ServerSyncBufferState
|
||||||
|
* href: unique href of an ObjectCacheEntry
|
||||||
|
* method: RestRequestMethod type
|
||||||
|
*/
|
||||||
|
export class ServerSyncBufferEntry {
|
||||||
|
href: string;
|
||||||
|
method: RestRequestMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ServerSyncBuffer State
|
||||||
|
*
|
||||||
|
* Consists list of ServerSyncBufferState
|
||||||
|
*/
|
||||||
|
export interface ServerSyncBufferState {
|
||||||
|
buffer: ServerSyncBufferEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`)
|
||||||
|
const initialState: ServerSyncBufferState = { buffer: [] };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ServerSyncBuffer Reducer
|
||||||
|
*
|
||||||
|
* @param state
|
||||||
|
* the current state
|
||||||
|
* @param action
|
||||||
|
* the action to perform on the state
|
||||||
|
* @return ServerSyncBufferState
|
||||||
|
* the new state
|
||||||
|
*/
|
||||||
|
export function serverSyncBufferReducer(state = initialState, action: ServerSyncBufferAction): ServerSyncBufferState {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case ServerSyncBufferActionTypes.ADD: {
|
||||||
|
return addToServerSyncQueue(state, action as AddToSSBAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
case ServerSyncBufferActionTypes.EMPTY: {
|
||||||
|
return emptyServerSyncQueue(state, action as EmptySSBAction);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new entry to the buffer with a specified method
|
||||||
|
*
|
||||||
|
* @param state
|
||||||
|
* the current state
|
||||||
|
* @param action
|
||||||
|
* an AddToSSBAction
|
||||||
|
* @return ServerSyncBufferState
|
||||||
|
* the new state, with a new entry added to the buffer
|
||||||
|
*/
|
||||||
|
function addToServerSyncQueue(state: ServerSyncBufferState, action: AddToSSBAction): ServerSyncBufferState {
|
||||||
|
const actionEntry = action.payload as ServerSyncBufferEntry;
|
||||||
|
if (hasNoValue(state.buffer.find((entry) => entry.href === actionEntry.href && entry.method === actionEntry.method))) {
|
||||||
|
return Object.assign({}, state, { buffer: state.buffer.concat(actionEntry) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all ServerSyncBuffers entry from the buffer with a specified method
|
||||||
|
* If no method is specified, empty the whole buffer
|
||||||
|
*
|
||||||
|
* @param state
|
||||||
|
* the current state
|
||||||
|
* @param action
|
||||||
|
* an AddToSSBAction
|
||||||
|
* @return ServerSyncBufferState
|
||||||
|
* the new state, with a new entry added to the buffer
|
||||||
|
*/
|
||||||
|
function emptyServerSyncQueue(state: ServerSyncBufferState, action: EmptySSBAction): ServerSyncBufferState {
|
||||||
|
let newBuffer = [];
|
||||||
|
if (hasValue(action.payload)) {
|
||||||
|
newBuffer = state.buffer.filter((entry) => entry.method !== action.payload);
|
||||||
|
}
|
||||||
|
return Object.assign({}, state, { buffer: newBuffer });
|
||||||
|
}
|
@@ -5,18 +5,21 @@ import { objectCacheReducer, ObjectCacheState } from './cache/object-cache.reduc
|
|||||||
import { indexReducer, IndexState } from './index/index.reducer';
|
import { indexReducer, IndexState } from './index/index.reducer';
|
||||||
import { requestReducer, RequestState } from './data/request.reducer';
|
import { requestReducer, RequestState } from './data/request.reducer';
|
||||||
import { authReducer, AuthState } from './auth/auth.reducer';
|
import { authReducer, AuthState } from './auth/auth.reducer';
|
||||||
|
import { serverSyncBufferReducer, ServerSyncBufferState } from './cache/server-sync-buffer.reducer';
|
||||||
|
|
||||||
export interface CoreState {
|
export interface CoreState {
|
||||||
'data/object': ObjectCacheState,
|
'cache/object': ObjectCacheState,
|
||||||
'data/response': ResponseCacheState,
|
'cache/response': ResponseCacheState,
|
||||||
|
'cache/syncbuffer': ServerSyncBufferState,
|
||||||
'data/request': RequestState,
|
'data/request': RequestState,
|
||||||
'index': IndexState,
|
'index': IndexState,
|
||||||
'auth': AuthState,
|
'auth': AuthState,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const coreReducers: ActionReducerMap<CoreState> = {
|
export const coreReducers: ActionReducerMap<CoreState> = {
|
||||||
'data/object': objectCacheReducer,
|
'cache/object': objectCacheReducer,
|
||||||
'data/response': responseCacheReducer,
|
'cache/response': responseCacheReducer,
|
||||||
|
'cache/syncbuffer': serverSyncBufferReducer,
|
||||||
'data/request': requestReducer,
|
'data/request': requestReducer,
|
||||||
'index': indexReducer,
|
'index': indexReducer,
|
||||||
'auth': authReducer
|
'auth': authReducer
|
||||||
|
12
src/config/auto-sync-config.interface.ts
Normal file
12
src/config/auto-sync-config.interface.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { RestRequestMethod } from '../app/core/data/request.models';
|
||||||
|
|
||||||
|
/* enum indices */
|
||||||
|
type TimePerMethod = {
|
||||||
|
[method in RestRequestMethod]: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface AutoSyncConfig {
|
||||||
|
defaultTime: number;
|
||||||
|
timePerMethod: TimePerMethod;
|
||||||
|
maxBufferSize: number;
|
||||||
|
};
|
@@ -1,6 +1,8 @@
|
|||||||
import { Config } from './config.interface';
|
import { Config } from './config.interface';
|
||||||
|
import { AutoSyncConfig } from './auto-sync-config.interface';
|
||||||
|
|
||||||
export interface CacheConfig extends Config {
|
export interface CacheConfig extends Config {
|
||||||
msToLive: number,
|
msToLive: number,
|
||||||
control: string
|
control: string,
|
||||||
|
autoSync: AutoSyncConfig
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user