diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index c307f49f8..927049e13 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -73,22 +73,6 @@ function EventTimelineSet(room, opts) { } utils.inherits(EventTimelineSet, EventEmitter); -/** - * Deserialize an event timeline set. This drops non-live timelines even - * if they are included in 'obj'. - * @param {Object} obj The EventTimelineSet as a JSON object. - * @param {Room} room The room it should be referencing. - * @param {Object} opts Options for the room. - * @return {EventTimelineSet} The timeline set - */ -EventTimelineSet.deserialize = function(obj, room, opts) { - const ets = new EventTimelineSet(room, opts); - // only keep the live timeline, they can always re-request older times. - ets._liveTimeline = EventTimeline.deserialize(obj._liveTimeline, ets); - ets._timelines = [ets._liveTimeline]; - return ets; -}; - /** * Get the filter object this timeline set is filtered on, if any * @return {?Filter} the optional filter for this timelineSet diff --git a/src/models/event-timeline.js b/src/models/event-timeline.js index 4307363d8..a3513cdc5 100644 --- a/src/models/event-timeline.js +++ b/src/models/event-timeline.js @@ -47,23 +47,6 @@ function EventTimeline(eventTimelineSet) { this._name = this._roomId + ":" + new Date().toISOString(); } -/** - * Deserialize an event timeline. This drops prev/next timelines even - * if they are included in 'obj'. - * @param {Object} obj The EventTimeline as a JSON object. - * @param {EventTimelineSet} eventTimelineSet The set it belongs to. - * @return {EventTimeline} The timeline. - */ -EventTimeline.deserialize = function(obj, eventTimelineSet) { - const timeline = new EventTimeline(eventTimelineSet); - timeline._events = obj._events.map((e) => MatrixEvent.deserialize(e)); - timeline._baseIndex = obj._baseIndex; - timeline._startState = RoomState.deserialize(obj._startState, timeline._roomId); - timeline._endState = RoomState.deserialize(obj._endState, timeline._roomId); - // drop prev/next timelines, they can always re-request this information. - return timeline; -}; - /** * Symbolic constant for methods which take a 'direction' argument: * refers to the start of the timeline, or backwards in time. diff --git a/src/models/event.js b/src/models/event.js index 4081502fb..0da0da745 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -87,18 +87,6 @@ module.exports.MatrixEvent = function MatrixEvent( }; utils.inherits(module.exports.MatrixEvent, EventEmitter); -/** - * Deserialize this event from a JSON object. - * @static - * @param {Object} obj The MatrixEvent object from the structured-clone algorithm. - * @return {MatrixEvent} An event - */ -module.exports.MatrixEvent.deserialize = function(obj) { - const ev = new module.exports.MatrixEvent(obj.event); - Object.assign(ev, obj); - return ev; -}; - utils.extend(module.exports.MatrixEvent.prototype, { diff --git a/src/models/room-member.js b/src/models/room-member.js index b7192ce1d..512fb60ec 100644 --- a/src/models/room-member.js +++ b/src/models/room-member.js @@ -19,7 +19,6 @@ limitations under the License. */ const EventEmitter = require("events").EventEmitter; const ContentRepo = require("../content-repo"); -const MatrixEvent = require("./event").MatrixEvent; const utils = require("../utils"); @@ -59,22 +58,6 @@ function RoomMember(roomId, userId) { } utils.inherits(RoomMember, EventEmitter); - -/** - * Deserialize a room member. - * @param {Object} obj The RoomMember as a JSON object. - * @return {RoomMember} The room member. - */ -RoomMember.deserialize = function(obj) { - const member = new RoomMember(obj.roomId, obj.userId); - Object.assign(member, obj); - // Convert JSON objects to class instances - if (member.events.member) { - member.events.member = MatrixEvent.deserialize(member.events.member); - } - return member; -}; - /** * Update this room member's membership event. May fire "RoomMember.name" if * this event updates this member's name. diff --git a/src/models/room-state.js b/src/models/room-state.js index 136bab4d6..8f606859a 100644 --- a/src/models/room-state.js +++ b/src/models/room-state.js @@ -21,7 +21,6 @@ const EventEmitter = require("events").EventEmitter; const utils = require("../utils"); const RoomMember = require("./room-member"); -const MatrixEvent = require("./event").MatrixEvent; /** * Construct room state. @@ -56,37 +55,6 @@ function RoomState(roomId) { } utils.inherits(RoomState, EventEmitter); -/** - * Deserialize a room state. - * @param {Object} obj The RoomState as a JSON object. - * @param {string} roomId The room it belongs to. - * @return {RoomState} The room state. - */ -RoomState.deserialize = function(obj, roomId) { - const state = new RoomState(roomId); - Object.assign(state, obj); - // convert JSON objects to class instances - Object.keys(state.members).forEach((userId) => { - state.members[userId] = RoomMember.deserialize(state.members[userId]); - }); - Object.keys(state.events).forEach((eventType) => { - Object.keys(state.events[eventType]).forEach((stateKey) => { - state.events[eventType][stateKey] = MatrixEvent.deserialize( - state.events[eventType][stateKey], - ); - }); - }); - Object.keys(state._sentinels).forEach((userId) => { - state._sentinels[userId] = RoomMember.deserialize(state._sentinels[userId]); - }); - Object.keys(state._tokenToInvite).forEach((token) => { - state._tokenToInvite[token] = MatrixEvent.deserialize( - state._tokenToInvite[token], - ); - }); - return state; -}; - /** * Get all RoomMembers in this room. * @return {Array} A list of RoomMembers. diff --git a/src/models/room.js b/src/models/room.js index 466e77865..9c44adfde 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -170,42 +170,6 @@ function Room(roomId, opts) { } utils.inherits(Room, EventEmitter); -/** - * Deserialize a room from a JSON object. - * @static - * @param {Object} obj The Room object from the structured-clone algorithm. - * @return {Room} A room - */ -Room.deserialize = function(obj) { - const room = new Room(obj.roomId, obj._opts); - Object.assign(room, obj); // copy normal props - // remove all in-flight requests as if we've been deserialized it's impossible - // to have any in-flight requests ongoing - room._txnToEvent = {}; - room._pendingEventList = []; // TODO: This removes unsent messages? - // remove all filtered timeline sets (from jumping to messages in the past), - // they'll just have to re-request this information. - room._filteredTimelineSets = {}; - - // create instances of MatrixEvent where appropriate - Object.keys(room.accountData).forEach((t) => { - room.accountData[t] = MatrixEvent.deserialize(room.accountData[t]); - }); - - // Deserialize the first timeline set and drop the rest, they can always - // backpaginate to get this data again. - if (obj._timelineSets.length > 0) { - room._timelineSets = [ - EventTimelineSet.deserialize(obj._timelineSets[0], room, obj._opts), - ]; - } - reEmit(this, this.getUnfilteredTimelineSet(), - ["Room.timeline", "Room.timelineReset"]); - this._fixUpLegacyTimelineFields(); // add refs to the recent timeline - - return room; -}; - /** * Get the list of pending sent events for this room * diff --git a/src/models/user.js b/src/models/user.js index 7db51479a..c1cf37fda 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -64,22 +64,24 @@ utils.inherits(User, EventEmitter); /** * Deserialize this user from a JSON object. * @static - * @param {Object} obj The User object from the structured-clone algorithm. + * @param {Object} obj The User object from User.serialize(). * @return {User} A user */ User.deserialize = function(obj) { const user = new User(obj.userId); - Object.assign(user, obj); // copy normal props - // create instances where appropriate - if (user.events.presence) { - user.events.presence = MatrixEvent.deserialize(user.events.presence); - } - if (user.events.profile) { - user.events.profile = MatrixEvent.deserialize(user.events.profile); + if (obj.event) { + user.setPresenceEvent(new MatrixEvent(obj.event)); } return user; }; +User.prototype.serialize = function() { + return { + userId: this.userId, + event: (this.events.presence ? this.events.presence.event : null), + }; +}; + /** * Update this User with the given presence event. May fire "User.presence", * "User.avatarUrl" and/or "User.displayName" if this event updates this user's diff --git a/src/store/indexeddb.js b/src/store/indexeddb.js index fd06b80f5..0f38e6775 100644 --- a/src/store/indexeddb.js +++ b/src/store/indexeddb.js @@ -105,7 +105,14 @@ IndexedDBStoreBackend.prototype = { * @return {Promise} Resolves if the events were persisted. */ persistAccountData: function(accountData) { - return this._upsert("accountData", accountData); + return q.try(() => { + const txn = this.db.transaction(["accountData"], "readwrite"); + const store = txn.objectStore("accountData"); + for (let i = 0; i < accountData.length; i++) { + store.put(accountData[i].event); // put == UPSERT + } + return promiseifyTxn(txn); + }); }, /** @@ -115,6 +122,7 @@ IndexedDBStoreBackend.prototype = { * @return {Promise} Resolves if the users were persisted. */ persistUsers: function(users) { + console.log("persistUsers =>", users); return this._upsert("users", users); }, @@ -139,7 +147,13 @@ IndexedDBStoreBackend.prototype = { * @return {Promise} A list of events. */ loadAccountData: function() { - return this._deserializeAll("accountData", MatrixEvent); + return q.try(() => { + const txn = this.db.transaction(["accountData"], "readonly"); + const store = txn.objectStore("accountData"); + return selectQuery(store, undefined, (cursor) => { + return new MatrixEvent(cursor.value); + }); + }); }, /** @@ -224,6 +238,11 @@ const IndexedDBStore = function IndexedDBStore(backend, opts) { this.backend = backend; this.startedUp = false; this._syncTs = Date.now(); // updated when writes to the database are performed + + // internal structs to determine deltas for syncs to the database. + this._userModifiedMap = { + // user_id : timestamp + }; }; utils.inherits(IndexedDBStore, MatrixInMemoryStore); @@ -245,6 +264,7 @@ IndexedDBStore.prototype.startup = function() { console.log("Loaded data from database. Reticulating splines..."); const [users, accountData, rooms, syncToken] = values; users.forEach((u) => { + this._userModifiedMap[u.userId] = u.getLastModifiedTime(); this.storeUser(u); }); this.storeAccountDataEvents(accountData); @@ -267,11 +287,25 @@ IndexedDBStore.prototype.setSyncToken = function(token) { MatrixInMemoryStore.prototype.setSyncToken.call(this, token); const now = Date.now(); if (now - this._syncTs > WRITE_DELAY_MS) { - // save contents to the database. - console.log("TODO: Write to database."); - this._syncTs = Date.now(); - return q(); + return this._syncToDatabase().catch((err) => {console.error("sync fail:", err);}); } + return null; +}; + +IndexedDBStore.prototype._syncToDatabase = function() { + console.log("_syncToDatabase"); + this._syncTs = Date.now(); // set now to guard against multi-writes + + // work out changed users (this doesn't handle deletions but you + // can't 'delete' users as they are just presence events). + const changedUsers = this.getUsers().filter((user) => { + return this._userModifiedMap[user.userId] !== user.getLastModifiedTime(); + }); + changedUsers.forEach((u) => { // update times + this._userModifiedMap[u.userId] = u.getLastModifiedTime(); + }); + + return this.backend.persistUsers(changedUsers); }; function createDatabase(db) { @@ -322,9 +356,11 @@ function selectQuery(store, keyRange, resultMapper) { function promiseifyTxn(txn) { return new q.Promise((resolve, reject) => { txn.oncomplete = function(event) { + console.log("txn success:", event); resolve(event); }; txn.onerror = function(event) { + console.error("txn fail:", event); reject(event); }; }); @@ -333,9 +369,11 @@ function promiseifyTxn(txn) { function promiseifyRequest(req) { return new q.Promise((resolve, reject) => { req.onsuccess = function(event) { + console.log("req success:", event); resolve(event); }; req.onerror = function(event) { + console.error("req fail:", event); reject(event); }; });