diff --git a/webclient/components/matrix/model-service.js b/webclient/components/matrix/model-service.js
new file mode 100644
index 0000000000..8b2ee877b1
--- /dev/null
+++ b/webclient/components/matrix/model-service.js
@@ -0,0 +1,170 @@
+/*
+Copyright 2014 OpenMarket Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+'use strict';
+
+/*
+This service serves as the entry point for all models in the app. If access to
+underlying data in a room is required, then this service should be used as the
+dependency.
+*/
+// NB: This is more explicit than linking top-level models to $rootScope
+// in that by adding this service as a dep you are clearly saying "this X
+// needs access to the underlying data store", rather than polluting the
+// $rootScope.
+angular.module('modelService', [])
+.factory('modelService', ['matrixService', function(matrixService) {
+
+ /***** Room Object *****/
+ var Room = function Room(room_id) {
+ this.room_id = room_id;
+ this.old_room_state = new RoomState();
+ this.current_room_state = new RoomState();
+ this.events = []; // events which can be displayed on the UI. TODO move?
+ };
+ Room.prototype = {
+ addMessageEvents: function addMessageEvents(events, toFront) {
+ for (var i=0; i<events.length; i++) {
+ this.addMessageEvent(events[i], toFront);
+ }
+ },
+
+ addMessageEvent: function addMessageEvent(event, toFront) {
+ // every message must reference the RoomMember which made it *at
+ // that time* so things like display names display correctly.
+ var stateAtTheTime = toFront ? this.old_room_state : this.current_room_state;
+ event.__room_member = stateAtTheTime.getStateEvent("m.room.member", event.user_id);
+ if (event.type === "m.room.member" && event.content.membership === "invite") {
+ // give information on both the inviter and invitee
+ event.__target_room_member = stateAtTheTime.getStateEvent("m.room.member", event.state_key);
+ }
+
+ if (toFront) {
+ this.events.unshift(event);
+ }
+ else {
+ this.events.push(event);
+ }
+ },
+
+ addOrReplaceMessageEvent: function addOrReplaceMessageEvent(event, toFront) {
+ // Start looking from the tail since the first goal of this function
+ // is to find a message among the latest ones
+ for (var i = this.events.length - 1; i >= 0; i--) {
+ var storedEvent = this.events[i];
+ if (storedEvent.event_id === event.event_id) {
+ // It's clobbering time!
+ this.events[i] = event;
+ return;
+ }
+ }
+ this.addMessageEvent(event, toFront);
+ },
+
+ leave: function leave() {
+ return matrixService.leave(this.room_id);
+ }
+ };
+
+ /***** Room State Object *****/
+ var RoomState = function RoomState() {
+ // list of RoomMember
+ this.members = {};
+ // state events, the key is a compound of event type + state_key
+ this.state_events = {};
+ this.pagination_token = "";
+ };
+ RoomState.prototype = {
+ // get a state event for this room from this.state_events. State events
+ // are unique per type+state_key tuple, with a lot of events using 0-len
+ // state keys. To make it not Really Annoying to access, this method is
+ // provided which can just be given the type and it will return the
+ // 0-len event by default.
+ state: function state(type, state_key) {
+ if (!type) {
+ return undefined; // event type MUST be specified
+ }
+ if (!state_key) {
+ return this.state_events[type]; // treat as 0-len state key
+ }
+ return this.state_events[type + state_key];
+ },
+
+ storeStateEvent: function storeState(event) {
+ this.state_events[event.type + event.state_key] = event;
+ if (event.type === "m.room.member") {
+ this.members[event.state_key] = event;
+ }
+ },
+
+ storeStateEvents: function storeState(events) {
+ if (!events) {
+ return;
+ }
+ for (var i=0; i<events.length; i++) {
+ this.storeStateEvent(events[i]);
+ }
+ },
+
+ getStateEvent: function getStateEvent(event_type, state_key) {
+ return this.state_events[event_type + state_key];
+ }
+ };
+
+ /***** Room Member Object *****/
+ var RoomMember = function RoomMember() {
+ this.event = {}; // the m.room.member event representing the RoomMember.
+ this.user = undefined; // the User
+ };
+
+ /***** User Object *****/
+ var User = function User() {
+ this.event = {}; // the m.presence event representing the User.
+ };
+
+ // rooms are stored here when they come in.
+ var rooms = {
+ // roomid: <Room>
+ };
+
+ console.log("Models inited.");
+
+ return {
+
+ getRoom: function(roomId) {
+ if(!rooms[roomId]) {
+ rooms[roomId] = new Room(roomId);
+ }
+ return rooms[roomId];
+ },
+
+ getRooms: function() {
+ return rooms;
+ },
+
+ /**
+ * Get the member object of a room member
+ * @param {String} room_id the room id
+ * @param {String} user_id the id of the user
+ * @returns {undefined | Object} the member object of this user in this room if he is part of the room
+ */
+ getMember: function(room_id, user_id) {
+ var room = this.getRoom(room_id);
+ return room.current_room_state.members[user_id];
+ }
+
+ };
+}]);
|