From 6ba37e90a32eb52f49383e0a5904aba073ef1ba2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 4 Apr 2019 09:27:31 +0200 Subject: [PATCH] work on memory store --- src/matrix/storage/common.js | 6 ++ src/matrix/storage/memory/Storage.js | 37 ++++++++++++ src/matrix/storage/memory/Transaction.js | 57 +++++++++++++++++++ .../memory/stores/RoomTimelineStore.js | 14 ++++- src/matrix/storage/memory/stores/Store.js | 27 +++++++++ 5 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 src/matrix/storage/common.js create mode 100644 src/matrix/storage/memory/Storage.js create mode 100644 src/matrix/storage/memory/Transaction.js create mode 100644 src/matrix/storage/memory/stores/Store.js diff --git a/src/matrix/storage/common.js b/src/matrix/storage/common.js new file mode 100644 index 00000000..0280549b --- /dev/null +++ b/src/matrix/storage/common.js @@ -0,0 +1,6 @@ +export const STORE_NAMES = Object.freeze(["session", "roomState", "roomSummary", "roomTimeline"]); + +export const STORE_MAP = Object.freeze(STORE_NAMES.reduce((nameMap, name) => { + nameMap[name] = name; + return nameMap; +}, {})); diff --git a/src/matrix/storage/memory/Storage.js b/src/matrix/storage/memory/Storage.js new file mode 100644 index 00000000..fb178b01 --- /dev/null +++ b/src/matrix/storage/memory/Storage.js @@ -0,0 +1,37 @@ +import Transaction from "./transaction.js"; +import { STORE_MAP, STORE_NAMES } from "../common.js"; + +export default class Storage { + constructor(initialStoreValues = {}) { + this._validateStoreNames(Object.keys(initialStoreValues)); + this.storeNames = STORE_MAP; + this._storeValues = STORE_NAMES.reduce((values, name) => { + values[name] = initialStoreValues[name] || null; + }, {}); + } + + _validateStoreNames(storeNames) { + const idx = storeNames.findIndex(name => !STORE_MAP.hasOwnProperty(name)); + if (idx !== -1) { + throw new Error(`Invalid store name ${storeNames[idx]}`); + } + } + + _createTxn(storeNames, writable) { + this._validateStoreNames(storeNames); + const storeValues = storeNames.reduce((values, name) => { + return values[name] = this._storeValues[name]; + }, {}); + return Promise.resolve(new Transaction(storeValues, writable)); + } + + readTxn(storeNames) { + // TODO: avoid concurrency + return this._createTxn(storeNames, false); + } + + readWriteTxn(storeNames) { + // TODO: avoid concurrency + return this._createTxn(storeNames, true); + } +} diff --git a/src/matrix/storage/memory/Transaction.js b/src/matrix/storage/memory/Transaction.js new file mode 100644 index 00000000..437962da --- /dev/null +++ b/src/matrix/storage/memory/Transaction.js @@ -0,0 +1,57 @@ +import RoomTimelineStore from "./stores/RoomTimelineStore.js"; + +export default class Transaction { + constructor(storeValues, writable) { + this._storeValues = storeValues; + this._txnStoreValues = {}; + this._writable = writable; + } + + _store(name, mapper) { + if (!this._txnStoreValues.hasOwnProperty(name)) { + if (!this._storeValues.hasOwnProperty(name)) { + throw new Error(`Transaction wasn't opened for store ${name}`); + } + const store = mapper(this._storeValues[name]); + const clone = store.cloneStoreValue(); + // extra prevention for writing + if (!this._writable) { + Object.freeze(clone); + } + this._txnStoreValues[name] = clone; + } + return mapper(this._txnStoreValues[name]); + } + + get session() { + throw new Error("not yet implemented"); + // return this._store("session", storeValue => new SessionStore(storeValue)); + } + + get roomSummary() { + throw new Error("not yet implemented"); + // return this._store("roomSummary", storeValue => new RoomSummaryStore(storeValue)); + } + + get roomTimeline() { + return this._store("roomTimeline", storeValue => new RoomTimelineStore(storeValue)); + } + + get roomState() { + throw new Error("not yet implemented"); + // return this._store("roomState", storeValue => new RoomStateStore(storeValue)); + } + + complete() { + for(let name of Object.keys(this._txnStoreValues)) { + this._storeValues[name] = this._txnStoreValues[name]; + } + this._txnStoreValues = null; + return Promise.resolve(); + } + + abort() { + this._txnStoreValues = null; + return Promise.resolve(); + } +} diff --git a/src/matrix/storage/memory/stores/RoomTimelineStore.js b/src/matrix/storage/memory/stores/RoomTimelineStore.js index a5072dba..40c521cb 100644 --- a/src/matrix/storage/memory/stores/RoomTimelineStore.js +++ b/src/matrix/storage/memory/stores/RoomTimelineStore.js @@ -1,5 +1,6 @@ import SortKey from "../sortkey.js"; import sortedIndex from "../../../utils/sortedIndex.js"; +import Store from "./Store"; function compareKeys(key, entry) { if (key.roomId === entry.roomId) { @@ -64,9 +65,13 @@ class Range { } } -export default class RoomTimelineStore { - constructor(initialTimeline = []) { - this._timeline = initialTimeline; +export default class RoomTimelineStore extends Store { + constructor(timeline, writable) { + super(timeline || [], writable); + } + + get _timeline() { + return this._storeValue; } /** Creates a range that only includes the given key @@ -176,6 +181,7 @@ export default class RoomTimelineStore { * @throws {StorageError} ... */ insert(entry) { + this.assertWritable(); const insertIndex = sortedIndex(this._timeline, entry, compareKeys); if (insertIndex < this._timeline.length) { const existingEntry = this._timeline[insertIndex]; @@ -193,6 +199,7 @@ export default class RoomTimelineStore { * @return {Promise<>} a promise resolving to undefined if the operation was successful, or a StorageError if not. */ update(entry) { + this.assertWritable(); let update = false; const updateIndex = sortedIndex(this._timeline, entry, compareKeys); if (updateIndex < this._timeline.length) { @@ -213,6 +220,7 @@ export default class RoomTimelineStore { } removeRange(roomId, range) { + this.assertWritable(); const {startIndex, count} = range.project(roomId); const removedEntries = this._timeline.splice(startIndex, count); return Promise.resolve(removedEntries); diff --git a/src/matrix/storage/memory/stores/Store.js b/src/matrix/storage/memory/stores/Store.js new file mode 100644 index 00000000..8028a783 --- /dev/null +++ b/src/matrix/storage/memory/stores/Store.js @@ -0,0 +1,27 @@ +export default class Store { + constructor(storeValue, writable) { + this._storeValue = storeValue; + this._writable = writable; + } + + // makes a copy deep enough that any modifications in the store + // won't affect the original + // used for transactions + cloneStoreValue() { + // assumes 1 level deep is enough, and that values will be replaced + // rather than updated. + if (Array.isArray(this._storeValue)) { + return this._storeValue.slice(); + } else if (typeof this._storeValue === "object") { + return Object.assign({}, this._storeValue); + } else { + return this._storeValue; + } + } + + assertWritable() { + if (!this._writable) { + throw new Error("Tried to write in read-only transaction"); + } + } +}