delta-store
Version:
An API for a store with change records
236 lines (189 loc) • 6.95 kB
text/typescript
/**
* Created by Papa on 1/7/2016.
*/
import {GoogleRealtime} from './GoogleRealtime';
import {Subject} from 'rxjs/Subject';
import {ChangeRecord} from '../../ChangeModel';
import {DocumentHandle} from './DocumentHandle';
import {ChangeRecordIterator} from '../../ChangeModel';
export enum Operation {
CHANGES_ADDED_BY_OTHERS, //
CLEAUP_BY_OWNER, //
GET_NEXT_CHANGE
}
export class GoogleRealtimeAdaptorException {
constructor(
public message:string,
public operation:Operation,
public event:gapi.drive.realtime.BaseModelEvent,
public exception?:any
) {
}
}
export class GoogleChangeRecordIterator implements ChangeRecordIterator {
private nextValue:ChangeRecord;
constructor(
private changeList:gapi.drive.realtime.CollaborativeList<ChangeRecord>,
private event:gapi.drive.realtime.BaseModelEvent,
private nextIndex:number = 0
) {
}
next():ChangeRecord {
if (this.hasNext()) {
let nextValue = this.nextValue;
this.nextValue = null;
this.nextIndex++;
return nextValue;
}
let message = 'Error accessing the changelist at index: ' + this.nextIndex;
throw generateException(message, Operation.GET_NEXT_CHANGE, this.event);
}
hasNext():boolean {
if (this.nextValue) {
return true;
}
try {
this.nextValue = this.changeList.get(this.nextIndex);
return !!this.nextValue;
} catch (exception) {
return false;
}
}
}
let
generateException = function (
message:string,
operation:Operation,
event:gapi.drive.realtime.BaseModelEvent,
exception?:any
):GoogleRealtimeAdaptorException {
message = message + '. User ID: ' + event.userId + ', Session ID: ' + event.sessionId;
return new GoogleRealtimeAdaptorException(
message,
Operation.CLEAUP_BY_OWNER,
event,
exception
);
};
export class GoogleRealtimeAdaptor {
constructor(
private googleRealtime:GoogleRealtime
) {
}
startTest():DocumentHandle {
let document = this.googleRealtime.createInMemoryDocument();
let eventHub = this.createDocumentHandle(document);
return eventHub;
}
startNewShare(
fileId:string
):Promise<DocumentHandle> {
return this.googleRealtime.initializeFile(fileId).then(( document ) => {
let eventHub = this.createDocumentHandle(document);
return eventHub;
});
}
private createDocumentHandle(
document:gapi.drive.realtime.Document
):DocumentHandle {
let changeList = this.googleRealtime.getChangeList(document);
let valuesAddedSubject = this.subscribeToChangesAddedByOthers(document);
let valuesArchivedSubject = this.subscribeToCleanupByOwner(document, false);
let otherChangesSubject = this.subscribeToUnexpectedModifications(changeList, document);
return new DocumentHandle(document, changeList, valuesAddedSubject, valuesArchivedSubject, otherChangesSubject);
}
private subscribeToChangesAddedByOthers(
document:gapi.drive.realtime.Document
):Subject<GoogleChangeRecordIterator> {
let valuesAddedSubject = new Subject<gapi.drive.realtime.ValuesAddedEvent<ChangeRecord>>();
let changeList = this.googleRealtime.getChangeList(document);
this.googleRealtime.subscribeToValuesAdded(changeList, valuesAddedSubject);
let changesAddedSubject = new Subject<GoogleChangeRecordIterator>();
valuesAddedSubject.subscribe(( event:gapi.drive.realtime.ValuesAddedEvent<ChangeRecord> ) => {
console.log('Changes by others. BaseModelEvent Type: ' + event.type);
if (event.isLocal) {
return;
}
if (event.target !== changeList) {
throw generateException('List cleaned up on an unexpected target', Operation.CHANGES_ADDED_BY_OTHERS, event);
}
if (event.movedFromIndex === 0 || event.movedFromIndex || event.movedFromList) {
throw generateException('List cleaned up is a move operation', Operation.CHANGES_ADDED_BY_OTHERS, event);
}
event.stopPropagation();
let iterator = new GoogleChangeRecordIterator(changeList, event, event.index);
changesAddedSubject.next(iterator);
});
return changesAddedSubject;
}
private subscribeToCleanupByOwner(
document:gapi.drive.realtime.Document,
iAmTheOwner:boolean
):Subject<GoogleChangeRecordIterator> {
let valuesRemovedSubject = new Subject<gapi.drive.realtime.ValuesRemovedEvent<ChangeRecord>>();
let changeList = this.googleRealtime.getChangeList(document);
this.googleRealtime.subscribeToValuesRemoved(changeList, valuesRemovedSubject);
let changesRemovedSubject = new Subject<GoogleChangeRecordIterator>();
valuesRemovedSubject.subscribe(( event:gapi.drive.realtime.ValuesRemovedEvent<ChangeRecord> ) => {
console.log('Clean-up by owner. BaseModelEvent Type: ' + event.type);
if (event.isLocal) {
if (iAmTheOwner) {
return;
}
throw generateException('List cleaned up by non-owner', Operation.CLEAUP_BY_OWNER, event);
}
if (event.target !== changeList) {
throw generateException('List cleaned up on an unexpected target', Operation.CLEAUP_BY_OWNER, event);
}
if (event.movedToIndex === 0 || event.movedToIndex || event.movedToList) {
throw generateException('List cleaned up is a move operation', Operation.CLEAUP_BY_OWNER, event);
}
if (event.index !== 0) {
throw generateException('List cleaned up from invalid index: ' + event.index, Operation.CLEAUP_BY_OWNER, event);
}
event.stopPropagation();
let iterator = new GoogleChangeRecordIterator(changeList, event);
changesRemovedSubject.next(iterator);
});
return changesRemovedSubject;
}
private subscribeToUnexpectedModifications(
changeList:gapi.drive.realtime.CollaborativeList<ChangeRecord>,
document:gapi.drive.realtime.Document
):Subject<GoogleRealtimeAdaptorException> {
let valuesRemovedSubject = new Subject<gapi.drive.realtime.ObjectChangedEvent>();
this.googleRealtime.subscribeToAnyObjectChanged(document, valuesRemovedSubject);
let changesRemovedSubject = new Subject<GoogleRealtimeAdaptorException>();
valuesRemovedSubject.subscribe(( event ) => {
let message = 'Unexpected change - ';
if (!event.events) {
message += ' target events not found';
} else if (event.events.length !== 1) {
message += ' unexpected number of target events: ' + event.events.length;
} else {
let causingEvent = event.events[0];
if (causingEvent.target !== changeList) {
message += ' unexpected target';
}
switch (causingEvent.type) {
case gapi.drive.realtime.EventType.VALUES_ADDED:
case gapi.drive.realtime.EventType.VALUES_REMOVED:
return;
default:
message += 'unexpected event type: ' + causingEvent.type;
}
}
let exception = new GoogleRealtimeAdaptorException(message, null, event);
changesRemovedSubject.next(exception);
});
return changesRemovedSubject;
}
openShare(
fileId:string
):Promise<DocumentHandle> {
return this.googleRealtime.loadFile(fileId).then(( document ) => {
let documentHandle = this.createDocumentHandle(document);
return documentHandle;
});
}
}