From 8bf3994c2e4726278355bc1398c4b9c94d242ad0 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 15 Aug 2014 10:20:14 +0100 Subject: [PATCH 1/7] Added event stream service which neatly blobs together requests / state for the event stream. This depends on matrix service to do the actual hit. Currently this has exactly the same behaviour as before. --- webclient/app.js | 3 +- .../components/matrix/event-stream-service.js | 69 +++++++++++++++++++ webclient/components/matrix/matrix-service.js | 9 +++ webclient/index.html | 1 + webclient/room/room-controller.js | 23 +++---- 5 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 webclient/components/matrix/event-stream-service.js diff --git a/webclient/app.js b/webclient/app.js index 0b613fa20..547431d9b 100644 --- a/webclient/app.js +++ b/webclient/app.js @@ -20,7 +20,8 @@ var matrixWebClient = angular.module('matrixWebClient', [ 'LoginController', 'RoomController', 'RoomsController', - 'matrixService' + 'matrixService', + 'eventStreamService' ]); matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider', diff --git a/webclient/components/matrix/event-stream-service.js b/webclient/components/matrix/event-stream-service.js new file mode 100644 index 000000000..0a3a12192 --- /dev/null +++ b/webclient/components/matrix/event-stream-service.js @@ -0,0 +1,69 @@ +/* +Copyright 2014 matrix.org + +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'; + +angular.module('eventStreamService', []) +.factory('eventStreamService', ['matrixService', function(matrixService) { + var settings = { + from: "END", + to: undefined, + limit: undefined, + shouldPoll: true + }; + + // interrupts the stream. Only valid if there is a stream conneciton + // open. + var interrupt = function(shouldPoll) { + console.log("[EventStream] interrupt("+shouldPoll+") "+ + JSON.stringify(settings)); + }; + + var saveStreamSettings = function() { + localStorage.setItem("streamSettings", JSON.stringify(settings)); + }; + + return { + // resume the stream from whereever it last got up to. Typically used + // when the page is opened. + resume: function() { + console.log("[EventStream] resume "+JSON.stringify(settings)); + // run the stream from the latest token + return matrixService.getEventStream(settings.from, 5000); + }, + + // pause the stream. Resuming it will continue from the current position + pause: function() { + console.log("[EventStream] pause "+JSON.stringify(settings)); + // kill any running stream + interrupt(false); + // save the latest token + saveStreamSettings(); + }, + + // stop the stream and wipe the position in the stream. Typically used + // when logging out. + stop: function() { + console.log("[EventStream] stop "+JSON.stringify(settings)); + // kill any running stream + interrupt(false); + // clear the latest token + settings.from = "END"; + saveStreamSettings(); + } + }; + +}]); diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 6d6611146..0a2d8005b 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -297,6 +297,15 @@ angular.module('matrixService', []) return doBaseRequest(config.identityServer, "POST", path, {}, data, headers); }, + // start listening on /events + getEventStream: function(from, timeout) { + var path = "/events"; + var params = { + from: from, + timeout: timeout + }; + return doRequest("GET", path, params); + }, // testLogin: function() { diff --git a/webclient/index.html b/webclient/index.html index e62ec3966..793b03d10 100644 --- a/webclient/index.html +++ b/webclient/index.html @@ -14,6 +14,7 @@ + diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index fb6e2025f..3f69a12c2 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -15,8 +15,8 @@ limitations under the License. */ angular.module('RoomController', []) -.controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', - function($scope, $http, $timeout, $routeParams, $location, matrixService) { +.controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', 'eventStreamService', + function($scope, $http, $timeout, $routeParams, $location, matrixService, eventStreamService) { 'use strict'; var MESSAGES_PER_PAGINATION = 10; $scope.room_id = $routeParams.room_id; @@ -83,13 +83,8 @@ angular.module('RoomController', []) }; var shortPoll = function() { - $http.get(matrixService.config().homeserver + matrixService.prefix + "/events", { - "params": { - "access_token": matrixService.config().access_token, - "from": $scope.state.events_from, - "timeout": 5000 - }}) - .then(function(response) { + eventStreamService.resume().then( + function(response) { $scope.state.stream_failure = undefined; console.log("Got response from "+$scope.state.events_from+" to "+response.data.end); $scope.state.events_from = response.data.end; @@ -103,10 +98,11 @@ angular.module('RoomController', []) else { $timeout(shortPoll, 0); } - }, function(response) { - $scope.state.stream_failure = response; + }, + function(error) { + $scope.state.stream_failure = error; - if (response.status == 403) { + if (error.status == 403) { $scope.stopPoll = true; } @@ -116,7 +112,8 @@ angular.module('RoomController', []) else { $timeout(shortPoll, 5000); } - }); + } + ); }; var updateMemberList = function(chunk) { From 5dbceaf5a40a7e90a4aca1a1612fa9ea13290a02 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 15 Aug 2014 11:31:13 +0100 Subject: [PATCH 2/7] Added event handler service which.. handles events. More specifically, it $broadcasts events depending on their type, and does processing on events (shuffling keys, adding events to $rootScope so displays will automatically update, sending delivery receipts, and so on). Some of this logic was previously contained in the RoomController, which fails the moment you add >1 room into the mix, hence requiring a Service to handle events, rather than having each individual controller maintain their part of the world. --- webclient/app.js | 3 +- .../matrix/event-handler-service.js | 87 +++++++++++++++++++ .../components/matrix/event-stream-service.js | 17 +++- webclient/index.html | 1 + webclient/room/room-controller.js | 48 +++++----- 5 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 webclient/components/matrix/event-handler-service.js diff --git a/webclient/app.js b/webclient/app.js index 547431d9b..bc78eb9d1 100644 --- a/webclient/app.js +++ b/webclient/app.js @@ -21,7 +21,8 @@ var matrixWebClient = angular.module('matrixWebClient', [ 'RoomController', 'RoomsController', 'matrixService', - 'eventStreamService' + 'eventStreamService', + 'eventHandlerService' ]); matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider', diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js new file mode 100644 index 000000000..30d7ab35c --- /dev/null +++ b/webclient/components/matrix/event-handler-service.js @@ -0,0 +1,87 @@ +/* +Copyright 2014 matrix.org + +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 handles what should happen when you get an event. This service does +not care where the event came from, it only needs enough context to be able to +process them. Events may be coming from the event stream, the REST API (via +direct GETs or via a pagination stream API), etc. + +Typically, this service will store events or broadcast them to any listeners +(e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope +if typically all the $on method would do is update its own $scope. +*/ +angular.module('eventHandlerService', []) +.factory('eventHandlerService', ['matrixService', '$rootScope', function(matrixService, $rootScope) { + var MSG_EVENT = "MSG_EVENT"; + var MEMBER_EVENT = "MEMBER_EVENT"; + var PRESENCE_EVENT = "PRESENCE_EVENT"; + + var handleMessage = function(event, isLiveEvent) { + if ("membership_target" in event.content) { + // event.user_id = event.content.membership_target; + } + + // $broadcast this, as controllers may want to do funky things such as + // scroll to the bottom, etc which cannot be expressed via simple $scope + // updates. + console.log("Bcast " + JSON.stringify(event)); + $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent); + }; + + var handleRoomMember = function(event, isLiveEvent) { + $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent); + }; + + var handlePresence = function(event, isLiveEvent) { + $rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent); + }; + + + return { + MSG_EVENT: MSG_EVENT, + MEMBER_EVENT: MEMBER_EVENT, + PRESENCE_EVENT: PRESENCE_EVENT, + + + handleEvent: function(event, isLiveEvent) { + switch(event.type) { + case "m.room.message": + handleMessage(event, isLiveEvent); + break; + case "m.room.member": + handleRoomMember(event, isLiveEvent); + break; + case "m.presence": + handlePresence(event, isLiveEvent); + break; + default: + console.log("Unable to handle event type " + event.type); + break; + } + }, + + // isLiveEvents determines whether notifications should be shown, whether + // messages get appended to the start/end of lists, etc. + handleEvents: function(events, isLiveEvents) { + for (var i=0; i + diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 3f69a12c2..301d3d644 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -15,8 +15,8 @@ limitations under the License. */ angular.module('RoomController', []) -.controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', 'eventStreamService', - function($scope, $http, $timeout, $routeParams, $location, matrixService, eventStreamService) { +.controller('RoomController', ['$scope', '$http', '$timeout', '$routeParams', '$location', 'matrixService', 'eventStreamService', 'eventHandlerService', + function($scope, $http, $timeout, $routeParams, $location, matrixService, eventStreamService, eventHandlerService) { 'use strict'; var MESSAGES_PER_PAGINATION = 10; $scope.room_id = $routeParams.room_id; @@ -42,34 +42,28 @@ angular.module('RoomController', []) },0); }; - var parseChunk = function(chunks, appendToStart) { - for (var i = 0; i < chunks.length; i++) { - var chunk = chunks[i]; - if (chunk.room_id == $scope.room_id && chunk.type == "m.room.message") { - if ("membership_target" in chunk.content) { - chunk.user_id = chunk.content.membership_target; - } - if (appendToStart) { - $scope.messages.unshift(chunk); - } - else { - $scope.messages.push(chunk); - scrollToBottom(); - } - } - else if (chunk.room_id == $scope.room_id && chunk.type == "m.room.member") { - updateMemberList(chunk); - } - else if (chunk.type === "m.presence") { - updatePresence(chunk); - } + $scope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) { + if (isLive) { + $scope.messages.push(event); + scrollToBottom(); } - }; + else { + $scope.messages.unshift(event); + } + }); + + $scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) { + updateMemberList(event); + }); + + $scope.$on(eventHandlerService.PRESENCE_EVENT, function(ngEvent, event, isLive) { + updatePresence(event); + }); var paginate = function(numItems) { matrixService.paginateBackMessages($scope.room_id, $scope.state.earliest_token, numItems).then( function(response) { - parseChunk(response.data.chunk, true); + eventHandlerService.handleEvents(response.data.chunk, false); $scope.state.earliest_token = response.data.end; if (response.data.chunk.length < MESSAGES_PER_PAGINATION) { // no more messages to paginate :( @@ -89,8 +83,8 @@ angular.module('RoomController', []) console.log("Got response from "+$scope.state.events_from+" to "+response.data.end); $scope.state.events_from = response.data.end; $scope.feedback = ""; - - parseChunk(response.data.chunk, false); + + eventHandlerService.handleEvents(response.data.chunk, true); if ($scope.stopPoll) { console.log("Stopping polling."); From c51cf4efcaad26695382924e142d9a1ad2658754 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 15 Aug 2014 12:51:20 +0100 Subject: [PATCH 3/7] Store messages in $rootScope so they can be accessed from multiple controllers without duplicated storage for each. This also gives updates. --- .../matrix/event-handler-service.js | 26 +++++++++++++++++-- webclient/room/room-controller.js | 7 +---- webclient/room/room.html | 8 +++--- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js index 30d7ab35c..f7411fd80 100644 --- a/webclient/components/matrix/event-handler-service.js +++ b/webclient/components/matrix/event-handler-service.js @@ -32,15 +32,37 @@ angular.module('eventHandlerService', []) var MEMBER_EVENT = "MEMBER_EVENT"; var PRESENCE_EVENT = "PRESENCE_EVENT"; + $rootScope.events = { + rooms: {}, // will contain roomId: { messages:[], members:[] } + }; + + var initRoom = function(room_id) { + console.log("Creating new handler entry for " + room_id); + $rootScope.events.rooms[room_id] = {}; + $rootScope.events.rooms[room_id].messages = []; + $rootScope.events.rooms[room_id].members = []; + } + var handleMessage = function(event, isLiveEvent) { if ("membership_target" in event.content) { - // event.user_id = event.content.membership_target; + event.user_id = event.content.membership_target; } + if (!(event.room_id in $rootScope.events.rooms)) { + initRoom(event.room_id); + } + + if (isLiveEvent) { + $rootScope.events.rooms[event.room_id].messages.push(event); + } + else { + $rootScope.events.rooms[event.room_id].messages.unshift(event); + } + + // TODO send delivery receipt if isLiveEvent // $broadcast this, as controllers may want to do funky things such as // scroll to the bottom, etc which cannot be expressed via simple $scope // updates. - console.log("Bcast " + JSON.stringify(event)); $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent); }; diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 301d3d644..4c5415eac 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -28,7 +28,6 @@ angular.module('RoomController', []) can_paginate: true, // this is toggled off when we run out of items stream_failure: undefined // the response when the stream fails }; - $scope.messages = []; $scope.members = {}; $scope.stopPoll = false; @@ -43,13 +42,9 @@ angular.module('RoomController', []) }; $scope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) { - if (isLive) { - $scope.messages.push(event); + if (isLive && event.room_id === $scope.room_id) { scrollToBottom(); } - else { - $scope.messages.unshift(event); - } }); $scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) { diff --git a/webclient/room/room.html b/webclient/room/room.html index 3b9ba713d..93917071d 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -22,14 +22,14 @@
- +
-
{{ members[msg.user_id].displayname || msg.user_id }}
+
{{ members[msg.user_id].displayname || msg.user_id }}
{{ msg.content.hsob_ts | date:'MMM d HH:mm:ss' }}
+ ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id === state.user_id"/>
@@ -40,7 +40,7 @@
+ ng-hide="events.rooms[room_id].messages[$index - 1].user_id === msg.user_id || msg.user_id !== state.user_id"/>
From 7ddb7a5cbbe9e4576742dc060ba35ca863b8d8b0 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 15 Aug 2014 13:43:07 +0100 Subject: [PATCH 4/7] Event streaming now happens on an app level, rather than a per-room level. Make eventStreamService manage it's own repolling provided no one calls stop() on it. Couple the stream with eventHandlerService so any controller can just blithely call eventStreamService.resume() and expect to 'get stuff' without having to handle promises (though resume() still returns a promise for that request and proxies it through $q). Kill and reset the stream if you logout. --- webclient/app-controller.js | 16 +++-- webclient/app.js | 6 +- .../components/matrix/event-stream-service.js | 62 +++++++++++++++++-- webclient/login/login-controller.js | 7 ++- webclient/room/room-controller.js | 4 -- 5 files changed, 76 insertions(+), 19 deletions(-) diff --git a/webclient/app-controller.js b/webclient/app-controller.js index 086fa3d94..7f5f93ef7 100644 --- a/webclient/app-controller.js +++ b/webclient/app-controller.js @@ -21,8 +21,8 @@ limitations under the License. 'use strict'; angular.module('MatrixWebClientController', ['matrixService']) -.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', - function($scope, $location, $rootScope, matrixService) { +.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', 'eventStreamService', + function($scope, $location, $rootScope, matrixService, eventStreamService) { // Check current URL to avoid to display the logout button on the login page $scope.location = $location.path(); @@ -44,11 +44,15 @@ angular.module('MatrixWebClientController', ['matrixService']) else { $scope.config = matrixService.config(); } - }; - + }; + + eventStreamService.resume(); // Logs the user out $scope.logout = function() { + // kill the event stream + eventStreamService.stop(); + // Clean permanent data matrixService.setConfig({}); matrixService.saveConfig(); @@ -57,7 +61,7 @@ angular.module('MatrixWebClientController', ['matrixService']) $location.path("login"); }; - // Listen to the event indicating that the access token is no more valid. + // Listen to the event indicating that the access token is no longer valid. // In this case, the user needs to log in again. $scope.$on("M_UNKNOWN_TOKEN", function() { console.log("Invalid access token -> log user out"); @@ -65,4 +69,4 @@ angular.module('MatrixWebClientController', ['matrixService']) }); }]); - \ No newline at end of file + diff --git a/webclient/app.js b/webclient/app.js index bc78eb9d1..6e0351067 100644 --- a/webclient/app.js +++ b/webclient/app.js @@ -61,12 +61,16 @@ matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider', $httpProvider.interceptors.push('AccessTokenInterceptor'); }]); -matrixWebClient.run(['$location', 'matrixService' , function($location, matrixService) { +matrixWebClient.run(['$location', 'matrixService', 'eventStreamService', function($location, matrixService, eventStreamService) { // If we have no persistent login information, go to the login page var config = matrixService.config(); if (!config || !config.access_token) { + eventStreamService.stop(); $location.path("login"); } + else { + eventStreamService.resume(); + } }]); matrixWebClient diff --git a/webclient/components/matrix/event-stream-service.js b/webclient/components/matrix/event-stream-service.js index 1cb9960b9..97018df88 100644 --- a/webclient/components/matrix/event-stream-service.js +++ b/webclient/components/matrix/event-stream-service.js @@ -19,19 +19,21 @@ limitations under the License. /* This service manages where in the event stream the web client currently is and provides methods to resume/pause/stop the event stream. This service is not -responsible for parsing event data. For that, see the eventDataHandler. +responsible for parsing event data. For that, see the eventHandlerService. */ angular.module('eventStreamService', []) -.factory('eventStreamService', ['matrixService', function(matrixService) { +.factory('eventStreamService', ['$q', '$timeout', 'matrixService', 'eventHandlerService', function($q, $timeout, matrixService, eventHandlerService) { var END = "END"; var START = "START"; var TIMEOUT_MS = 5000; + var ERR_TIMEOUT_MS = 5000; var settings = { from: "END", to: undefined, limit: undefined, - shouldPoll: true + shouldPoll: true, + isActive: false }; // interrupts the stream. Only valid if there is a stream conneciton @@ -39,19 +41,69 @@ angular.module('eventStreamService', []) var interrupt = function(shouldPoll) { console.log("p[EventStream] interrupt("+shouldPoll+") "+ JSON.stringify(settings)); + settings.shouldPoll = shouldPoll; + settings.isActive = false; }; var saveStreamSettings = function() { localStorage.setItem("streamSettings", JSON.stringify(settings)); }; + var startEventStream = function() { + settings.shouldPoll = true; + settings.isActive = true; + var deferred = $q.defer(); + // run the stream from the latest token + matrixService.getEventStream(settings.from, TIMEOUT_MS).then( + function(response) { + if (!settings.isActive) { + console.log("[EventStream] Got response but now inactive. Dropping data."); + return; + } + + settings.from = response.data.end; + + console.log("[EventStream] Got response from "+settings.from+" to "+response.data.end); + eventHandlerService.handleEvents(response.data.chunk, true); + + deferred.resolve(response); + + if (settings.shouldPoll) { + $timeout(startEventStream, 0); + } + else { + console.log("[EventStream] Stopping poll."); + } + }, + function(error) { + if (error.status == 403) { + settings.shouldPoll = false; + } + + deferred.reject(error); + + if (settings.shouldPoll) { + $timeout(startEventStream, ERR_TIMEOUT_MS); + } + else { + console.log("[EventStream] Stopping polling."); + } + } + ); + return deferred.promise; + }; + return { // resume the stream from whereever it last got up to. Typically used // when the page is opened. resume: function() { + if (settings.isActive) { + console.log("[EventStream] Already active, ignoring resume()"); + return; + } + console.log("[EventStream] resume "+JSON.stringify(settings)); - // run the stream from the latest token - return matrixService.getEventStream(settings.from, TIMEOUT_MS); + return startEventStream(); }, // pause the stream. Resuming it will continue from the current position diff --git a/webclient/login/login-controller.js b/webclient/login/login-controller.js index 8bd6a4e84..aa928ef48 100644 --- a/webclient/login/login-controller.js +++ b/webclient/login/login-controller.js @@ -1,6 +1,6 @@ angular.module('LoginController', ['matrixService']) -.controller('LoginController', ['$scope', '$location', 'matrixService', - function($scope, $location, matrixService) { +.controller('LoginController', ['$scope', '$location', 'matrixService', 'eventStreamService', + function($scope, $location, matrixService, eventStreamService) { 'use strict'; @@ -51,7 +51,7 @@ angular.module('LoginController', ['matrixService']) // And permanently save it matrixService.saveConfig(); - + eventStreamService.resume(); // Go to the user's rooms list page $location.path("rooms"); }, @@ -83,6 +83,7 @@ angular.module('LoginController', ['matrixService']) access_token: response.data.access_token }); matrixService.saveConfig(); + eventStreamService.resume(); $location.path("rooms"); } else { diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 4c5415eac..49c73ff4b 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -119,7 +119,6 @@ angular.module('RoomController', []) function(response) { var member = $scope.members[chunk.target_user_id]; if (member !== undefined) { - console.log("Updated displayname "+chunk.target_user_id+" to " + response.data.displayname); member.displayname = response.data.displayname; } } @@ -128,7 +127,6 @@ angular.module('RoomController', []) function(response) { var member = $scope.members[chunk.target_user_id]; if (member !== undefined) { - console.log("Updated image for "+chunk.target_user_id+" to " + response.data.avatar_url); member.avatar_url = response.data.avatar_url; } } @@ -204,8 +202,6 @@ angular.module('RoomController', []) matrixService.join($scope.room_id).then( function() { console.log("Joined room "+$scope.room_id); - // Now start reading from the stream - $timeout(shortPoll, 0); // Get the current member list matrixService.getMemberList($scope.room_id).then( From 5ac87292c4810d816f16b1d2dd6a0dac643812e5 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 15 Aug 2014 14:06:48 +0100 Subject: [PATCH 5/7] Remove old polling stuff from RoomController. Added service comments. Do not start the event stream on startup unless you have credentials. --- webclient/app-controller.js | 4 +- .../components/matrix/event-stream-service.js | 9 +++-- webclient/components/matrix/matrix-service.js | 12 ++++++ webclient/room/room-controller.js | 40 ------------------- 4 files changed, 20 insertions(+), 45 deletions(-) diff --git a/webclient/app-controller.js b/webclient/app-controller.js index 7f5f93ef7..7fa87e30c 100644 --- a/webclient/app-controller.js +++ b/webclient/app-controller.js @@ -46,7 +46,9 @@ angular.module('MatrixWebClientController', ['matrixService']) } }; - eventStreamService.resume(); + if (matrixService.config()) { + eventStreamService.resume(); + } // Logs the user out $scope.logout = function() { diff --git a/webclient/components/matrix/event-stream-service.js b/webclient/components/matrix/event-stream-service.js index 97018df88..9f678e845 100644 --- a/webclient/components/matrix/event-stream-service.js +++ b/webclient/components/matrix/event-stream-service.js @@ -17,9 +17,10 @@ limitations under the License. 'use strict'; /* -This service manages where in the event stream the web client currently is and -provides methods to resume/pause/stop the event stream. This service is not -responsible for parsing event data. For that, see the eventHandlerService. +This service manages where in the event stream the web client currently is, +repolling the event stream, and provides methods to resume/pause/stop the event +stream. This service is not responsible for parsing event data. For that, see +the eventHandlerService. */ angular.module('eventStreamService', []) .factory('eventStreamService', ['$q', '$timeout', 'matrixService', 'eventHandlerService', function($q, $timeout, matrixService, eventHandlerService) { @@ -39,7 +40,7 @@ angular.module('eventStreamService', []) // interrupts the stream. Only valid if there is a stream conneciton // open. var interrupt = function(shouldPoll) { - console.log("p[EventStream] interrupt("+shouldPoll+") "+ + console.log("[EventStream] interrupt("+shouldPoll+") "+ JSON.stringify(settings)); settings.shouldPoll = shouldPoll; settings.isActive = false; diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 0a2d8005b..0cc85db28 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -16,6 +16,12 @@ limitations under the License. 'use strict'; +/* +This service wraps up Matrix API calls. + +This serves to isolate the caller from changes to the underlying url paths, as +well as attach common params (e.g. access_token) to requests. +*/ angular.module('matrixService', []) .factory('matrixService', ['$http', '$q', '$rootScope', function($http, $q, $rootScope) { @@ -36,10 +42,16 @@ angular.module('matrixService', []) var MAPPING_PREFIX = "alias_for_"; var doRequest = function(method, path, params, data) { + if (!config) { + console.warn("No config exists. Cannot perform request to "+path); + return; + } + // Inject the access token if (!params) { params = {}; } + params.access_token = config.access_token; return doBaseRequest(config.homeserver, method, path, params, data, undefined); diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 49c73ff4b..0d54c6f4d 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -29,7 +29,6 @@ angular.module('RoomController', []) stream_failure: undefined // the response when the stream fails }; $scope.members = {}; - $scope.stopPoll = false; $scope.imageURLToSend = ""; $scope.userIDToInvite = ""; @@ -71,40 +70,6 @@ angular.module('RoomController', []) ) }; - var shortPoll = function() { - eventStreamService.resume().then( - function(response) { - $scope.state.stream_failure = undefined; - console.log("Got response from "+$scope.state.events_from+" to "+response.data.end); - $scope.state.events_from = response.data.end; - $scope.feedback = ""; - - eventHandlerService.handleEvents(response.data.chunk, true); - - if ($scope.stopPoll) { - console.log("Stopping polling."); - } - else { - $timeout(shortPoll, 0); - } - }, - function(error) { - $scope.state.stream_failure = error; - - if (error.status == 403) { - $scope.stopPoll = true; - } - - if ($scope.stopPoll) { - console.log("Stopping polling."); - } - else { - $timeout(shortPoll, 5000); - } - } - ); - }; - var updateMemberList = function(chunk) { var isNewMember = !(chunk.target_user_id in $scope.members); if (isNewMember) { @@ -260,9 +225,4 @@ angular.module('RoomController', []) $scope.loadMoreHistory = function() { paginate(MESSAGES_PER_PAGINATION); }; - - $scope.$on('$destroy', function(e) { - console.log("onDestroyed: Stopping poll."); - $scope.stopPoll = true; - }); }]); From 5c88e575556ce54f955f76d39d77019a207ca89b Mon Sep 17 00:00:00 2001 From: "Paul \"LeoNerd\" Evans" Date: Fri, 15 Aug 2014 15:06:36 +0100 Subject: [PATCH 6/7] Create a room-nuking script as it's useful for emergency debugging --- nuke-room-from-db.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 nuke-room-from-db.sh diff --git a/nuke-room-from-db.sh b/nuke-room-from-db.sh new file mode 100755 index 000000000..58c036c89 --- /dev/null +++ b/nuke-room-from-db.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +## CAUTION: +## This script will remove (hopefully) all trace of the given room ID from +## your homeserver.db + +## Do not run it lightly. + +ROOMID="$1" + +sqlite3 homeserver.db < Date: Fri, 15 Aug 2014 15:44:53 +0100 Subject: [PATCH 7/7] Fix imsync's SELECT query to only find the rooms I'm actually joined in, not every room I have ever joined --- synapse/storage/roommember.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py index ef73be4af..c45d128f1 100644 --- a/synapse/storage/roommember.py +++ b/synapse/storage/roommember.py @@ -111,9 +111,12 @@ class RoomMemberStore(SQLBaseStore): for membership in membership_list: args.append(membership) + # sub-select finds the row ID for the most recent (i.e. current) + # state change of this user per room, then the outer select finds those query = ("SELECT room_id, membership FROM room_memberships" - + " WHERE user_id=? AND " + where_membership - + " GROUP BY room_id ORDER BY id DESC") + + " WHERE id IN (SELECT MAX(id) FROM room_memberships" + + " WHERE user_id=? GROUP BY room_id)" + + " AND " + where_membership) return self._execute( self.cursor_to_dict, query, *args )