1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-09 10:22:46 +03:00

Persist User objects. Back out everything else.

We can reasonable persist User and account data objects. Other
objects get into horrible circular loops. We'll rethink how this
is being done.
This commit is contained in:
Kegan Dougal
2017-02-02 17:25:20 +00:00
parent 896fc5a3f0
commit ad9d58ebc2
8 changed files with 54 additions and 144 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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, {

View File

@@ -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.

View File

@@ -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<RoomMember>} A list of RoomMembers.

View File

@@ -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
*

View File

@@ -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

View File

@@ -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<MatrixEvent[]>} 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);
};
});