mirror of
https://github.com/element-hq/synapse.git
synced 2024-12-20 19:10:45 +03:00
Merge branch 'develop' of github.com:matrix-org/synapse into erikj-perf
Conflicts: synapse/app/homeserver.py
This commit is contained in:
commit
7f058c5ff7
70 changed files with 818 additions and 1658 deletions
|
@ -1,3 +1,11 @@
|
||||||
|
Changes in synapse 0.6.1 (2015-01-07)
|
||||||
|
=====================================
|
||||||
|
|
||||||
|
* Major optimizations to improve performance of initial sync and event sending
|
||||||
|
in large rooms (by up to 10x)
|
||||||
|
* Media repository now includes a Content-Length header on media downloads.
|
||||||
|
* Improve quality of thumbnails by changing resizing algorithm.
|
||||||
|
|
||||||
Changes in synapse 0.6.0 (2014-12-16)
|
Changes in synapse 0.6.0 (2014-12-16)
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,15 @@ To install the synapse homeserver run::
|
||||||
This installs synapse, along with the libraries it uses, into
|
This installs synapse, along with the libraries it uses, into
|
||||||
``$HOME/.local/lib/`` on Linux or ``$HOME/Library/Python/2.7/lib/`` on OSX.
|
``$HOME/.local/lib/`` on Linux or ``$HOME/Library/Python/2.7/lib/`` on OSX.
|
||||||
|
|
||||||
|
Your python may not give priority to locally installed libraries over system
|
||||||
|
libraries, in which case you must add your local packages to your python path::
|
||||||
|
|
||||||
|
$ # on Linux:
|
||||||
|
$ export PYTHONPATH=$HOME/.local/lib/python2.7/site-packages:$PYTHONPATH
|
||||||
|
|
||||||
|
$ # on OSX:
|
||||||
|
$ export PYTHONPATH=$HOME/Library/Python/2.7/lib/python/site-packages:$PYTHONPATH
|
||||||
|
|
||||||
For reliable VoIP calls to be routed via this homeserver, you MUST configure
|
For reliable VoIP calls to be routed via this homeserver, you MUST configure
|
||||||
a TURN server. See docs/turn-howto.rst for details.
|
a TURN server. See docs/turn-howto.rst for details.
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ resulting conflicts during the upgrade process.
|
||||||
Before running the command the homeserver should be first completely
|
Before running the command the homeserver should be first completely
|
||||||
shutdown. To run it, simply specify the location of the database, e.g.:
|
shutdown. To run it, simply specify the location of the database, e.g.:
|
||||||
|
|
||||||
./database-prepare-for-0.5.0.sh "homeserver.db"
|
./scripts/database-prepare-for-0.5.0.sh "homeserver.db"
|
||||||
|
|
||||||
Once this has successfully completed it will be safe to restart the
|
Once this has successfully completed it will be safe to restart the
|
||||||
homeserver. You may notice that the homeserver takes a few seconds longer to
|
homeserver. You may notice that the homeserver takes a few seconds longer to
|
||||||
|
@ -147,7 +147,7 @@ rooms the home server was a member of and room alias mappings.
|
||||||
Before running the command the homeserver should be first completely
|
Before running the command the homeserver should be first completely
|
||||||
shutdown. To run it, simply specify the location of the database, e.g.:
|
shutdown. To run it, simply specify the location of the database, e.g.:
|
||||||
|
|
||||||
./database-prepare-for-0.0.1.sh "homeserver.db"
|
./scripts/database-prepare-for-0.0.1.sh "homeserver.db"
|
||||||
|
|
||||||
Once this has successfully completed it will be safe to restart the
|
Once this has successfully completed it will be safe to restart the
|
||||||
homeserver. You may notice that the homeserver takes a few seconds longer to
|
homeserver. You may notice that the homeserver takes a few seconds longer to
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
0.6.0
|
0.6.1b
|
||||||
|
|
|
@ -23,14 +23,27 @@ import argparse
|
||||||
from synapse.events import FrozenEvent
|
from synapse.events import FrozenEvent
|
||||||
|
|
||||||
|
|
||||||
def make_graph(db_name, room_id, file_prefix):
|
def make_graph(db_name, room_id, file_prefix, limit):
|
||||||
conn = sqlite3.connect(db_name)
|
conn = sqlite3.connect(db_name)
|
||||||
|
|
||||||
c = conn.execute(
|
sql = (
|
||||||
"SELECT json FROM event_json where room_id = ?",
|
"SELECT json FROM event_json as j "
|
||||||
(room_id,)
|
"INNER JOIN events as e ON e.event_id = j.event_id "
|
||||||
|
"WHERE j.room_id = ?"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
args = [room_id]
|
||||||
|
|
||||||
|
if limit:
|
||||||
|
sql += (
|
||||||
|
" ORDER BY topological_ordering DESC, stream_ordering DESC "
|
||||||
|
"LIMIT ?"
|
||||||
|
)
|
||||||
|
|
||||||
|
args.append(limit)
|
||||||
|
|
||||||
|
c = conn.execute(sql, args)
|
||||||
|
|
||||||
events = [FrozenEvent(json.loads(e[0])) for e in c.fetchall()]
|
events = [FrozenEvent(json.loads(e[0])) for e in c.fetchall()]
|
||||||
|
|
||||||
events.sort(key=lambda e: e.depth)
|
events.sort(key=lambda e: e.depth)
|
||||||
|
@ -128,11 +141,16 @@ if __name__ == "__main__":
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-p", "--prefix", dest="prefix",
|
"-p", "--prefix", dest="prefix",
|
||||||
help="String to prefix output files with"
|
help="String to prefix output files with",
|
||||||
|
default="graph_output"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-l", "--limit",
|
||||||
|
help="Only retrieve the last N events.",
|
||||||
)
|
)
|
||||||
parser.add_argument('db')
|
parser.add_argument('db')
|
||||||
parser.add_argument('room')
|
parser.add_argument('room')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
make_graph(args.db, args.room, args.prefix)
|
make_graph(args.db, args.room, args.prefix, args.limit)
|
|
@ -1,17 +0,0 @@
|
||||||
.loggedin {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-spacing:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,td
|
|
||||||
{
|
|
||||||
padding:5px;
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
<div>
|
|
||||||
<p>This room creation / message sending demo requires a home server to be running on http://localhost:8008</p>
|
|
||||||
</div>
|
|
||||||
<form class="loginForm">
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
<div class="loggedin">
|
|
||||||
<form class="createRoomForm">
|
|
||||||
<input type="text" id="roomAlias" placeholder="Room alias (optional)"></input>
|
|
||||||
<input type="button" class="createRoom" value="Create Room"></input>
|
|
||||||
</form>
|
|
||||||
<form class="sendMessageForm">
|
|
||||||
<input type="text" id="roomId" placeholder="Room ID"></input>
|
|
||||||
<input type="text" id="messageBody" placeholder="Message body"></input>
|
|
||||||
<input type="button" class="sendMessage" value="Send Message"></input>
|
|
||||||
</form>
|
|
||||||
<table id="rooms">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Room ID</th>
|
|
||||||
<th>My state</th>
|
|
||||||
<th>Room Alias</th>
|
|
||||||
<th>Latest message</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var showLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
getCurrentRoomList();
|
|
||||||
$(".loggedin").css({visibility: "visible"});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var getCurrentRoomList = function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
var rooms = data.rooms;
|
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
|
||||||
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
|
|
||||||
addRoom(rooms[i]);
|
|
||||||
}
|
|
||||||
}).fail(function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.createRoom').live('click', function() {
|
|
||||||
var roomAlias = $("#roomAlias").val();
|
|
||||||
var data = {};
|
|
||||||
if (roomAlias.length > 0) {
|
|
||||||
data.room_alias_name = roomAlias;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
data.membership = "join"; // you are automatically joined into every room you make.
|
|
||||||
data.latest_message = "";
|
|
||||||
addRoom(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var addRoom = function(data) {
|
|
||||||
row = "<tr>" +
|
|
||||||
"<td>"+data.room_id+"</td>" +
|
|
||||||
"<td>"+data.membership+"</td>" +
|
|
||||||
"<td>"+data.room_alias+"</td>" +
|
|
||||||
"<td>"+data.latest_message+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
$("#rooms").append(row);
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.sendMessage').live('click', function() {
|
|
||||||
var roomId = $("#roomId").val();
|
|
||||||
var body = $("#messageBody").val();
|
|
||||||
var msgId = $.now();
|
|
||||||
|
|
||||||
if (roomId.length === 0 || body.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
msgtype: "m.text",
|
|
||||||
body: body
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#messageBody").val("");
|
|
||||||
// wipe the table and reload it. Using the event stream would be the best
|
|
||||||
// solution but that is out of scope of this fiddle.
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
getCurrentRoomList();
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
.loggedin {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-spacing:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,td
|
|
||||||
{
|
|
||||||
padding:5px;
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
<div>
|
|
||||||
<p>This event stream demo requires a home server to be running on http://localhost:8008</p>
|
|
||||||
</div>
|
|
||||||
<form class="loginForm">
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
<div class="loggedin">
|
|
||||||
<form class="sendMessageForm">
|
|
||||||
<input type="button" class="sendMessage" value="Send random message"></input>
|
|
||||||
</form>
|
|
||||||
<p id="streamErrorText"></p>
|
|
||||||
<table id="rooms">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Room ID</th>
|
|
||||||
<th>Latest message</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var eventStreamInfo = {
|
|
||||||
from: "END"
|
|
||||||
};
|
|
||||||
|
|
||||||
var roomInfo = [];
|
|
||||||
|
|
||||||
var longpollEventStream = function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$from", eventStreamInfo.from);
|
|
||||||
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
eventStreamInfo.from = data.end;
|
|
||||||
|
|
||||||
var hasNewLatestMessage = false;
|
|
||||||
for (var i=0; i<data.chunk.length; ++i) {
|
|
||||||
if (data.chunk[i].type === "m.room.message") {
|
|
||||||
for (var j=0; j<roomInfo.length; ++j) {
|
|
||||||
if (roomInfo[j].room_id === data.chunk[i].room_id) {
|
|
||||||
roomInfo[j].latest_message = data.chunk[i].content.body;
|
|
||||||
hasNewLatestMessage = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNewLatestMessage) {
|
|
||||||
setRooms(roomInfo);
|
|
||||||
}
|
|
||||||
$("#streamErrorText").text("");
|
|
||||||
longpollEventStream();
|
|
||||||
}).fail(function(err) {
|
|
||||||
$("#streamErrorText").text("Event stream error: "+JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
setTimeout(longpollEventStream, 5000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var showLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
longpollEventStream();
|
|
||||||
getCurrentRoomList();
|
|
||||||
$(".loggedin").css({visibility: "visible"});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var getCurrentRoomList = function() {
|
|
||||||
$("#roomId").val("");
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
var rooms = data.rooms;
|
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
|
||||||
if ("messages" in rooms[i]) {
|
|
||||||
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
roomInfo = rooms;
|
|
||||||
setRooms(roomInfo);
|
|
||||||
}).fail(function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.sendMessage').live('click', function() {
|
|
||||||
if (roomInfo.length === 0) {
|
|
||||||
alert("There is no room to send a message to!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = Math.floor(Math.random() * roomInfo.length);
|
|
||||||
|
|
||||||
sendMessage(roomInfo[index].room_id);
|
|
||||||
});
|
|
||||||
|
|
||||||
var sendMessage = function(roomId) {
|
|
||||||
var body = "jsfiddle message @" + $.now();
|
|
||||||
|
|
||||||
if (roomId.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
msgtype: "m.text",
|
|
||||||
body: body
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#messageBody").val("");
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var setRooms = function(roomList) {
|
|
||||||
// wipe existing entries
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
|
|
||||||
var rows = "";
|
|
||||||
for (var i=0; i<roomList.length; ++i) {
|
|
||||||
row = "<tr>" +
|
|
||||||
"<td>"+roomList[i].room_id+"</td>" +
|
|
||||||
"<td>"+roomList[i].latest_message+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
rows += row;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#rooms").append(rows);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
.roomListDashboard, .roomContents, .sendMessageForm {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomList {
|
|
||||||
background-color: #909090;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageWrapper {
|
|
||||||
background-color: #EEEEEE;
|
|
||||||
height: 400px;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.membersWrapper {
|
|
||||||
background-color: #EEEEEE;
|
|
||||||
height: 200px;
|
|
||||||
width: 50%;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textEntry {
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-spacing:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,td
|
|
||||||
{
|
|
||||||
padding:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roomList tr:not(:first-child):hover {
|
|
||||||
background-color: orange;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
name: Example Matrix Client
|
|
||||||
description: Includes login, live event streaming, creating rooms, sending messages and viewing member lists.
|
|
||||||
authors:
|
|
||||||
- matrix.org
|
|
||||||
resources:
|
|
||||||
- http://matrix.org
|
|
||||||
normalize_css: no
|
|
|
@ -1,56 +0,0 @@
|
||||||
<div class="signUp">
|
|
||||||
<p>Matrix example application: Requires a local home server running at http://localhost:8008</p>
|
|
||||||
<form class="registrationForm">
|
|
||||||
<p>No account? Register:</p>
|
|
||||||
<input type="text" id="userReg" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordReg" placeholder="Password"></input>
|
|
||||||
<input type="button" class="register" value="Register"></input>
|
|
||||||
</form>
|
|
||||||
<form class="loginForm">
|
|
||||||
<p>Got an account? Login:</p>
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="roomListDashboard">
|
|
||||||
<form class="createRoomForm">
|
|
||||||
<input type="text" id="roomAlias" placeholder="Room alias"></input>
|
|
||||||
<input type="button" class="createRoom" value="Create Room"></input>
|
|
||||||
</form>
|
|
||||||
<table id="rooms" class="roomList">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Room</th>
|
|
||||||
<th>My state</th>
|
|
||||||
<th>Latest message</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="roomContents">
|
|
||||||
<p id="roomName">Select a room</p>
|
|
||||||
<div class="messageWrapper">
|
|
||||||
<table id="messages">
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<form class="sendMessageForm">
|
|
||||||
<input type="text" class="textEntry" id="body" placeholder="Enter text here..." onkeydown="javascript:if (event.keyCode == 13) document.getElementById('sendMsg').focus()"></input>
|
|
||||||
<input type="button" class="sendMessage" id="sendMsg" value="Send"></input>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>Member list:</p>
|
|
||||||
<div class="membersWrapper">
|
|
||||||
<table id="members">
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,327 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var eventStreamInfo = {
|
|
||||||
from: "END"
|
|
||||||
};
|
|
||||||
|
|
||||||
var roomInfo = [];
|
|
||||||
var memberInfo = [];
|
|
||||||
var viewingRoomId;
|
|
||||||
|
|
||||||
// ************** Event Streaming **************
|
|
||||||
var longpollEventStream = function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$from", eventStreamInfo.from);
|
|
||||||
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
eventStreamInfo.from = data.end;
|
|
||||||
|
|
||||||
var hasNewLatestMessage = false;
|
|
||||||
var updatedMemberList = false;
|
|
||||||
var i=0;
|
|
||||||
var j=0;
|
|
||||||
for (i=0; i<data.chunk.length; ++i) {
|
|
||||||
if (data.chunk[i].type === "m.room.message") {
|
|
||||||
console.log("Got new message: " + JSON.stringify(data.chunk[i]));
|
|
||||||
if (viewingRoomId === data.chunk[i].room_id) {
|
|
||||||
addMessage(data.chunk[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j=0; j<roomInfo.length; ++j) {
|
|
||||||
if (roomInfo[j].room_id === data.chunk[i].room_id) {
|
|
||||||
roomInfo[j].latest_message = data.chunk[i].content.body;
|
|
||||||
hasNewLatestMessage = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (data.chunk[i].type === "m.room.member") {
|
|
||||||
if (viewingRoomId === data.chunk[i].room_id) {
|
|
||||||
console.log("Got new member: " + JSON.stringify(data.chunk[i]));
|
|
||||||
addMessage(data.chunk[i]);
|
|
||||||
for (j=0; j<memberInfo.length; ++j) {
|
|
||||||
if (memberInfo[j].state_key === data.chunk[i].state_key) {
|
|
||||||
memberInfo[j] = data.chunk[i];
|
|
||||||
updatedMemberList = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!updatedMemberList) {
|
|
||||||
memberInfo.push(data.chunk[i]);
|
|
||||||
updatedMemberList = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data.chunk[i].state_key === accountInfo.user_id) {
|
|
||||||
getCurrentRoomList(); // update our join/invite list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Discarding: " + JSON.stringify(data.chunk[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNewLatestMessage) {
|
|
||||||
setRooms(roomInfo);
|
|
||||||
}
|
|
||||||
if (updatedMemberList) {
|
|
||||||
$("#members").empty();
|
|
||||||
for (i=0; i<memberInfo.length; ++i) {
|
|
||||||
addMember(memberInfo[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
longpollEventStream();
|
|
||||||
}).fail(function(err) {
|
|
||||||
setTimeout(longpollEventStream, 5000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ************** Registration and Login **************
|
|
||||||
var onLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
longpollEventStream();
|
|
||||||
getCurrentRoomList();
|
|
||||||
$(".roomListDashboard").css({visibility: "visible"});
|
|
||||||
$(".roomContents").css({visibility: "visible"});
|
|
||||||
$(".signUp").css({display: "none"});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
onLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert("Unable to login: is the home server running?");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.register').live('click', function() {
|
|
||||||
var user = $("#userReg").val();
|
|
||||||
var password = $("#passwordReg").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
onLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var msg = "Is the home server running?";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson !== null) {
|
|
||||||
msg = errJson.error;
|
|
||||||
}
|
|
||||||
alert("Unable to register: "+msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ************** Creating a room ******************
|
|
||||||
$('.createRoom').live('click', function() {
|
|
||||||
var roomAlias = $("#roomAlias").val();
|
|
||||||
var data = {};
|
|
||||||
if (roomAlias.length > 0) {
|
|
||||||
data.room_alias_name = roomAlias;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(response) {
|
|
||||||
$("#roomAlias").val("");
|
|
||||||
response.membership = "join"; // you are automatically joined into every room you make.
|
|
||||||
response.latest_message = "";
|
|
||||||
|
|
||||||
roomInfo.push(response);
|
|
||||||
setRooms(roomInfo);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ************** Getting current state **************
|
|
||||||
var getCurrentRoomList = function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
var rooms = data.rooms;
|
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
|
||||||
if ("messages" in rooms[i]) {
|
|
||||||
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
roomInfo = rooms;
|
|
||||||
setRooms(roomInfo);
|
|
||||||
}).fail(function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var loadRoomContent = function(roomId) {
|
|
||||||
console.log("loadRoomContent " + roomId);
|
|
||||||
viewingRoomId = roomId;
|
|
||||||
$("#roomName").text("Room: "+roomId);
|
|
||||||
$(".sendMessageForm").css({visibility: "visible"});
|
|
||||||
getMessages(roomId);
|
|
||||||
getMemberList(roomId);
|
|
||||||
};
|
|
||||||
|
|
||||||
var getMessages = function(roomId) {
|
|
||||||
$("#messages").empty();
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
|
|
||||||
encodeURIComponent(roomId) + "/messages?access_token=" + accountInfo.access_token + "&from=END&dir=b&limit=10";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
for (var i=data.chunk.length-1; i>=0; --i) {
|
|
||||||
addMessage(data.chunk[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var getMemberList = function(roomId) {
|
|
||||||
$("#members").empty();
|
|
||||||
memberInfo = [];
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
|
|
||||||
encodeURIComponent(roomId) + "/members?access_token=" + accountInfo.access_token;
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
for (var i=0; i<data.chunk.length; ++i) {
|
|
||||||
memberInfo.push(data.chunk[i]);
|
|
||||||
addMember(data.chunk[i]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ************** Sending messages **************
|
|
||||||
$('.sendMessage').live('click', function() {
|
|
||||||
if (viewingRoomId === undefined) {
|
|
||||||
alert("There is no room to send a message to!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var body = $("#body").val();
|
|
||||||
sendMessage(viewingRoomId, body);
|
|
||||||
});
|
|
||||||
|
|
||||||
var sendMessage = function(roomId, body) {
|
|
||||||
var msgId = $.now();
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
msgtype: "m.text",
|
|
||||||
body: body
|
|
||||||
};
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#body").val("");
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ************** Navigation and DOM manipulation **************
|
|
||||||
var setRooms = function(roomList) {
|
|
||||||
// wipe existing entries
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
|
|
||||||
var rows = "";
|
|
||||||
for (var i=0; i<roomList.length; ++i) {
|
|
||||||
row = "<tr>" +
|
|
||||||
"<td>"+roomList[i].room_id+"</td>" +
|
|
||||||
"<td>"+roomList[i].membership+"</td>" +
|
|
||||||
"<td>"+roomList[i].latest_message+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
rows += row;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#rooms").append(rows);
|
|
||||||
|
|
||||||
$('#rooms').find("tr").click(function(){
|
|
||||||
var roomId = $(this).find('td:eq(0)').text();
|
|
||||||
var membership = $(this).find('td:eq(1)').text();
|
|
||||||
if (membership !== "join") {
|
|
||||||
console.log("Joining room " + roomId);
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/join?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({membership: "join"}),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
loadRoomContent(roomId);
|
|
||||||
getCurrentRoomList();
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
loadRoomContent(roomId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var addMessage = function(data) {
|
|
||||||
|
|
||||||
var msg = data.content.body;
|
|
||||||
if (data.type === "m.room.member") {
|
|
||||||
if (data.content.membership === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data.content.membership === "invite") {
|
|
||||||
msg = "<em>invited " + data.state_key + " to the room</em>";
|
|
||||||
}
|
|
||||||
else if (data.content.membership === "join") {
|
|
||||||
msg = "<em>joined the room</em>";
|
|
||||||
}
|
|
||||||
else if (data.content.membership === "leave") {
|
|
||||||
msg = "<em>left the room</em>";
|
|
||||||
}
|
|
||||||
else if (data.content.membership === "ban") {
|
|
||||||
msg = "<em>was banned from the room</em>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (msg === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var row = "<tr>" +
|
|
||||||
"<td>"+data.user_id+"</td>" +
|
|
||||||
"<td>"+msg+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
$("#messages").append(row);
|
|
||||||
};
|
|
||||||
|
|
||||||
var addMember = function(data) {
|
|
||||||
var row = "<tr>" +
|
|
||||||
"<td>"+data.state_key+"</td>" +
|
|
||||||
"<td>"+data.content.membership+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
$("#members").append(row);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
.loggedin {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
<div>
|
|
||||||
<p>This registration/login demo requires a home server to be running on http://localhost:8008</p>
|
|
||||||
</div>
|
|
||||||
<form class="registrationForm">
|
|
||||||
<input type="text" id="user" placeholder="Username"></input>
|
|
||||||
<input type="password" id="password" placeholder="Password"></input>
|
|
||||||
<input type="button" class="register" value="Register"></input>
|
|
||||||
</form>
|
|
||||||
<form class="loginForm">
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
<div class="loggedin">
|
|
||||||
<p id="welcomeText"></p>
|
|
||||||
<input type="button" class="testToken" value="Test token"></input>
|
|
||||||
<input type="button" class="logout" value="Logout"></input>
|
|
||||||
<p id="imSyncText"></p>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var showLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
$(".loggedin").css({visibility: "visible"});
|
|
||||||
$("#welcomeText").text("Welcome " + accountInfo.user_id+". Your access token is: " +
|
|
||||||
accountInfo.access_token);
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.register').live('click', function() {
|
|
||||||
var user = $("#user").val();
|
|
||||||
var password = $("#password").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/register",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var login = function(user, password) {
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.getJSON("http://localhost:8008/_matrix/client/api/v1/login", function(data) {
|
|
||||||
if (data.flows[0].type !== "m.login.password") {
|
|
||||||
alert("I don't know how to login with this type: " + data.type);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
login(user, password);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.logout').live('click', function() {
|
|
||||||
accountInfo = {};
|
|
||||||
$("#imSyncText").text("");
|
|
||||||
$(".loggedin").css({visibility: "hidden"});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.testToken').live('click', function() {
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
$("#imSyncText").text(JSON.stringify(data, undefined, 2));
|
|
||||||
}).fail(function(err) {
|
|
||||||
$("#imSyncText").text(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
.loggedin {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-spacing:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,td
|
|
||||||
{
|
|
||||||
padding:5px;
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
<div>
|
|
||||||
<p>This room membership demo requires a home server to be running on http://localhost:8008</p>
|
|
||||||
</div>
|
|
||||||
<form class="loginForm">
|
|
||||||
<input type="text" id="userLogin" placeholder="Username"></input>
|
|
||||||
<input type="password" id="passwordLogin" placeholder="Password"></input>
|
|
||||||
<input type="button" class="login" value="Login"></input>
|
|
||||||
</form>
|
|
||||||
<div class="loggedin">
|
|
||||||
<form class="createRoomForm">
|
|
||||||
<input type="button" class="createRoom" value="Create Room"></input>
|
|
||||||
</form>
|
|
||||||
<form class="changeMembershipForm">
|
|
||||||
<input type="text" id="roomId" placeholder="Room ID"></input>
|
|
||||||
<input type="text" id="targetUser" placeholder="Target User ID"></input>
|
|
||||||
<select id="membership">
|
|
||||||
<option value="invite">invite</option>
|
|
||||||
<option value="join">join</option>
|
|
||||||
<option value="leave">leave</option>
|
|
||||||
</select>
|
|
||||||
<input type="button" class="changeMembership" value="Change Membership"></input>
|
|
||||||
</form>
|
|
||||||
<form class="joinAliasForm">
|
|
||||||
<input type="text" id="roomAlias" placeholder="Room Alias (#name:domain)"></input>
|
|
||||||
<input type="button" class="joinAlias" value="Join via Alias"></input>
|
|
||||||
</form>
|
|
||||||
<table id="rooms">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Room ID</th>
|
|
||||||
<th>My state</th>
|
|
||||||
<th>Room Alias</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
var accountInfo = {};
|
|
||||||
|
|
||||||
var showLoggedIn = function(data) {
|
|
||||||
accountInfo = data;
|
|
||||||
getCurrentRoomList();
|
|
||||||
$(".loggedin").css({visibility: "visible"});
|
|
||||||
$("#membership").change(function() {
|
|
||||||
if ($("#membership").val() === "invite") {
|
|
||||||
$("#targetUser").css({visibility: "visible"});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$("#targetUser").css({visibility: "hidden"});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.login').live('click', function() {
|
|
||||||
var user = $("#userLogin").val();
|
|
||||||
var password = $("#passwordLogin").val();
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/login",
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
showLoggedIn(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
var errMsg = "To try this, you need a home server running!";
|
|
||||||
var errJson = $.parseJSON(err.responseText);
|
|
||||||
if (errJson) {
|
|
||||||
errMsg = JSON.stringify(errJson);
|
|
||||||
}
|
|
||||||
alert(errMsg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var getCurrentRoomList = function() {
|
|
||||||
$("#roomId").val("");
|
|
||||||
// wipe the table and reload it. Using the event stream would be the best
|
|
||||||
// solution but that is out of scope of this fiddle.
|
|
||||||
$("#rooms").find("tr:gt(0)").remove();
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
|
|
||||||
$.getJSON(url, function(data) {
|
|
||||||
var rooms = data.rooms;
|
|
||||||
for (var i=0; i<rooms.length; ++i) {
|
|
||||||
addRoom(rooms[i]);
|
|
||||||
}
|
|
||||||
}).fail(function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.createRoom').live('click', function() {
|
|
||||||
var data = {};
|
|
||||||
$.ajax({
|
|
||||||
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
data.membership = "join"; // you are automatically joined into every room you make.
|
|
||||||
data.latest_message = "";
|
|
||||||
addRoom(data);
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
var addRoom = function(data) {
|
|
||||||
row = "<tr>" +
|
|
||||||
"<td>"+data.room_id+"</td>" +
|
|
||||||
"<td>"+data.membership+"</td>" +
|
|
||||||
"<td>"+data.room_alias+"</td>" +
|
|
||||||
"</tr>";
|
|
||||||
$("#rooms").append(row);
|
|
||||||
};
|
|
||||||
|
|
||||||
$('.changeMembership').live('click', function() {
|
|
||||||
var roomId = $("#roomId").val();
|
|
||||||
var member = $("#targetUser").val();
|
|
||||||
var membership = $("#membership").val();
|
|
||||||
|
|
||||||
if (roomId.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/$membership?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomid", encodeURIComponent(roomId));
|
|
||||||
url = url.replace("$membership", membership);
|
|
||||||
|
|
||||||
var data = {};
|
|
||||||
|
|
||||||
if (membership === "invite") {
|
|
||||||
data = {
|
|
||||||
user_id: member
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
getCurrentRoomList();
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.joinAlias').live('click', function() {
|
|
||||||
var roomAlias = $("#roomAlias").val();
|
|
||||||
var url = "http://localhost:8008/_matrix/client/api/v1/join/$roomalias?access_token=$token";
|
|
||||||
url = url.replace("$token", accountInfo.access_token);
|
|
||||||
url = url.replace("$roomalias", encodeURIComponent(roomAlias));
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
type: "POST",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
data: JSON.stringify({}),
|
|
||||||
dataType: "json",
|
|
||||||
success: function(data) {
|
|
||||||
getCurrentRoomList();
|
|
||||||
},
|
|
||||||
error: function(err) {
|
|
||||||
alert(JSON.stringify($.parseJSON(err.responseText)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
33
scripts/copyrighter-sql.pl
Executable file
33
scripts/copyrighter-sql.pl
Executable file
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/perl -pi
|
||||||
|
# Copyright 2015 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.
|
||||||
|
|
||||||
|
$copyright = <<EOT;
|
||||||
|
/* Copyright 2015 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.
|
||||||
|
*/
|
||||||
|
EOT
|
||||||
|
|
||||||
|
s/^(# -\*- coding: utf-8 -\*-\n)?/$1$copyright/ if ($. == 1);
|
|
@ -13,7 +13,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
""" This is a reference implementation of a synapse home server.
|
""" This is a reference implementation of a Matrix home server.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "0.6.0"
|
__version__ = "0.6.1b"
|
||||||
|
|
|
@ -18,6 +18,8 @@ from synapse.storage import prepare_database, UpgradeDatabaseException
|
||||||
|
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
|
|
||||||
|
from synapse.python_dependencies import check_requirements
|
||||||
|
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor
|
||||||
from twisted.application import service
|
from twisted.application import service
|
||||||
from twisted.enterprise import adbapi
|
from twisted.enterprise import adbapi
|
||||||
|
@ -40,6 +42,8 @@ from synapse.util.logcontext import LoggingContext
|
||||||
from daemonize import Daemonize
|
from daemonize import Daemonize
|
||||||
import twisted.manhole.telnet
|
import twisted.manhole.telnet
|
||||||
|
|
||||||
|
import synapse
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -199,7 +203,10 @@ def setup(config_options, should_run=True):
|
||||||
|
|
||||||
config.setup_logging()
|
config.setup_logging()
|
||||||
|
|
||||||
|
check_requirements()
|
||||||
|
|
||||||
logger.info("Server hostname: %s", config.server_name)
|
logger.info("Server hostname: %s", config.server_name)
|
||||||
|
logger.info("Server version: %s", synapse.__version__)
|
||||||
|
|
||||||
if re.search(":[0-9]+$", config.server_name):
|
if re.search(":[0-9]+$", config.server_name):
|
||||||
domain_with_port = config.server_name
|
domain_with_port = config.server_name
|
||||||
|
@ -235,13 +242,20 @@ def setup(config_options, should_run=True):
|
||||||
except UpgradeDatabaseException:
|
except UpgradeDatabaseException:
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
"\nFailed to upgrade database.\n"
|
"\nFailed to upgrade database.\n"
|
||||||
"Have you checked for version specific instructions in UPGRADES.rst?\n"
|
"Have you checked for version specific instructions in"
|
||||||
|
" UPGRADES.rst?\n"
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
logger.info("Database prepared in %s.", db_name)
|
logger.info("Database prepared in %s.", db_name)
|
||||||
|
|
||||||
hs.get_db_pool()
|
db_pool = hs.get_db_pool()
|
||||||
|
|
||||||
|
if db_name == ":memory:":
|
||||||
|
# Memory databases will need to be setup each time they are opened.
|
||||||
|
reactor.callWhenRunning(
|
||||||
|
db_pool.runWithConnection, prepare_database
|
||||||
|
)
|
||||||
|
|
||||||
if config.manhole:
|
if config.manhole:
|
||||||
f = twisted.manhole.telnet.ShellFactory()
|
f = twisted.manhole.telnet.ShellFactory()
|
||||||
|
@ -292,6 +306,7 @@ def run():
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
with LoggingContext("main"):
|
with LoggingContext("main"):
|
||||||
|
check_requirements()
|
||||||
setup(sys.argv[1:])
|
setup(sys.argv[1:])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,10 @@ import os
|
||||||
class DatabaseConfig(Config):
|
class DatabaseConfig(Config):
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
super(DatabaseConfig, self).__init__(args)
|
super(DatabaseConfig, self).__init__(args)
|
||||||
self.database_path = self.abspath(args.database_path)
|
if args.database_path == ":memory:":
|
||||||
|
self.database_path = ":memory:"
|
||||||
|
else:
|
||||||
|
self.database_path = self.abspath(args.database_path)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_arguments(cls, parser):
|
def add_arguments(cls, parser):
|
||||||
|
|
|
@ -66,7 +66,10 @@ class LoggingConfig(Config):
|
||||||
|
|
||||||
formatter = logging.Formatter(log_format)
|
formatter = logging.Formatter(log_format)
|
||||||
if self.log_file:
|
if self.log_file:
|
||||||
handler = logging.FileHandler(self.log_file)
|
# TODO: Customisable file size / backup count
|
||||||
|
handler = logging.handlers.RotatingFileHandler(
|
||||||
|
self.log_file, maxBytes=(1000 * 1000 * 100), backupCount=3
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
handler = logging.StreamHandler()
|
handler = logging.StreamHandler()
|
||||||
handler.setFormatter(formatter)
|
handler.setFormatter(formatter)
|
||||||
|
|
|
@ -47,8 +47,12 @@ class ServerConfig(Config):
|
||||||
def add_arguments(cls, parser):
|
def add_arguments(cls, parser):
|
||||||
super(ServerConfig, cls).add_arguments(parser)
|
super(ServerConfig, cls).add_arguments(parser)
|
||||||
server_group = parser.add_argument_group("server")
|
server_group = parser.add_argument_group("server")
|
||||||
server_group.add_argument("-H", "--server-name", default="localhost",
|
server_group.add_argument(
|
||||||
help="The name of the server")
|
"-H", "--server-name", default="localhost",
|
||||||
|
help="The domain name of the server, with optional explicit port. "
|
||||||
|
"This is used by remote servers to connect to this server, "
|
||||||
|
"e.g. matrix.org, localhost:8080, etc."
|
||||||
|
)
|
||||||
server_group.add_argument("--signing-key-path",
|
server_group.add_argument("--signing-key-path",
|
||||||
help="The signing key to sign messages with")
|
help="The signing key to sign messages with")
|
||||||
server_group.add_argument("-p", "--bind-port", metavar="PORT",
|
server_group.add_argument("-p", "--bind-port", metavar="PORT",
|
||||||
|
|
|
@ -33,12 +33,6 @@ class EventBuilder(EventBase):
|
||||||
unsigned=unsigned
|
unsigned=unsigned
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_event_key(self, key, value):
|
|
||||||
self._event_dict[key] = value
|
|
||||||
|
|
||||||
def update_event_keys(self, other_dict):
|
|
||||||
self._event_dict.update(other_dict)
|
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
return FrozenEvent.from_event(self)
|
return FrozenEvent.from_event(self)
|
||||||
|
|
||||||
|
|
|
@ -89,16 +89,24 @@ def prune_event(event):
|
||||||
return type(event)(allowed_fields)
|
return type(event)(allowed_fields)
|
||||||
|
|
||||||
|
|
||||||
def serialize_event(hs, e):
|
def serialize_event(hs, e, client_event=True):
|
||||||
# FIXME(erikj): To handle the case of presence events and the like
|
# FIXME(erikj): To handle the case of presence events and the like
|
||||||
if not isinstance(e, EventBase):
|
if not isinstance(e, EventBase):
|
||||||
return e
|
return e
|
||||||
|
|
||||||
# Should this strip out None's?
|
# Should this strip out None's?
|
||||||
d = {k: v for k, v in e.get_dict().items()}
|
d = {k: v for k, v in e.get_dict().items()}
|
||||||
|
|
||||||
|
if not client_event:
|
||||||
|
# set the age and keep all other keys
|
||||||
|
if "age_ts" in d["unsigned"]:
|
||||||
|
now = int(hs.get_clock().time_msec())
|
||||||
|
d["unsigned"]["age"] = now - d["unsigned"]["age_ts"]
|
||||||
|
return d
|
||||||
|
|
||||||
if "age_ts" in d["unsigned"]:
|
if "age_ts" in d["unsigned"]:
|
||||||
now = int(hs.get_clock().time_msec())
|
now = int(hs.get_clock().time_msec())
|
||||||
d["unsigned"]["age"] = now - d["unsigned"]["age_ts"]
|
d["age"] = now - d["unsigned"]["age_ts"]
|
||||||
del d["unsigned"]["age_ts"]
|
del d["unsigned"]["age_ts"]
|
||||||
|
|
||||||
d["user_id"] = d.pop("sender", None)
|
d["user_id"] = d.pop("sender", None)
|
||||||
|
|
|
@ -256,23 +256,21 @@ class ReplicationLayer(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def get_state_for_context(self, destination, context, event_id):
|
def get_state_for_room(self, destination, room_id, event_id):
|
||||||
"""Requests all of the `current` state PDUs for a given context from
|
"""Requests all of the `current` state PDUs for a given room from
|
||||||
a remote home server.
|
a remote home server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
destination (str): The remote homeserver to query for the state.
|
destination (str): The remote homeserver to query for the state.
|
||||||
context (str): The context we're interested in.
|
room_id (str): The id of the room we're interested in.
|
||||||
event_id (str): The id of the event we want the state at.
|
event_id (str): The id of the event we want the state at.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred: Results in a list of PDUs.
|
Deferred: Results in a list of PDUs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = yield self.transport_layer.get_context_state(
|
result = yield self.transport_layer.get_room_state(
|
||||||
destination,
|
destination, room_id, event_id=event_id,
|
||||||
context,
|
|
||||||
event_id=event_id,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pdus = [
|
pdus = [
|
||||||
|
@ -288,9 +286,9 @@ class ReplicationLayer(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def get_event_auth(self, destination, context, event_id):
|
def get_event_auth(self, destination, room_id, event_id):
|
||||||
res = yield self.transport_layer.get_event_auth(
|
res = yield self.transport_layer.get_event_auth(
|
||||||
destination, context, event_id,
|
destination, room_id, event_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
auth_chain = [
|
auth_chain = [
|
||||||
|
@ -304,9 +302,9 @@ class ReplicationLayer(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def on_backfill_request(self, origin, context, versions, limit):
|
def on_backfill_request(self, origin, room_id, versions, limit):
|
||||||
pdus = yield self.handler.on_backfill_request(
|
pdus = yield self.handler.on_backfill_request(
|
||||||
origin, context, versions, limit
|
origin, room_id, versions, limit
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((200, self._transaction_from_pdus(pdus).get_dict()))
|
defer.returnValue((200, self._transaction_from_pdus(pdus).get_dict()))
|
||||||
|
@ -380,12 +378,10 @@ class ReplicationLayer(object):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def on_context_state_request(self, origin, context, event_id):
|
def on_context_state_request(self, origin, room_id, event_id):
|
||||||
if event_id:
|
if event_id:
|
||||||
pdus = yield self.handler.get_state_for_pdu(
|
pdus = yield self.handler.get_state_for_pdu(
|
||||||
origin,
|
origin, room_id, event_id,
|
||||||
context,
|
|
||||||
event_id,
|
|
||||||
)
|
)
|
||||||
auth_chain = yield self.store.get_auth_chain(
|
auth_chain = yield self.store.get_auth_chain(
|
||||||
[pdu.event_id for pdu in pdus]
|
[pdu.event_id for pdu in pdus]
|
||||||
|
@ -413,7 +409,7 @@ class ReplicationLayer(object):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def on_pull_request(self, origin, versions):
|
def on_pull_request(self, origin, versions):
|
||||||
raise NotImplementedError("Pull transacions not implemented")
|
raise NotImplementedError("Pull transactions not implemented")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_query_request(self, query_type, args):
|
def on_query_request(self, query_type, args):
|
||||||
|
@ -422,30 +418,21 @@ class ReplicationLayer(object):
|
||||||
defer.returnValue((200, response))
|
defer.returnValue((200, response))
|
||||||
else:
|
else:
|
||||||
defer.returnValue(
|
defer.returnValue(
|
||||||
(404, "No handler for Query type '%s'" % (query_type, ))
|
(404, "No handler for Query type '%s'" % (query_type,))
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_make_join_request(self, context, user_id):
|
def on_make_join_request(self, room_id, user_id):
|
||||||
pdu = yield self.handler.on_make_join_request(context, user_id)
|
pdu = yield self.handler.on_make_join_request(room_id, user_id)
|
||||||
time_now = self._clock.time_msec()
|
time_now = self._clock.time_msec()
|
||||||
defer.returnValue({
|
defer.returnValue({"event": pdu.get_pdu_json(time_now)})
|
||||||
"event": pdu.get_pdu_json(time_now),
|
|
||||||
})
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_invite_request(self, origin, content):
|
def on_invite_request(self, origin, content):
|
||||||
pdu = self.event_from_pdu_json(content)
|
pdu = self.event_from_pdu_json(content)
|
||||||
ret_pdu = yield self.handler.on_invite_request(origin, pdu)
|
ret_pdu = yield self.handler.on_invite_request(origin, pdu)
|
||||||
time_now = self._clock.time_msec()
|
time_now = self._clock.time_msec()
|
||||||
defer.returnValue(
|
defer.returnValue((200, {"event": ret_pdu.get_pdu_json(time_now)}))
|
||||||
(
|
|
||||||
200,
|
|
||||||
{
|
|
||||||
"event": ret_pdu.get_pdu_json(time_now),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_send_join_request(self, origin, content):
|
def on_send_join_request(self, origin, content):
|
||||||
|
@ -462,26 +449,17 @@ class ReplicationLayer(object):
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_event_auth(self, origin, context, event_id):
|
def on_event_auth(self, origin, room_id, event_id):
|
||||||
time_now = self._clock.time_msec()
|
time_now = self._clock.time_msec()
|
||||||
auth_pdus = yield self.handler.on_event_auth(event_id)
|
auth_pdus = yield self.handler.on_event_auth(event_id)
|
||||||
defer.returnValue(
|
defer.returnValue((200, {
|
||||||
(
|
"auth_chain": [a.get_pdu_json(time_now) for a in auth_pdus],
|
||||||
200,
|
}))
|
||||||
{
|
|
||||||
"auth_chain": [
|
|
||||||
a.get_pdu_json(time_now) for a in auth_pdus
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def make_join(self, destination, context, user_id):
|
def make_join(self, destination, room_id, user_id):
|
||||||
ret = yield self.transport_layer.make_join(
|
ret = yield self.transport_layer.make_join(
|
||||||
destination=destination,
|
destination, room_id, user_id
|
||||||
context=context,
|
|
||||||
user_id=user_id,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pdu_dict = ret["event"]
|
pdu_dict = ret["event"]
|
||||||
|
@ -494,10 +472,10 @@ class ReplicationLayer(object):
|
||||||
def send_join(self, destination, pdu):
|
def send_join(self, destination, pdu):
|
||||||
time_now = self._clock.time_msec()
|
time_now = self._clock.time_msec()
|
||||||
_, content = yield self.transport_layer.send_join(
|
_, content = yield self.transport_layer.send_join(
|
||||||
destination,
|
destination=destination,
|
||||||
pdu.room_id,
|
room_id=pdu.room_id,
|
||||||
pdu.event_id,
|
event_id=pdu.event_id,
|
||||||
pdu.get_pdu_json(time_now),
|
content=pdu.get_pdu_json(time_now),
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug("Got content: %s", content)
|
logger.debug("Got content: %s", content)
|
||||||
|
@ -507,9 +485,6 @@ class ReplicationLayer(object):
|
||||||
for p in content.get("state", [])
|
for p in content.get("state", [])
|
||||||
]
|
]
|
||||||
|
|
||||||
# FIXME: We probably want to do something with the auth_chain given
|
|
||||||
# to us
|
|
||||||
|
|
||||||
auth_chain = [
|
auth_chain = [
|
||||||
self.event_from_pdu_json(p, outlier=True)
|
self.event_from_pdu_json(p, outlier=True)
|
||||||
for p in content.get("auth_chain", [])
|
for p in content.get("auth_chain", [])
|
||||||
|
@ -523,11 +498,11 @@ class ReplicationLayer(object):
|
||||||
})
|
})
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def send_invite(self, destination, context, event_id, pdu):
|
def send_invite(self, destination, room_id, event_id, pdu):
|
||||||
time_now = self._clock.time_msec()
|
time_now = self._clock.time_msec()
|
||||||
code, content = yield self.transport_layer.send_invite(
|
code, content = yield self.transport_layer.send_invite(
|
||||||
destination=destination,
|
destination=destination,
|
||||||
context=context,
|
room_id=room_id,
|
||||||
event_id=event_id,
|
event_id=event_id,
|
||||||
content=pdu.get_pdu_json(time_now),
|
content=pdu.get_pdu_json(time_now),
|
||||||
)
|
)
|
||||||
|
@ -657,7 +632,7 @@ class ReplicationLayer(object):
|
||||||
"_handle_new_pdu getting state for %s",
|
"_handle_new_pdu getting state for %s",
|
||||||
pdu.room_id
|
pdu.room_id
|
||||||
)
|
)
|
||||||
state, auth_chain = yield self.get_state_for_context(
|
state, auth_chain = yield self.get_state_for_room(
|
||||||
origin, pdu.room_id, pdu.event_id,
|
origin, pdu.room_id, pdu.event_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -816,7 +791,7 @@ class _TransactionQueue(object):
|
||||||
logger.info("TX [%s] is ready for retry", destination)
|
logger.info("TX [%s] is ready for retry", destination)
|
||||||
|
|
||||||
logger.info("TX [%s] _attempt_new_transaction", destination)
|
logger.info("TX [%s] _attempt_new_transaction", destination)
|
||||||
|
|
||||||
if destination in self.pending_transactions:
|
if destination in self.pending_transactions:
|
||||||
# XXX: pending_transactions can get stuck on by a never-ending
|
# XXX: pending_transactions can get stuck on by a never-ending
|
||||||
# request at which point pending_pdus_by_dest just keeps growing.
|
# request at which point pending_pdus_by_dest just keeps growing.
|
||||||
|
@ -830,14 +805,15 @@ class _TransactionQueue(object):
|
||||||
pending_failures = self.pending_failures_by_dest.pop(destination, [])
|
pending_failures = self.pending_failures_by_dest.pop(destination, [])
|
||||||
|
|
||||||
if pending_pdus:
|
if pending_pdus:
|
||||||
logger.info("TX [%s] len(pending_pdus_by_dest[dest]) = %d", destination, len(pending_pdus))
|
logger.info("TX [%s] len(pending_pdus_by_dest[dest]) = %d",
|
||||||
|
destination, len(pending_pdus))
|
||||||
|
|
||||||
if not pending_pdus and not pending_edus and not pending_failures:
|
if not pending_pdus and not pending_edus and not pending_failures:
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"TX [%s] Attempting new transaction "
|
"TX [%s] Attempting new transaction"
|
||||||
"(pdus: %d, edus: %d, failures: %d)",
|
" (pdus: %d, edus: %d, failures: %d)",
|
||||||
destination,
|
destination,
|
||||||
len(pending_pdus),
|
len(pending_pdus),
|
||||||
len(pending_edus),
|
len(pending_edus),
|
||||||
|
|
62
synapse/federation/transport/__init__.py
Normal file
62
synapse/federation/transport/__init__.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014, 2015 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.
|
||||||
|
|
||||||
|
"""The transport layer is responsible for both sending transactions to remote
|
||||||
|
home servers and receiving a variety of requests from other home servers.
|
||||||
|
|
||||||
|
By default this is done over HTTPS (and all home servers are required to
|
||||||
|
support HTTPS), however individual pairings of servers may decide to
|
||||||
|
communicate over a different (albeit still reliable) protocol.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .server import TransportLayerServer
|
||||||
|
from .client import TransportLayerClient
|
||||||
|
|
||||||
|
|
||||||
|
class TransportLayer(TransportLayerServer, TransportLayerClient):
|
||||||
|
"""This is a basic implementation of the transport layer that translates
|
||||||
|
transactions and other requests to/from HTTP.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
server_name (str): Local home server host
|
||||||
|
|
||||||
|
server (synapse.http.server.HttpServer): the http server to
|
||||||
|
register listeners on
|
||||||
|
|
||||||
|
client (synapse.http.client.HttpClient): the http client used to
|
||||||
|
send requests
|
||||||
|
|
||||||
|
request_handler (TransportRequestHandler): The handler to fire when we
|
||||||
|
receive requests for data.
|
||||||
|
|
||||||
|
received_handler (TransportReceivedHandler): The handler to fire when
|
||||||
|
we receive data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, homeserver, server_name, server, client):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
server_name (str): Local home server host
|
||||||
|
server (synapse.protocol.http.HttpServer): the http server to
|
||||||
|
register listeners on
|
||||||
|
client (synapse.protocol.http.HttpClient): the http client used to
|
||||||
|
send requests
|
||||||
|
"""
|
||||||
|
self.keyring = homeserver.get_keyring()
|
||||||
|
self.server_name = server_name
|
||||||
|
self.server = server
|
||||||
|
self.client = client
|
||||||
|
self.request_handler = None
|
||||||
|
self.received_handler = None
|
215
synapse/federation/transport/client.py
Normal file
215
synapse/federation/transport/client.py
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014, 2015 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.
|
||||||
|
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from synapse.api.urls import FEDERATION_PREFIX as PREFIX
|
||||||
|
from synapse.util.logutils import log_function
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TransportLayerClient(object):
|
||||||
|
"""Sends federation HTTP requests to other servers"""
|
||||||
|
|
||||||
|
@log_function
|
||||||
|
def get_room_state(self, destination, room_id, event_id):
|
||||||
|
""" Requests all state for a given room from the given server at the
|
||||||
|
given event.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
destination (str): The host name of the remote home server we want
|
||||||
|
to get the state from.
|
||||||
|
context (str): The name of the context we want the state of
|
||||||
|
event_id (str): The event we want the context at.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred: Results in a dict received from the remote homeserver.
|
||||||
|
"""
|
||||||
|
logger.debug("get_room_state dest=%s, room=%s",
|
||||||
|
destination, room_id)
|
||||||
|
|
||||||
|
path = PREFIX + "/state/%s/" % room_id
|
||||||
|
return self.client.get_json(
|
||||||
|
destination, path=path, args={"event_id": event_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
@log_function
|
||||||
|
def get_event(self, destination, event_id):
|
||||||
|
""" Requests the pdu with give id and origin from the given server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
destination (str): The host name of the remote home server we want
|
||||||
|
to get the state from.
|
||||||
|
event_id (str): The id of the event being requested.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred: Results in a dict received from the remote homeserver.
|
||||||
|
"""
|
||||||
|
logger.debug("get_pdu dest=%s, event_id=%s",
|
||||||
|
destination, event_id)
|
||||||
|
|
||||||
|
path = PREFIX + "/event/%s/" % (event_id, )
|
||||||
|
return self.client.get_json(destination, path=path)
|
||||||
|
|
||||||
|
@log_function
|
||||||
|
def backfill(self, destination, room_id, event_tuples, limit):
|
||||||
|
""" Requests `limit` previous PDUs in a given context before list of
|
||||||
|
PDUs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dest (str)
|
||||||
|
room_id (str)
|
||||||
|
event_tuples (list)
|
||||||
|
limt (int)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred: Results in a dict received from the remote homeserver.
|
||||||
|
"""
|
||||||
|
logger.debug(
|
||||||
|
"backfill dest=%s, room_id=%s, event_tuples=%s, limit=%s",
|
||||||
|
destination, room_id, repr(event_tuples), str(limit)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not event_tuples:
|
||||||
|
# TODO: raise?
|
||||||
|
return
|
||||||
|
|
||||||
|
path = PREFIX + "/backfill/%s/" % (room_id,)
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"v": event_tuples,
|
||||||
|
"limit": [str(limit)],
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.client.get_json(
|
||||||
|
destination,
|
||||||
|
path=path,
|
||||||
|
args=args,
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
@log_function
|
||||||
|
def send_transaction(self, transaction, json_data_callback=None):
|
||||||
|
""" Sends the given Transaction to its destination
|
||||||
|
|
||||||
|
Args:
|
||||||
|
transaction (Transaction)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred: Results of the deferred is a tuple in the form of
|
||||||
|
(response_code, response_body) where the response_body is a
|
||||||
|
python dict decoded from json
|
||||||
|
"""
|
||||||
|
logger.debug(
|
||||||
|
"send_data dest=%s, txid=%s",
|
||||||
|
transaction.destination, transaction.transaction_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if transaction.destination == self.server_name:
|
||||||
|
raise RuntimeError("Transport layer cannot send to itself!")
|
||||||
|
|
||||||
|
# FIXME: This is only used by the tests. The actual json sent is
|
||||||
|
# generated by the json_data_callback.
|
||||||
|
json_data = transaction.get_dict()
|
||||||
|
|
||||||
|
code, response = yield self.client.put_json(
|
||||||
|
transaction.destination,
|
||||||
|
path=PREFIX + "/send/%s/" % transaction.transaction_id,
|
||||||
|
data=json_data,
|
||||||
|
json_data_callback=json_data_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"send_data dest=%s, txid=%s, got response: %d",
|
||||||
|
transaction.destination, transaction.transaction_id, code
|
||||||
|
)
|
||||||
|
|
||||||
|
defer.returnValue((code, response))
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
@log_function
|
||||||
|
def make_query(self, destination, query_type, args, retry_on_dns_fail):
|
||||||
|
path = PREFIX + "/query/%s" % query_type
|
||||||
|
|
||||||
|
response = yield self.client.get_json(
|
||||||
|
destination=destination,
|
||||||
|
path=path,
|
||||||
|
args=args,
|
||||||
|
retry_on_dns_fail=retry_on_dns_fail,
|
||||||
|
)
|
||||||
|
|
||||||
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
@log_function
|
||||||
|
def make_join(self, destination, room_id, user_id, retry_on_dns_fail=True):
|
||||||
|
path = PREFIX + "/make_join/%s/%s" % (room_id, user_id)
|
||||||
|
|
||||||
|
response = yield self.client.get_json(
|
||||||
|
destination=destination,
|
||||||
|
path=path,
|
||||||
|
retry_on_dns_fail=retry_on_dns_fail,
|
||||||
|
)
|
||||||
|
|
||||||
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
@log_function
|
||||||
|
def send_join(self, destination, room_id, event_id, content):
|
||||||
|
path = PREFIX + "/send_join/%s/%s" % (room_id, event_id)
|
||||||
|
|
||||||
|
code, content = yield self.client.put_json(
|
||||||
|
destination=destination,
|
||||||
|
path=path,
|
||||||
|
data=content,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not 200 <= code < 300:
|
||||||
|
raise RuntimeError("Got %d from send_join", code)
|
||||||
|
|
||||||
|
defer.returnValue(json.loads(content))
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
@log_function
|
||||||
|
def send_invite(self, destination, room_id, event_id, content):
|
||||||
|
path = PREFIX + "/invite/%s/%s" % (room_id, event_id)
|
||||||
|
|
||||||
|
code, content = yield self.client.put_json(
|
||||||
|
destination=destination,
|
||||||
|
path=path,
|
||||||
|
data=content,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not 200 <= code < 300:
|
||||||
|
raise RuntimeError("Got %d from send_invite", code)
|
||||||
|
|
||||||
|
defer.returnValue(json.loads(content))
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
@log_function
|
||||||
|
def get_event_auth(self, destination, room_id, event_id):
|
||||||
|
path = PREFIX + "/event_auth/%s/%s" % (room_id, event_id)
|
||||||
|
|
||||||
|
response = yield self.client.get_json(
|
||||||
|
destination=destination,
|
||||||
|
path=path,
|
||||||
|
)
|
||||||
|
|
||||||
|
defer.returnValue(response)
|
|
@ -13,14 +13,6 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
"""The transport layer is responsible for both sending transactions to remote
|
|
||||||
home servers and receiving a variety of requests from other home servers.
|
|
||||||
|
|
||||||
Typically, this is done over HTTP (and all home servers are required to
|
|
||||||
support HTTP), however individual pairings of servers may decide to communicate
|
|
||||||
over a different (albeit still reliable) protocol.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.urls import FEDERATION_PREFIX as PREFIX
|
from synapse.api.urls import FEDERATION_PREFIX as PREFIX
|
||||||
|
@ -35,241 +27,8 @@ import re
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TransportLayer(object):
|
class TransportLayerServer(object):
|
||||||
"""This is a basic implementation of the transport layer that translates
|
"""Handles incoming federation HTTP requests"""
|
||||||
transactions and other requests to/from HTTP.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
server_name (str): Local home server host
|
|
||||||
|
|
||||||
server (synapse.http.server.HttpServer): the http server to
|
|
||||||
register listeners on
|
|
||||||
|
|
||||||
client (synapse.http.client.HttpClient): the http client used to
|
|
||||||
send requests
|
|
||||||
|
|
||||||
request_handler (TransportRequestHandler): The handler to fire when we
|
|
||||||
receive requests for data.
|
|
||||||
|
|
||||||
received_handler (TransportReceivedHandler): The handler to fire when
|
|
||||||
we receive data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, homeserver, server_name, server, client):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
server_name (str): Local home server host
|
|
||||||
server (synapse.protocol.http.HttpServer): the http server to
|
|
||||||
register listeners on
|
|
||||||
client (synapse.protocol.http.HttpClient): the http client used to
|
|
||||||
send requests
|
|
||||||
"""
|
|
||||||
self.keyring = homeserver.get_keyring()
|
|
||||||
self.server_name = server_name
|
|
||||||
self.server = server
|
|
||||||
self.client = client
|
|
||||||
self.request_handler = None
|
|
||||||
self.received_handler = None
|
|
||||||
|
|
||||||
@log_function
|
|
||||||
def get_context_state(self, destination, context, event_id=None):
|
|
||||||
""" Requests all state for a given context (i.e. room) from the
|
|
||||||
given server.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
destination (str): The host name of the remote home server we want
|
|
||||||
to get the state from.
|
|
||||||
context (str): The name of the context we want the state of
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred: Results in a dict received from the remote homeserver.
|
|
||||||
"""
|
|
||||||
logger.debug("get_context_state dest=%s, context=%s",
|
|
||||||
destination, context)
|
|
||||||
|
|
||||||
subpath = "/state/%s/" % context
|
|
||||||
|
|
||||||
args = {}
|
|
||||||
if event_id:
|
|
||||||
args["event_id"] = event_id
|
|
||||||
|
|
||||||
return self._do_request_for_transaction(
|
|
||||||
destination, subpath, args=args
|
|
||||||
)
|
|
||||||
|
|
||||||
@log_function
|
|
||||||
def get_event(self, destination, event_id):
|
|
||||||
""" Requests the pdu with give id and origin from the given server.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
destination (str): The host name of the remote home server we want
|
|
||||||
to get the state from.
|
|
||||||
event_id (str): The id of the event being requested.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred: Results in a dict received from the remote homeserver.
|
|
||||||
"""
|
|
||||||
logger.debug("get_pdu dest=%s, event_id=%s",
|
|
||||||
destination, event_id)
|
|
||||||
|
|
||||||
subpath = "/event/%s/" % (event_id, )
|
|
||||||
|
|
||||||
return self._do_request_for_transaction(destination, subpath)
|
|
||||||
|
|
||||||
@log_function
|
|
||||||
def backfill(self, dest, context, event_tuples, limit):
|
|
||||||
""" Requests `limit` previous PDUs in a given context before list of
|
|
||||||
PDUs.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
dest (str)
|
|
||||||
context (str)
|
|
||||||
event_tuples (list)
|
|
||||||
limt (int)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred: Results in a dict received from the remote homeserver.
|
|
||||||
"""
|
|
||||||
logger.debug(
|
|
||||||
"backfill dest=%s, context=%s, event_tuples=%s, limit=%s",
|
|
||||||
dest, context, repr(event_tuples), str(limit)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not event_tuples:
|
|
||||||
# TODO: raise?
|
|
||||||
return
|
|
||||||
|
|
||||||
subpath = "/backfill/%s/" % (context,)
|
|
||||||
|
|
||||||
args = {
|
|
||||||
"v": event_tuples,
|
|
||||||
"limit": [str(limit)],
|
|
||||||
}
|
|
||||||
|
|
||||||
return self._do_request_for_transaction(
|
|
||||||
dest,
|
|
||||||
subpath,
|
|
||||||
args=args,
|
|
||||||
)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
@log_function
|
|
||||||
def send_transaction(self, transaction, json_data_callback=None):
|
|
||||||
""" Sends the given Transaction to its destination
|
|
||||||
|
|
||||||
Args:
|
|
||||||
transaction (Transaction)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred: Results of the deferred is a tuple in the form of
|
|
||||||
(response_code, response_body) where the response_body is a
|
|
||||||
python dict decoded from json
|
|
||||||
"""
|
|
||||||
logger.debug(
|
|
||||||
"send_data dest=%s, txid=%s",
|
|
||||||
transaction.destination, transaction.transaction_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if transaction.destination == self.server_name:
|
|
||||||
raise RuntimeError("Transport layer cannot send to itself!")
|
|
||||||
|
|
||||||
# FIXME: This is only used by the tests. The actual json sent is
|
|
||||||
# generated by the json_data_callback.
|
|
||||||
json_data = transaction.get_dict()
|
|
||||||
|
|
||||||
code, response = yield self.client.put_json(
|
|
||||||
transaction.destination,
|
|
||||||
path=PREFIX + "/send/%s/" % transaction.transaction_id,
|
|
||||||
data=json_data,
|
|
||||||
json_data_callback=json_data_callback,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
"send_data dest=%s, txid=%s, got response: %d",
|
|
||||||
transaction.destination, transaction.transaction_id, code
|
|
||||||
)
|
|
||||||
|
|
||||||
defer.returnValue((code, response))
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
@log_function
|
|
||||||
def make_query(self, destination, query_type, args, retry_on_dns_fail):
|
|
||||||
path = PREFIX + "/query/%s" % query_type
|
|
||||||
|
|
||||||
response = yield self.client.get_json(
|
|
||||||
destination=destination,
|
|
||||||
path=path,
|
|
||||||
args=args,
|
|
||||||
retry_on_dns_fail=retry_on_dns_fail,
|
|
||||||
)
|
|
||||||
|
|
||||||
defer.returnValue(response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
@log_function
|
|
||||||
def make_join(self, destination, context, user_id, retry_on_dns_fail=True):
|
|
||||||
path = PREFIX + "/make_join/%s/%s" % (context, user_id,)
|
|
||||||
|
|
||||||
response = yield self.client.get_json(
|
|
||||||
destination=destination,
|
|
||||||
path=path,
|
|
||||||
retry_on_dns_fail=retry_on_dns_fail,
|
|
||||||
)
|
|
||||||
|
|
||||||
defer.returnValue(response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
@log_function
|
|
||||||
def send_join(self, destination, context, event_id, content):
|
|
||||||
path = PREFIX + "/send_join/%s/%s" % (
|
|
||||||
context,
|
|
||||||
event_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
code, content = yield self.client.put_json(
|
|
||||||
destination=destination,
|
|
||||||
path=path,
|
|
||||||
data=content,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not 200 <= code < 300:
|
|
||||||
raise RuntimeError("Got %d from send_join", code)
|
|
||||||
|
|
||||||
defer.returnValue(json.loads(content))
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
@log_function
|
|
||||||
def send_invite(self, destination, context, event_id, content):
|
|
||||||
path = PREFIX + "/invite/%s/%s" % (
|
|
||||||
context,
|
|
||||||
event_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
code, content = yield self.client.put_json(
|
|
||||||
destination=destination,
|
|
||||||
path=path,
|
|
||||||
data=content,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not 200 <= code < 300:
|
|
||||||
raise RuntimeError("Got %d from send_invite", code)
|
|
||||||
|
|
||||||
defer.returnValue(json.loads(content))
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
@log_function
|
|
||||||
def get_event_auth(self, destination, context, event_id):
|
|
||||||
path = PREFIX + "/event_auth/%s/%s" % (
|
|
||||||
context,
|
|
||||||
event_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
response = yield self.client.get_json(
|
|
||||||
destination=destination,
|
|
||||||
path=path,
|
|
||||||
)
|
|
||||||
|
|
||||||
defer.returnValue(response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _authenticate_request(self, request):
|
def _authenticate_request(self, request):
|
||||||
|
@ -373,8 +132,6 @@ class TransportLayer(object):
|
||||||
"""
|
"""
|
||||||
self.request_handler = handler
|
self.request_handler = handler
|
||||||
|
|
||||||
# TODO(markjh): Namespace the federation URI paths
|
|
||||||
|
|
||||||
# This is for when someone asks us for everything since version X
|
# This is for when someone asks us for everything since version X
|
||||||
self.server.register_path(
|
self.server.register_path(
|
||||||
"GET",
|
"GET",
|
||||||
|
@ -528,34 +285,6 @@ class TransportLayer(object):
|
||||||
|
|
||||||
defer.returnValue((code, response))
|
defer.returnValue((code, response))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
@log_function
|
|
||||||
def _do_request_for_transaction(self, destination, subpath, args={}):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
destination (str)
|
|
||||||
path (str)
|
|
||||||
args (dict): This is parsed directly to the HttpClient.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Deferred: Results in a dict.
|
|
||||||
"""
|
|
||||||
|
|
||||||
data = yield self.client.get_json(
|
|
||||||
destination,
|
|
||||||
path=PREFIX + subpath,
|
|
||||||
args=args,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add certain keys to the JSON, ready for decoding as a Transaction
|
|
||||||
data.update(
|
|
||||||
origin=destination,
|
|
||||||
destination=self.server_name,
|
|
||||||
transaction_id=None
|
|
||||||
)
|
|
||||||
|
|
||||||
defer.returnValue(data)
|
|
||||||
|
|
||||||
@log_function
|
@log_function
|
||||||
def _on_backfill_request(self, origin, context, v_list, limits):
|
def _on_backfill_request(self, origin, context, v_list, limits):
|
||||||
if not limits:
|
if not limits:
|
|
@ -144,7 +144,5 @@ class BaseHandler(object):
|
||||||
yield self.notifier.on_new_room_event(event, extra_users=extra_users)
|
yield self.notifier.on_new_room_event(event, extra_users=extra_users)
|
||||||
|
|
||||||
yield federation_handler.handle_new_event(
|
yield federation_handler.handle_new_event(
|
||||||
event,
|
event, destinations=destinations,
|
||||||
None,
|
|
||||||
destinations=destinations,
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -46,7 +46,8 @@ class EventStreamHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def get_stream(self, auth_user_id, pagin_config, timeout=0):
|
def get_stream(self, auth_user_id, pagin_config, timeout=0,
|
||||||
|
as_client_event=True):
|
||||||
auth_user = self.hs.parse_userid(auth_user_id)
|
auth_user = self.hs.parse_userid(auth_user_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -69,16 +70,16 @@ class EventStreamHandler(BaseHandler):
|
||||||
pagin_config.from_token = None
|
pagin_config.from_token = None
|
||||||
|
|
||||||
rm_handler = self.hs.get_handlers().room_member_handler
|
rm_handler = self.hs.get_handlers().room_member_handler
|
||||||
logger.debug("BETA")
|
|
||||||
room_ids = yield rm_handler.get_rooms_for_user(auth_user)
|
room_ids = yield rm_handler.get_rooms_for_user(auth_user)
|
||||||
|
|
||||||
logger.debug("ALPHA")
|
|
||||||
with PreserveLoggingContext():
|
with PreserveLoggingContext():
|
||||||
events, tokens = yield self.notifier.get_events_for(
|
events, tokens = yield self.notifier.get_events_for(
|
||||||
auth_user, room_ids, pagin_config, timeout
|
auth_user, room_ids, pagin_config, timeout
|
||||||
)
|
)
|
||||||
|
|
||||||
chunks = [self.hs.serialize_event(e) for e in events]
|
chunks = [
|
||||||
|
self.hs.serialize_event(e, as_client_event) for e in events
|
||||||
|
]
|
||||||
|
|
||||||
chunk = {
|
chunk = {
|
||||||
"chunk": chunks,
|
"chunk": chunks,
|
||||||
|
|
|
@ -75,14 +75,14 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
@log_function
|
@log_function
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def handle_new_event(self, event, snapshot, destinations):
|
def handle_new_event(self, event, destinations):
|
||||||
""" Takes in an event from the client to server side, that has already
|
""" Takes in an event from the client to server side, that has already
|
||||||
been authed and handled by the state module, and sends it to any
|
been authed and handled by the state module, and sends it to any
|
||||||
remote home servers that may be interested.
|
remote home servers that may be interested.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event
|
event: The event to send
|
||||||
snapshot (.storage.Snapshot): THe snapshot the event happened after
|
destinations: A list of destinations to send it to
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred: Resolved when it has successfully been queued for
|
Deferred: Resolved when it has successfully been queued for
|
||||||
|
@ -154,7 +154,7 @@ class FederationHandler(BaseHandler):
|
||||||
replication = self.replication_layer
|
replication = self.replication_layer
|
||||||
|
|
||||||
if not state:
|
if not state:
|
||||||
state, auth_chain = yield replication.get_state_for_context(
|
state, auth_chain = yield replication.get_state_for_room(
|
||||||
origin, context=event.room_id, event_id=event.event_id,
|
origin, context=event.room_id, event_id=event.event_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -281,7 +281,7 @@ class FederationHandler(BaseHandler):
|
||||||
"""
|
"""
|
||||||
pdu = yield self.replication_layer.send_invite(
|
pdu = yield self.replication_layer.send_invite(
|
||||||
destination=target_host,
|
destination=target_host,
|
||||||
context=event.room_id,
|
room_id=event.room_id,
|
||||||
event_id=event.event_id,
|
event_id=event.event_id,
|
||||||
pdu=event
|
pdu=event
|
||||||
)
|
)
|
||||||
|
@ -617,13 +617,13 @@ class FederationHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
def on_backfill_request(self, origin, context, pdu_list, limit):
|
def on_backfill_request(self, origin, room_id, pdu_list, limit):
|
||||||
in_room = yield self.auth.check_host_in_room(context, origin)
|
in_room = yield self.auth.check_host_in_room(room_id, origin)
|
||||||
if not in_room:
|
if not in_room:
|
||||||
raise AuthError(403, "Host not in room.")
|
raise AuthError(403, "Host not in room.")
|
||||||
|
|
||||||
events = yield self.store.get_backfill_events(
|
events = yield self.store.get_backfill_events(
|
||||||
context,
|
room_id,
|
||||||
pdu_list,
|
pdu_list,
|
||||||
limit
|
limit
|
||||||
)
|
)
|
||||||
|
|
|
@ -67,7 +67,7 @@ class MessageHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_messages(self, user_id=None, room_id=None, pagin_config=None,
|
def get_messages(self, user_id=None, room_id=None, pagin_config=None,
|
||||||
feedback=False):
|
feedback=False, as_client_event=True):
|
||||||
"""Get messages in a room.
|
"""Get messages in a room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -76,6 +76,7 @@ class MessageHandler(BaseHandler):
|
||||||
pagin_config (synapse.api.streams.PaginationConfig): The pagination
|
pagin_config (synapse.api.streams.PaginationConfig): The pagination
|
||||||
config rules to apply, if any.
|
config rules to apply, if any.
|
||||||
feedback (bool): True to get compressed feedback with the messages
|
feedback (bool): True to get compressed feedback with the messages
|
||||||
|
as_client_event (bool): True to get events in client-server format.
|
||||||
Returns:
|
Returns:
|
||||||
dict: Pagination API results
|
dict: Pagination API results
|
||||||
"""
|
"""
|
||||||
|
@ -99,7 +100,9 @@ class MessageHandler(BaseHandler):
|
||||||
)
|
)
|
||||||
|
|
||||||
chunk = {
|
chunk = {
|
||||||
"chunk": [self.hs.serialize_event(e) for e in events],
|
"chunk": [
|
||||||
|
self.hs.serialize_event(e, as_client_event) for e in events
|
||||||
|
],
|
||||||
"start": pagin_config.from_token.to_string(),
|
"start": pagin_config.from_token.to_string(),
|
||||||
"end": next_token.to_string(),
|
"end": next_token.to_string(),
|
||||||
}
|
}
|
||||||
|
@ -211,7 +214,7 @@ class MessageHandler(BaseHandler):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def snapshot_all_rooms(self, user_id=None, pagin_config=None,
|
def snapshot_all_rooms(self, user_id=None, pagin_config=None,
|
||||||
feedback=False):
|
feedback=False, as_client_event=True):
|
||||||
"""Retrieve a snapshot of all rooms the user is invited or has joined.
|
"""Retrieve a snapshot of all rooms the user is invited or has joined.
|
||||||
|
|
||||||
This snapshot may include messages for all rooms where the user is
|
This snapshot may include messages for all rooms where the user is
|
||||||
|
@ -222,6 +225,7 @@ class MessageHandler(BaseHandler):
|
||||||
pagin_config (synapse.api.streams.PaginationConfig): The pagination
|
pagin_config (synapse.api.streams.PaginationConfig): The pagination
|
||||||
config used to determine how many messages *PER ROOM* to return.
|
config used to determine how many messages *PER ROOM* to return.
|
||||||
feedback (bool): True to get feedback along with these messages.
|
feedback (bool): True to get feedback along with these messages.
|
||||||
|
as_client_event (bool): True to get events in client-server format.
|
||||||
Returns:
|
Returns:
|
||||||
A list of dicts with "room_id" and "membership" keys for all rooms
|
A list of dicts with "room_id" and "membership" keys for all rooms
|
||||||
the user is currently invited or joined in on. Rooms where the user
|
the user is currently invited or joined in on. Rooms where the user
|
||||||
|
@ -280,7 +284,10 @@ class MessageHandler(BaseHandler):
|
||||||
end_token = now_token.copy_and_replace("room_key", token[1])
|
end_token = now_token.copy_and_replace("room_key", token[1])
|
||||||
|
|
||||||
d["messages"] = {
|
d["messages"] = {
|
||||||
"chunk": [self.hs.serialize_event(m) for m in messages],
|
"chunk": [
|
||||||
|
self.hs.serialize_event(m, as_client_event)
|
||||||
|
for m in messages
|
||||||
|
],
|
||||||
"start": start_token.to_string(),
|
"start": start_token.to_string(),
|
||||||
"end": end_token.to_string(),
|
"end": end_token.to_string(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -425,10 +425,22 @@ class RoomMemberHandler(BaseHandler):
|
||||||
event.room_id,
|
event.room_id,
|
||||||
self.hs.hostname
|
self.hs.hostname
|
||||||
)
|
)
|
||||||
|
if not is_host_in_room:
|
||||||
|
# is *anyone* in the room?
|
||||||
|
room_member_keys = [
|
||||||
|
v for (k, v) in context.current_state.keys() if (
|
||||||
|
k == "m.room.member"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if len(room_member_keys) == 0:
|
||||||
|
# has the room been created so we can join it?
|
||||||
|
create_event = context.current_state.get(("m.room.create", ""))
|
||||||
|
if create_event:
|
||||||
|
is_host_in_room = True
|
||||||
|
|
||||||
if is_host_in_room:
|
if is_host_in_room:
|
||||||
should_do_dance = False
|
should_do_dance = False
|
||||||
elif room_host:
|
elif room_host: # TODO: Shouldn't this be remote_room_host?
|
||||||
should_do_dance = True
|
should_do_dance = True
|
||||||
else:
|
else:
|
||||||
# TODO(markjh): get prev_state from snapshot
|
# TODO(markjh): get prev_state from snapshot
|
||||||
|
@ -442,7 +454,8 @@ class RoomMemberHandler(BaseHandler):
|
||||||
should_do_dance = not self.hs.is_mine(inviter)
|
should_do_dance = not self.hs.is_mine(inviter)
|
||||||
room_host = inviter.domain
|
room_host = inviter.domain
|
||||||
else:
|
else:
|
||||||
should_do_dance = False
|
# return the same error as join_room_alias does
|
||||||
|
raise SynapseError(404, "No known servers")
|
||||||
|
|
||||||
if should_do_dance:
|
if should_do_dance:
|
||||||
handler = self.hs.get_handlers().federation_handler
|
handler = self.hs.get_handlers().federation_handler
|
||||||
|
|
|
@ -83,9 +83,15 @@ class TypingNotificationHandler(BaseHandler):
|
||||||
if member in self._member_typing_timer:
|
if member in self._member_typing_timer:
|
||||||
self.clock.cancel_call_later(self._member_typing_timer[member])
|
self.clock.cancel_call_later(self._member_typing_timer[member])
|
||||||
|
|
||||||
|
def _cb():
|
||||||
|
logger.debug(
|
||||||
|
"%s has timed out in %s", target_user.to_string(), room_id
|
||||||
|
)
|
||||||
|
self._stopped_typing(member)
|
||||||
|
|
||||||
self._member_typing_until[member] = until
|
self._member_typing_until[member] = until
|
||||||
self._member_typing_timer[member] = self.clock.call_later(
|
self._member_typing_timer[member] = self.clock.call_later(
|
||||||
timeout / 1000, lambda: self._stopped_typing(member)
|
timeout / 1000.0, _cb
|
||||||
)
|
)
|
||||||
|
|
||||||
if was_present:
|
if was_present:
|
||||||
|
@ -114,6 +120,10 @@ class TypingNotificationHandler(BaseHandler):
|
||||||
|
|
||||||
member = RoomMember(room_id=room_id, user=target_user)
|
member = RoomMember(room_id=room_id, user=target_user)
|
||||||
|
|
||||||
|
if member in self._member_typing_timer:
|
||||||
|
self.clock.cancel_call_later(self._member_typing_timer[member])
|
||||||
|
del self._member_typing_timer[member]
|
||||||
|
|
||||||
yield self._stopped_typing(member)
|
yield self._stopped_typing(member)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
@ -136,8 +146,10 @@ class TypingNotificationHandler(BaseHandler):
|
||||||
|
|
||||||
del self._member_typing_until[member]
|
del self._member_typing_until[member]
|
||||||
|
|
||||||
self.clock.cancel_call_later(self._member_typing_timer[member])
|
if member in self._member_typing_timer:
|
||||||
del self._member_typing_timer[member]
|
# Don't cancel it - either it already expired, or the real
|
||||||
|
# stopped_typing() will cancel it
|
||||||
|
del self._member_typing_timer[member]
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _push_update(self, room_id, user, typing):
|
def _push_update(self, room_id, user, typing):
|
||||||
|
|
|
@ -53,7 +53,7 @@ class SimpleHttpClient(object):
|
||||||
uri.encode("ascii"),
|
uri.encode("ascii"),
|
||||||
headers=Headers({
|
headers=Headers({
|
||||||
b"Content-Type": [b"application/x-www-form-urlencoded"],
|
b"Content-Type": [b"application/x-www-form-urlencoded"],
|
||||||
b"User-Agent": AGENT_NAME,
|
b"User-Agent": [AGENT_NAME],
|
||||||
}),
|
}),
|
||||||
bodyProducer=FileBodyProducer(StringIO(query_bytes))
|
bodyProducer=FileBodyProducer(StringIO(query_bytes))
|
||||||
)
|
)
|
||||||
|
@ -89,7 +89,7 @@ class SimpleHttpClient(object):
|
||||||
"GET",
|
"GET",
|
||||||
uri.encode("ascii"),
|
uri.encode("ascii"),
|
||||||
headers=Headers({
|
headers=Headers({
|
||||||
b"User-Agent": AGENT_NAME,
|
b"User-Agent": [AGENT_NAME],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ class CaptchaServerHttpClient(SimpleHttpClient):
|
||||||
bodyProducer=FileBodyProducer(StringIO(query_bytes)),
|
bodyProducer=FileBodyProducer(StringIO(query_bytes)),
|
||||||
headers=Headers({
|
headers=Headers({
|
||||||
b"Content-Type": [b"application/x-www-form-urlencoded"],
|
b"Content-Type": [b"application/x-www-form-urlencoded"],
|
||||||
b"User-Agent": AGENT_NAME,
|
b"User-Agent": [AGENT_NAME],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,6 @@ class MatrixFederationHttpClient(object):
|
||||||
requests.
|
requests.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
self.hs = hs
|
self.hs = hs
|
||||||
self.signing_key = hs.config.signing_key[0]
|
self.signing_key = hs.config.signing_key[0]
|
||||||
|
|
|
@ -22,7 +22,8 @@ except IOError as e:
|
||||||
if str(e).startswith("decoder jpeg not available"):
|
if str(e).startswith("decoder jpeg not available"):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"FATAL: jpeg codec not supported. Install pillow correctly! "
|
"FATAL: jpeg codec not supported. Install pillow correctly! "
|
||||||
" 'sudo apt-get install libjpeg-dev' then 'pip install -I pillow'"
|
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
|
||||||
|
" pip install pillow --user'"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
# any other exception is fine
|
# any other exception is fine
|
||||||
|
@ -36,7 +37,8 @@ except IOError as e:
|
||||||
if str(e).startswith("decoder zip not available"):
|
if str(e).startswith("decoder zip not available"):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"FATAL: zip codec not supported. Install pillow correctly! "
|
"FATAL: zip codec not supported. Install pillow correctly! "
|
||||||
" 'sudo apt-get install libjpeg-dev' then 'pip install -I pillow'"
|
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
|
||||||
|
" pip install pillow --user'"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
# any other exception is fine
|
# any other exception is fine
|
||||||
|
|
|
@ -82,7 +82,7 @@ class Thumbnailer(object):
|
||||||
|
|
||||||
def save_image(self, output_image, output_type, output_path):
|
def save_image(self, output_image, output_type, output_path):
|
||||||
output_bytes_io = BytesIO()
|
output_bytes_io = BytesIO()
|
||||||
output_image.save(output_bytes_io, self.FORMATS[output_type])
|
output_image.save(output_bytes_io, self.FORMATS[output_type], quality=70)
|
||||||
output_bytes = output_bytes_io.getvalue()
|
output_bytes = output_bytes_io.getvalue()
|
||||||
with open(output_path, "wb") as output_file:
|
with open(output_path, "wb") as output_file:
|
||||||
output_file.write(output_bytes)
|
output_file.write(output_bytes)
|
||||||
|
|
|
@ -244,14 +244,14 @@ class Notifier(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
if timeout:
|
if timeout:
|
||||||
self.clock.call_later(timeout/1000.0, _timeout_listener)
|
|
||||||
|
|
||||||
self._register_with_keys(listener)
|
self._register_with_keys(listener)
|
||||||
|
|
||||||
yield self._check_for_updates(listener)
|
yield self._check_for_updates(listener)
|
||||||
|
|
||||||
if not timeout:
|
if not timeout:
|
||||||
_timeout_listener()
|
_timeout_listener()
|
||||||
|
else:
|
||||||
|
self.clock.call_later(timeout/1000.0, _timeout_listener)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
80
synapse/python_dependencies.py
Normal file
80
synapse/python_dependencies.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import logging
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
REQUIREMENTS = {
|
||||||
|
"syutil==0.0.2": ["syutil"],
|
||||||
|
"matrix_angular_sdk==0.6.0": ["syweb==0.6.0"],
|
||||||
|
"Twisted>=14.0.0": ["twisted>=14.0.0"],
|
||||||
|
"service_identity>=1.0.0": ["service_identity>=1.0.0"],
|
||||||
|
"pyopenssl>=0.14": ["OpenSSL>=0.14"],
|
||||||
|
"pyyaml": ["yaml"],
|
||||||
|
"pyasn1": ["pyasn1"],
|
||||||
|
"pynacl": ["nacl"],
|
||||||
|
"daemonize": ["daemonize"],
|
||||||
|
"py-bcrypt": ["bcrypt"],
|
||||||
|
"frozendict>=0.4": ["frozendict"],
|
||||||
|
"pillow": ["PIL"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MissingRequirementError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def check_requirements():
|
||||||
|
"""Checks that all the modules needed by synapse have been correctly
|
||||||
|
installed and are at the correct version"""
|
||||||
|
for dependency, module_requirements in REQUIREMENTS.items():
|
||||||
|
for module_requirement in module_requirements:
|
||||||
|
if ">=" in module_requirement:
|
||||||
|
module_name, required_version = module_requirement.split(">=")
|
||||||
|
version_test = ">="
|
||||||
|
elif "==" in module_requirement:
|
||||||
|
module_name, required_version = module_requirement.split("==")
|
||||||
|
version_test = "=="
|
||||||
|
else:
|
||||||
|
module_name = module_requirement
|
||||||
|
version_test = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
module = __import__(module_name)
|
||||||
|
except ImportError:
|
||||||
|
logging.exception(
|
||||||
|
"Can't import %r which is part of %r",
|
||||||
|
module_name, dependency
|
||||||
|
)
|
||||||
|
raise MissingRequirementError(
|
||||||
|
"Can't import %r which is part of %r"
|
||||||
|
% (module_name, dependency)
|
||||||
|
)
|
||||||
|
version = getattr(module, "__version__", None)
|
||||||
|
file_path = getattr(module, "__file__", None)
|
||||||
|
logger.info(
|
||||||
|
"Using %r version %r from %r to satisfy %r",
|
||||||
|
module_name, version, file_path, dependency
|
||||||
|
)
|
||||||
|
|
||||||
|
if version_test == ">=":
|
||||||
|
if version is None:
|
||||||
|
raise MissingRequirementError(
|
||||||
|
"Version of %r isn't set as __version__ of module %r"
|
||||||
|
% (dependency, module_name)
|
||||||
|
)
|
||||||
|
if LooseVersion(version) < LooseVersion(required_version):
|
||||||
|
raise MissingRequirementError(
|
||||||
|
"Version of %r in %r is too old. %r < %r"
|
||||||
|
% (dependency, file_path, version, required_version)
|
||||||
|
)
|
||||||
|
elif version_test == "==":
|
||||||
|
if version is None:
|
||||||
|
raise MissingRequirementError(
|
||||||
|
"Version of %r isn't set as __version__ of module %r"
|
||||||
|
% (dependency, module_name)
|
||||||
|
)
|
||||||
|
if LooseVersion(version) != LooseVersion(required_version):
|
||||||
|
raise MissingRequirementError(
|
||||||
|
"Unexpected version of %r in %r. %r != %r"
|
||||||
|
% (dependency, file_path, version, required_version)
|
||||||
|
)
|
|
@ -44,8 +44,11 @@ class EventStreamRestServlet(RestServlet):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise SynapseError(400, "timeout must be in milliseconds.")
|
raise SynapseError(400, "timeout must be in milliseconds.")
|
||||||
|
|
||||||
|
as_client_event = "raw" not in request.args
|
||||||
|
|
||||||
chunk = yield handler.get_stream(
|
chunk = yield handler.get_stream(
|
||||||
auth_user.to_string(), pagin_config, timeout=timeout
|
auth_user.to_string(), pagin_config, timeout=timeout,
|
||||||
|
as_client_event=as_client_event
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
logger.exception("Event stream failed")
|
logger.exception("Event stream failed")
|
||||||
|
|
|
@ -27,12 +27,15 @@ class InitialSyncRestServlet(RestServlet):
|
||||||
def on_GET(self, request):
|
def on_GET(self, request):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user = yield self.auth.get_user_by_req(request)
|
||||||
with_feedback = "feedback" in request.args
|
with_feedback = "feedback" in request.args
|
||||||
|
as_client_event = "raw" not in request.args
|
||||||
pagination_config = PaginationConfig.from_request(request)
|
pagination_config = PaginationConfig.from_request(request)
|
||||||
handler = self.handlers.message_handler
|
handler = self.handlers.message_handler
|
||||||
content = yield handler.snapshot_all_rooms(
|
content = yield handler.snapshot_all_rooms(
|
||||||
user_id=user.to_string(),
|
user_id=user.to_string(),
|
||||||
pagin_config=pagination_config,
|
pagin_config=pagination_config,
|
||||||
feedback=with_feedback)
|
feedback=with_feedback,
|
||||||
|
as_client_event=as_client_event
|
||||||
|
)
|
||||||
|
|
||||||
defer.returnValue((200, content))
|
defer.returnValue((200, content))
|
||||||
|
|
||||||
|
|
|
@ -246,7 +246,7 @@ class JoinRoomAliasServlet(RestServlet):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
defer.returnValue((200, {}))
|
defer.returnValue((200, {"room_id": identifier.to_string()}))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, room_identifier, txn_id):
|
def on_PUT(self, request, room_identifier, txn_id):
|
||||||
|
@ -314,12 +314,15 @@ class RoomMessageListRestServlet(RestServlet):
|
||||||
request, default_limit=10,
|
request, default_limit=10,
|
||||||
)
|
)
|
||||||
with_feedback = "feedback" in request.args
|
with_feedback = "feedback" in request.args
|
||||||
|
as_client_event = "raw" not in request.args
|
||||||
handler = self.handlers.message_handler
|
handler = self.handlers.message_handler
|
||||||
msgs = yield handler.get_messages(
|
msgs = yield handler.get_messages(
|
||||||
room_id=room_id,
|
room_id=room_id,
|
||||||
user_id=user.to_string(),
|
user_id=user.to_string(),
|
||||||
pagin_config=pagination_config,
|
pagin_config=pagination_config,
|
||||||
feedback=with_feedback)
|
feedback=with_feedback,
|
||||||
|
as_client_event=as_client_event
|
||||||
|
)
|
||||||
|
|
||||||
defer.returnValue((200, msgs))
|
defer.returnValue((200, msgs))
|
||||||
|
|
||||||
|
|
|
@ -149,8 +149,8 @@ class BaseHomeServer(object):
|
||||||
object."""
|
object."""
|
||||||
return EventID.from_string(s)
|
return EventID.from_string(s)
|
||||||
|
|
||||||
def serialize_event(self, e):
|
def serialize_event(self, e, as_client_event=True):
|
||||||
return serialize_event(self, e)
|
return serialize_event(self, e, as_client_event)
|
||||||
|
|
||||||
def get_ip_from_request(self, request):
|
def get_ip_from_request(self, request):
|
||||||
# May be an X-Forwarding-For header depending on config
|
# May be an X-Forwarding-For header depending on config
|
||||||
|
|
|
@ -58,13 +58,6 @@ class RoomStore(SQLBaseStore):
|
||||||
logger.error("store_room with room_id=%s failed: %s", room_id, e)
|
logger.error("store_room with room_id=%s failed: %s", room_id, e)
|
||||||
raise StoreError(500, "Problem creating room.")
|
raise StoreError(500, "Problem creating room.")
|
||||||
|
|
||||||
def store_room_config(self, room_id, visibility):
|
|
||||||
return self._simple_update_one(
|
|
||||||
table=RoomsTable.table_name,
|
|
||||||
keyvalues={"room_id": room_id},
|
|
||||||
updatevalues={"is_public": visibility}
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_room(self, room_id):
|
def get_room(self, room_id):
|
||||||
"""Retrieve a room.
|
"""Retrieve a room.
|
||||||
|
|
||||||
|
|
|
@ -78,12 +78,6 @@ class StateStore(SQLBaseStore):
|
||||||
f,
|
f,
|
||||||
)
|
)
|
||||||
|
|
||||||
def store_state_groups(self, event):
|
|
||||||
return self.runInteraction(
|
|
||||||
"store_state_groups",
|
|
||||||
self._store_state_groups_txn, event
|
|
||||||
)
|
|
||||||
|
|
||||||
def _store_state_groups_txn(self, txn, event, context):
|
def _store_state_groups_txn(self, txn, event, context):
|
||||||
if context.current_state is None:
|
if context.current_state is None:
|
||||||
return
|
return
|
||||||
|
|
|
@ -39,6 +39,8 @@ from ._base import SQLBaseStore
|
||||||
from synapse.api.errors import SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,91 +54,79 @@ _STREAM_TOKEN = "stream"
|
||||||
_TOPOLOGICAL_TOKEN = "topological"
|
_TOPOLOGICAL_TOKEN = "topological"
|
||||||
|
|
||||||
|
|
||||||
def _parse_stream_token(string):
|
class _StreamToken(namedtuple("_StreamToken", "topological stream")):
|
||||||
try:
|
"""Tokens are positions between events. The token "s1" comes after event 1.
|
||||||
if string[0] != 's':
|
|
||||||
raise
|
|
||||||
return int(string[1:])
|
|
||||||
except:
|
|
||||||
raise SynapseError(400, "Invalid token")
|
|
||||||
|
|
||||||
|
s0 s1
|
||||||
|
| |
|
||||||
|
[0] V [1] V [2]
|
||||||
|
|
||||||
def _parse_topological_token(string):
|
Tokens can either be a point in the live event stream or a cursor going
|
||||||
try:
|
through historic events.
|
||||||
if string[0] != 't':
|
|
||||||
raise
|
|
||||||
parts = string[1:].split('-', 1)
|
|
||||||
return (int(parts[0]), int(parts[1]))
|
|
||||||
except:
|
|
||||||
raise SynapseError(400, "Invalid token")
|
|
||||||
|
|
||||||
|
When traversing the live event stream events are ordered by when they
|
||||||
|
arrived at the homeserver.
|
||||||
|
|
||||||
def is_stream_token(string):
|
When traversing historic events the events are ordered by their depth in
|
||||||
try:
|
the event graph "topological_ordering" and then by when they arrived at the
|
||||||
_parse_stream_token(string)
|
homeserver "stream_ordering".
|
||||||
return True
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
Live tokens start with an "s" followed by the "stream_ordering" id of the
|
||||||
|
event it comes after. Historic tokens start with a "t" followed by the
|
||||||
|
"topological_ordering" id of the event it comes after, follewed by "-",
|
||||||
|
followed by the "stream_ordering" id of the event it comes after.
|
||||||
|
"""
|
||||||
|
__slots__ = []
|
||||||
|
|
||||||
def is_topological_token(string):
|
@classmethod
|
||||||
try:
|
def parse(cls, string):
|
||||||
_parse_topological_token(string)
|
try:
|
||||||
return True
|
if string[0] == 's':
|
||||||
except:
|
return cls(None, int(string[1:]))
|
||||||
return False
|
if string[0] == 't':
|
||||||
|
parts = string[1:].split('-', 1)
|
||||||
|
return cls(int(parts[1]), int(parts[0]))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
raise SynapseError(400, "Invalid token %r" % (string,))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_stream_token(cls, string):
|
||||||
|
try:
|
||||||
|
if string[0] == 's':
|
||||||
|
return cls(None, int(string[1:]))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
raise SynapseError(400, "Invalid token %r" % (string,))
|
||||||
|
|
||||||
def _get_token_bound(token, comparison):
|
def __str__(self):
|
||||||
try:
|
if self.topological is not None:
|
||||||
s = _parse_stream_token(token)
|
return "t%d-%d" % (self.topological, self.stream)
|
||||||
return "%s %s %d" % ("stream_ordering", comparison, s)
|
else:
|
||||||
except:
|
return "s%d" % (self.stream,)
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
def lower_bound(self):
|
||||||
top, stream = _parse_topological_token(token)
|
if self.topological is None:
|
||||||
return "%s %s %d AND %s %s %d" % (
|
return "(%d < %s)" % (self.stream, "stream_ordering")
|
||||||
"topological_ordering", comparison, top,
|
else:
|
||||||
"stream_ordering", comparison, stream,
|
return "(%d < %s OR (%d == %s AND %d < %s))" % (
|
||||||
)
|
self.topological, "topological_ordering",
|
||||||
except:
|
self.topological, "topological_ordering",
|
||||||
pass
|
self.stream, "stream_ordering",
|
||||||
|
)
|
||||||
|
|
||||||
raise SynapseError(400, "Invalid token")
|
def upper_bound(self):
|
||||||
|
if self.topological is None:
|
||||||
|
return "(%d >= %s)" % (self.stream, "stream_ordering")
|
||||||
|
else:
|
||||||
|
return "(%d > %s OR (%d == %s AND %d >= %s))" % (
|
||||||
|
self.topological, "topological_ordering",
|
||||||
|
self.topological, "topological_ordering",
|
||||||
|
self.stream, "stream_ordering",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StreamStore(SQLBaseStore):
|
class StreamStore(SQLBaseStore):
|
||||||
@log_function
|
|
||||||
def get_room_events(self, user_id, from_key, to_key, room_id, limit=0,
|
|
||||||
direction='f', with_feedback=False):
|
|
||||||
# We deal with events request in two different ways depending on if
|
|
||||||
# this looks like an /events request or a pagination request.
|
|
||||||
is_events = (
|
|
||||||
direction == 'f'
|
|
||||||
and user_id
|
|
||||||
and is_stream_token(from_key)
|
|
||||||
and to_key and is_stream_token(to_key)
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_events:
|
|
||||||
return self.get_room_events_stream(
|
|
||||||
user_id=user_id,
|
|
||||||
from_key=from_key,
|
|
||||||
to_key=to_key,
|
|
||||||
room_id=room_id,
|
|
||||||
limit=limit,
|
|
||||||
with_feedback=with_feedback,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return self.paginate_room_events(
|
|
||||||
from_key=from_key,
|
|
||||||
to_key=to_key,
|
|
||||||
room_id=room_id,
|
|
||||||
limit=limit,
|
|
||||||
with_feedback=with_feedback,
|
|
||||||
)
|
|
||||||
|
|
||||||
@log_function
|
@log_function
|
||||||
def get_room_events_stream(self, user_id, from_key, to_key, room_id,
|
def get_room_events_stream(self, user_id, from_key, to_key, room_id,
|
||||||
limit=0, with_feedback=False):
|
limit=0, with_feedback=False):
|
||||||
|
@ -162,8 +152,8 @@ class StreamStore(SQLBaseStore):
|
||||||
limit = MAX_STREAM_SIZE
|
limit = MAX_STREAM_SIZE
|
||||||
|
|
||||||
# From and to keys should be integers from ordering.
|
# From and to keys should be integers from ordering.
|
||||||
from_id = _parse_stream_token(from_key)
|
from_id = _StreamToken.parse_stream_token(from_key)
|
||||||
to_id = _parse_stream_token(to_key)
|
to_id = _StreamToken.parse_stream_token(to_key)
|
||||||
|
|
||||||
if from_key == to_key:
|
if from_key == to_key:
|
||||||
return defer.succeed(([], to_key))
|
return defer.succeed(([], to_key))
|
||||||
|
@ -181,7 +171,7 @@ class StreamStore(SQLBaseStore):
|
||||||
}
|
}
|
||||||
|
|
||||||
def f(txn):
|
def f(txn):
|
||||||
txn.execute(sql, (user_id, user_id, from_id, to_id,))
|
txn.execute(sql, (user_id, user_id, from_id.stream, to_id.stream,))
|
||||||
|
|
||||||
rows = self.cursor_to_dict(txn)
|
rows = self.cursor_to_dict(txn)
|
||||||
|
|
||||||
|
@ -211,17 +201,21 @@ class StreamStore(SQLBaseStore):
|
||||||
# Tokens really represent positions between elements, but we use
|
# Tokens really represent positions between elements, but we use
|
||||||
# the convention of pointing to the event before the gap. Hence
|
# the convention of pointing to the event before the gap. Hence
|
||||||
# we have a bit of asymmetry when it comes to equalities.
|
# we have a bit of asymmetry when it comes to equalities.
|
||||||
from_comp = '<=' if direction == 'b' else '>'
|
|
||||||
to_comp = '>' if direction == 'b' else '<='
|
|
||||||
order = "DESC" if direction == 'b' else "ASC"
|
|
||||||
|
|
||||||
args = [room_id]
|
args = [room_id]
|
||||||
|
if direction == 'b':
|
||||||
bounds = _get_token_bound(from_key, from_comp)
|
order = "DESC"
|
||||||
if to_key:
|
bounds = _StreamToken.parse(from_key).upper_bound()
|
||||||
bounds = "%s AND %s" % (
|
if to_key:
|
||||||
bounds, _get_token_bound(to_key, to_comp)
|
bounds = "%s AND %s" % (
|
||||||
)
|
bounds, _StreamToken.parse(to_key).lower_bound()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
order = "ASC"
|
||||||
|
bounds = _StreamToken.parse(from_key).lower_bound()
|
||||||
|
if to_key:
|
||||||
|
bounds = "%s AND %s" % (
|
||||||
|
bounds, _StreamToken.parse(to_key).upper_bound()
|
||||||
|
)
|
||||||
|
|
||||||
if int(limit) > 0:
|
if int(limit) > 0:
|
||||||
args.append(int(limit))
|
args.append(int(limit))
|
||||||
|
@ -249,9 +243,13 @@ class StreamStore(SQLBaseStore):
|
||||||
topo = rows[-1]["topological_ordering"]
|
topo = rows[-1]["topological_ordering"]
|
||||||
toke = rows[-1]["stream_ordering"]
|
toke = rows[-1]["stream_ordering"]
|
||||||
if direction == 'b':
|
if direction == 'b':
|
||||||
topo -= 1
|
# Tokens are positions between events.
|
||||||
|
# This token points *after* the last event in the chunk.
|
||||||
|
# We need it to point to the event before it in the chunk
|
||||||
|
# when we are going backwards so we subtract one from the
|
||||||
|
# stream part.
|
||||||
toke -= 1
|
toke -= 1
|
||||||
next_token = "t%s-%s" % (topo, toke)
|
next_token = str(_StreamToken(topo, toke))
|
||||||
else:
|
else:
|
||||||
# TODO (erikj): We should work out what to do here instead.
|
# TODO (erikj): We should work out what to do here instead.
|
||||||
next_token = to_key if to_key else from_key
|
next_token = to_key if to_key else from_key
|
||||||
|
@ -284,9 +282,14 @@ class StreamStore(SQLBaseStore):
|
||||||
rows.reverse() # As we selected with reverse ordering
|
rows.reverse() # As we selected with reverse ordering
|
||||||
|
|
||||||
if rows:
|
if rows:
|
||||||
|
# Tokens are positions between events.
|
||||||
|
# This token points *after* the last event in the chunk.
|
||||||
|
# We need it to point to the event before it in the chunk
|
||||||
|
# since we are going backwards so we subtract one from the
|
||||||
|
# stream part.
|
||||||
topo = rows[0]["topological_ordering"]
|
topo = rows[0]["topological_ordering"]
|
||||||
toke = rows[0]["stream_ordering"]
|
toke = rows[0]["stream_ordering"] - 1
|
||||||
start_token = "t%s-%s" % (topo, toke)
|
start_token = str(_StreamToken(topo, toke))
|
||||||
|
|
||||||
token = (start_token, end_token)
|
token = (start_token, end_token)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -59,23 +59,29 @@ class JustPresenceHandlers(object):
|
||||||
def __init__(self, hs):
|
def __init__(self, hs):
|
||||||
self.presence_handler = PresenceHandler(hs)
|
self.presence_handler = PresenceHandler(hs)
|
||||||
|
|
||||||
class PresenceStateTestCase(unittest.TestCase):
|
|
||||||
""" Tests presence management. """
|
|
||||||
|
|
||||||
|
class PresenceTestCase(unittest.TestCase):
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
db_pool = SQLiteMemoryDbPool()
|
db_pool = SQLiteMemoryDbPool()
|
||||||
yield db_pool.prepare()
|
yield db_pool.prepare()
|
||||||
|
|
||||||
|
self.clock = MockClock()
|
||||||
|
|
||||||
self.mock_config = NonCallableMock()
|
self.mock_config = NonCallableMock()
|
||||||
self.mock_config.signing_key = [MockKey()]
|
self.mock_config.signing_key = [MockKey()]
|
||||||
|
|
||||||
|
self.mock_federation_resource = MockHttpResource()
|
||||||
|
|
||||||
|
self.mock_http_client = Mock(spec=[])
|
||||||
|
self.mock_http_client.put_json = DeferredMockCallable()
|
||||||
|
|
||||||
hs = HomeServer("test",
|
hs = HomeServer("test",
|
||||||
clock=MockClock(),
|
clock=self.clock,
|
||||||
db_pool=db_pool,
|
db_pool=db_pool,
|
||||||
handlers=None,
|
handlers=None,
|
||||||
resource_for_federation=Mock(),
|
resource_for_federation=self.mock_federation_resource,
|
||||||
http_client=None,
|
http_client=self.mock_http_client,
|
||||||
config=self.mock_config,
|
config=self.mock_config,
|
||||||
keyring=Mock(),
|
keyring=Mock(),
|
||||||
)
|
)
|
||||||
|
@ -92,24 +98,33 @@ class PresenceStateTestCase(unittest.TestCase):
|
||||||
self.u_banana = hs.parse_userid("@banana:test")
|
self.u_banana = hs.parse_userid("@banana:test")
|
||||||
self.u_clementine = hs.parse_userid("@clementine:test")
|
self.u_clementine = hs.parse_userid("@clementine:test")
|
||||||
|
|
||||||
yield self.store.create_presence(self.u_apple.localpart)
|
for u in self.u_apple, self.u_banana, self.u_clementine:
|
||||||
|
yield self.store.create_presence(u.localpart)
|
||||||
|
|
||||||
yield self.store.set_presence_state(
|
yield self.store.set_presence_state(
|
||||||
self.u_apple.localpart, {"state": ONLINE, "status_msg": "Online"}
|
self.u_apple.localpart, {"state": ONLINE, "status_msg": "Online"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ID of a local user that does not exist
|
||||||
|
self.u_durian = hs.parse_userid("@durian:test")
|
||||||
|
|
||||||
|
# A remote user
|
||||||
|
self.u_cabbage = hs.parse_userid("@cabbage:elsewhere")
|
||||||
|
|
||||||
self.handler = hs.get_handlers().presence_handler
|
self.handler = hs.get_handlers().presence_handler
|
||||||
|
|
||||||
|
self.room_id = "a-room"
|
||||||
self.room_members = []
|
self.room_members = []
|
||||||
|
|
||||||
def get_rooms_for_user(user):
|
def get_rooms_for_user(user):
|
||||||
if user in self.room_members:
|
if user in self.room_members:
|
||||||
return defer.succeed(["a-room"])
|
return defer.succeed([self.room_id])
|
||||||
else:
|
else:
|
||||||
return defer.succeed([])
|
return defer.succeed([])
|
||||||
room_member_handler.get_rooms_for_user = get_rooms_for_user
|
room_member_handler.get_rooms_for_user = get_rooms_for_user
|
||||||
|
|
||||||
def get_room_members(room_id):
|
def get_room_members(room_id):
|
||||||
if room_id == "a-room":
|
if room_id == self.room_id:
|
||||||
return defer.succeed(self.room_members)
|
return defer.succeed(self.room_members)
|
||||||
else:
|
else:
|
||||||
return defer.succeed([])
|
return defer.succeed([])
|
||||||
|
@ -128,6 +143,10 @@ class PresenceStateTestCase(unittest.TestCase):
|
||||||
self.handler.start_polling_presence = self.mock_start
|
self.handler.start_polling_presence = self.mock_start
|
||||||
self.handler.stop_polling_presence = self.mock_stop
|
self.handler.stop_polling_presence = self.mock_stop
|
||||||
|
|
||||||
|
|
||||||
|
class PresenceStateTestCase(PresenceTestCase):
|
||||||
|
""" Tests presence management. """
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_my_state(self):
|
def test_get_my_state(self):
|
||||||
state = yield self.handler.get_state(
|
state = yield self.handler.get_state(
|
||||||
|
@ -206,56 +225,9 @@ class PresenceStateTestCase(unittest.TestCase):
|
||||||
self.mock_stop.assert_called_with(self.u_apple)
|
self.mock_stop.assert_called_with(self.u_apple)
|
||||||
|
|
||||||
|
|
||||||
class PresenceInvitesTestCase(unittest.TestCase):
|
class PresenceInvitesTestCase(PresenceTestCase):
|
||||||
""" Tests presence management. """
|
""" Tests presence management. """
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def setUp(self):
|
|
||||||
self.mock_http_client = Mock(spec=[])
|
|
||||||
self.mock_http_client.put_json = DeferredMockCallable()
|
|
||||||
|
|
||||||
self.mock_federation_resource = MockHttpResource()
|
|
||||||
|
|
||||||
db_pool = SQLiteMemoryDbPool()
|
|
||||||
yield db_pool.prepare()
|
|
||||||
|
|
||||||
self.mock_config = NonCallableMock()
|
|
||||||
self.mock_config.signing_key = [MockKey()]
|
|
||||||
|
|
||||||
hs = HomeServer("test",
|
|
||||||
clock=MockClock(),
|
|
||||||
db_pool=db_pool,
|
|
||||||
handlers=None,
|
|
||||||
resource_for_client=Mock(),
|
|
||||||
resource_for_federation=self.mock_federation_resource,
|
|
||||||
http_client=self.mock_http_client,
|
|
||||||
config=self.mock_config,
|
|
||||||
keyring=Mock(),
|
|
||||||
)
|
|
||||||
hs.handlers = JustPresenceHandlers(hs)
|
|
||||||
|
|
||||||
self.store = hs.get_datastore()
|
|
||||||
|
|
||||||
# Some local users to test with
|
|
||||||
self.u_apple = hs.parse_userid("@apple:test")
|
|
||||||
self.u_banana = hs.parse_userid("@banana:test")
|
|
||||||
yield self.store.create_presence(self.u_apple.localpart)
|
|
||||||
yield self.store.create_presence(self.u_banana.localpart)
|
|
||||||
|
|
||||||
# ID of a local user that does not exist
|
|
||||||
self.u_durian = hs.parse_userid("@durian:test")
|
|
||||||
|
|
||||||
# A remote user
|
|
||||||
self.u_cabbage = hs.parse_userid("@cabbage:elsewhere")
|
|
||||||
|
|
||||||
self.handler = hs.get_handlers().presence_handler
|
|
||||||
|
|
||||||
self.mock_start = Mock()
|
|
||||||
self.mock_stop = Mock()
|
|
||||||
|
|
||||||
self.handler.start_polling_presence = self.mock_start
|
|
||||||
self.handler.stop_polling_presence = self.mock_stop
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_invite_local(self):
|
def test_invite_local(self):
|
||||||
# TODO(paul): This test will likely break if/when real auth permissions
|
# TODO(paul): This test will likely break if/when real auth permissions
|
||||||
|
@ -558,24 +530,25 @@ class PresencePushTestCase(unittest.TestCase):
|
||||||
])
|
])
|
||||||
self.room_member_handler = hs.handlers.room_member_handler
|
self.room_member_handler = hs.handlers.room_member_handler
|
||||||
|
|
||||||
|
self.room_id = "a-room"
|
||||||
self.room_members = []
|
self.room_members = []
|
||||||
|
|
||||||
def get_rooms_for_user(user):
|
def get_rooms_for_user(user):
|
||||||
if user in self.room_members:
|
if user in self.room_members:
|
||||||
return defer.succeed(["a-room"])
|
return defer.succeed([self.room_id])
|
||||||
else:
|
else:
|
||||||
return defer.succeed([])
|
return defer.succeed([])
|
||||||
self.room_member_handler.get_rooms_for_user = get_rooms_for_user
|
self.room_member_handler.get_rooms_for_user = get_rooms_for_user
|
||||||
|
|
||||||
def get_room_members(room_id):
|
def get_room_members(room_id):
|
||||||
if room_id == "a-room":
|
if room_id == self.room_id:
|
||||||
return defer.succeed(self.room_members)
|
return defer.succeed(self.room_members)
|
||||||
else:
|
else:
|
||||||
return defer.succeed([])
|
return defer.succeed([])
|
||||||
self.room_member_handler.get_room_members = get_room_members
|
self.room_member_handler.get_room_members = get_room_members
|
||||||
|
|
||||||
def get_room_hosts(room_id):
|
def get_room_hosts(room_id):
|
||||||
if room_id == "a-room":
|
if room_id == self.room_id:
|
||||||
hosts = set([u.domain for u in self.room_members])
|
hosts = set([u.domain for u in self.room_members])
|
||||||
return defer.succeed(hosts)
|
return defer.succeed(hosts)
|
||||||
else:
|
else:
|
||||||
|
@ -911,7 +884,7 @@ class PresencePushTestCase(unittest.TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
yield self.distributor.fire("user_joined_room", self.u_clementine,
|
yield self.distributor.fire("user_joined_room", self.u_clementine,
|
||||||
"a-room"
|
self.room_id
|
||||||
)
|
)
|
||||||
|
|
||||||
self.room_members.append(self.u_clementine)
|
self.room_members.append(self.u_clementine)
|
||||||
|
@ -974,7 +947,7 @@ class PresencePushTestCase(unittest.TestCase):
|
||||||
self.room_members = [self.u_apple, self.u_banana]
|
self.room_members = [self.u_apple, self.u_banana]
|
||||||
|
|
||||||
yield self.distributor.fire("user_joined_room", self.u_potato,
|
yield self.distributor.fire("user_joined_room", self.u_potato,
|
||||||
"a-room"
|
self.room_id
|
||||||
)
|
)
|
||||||
|
|
||||||
yield put_json.await_calls()
|
yield put_json.await_calls()
|
||||||
|
@ -1003,7 +976,7 @@ class PresencePushTestCase(unittest.TestCase):
|
||||||
self.room_members.append(self.u_potato)
|
self.room_members.append(self.u_potato)
|
||||||
|
|
||||||
yield self.distributor.fire("user_joined_room", self.u_clementine,
|
yield self.distributor.fire("user_joined_room", self.u_clementine,
|
||||||
"a-room"
|
self.room_id
|
||||||
)
|
)
|
||||||
|
|
||||||
put_json.await_calls()
|
put_json.await_calls()
|
||||||
|
|
|
@ -223,7 +223,7 @@ class RoomMemberHandlerTestCase(unittest.TestCase):
|
||||||
yield room_handler.change_membership(event, context)
|
yield room_handler.change_membership(event, context)
|
||||||
|
|
||||||
self.federation.handle_new_event.assert_called_once_with(
|
self.federation.handle_new_event.assert_called_once_with(
|
||||||
event, None, destinations=set()
|
event, destinations=set()
|
||||||
)
|
)
|
||||||
|
|
||||||
self.datastore.persist_event.assert_called_once_with(
|
self.datastore.persist_event.assert_called_once_with(
|
||||||
|
@ -301,7 +301,7 @@ class RoomMemberHandlerTestCase(unittest.TestCase):
|
||||||
yield room_handler.change_membership(event, context)
|
yield room_handler.change_membership(event, context)
|
||||||
|
|
||||||
self.federation.handle_new_event.assert_called_once_with(
|
self.federation.handle_new_event.assert_called_once_with(
|
||||||
event, None, destinations=set(['red'])
|
event, destinations=set(['red'])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.datastore.persist_event.assert_called_once_with(
|
self.datastore.persist_event.assert_called_once_with(
|
||||||
|
|
|
@ -352,3 +352,29 @@ class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
}},
|
}},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# SYN-230 - see if we can still set after timeout
|
||||||
|
|
||||||
|
yield self.handler.started_typing(
|
||||||
|
target_user=self.u_apple,
|
||||||
|
auth_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
timeout=10000,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.on_new_user_event.assert_has_calls([
|
||||||
|
call(rooms=[self.room_id]),
|
||||||
|
])
|
||||||
|
self.on_new_user_event.reset_mock()
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 3)
|
||||||
|
self.assertEquals(
|
||||||
|
self.event_source.get_new_events_for_user(self.u_apple, 0, None)[0],
|
||||||
|
[
|
||||||
|
{"type": "m.typing",
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"content": {
|
||||||
|
"user_ids": [self.u_apple.to_string()],
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
|
@ -294,7 +294,7 @@ class RoomPermissionsTestCase(RestTestCase):
|
||||||
# set [invite/join/left] of self, set [invite/join/left] of other,
|
# set [invite/join/left] of self, set [invite/join/left] of other,
|
||||||
# expect all 403s
|
# expect all 403s
|
||||||
for usr in [self.user_id, self.rmcreator_id]:
|
for usr in [self.user_id, self.rmcreator_id]:
|
||||||
yield self.join(room=room, user=usr, expect_code=403)
|
yield self.join(room=room, user=usr, expect_code=404)
|
||||||
yield self.leave(room=room, user=usr, expect_code=403)
|
yield self.leave(room=room, user=usr, expect_code=403)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
|
|
@ -21,7 +21,7 @@ from twisted.internet import defer
|
||||||
import synapse.rest.room
|
import synapse.rest.room
|
||||||
from synapse.server import HomeServer
|
from synapse.server import HomeServer
|
||||||
|
|
||||||
from ..utils import MockHttpResource, SQLiteMemoryDbPool, MockKey
|
from ..utils import MockHttpResource, MockClock, SQLiteMemoryDbPool, MockKey
|
||||||
from .utils import RestTestCase
|
from .utils import RestTestCase
|
||||||
|
|
||||||
from mock import Mock, NonCallableMock
|
from mock import Mock, NonCallableMock
|
||||||
|
@ -36,6 +36,8 @@ class RoomTypingTestCase(RestTestCase):
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.clock = MockClock()
|
||||||
|
|
||||||
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
||||||
self.auth_user_id = self.user_id
|
self.auth_user_id = self.user_id
|
||||||
|
|
||||||
|
@ -47,6 +49,7 @@ class RoomTypingTestCase(RestTestCase):
|
||||||
|
|
||||||
hs = HomeServer(
|
hs = HomeServer(
|
||||||
"red",
|
"red",
|
||||||
|
clock=self.clock,
|
||||||
db_pool=db_pool,
|
db_pool=db_pool,
|
||||||
http_client=None,
|
http_client=None,
|
||||||
replication_layer=Mock(),
|
replication_layer=Mock(),
|
||||||
|
@ -77,6 +80,30 @@ class RoomTypingTestCase(RestTestCase):
|
||||||
return defer.succeed(None)
|
return defer.succeed(None)
|
||||||
hs.get_datastore().insert_client_ip = _insert_client_ip
|
hs.get_datastore().insert_client_ip = _insert_client_ip
|
||||||
|
|
||||||
|
def get_room_members(room_id):
|
||||||
|
if room_id == self.room_id:
|
||||||
|
return defer.succeed([hs.parse_userid(self.user_id)])
|
||||||
|
else:
|
||||||
|
return defer.succeed([])
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def fetch_room_distributions_into(room_id, localusers=None,
|
||||||
|
remotedomains=None, ignore_user=None):
|
||||||
|
|
||||||
|
members = yield get_room_members(room_id)
|
||||||
|
for member in members:
|
||||||
|
if ignore_user is not None and member == ignore_user:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if hs.is_mine(member):
|
||||||
|
if localusers is not None:
|
||||||
|
localusers.add(member)
|
||||||
|
else:
|
||||||
|
if remotedomains is not None:
|
||||||
|
remotedomains.add(member.domain)
|
||||||
|
hs.get_handlers().room_member_handler.fetch_room_distributions_into = (
|
||||||
|
fetch_room_distributions_into)
|
||||||
|
|
||||||
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
||||||
|
|
||||||
self.room_id = yield self.create_room_as(self.user_id)
|
self.room_id = yield self.create_room_as(self.user_id)
|
||||||
|
@ -113,3 +140,25 @@ class RoomTypingTestCase(RestTestCase):
|
||||||
'{"typing": false}'
|
'{"typing": false}'
|
||||||
)
|
)
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_typing_timeout(self):
|
||||||
|
(code, _) = yield self.mock_resource.trigger("PUT",
|
||||||
|
"/rooms/%s/typing/%s" % (self.room_id, self.user_id),
|
||||||
|
'{"typing": true, "timeout": 30000}'
|
||||||
|
)
|
||||||
|
self.assertEquals(200, code)
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
|
|
||||||
|
self.clock.advance_time(31);
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 2)
|
||||||
|
|
||||||
|
(code, _) = yield self.mock_resource.trigger("PUT",
|
||||||
|
"/rooms/%s/typing/%s" % (self.room_id, self.user_id),
|
||||||
|
'{"typing": true, "timeout": 30000}'
|
||||||
|
)
|
||||||
|
self.assertEquals(200, code)
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 3)
|
||||||
|
|
|
@ -56,17 +56,6 @@ class RoomStoreTestCase(unittest.TestCase):
|
||||||
(yield self.store.get_room(self.room.to_string()))
|
(yield self.store.get_room(self.room.to_string()))
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_store_room_config(self):
|
|
||||||
yield self.store.store_room_config(self.room.to_string(),
|
|
||||||
visibility=False
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertObjectHasAttributes(
|
|
||||||
{"is_public": False},
|
|
||||||
(yield self.store.get_room(self.room.to_string()))
|
|
||||||
)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_rooms(self):
|
def test_get_rooms(self):
|
||||||
# get_rooms does an INNER JOIN on the room_aliases table :(
|
# get_rooms does an INNER JOIN on the room_aliases table :(
|
||||||
|
|
|
@ -69,6 +69,8 @@ class TestCase(unittest.TestCase):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
logging.getLogger().setLevel(level)
|
logging.getLogger().setLevel(level)
|
||||||
|
# Don't set SQL logging
|
||||||
|
logging.getLogger("synapse.storage").setLevel(old_level)
|
||||||
return orig()
|
return orig()
|
||||||
|
|
||||||
def assertObjectHasAttributes(self, attrs, obj):
|
def assertObjectHasAttributes(self, attrs, obj):
|
||||||
|
|
|
@ -138,7 +138,8 @@ class MockClock(object):
|
||||||
now = 1000
|
now = 1000
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# list of tuples of (absolute_time, callback) in no particular order
|
# list of lists of [absolute_time, callback, expired] in no particular
|
||||||
|
# order
|
||||||
self.timers = []
|
self.timers = []
|
||||||
|
|
||||||
def time(self):
|
def time(self):
|
||||||
|
@ -154,11 +155,16 @@ class MockClock(object):
|
||||||
LoggingContext.thread_local.current_context = current_context
|
LoggingContext.thread_local.current_context = current_context
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
t = (self.now + delay, wrapped_callback)
|
t = [self.now + delay, wrapped_callback, False]
|
||||||
self.timers.append(t)
|
self.timers.append(t)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def cancel_call_later(self, timer):
|
def cancel_call_later(self, timer):
|
||||||
|
if timer[2]:
|
||||||
|
raise Exception("Cannot cancel an expired timer")
|
||||||
|
|
||||||
|
timer[2] = True
|
||||||
self.timers = [t for t in self.timers if t != timer]
|
self.timers = [t for t in self.timers if t != timer]
|
||||||
|
|
||||||
# For unit testing
|
# For unit testing
|
||||||
|
@ -168,11 +174,17 @@ class MockClock(object):
|
||||||
timers = self.timers
|
timers = self.timers
|
||||||
self.timers = []
|
self.timers = []
|
||||||
|
|
||||||
for time, callback in timers:
|
for t in timers:
|
||||||
|
time, callback, expired = t
|
||||||
|
|
||||||
|
if expired:
|
||||||
|
raise Exception("Timer already expired")
|
||||||
|
|
||||||
if self.now >= time:
|
if self.now >= time:
|
||||||
|
t[2] = True
|
||||||
callback()
|
callback()
|
||||||
else:
|
else:
|
||||||
self.timers.append((time, callback))
|
self.timers.append(t)
|
||||||
|
|
||||||
|
|
||||||
class SQLiteMemoryDbPool(ConnectionPool, object):
|
class SQLiteMemoryDbPool(ConnectionPool, object):
|
||||||
|
|
Loading…
Reference in a new issue