1
0

Compare commits

...

112 Commits

Author SHA1 Message Date
Erik Johnston
03c0da9f65 In get_recent_events_for_room, get the full event json rather than just the event_ids so we don't have to do so many db queries 2015-01-22 14:01:10 +00:00
Erik Johnston
33d2d82f6d In get_state_groups, get the full event json rather than just the event_ids so we don't have to do so many db queries 2015-01-22 13:37:44 +00:00
Mark Haines
d3d0713de5 Move experiments, graph and cmdclient into contrib 2015-01-22 11:57:54 +00:00
Mark Haines
8907e143c1 Remove jsfiddles 2015-01-22 11:15:03 +00:00
Mark Haines
f4ce61ed36 Move scripts into scripts 2015-01-22 11:14:14 +00:00
Paul "LeoNerd" Evans
73315ce9de Abstract out the room ID from presence tests, so it's stored in self 2015-01-21 20:01:57 +00:00
Paul "LeoNerd" Evans
dbe71e670c Use common base class for two Presence unit-tests, avoiding boilerplate copypasta 2015-01-21 16:58:16 +00:00
Mark Haines
dc70d1fef8 Only start the notifier timeout once we've had a chance to check for updates. Otherwise the timeout could fire while we are waiting for the database to return any updates it might have 2015-01-19 16:24:54 +00:00
Mark Haines
42529cbced Fix pyflakes errors 2015-01-19 15:33:04 +00:00
Mark Haines
00e9c08609 Fix syntax 2015-01-19 15:30:48 +00:00
Mark Haines
3e85e52b3f Allow ':memory:' as the database path for sqlite3 2015-01-19 15:26:19 +00:00
Mark Haines
5fed042640 Finish renaming "context" to "room_id" in federation codebase 2015-01-16 19:01:03 +00:00
Mark Haines
2408c4b0a4 Fold _do_request_for_transaction into the methods that called it since it was a trivial wrapper around client.get_json 2015-01-16 19:01:03 +00:00
Mark Haines
602684eac5 Split transport layer into client and server parts 2015-01-16 19:01:03 +00:00
Mark Haines
2bdee98269 Remove temporary debug logging that was accidentally committed 2015-01-16 19:00:40 +00:00
Paul "LeoNerd" Evans
34a5fbe2b7 Have /join/:room_id return the room ID in response anyway, for consistency of clients (SYN-234) 2015-01-13 17:29:24 +00:00
Paul "LeoNerd" Evans
cf7e723808 Have MockClock detect attempts to cancel expired timers, to prevent a repeat of SYN-230 2015-01-13 16:58:36 +00:00
Paul "LeoNerd" Evans
c2e7c84e58 Don't try to cancel already-expired timers - SYN-230 2015-01-13 16:58:36 +00:00
Mark Haines
3891597eb3 Remove unused functions 2015-01-13 15:57:26 +00:00
Mark Haines
fda63064fc get_room_events isn't called anywhere 2015-01-13 14:43:26 +00:00
Mark Haines
895fcb377e Fix stream token ordering 2015-01-13 14:38:53 +00:00
Erik Johnston
38e3241eb7 Merge branch 'hotfixes-v0.6.1b' of github.com:matrix-org/synapse into develop 2015-01-13 10:01:22 +00:00
Erik Johnston
1d3d37937d Bump version 2015-01-13 09:59:47 +00:00
Erik Johnston
39585bf556 Insert 'age' into top level when returning events to clients 2015-01-13 09:57:32 +00:00
Paul "LeoNerd" Evans
02ffbb20d0 Use float rather than integer divisions to turn msec into sec - so timeouts under 1000msec will actually work 2015-01-12 19:09:14 +00:00
Paul "LeoNerd" Evans
9c804bc3fd Check that setting typing notification still works after explicit timeout at REST layer - SYN-230 2015-01-12 18:31:48 +00:00
Paul "LeoNerd" Evans
67d8305aea Make typing notification timeouts print a (debug) logging message 2015-01-12 18:22:00 +00:00
Paul "LeoNerd" Evans
db72a07ef5 Don't make @unittest.DEBUG print the huge amount of verbosity generated by the synapse.storage loggers 2015-01-12 18:16:27 +00:00
Paul "LeoNerd" Evans
968dc988f9 Check that setting typing notification still works after explicit timeout - SYN-230 2015-01-12 18:01:49 +00:00
Kegan Dougal
c43d898119 SYN-178: Fix off by one. 2015-01-12 17:38:40 +00:00
Mark Haines
d8fcc4e00a Add copyrighter script for sql 2015-01-12 14:31:05 +00:00
Matthew Hodgson
bfb198a6eb don't clobber pythonpath 2015-01-09 18:14:05 +00:00
Matthew Hodgson
28db5dde4c oops 2015-01-08 20:38:55 +00:00
Matthew Hodgson
80e89772e2 spell out that local libs may need to be explicitly given priority 2015-01-08 20:37:08 +00:00
Mark Haines
63403aa7a5 Check the existance and versions of necessary modules when starting synapse, log which modules are used 2015-01-08 17:08:57 +00:00
Kegan Dougal
9d0dcf2e3c SYN-142: Rotate logs if logging to file. Fixed to a 4 file rotate with 100MB/file for now. 2015-01-08 15:31:29 +00:00
Matthew Hodgson
7f83613733 make our JPEG thumbnail quality less horrifically ugly 2015-01-08 15:11:22 +00:00
Kegan Dougal
b5924cae04 Add raw query param for scrollback. 2015-01-08 14:37:55 +00:00
Erik Johnston
379a653ae3 Add better help message for --server-name config option. 2015-01-08 14:32:53 +00:00
Kegan Dougal
edb557b2ad Return the raw federation event rather than adding extra keys for federation data. 2015-01-08 14:28:08 +00:00
Erik Johnston
5940ec993b Add missing continuation indent. 2015-01-08 13:59:29 +00:00
Kegan Dougal
5720ab59e0 Add 'raw' query parameter to expose the event graph and signatures to savvy clients. 2015-01-08 13:57:40 +00:00
Erik Johnston
d44dd47fbf Add optional limit to graph script 2015-01-08 10:53:03 +00:00
Mark Haines
8ac9199f56 Merge branch 'master' into develop 2015-01-08 09:43:48 +00:00
Mark Haines
f3467d4646 Merge branch 'hotfixes-v0.6.1' 2015-01-08 09:43:01 +00:00
Mark Haines
5a0e687d5c Bump version 2015-01-08 09:42:23 +00:00
Mark Haines
c9d2cecac9 SYN-231: User agent header broken 2015-01-08 09:41:11 +00:00
Erik Johnston
42507b0011 Log server version on startup 2015-01-07 17:25:28 +00:00
Kegan Dougal
76e1565200 Change error message for missing pillow libs. 2015-01-07 17:11:19 +00:00
Kegan Dougal
333836ff92 PEP8 and pyflakes warnings 2015-01-07 16:18:12 +00:00
Kegan Dougal
a09882de83 Update tests 2015-01-07 16:12:14 +00:00
Kegan Dougal
4c68460392 SYN-154: Tweak how the m.room.create check is done.
Don't perform the check in auth.is_host_in_room but instead do it in _do_join
and also assert that there are no m.room.members in the room before doing so.
2015-01-07 16:09:00 +00:00
Kegan Dougal
9cb4f75d53 SYN-154: Better error messages when joining an unknown room by ID.
The simple fix doesn't work here because room creation also involves
unknown room IDs. The check relies on the presence of m.room.create for
rooms being created, whereas bogus room IDs have no state events at all.
2015-01-07 15:21:48 +00:00
Matthew Hodgson
9b8e348b15 *cough* 2015-01-07 15:08:32 +00:00
Erik Johnston
cc7a267a85 Merge branch 'release-v0.6.1' of github.com:matrix-org/synapse into develop 2015-01-07 14:55:06 +00:00
Erik Johnston
bacaa215eb Merge branch 'release-v0.6.1' of github.com:matrix-org/synapse 2015-01-07 14:34:00 +00:00
Erik Johnston
72d8d1265b Improve change log 2015-01-07 14:16:38 +00:00
Erik Johnston
89fc09c3d1 Bump version and changelog 2015-01-07 13:56:56 +00:00
Erik Johnston
a039e2544c Remove unused import 2015-01-07 09:48:03 +00:00
Erik Johnston
6536161e2a Merge branch 'erikj-perf' of github.com:matrix-org/synapse into develop 2015-01-07 09:45:42 +00:00
Erik Johnston
1497e50649 Merge branch 'develop' of github.com:matrix-org/synapse into erikj-perf 2015-01-07 09:40:42 +00:00
Mark Haines
5cf45c4319 Merge branch 'master' into develop 2015-01-06 19:48:53 +00:00
Erik Johnston
dfa05f0cd6 Optimize FrozenEvent creation 2015-01-06 18:51:03 +00:00
Erik Johnston
36a2a877e2 Use time.time() instead of time.clock() 2015-01-06 16:34:41 +00:00
Erik Johnston
d5ae67e67d Fix typo where we used wrong var. 2015-01-06 16:05:01 +00:00
Erik Johnston
fd9a8db7ea Only fetch the columns we need. 2015-01-06 15:59:31 +00:00
Erik Johnston
9e5545a6fa RoomsForUser now has sender instead of user_id 2015-01-06 15:53:50 +00:00
Erik Johnston
a01416cf21 Add delta and bump DB version 2015-01-06 15:42:18 +00:00
Erik Johnston
f6da237c35 Add index on transaction_id to sent_transcations 2015-01-06 15:40:38 +00:00
Erik Johnston
9bd07bed23 Actually time that function 2015-01-06 15:28:56 +00:00
Erik Johnston
03a501456c Time how long calls to _get_destination_retry_timings take 2015-01-06 15:22:28 +00:00
Erik Johnston
52b2c6c9c7 Don't include None's in _get_events_txn 2015-01-06 14:56:57 +00:00
Erik Johnston
8a12df8cf3 Merge branch 'erikj-perf' of github.com:matrix-org/synapse into develop 2015-01-06 14:45:57 +00:00
Erik Johnston
96707ed718 Name 'user_rooms_intersect' transaction 2015-01-06 14:44:27 +00:00
Erik Johnston
76ec154e95 We don't need the full events for get_rooms_for_user_where_membership_is 2015-01-06 14:37:00 +00:00
Mark Haines
bc2ec808f4 SYN-32 Use the ANTIALIAS resize method for thumbnailing images 2015-01-06 14:14:17 +00:00
Matrix
0529a7e2e9 Add some logging for when we are sending transactions. 2015-01-06 14:06:25 +00:00
Mark Haines
b9f77d1ae1 Increase default maximum attachment size to 10M 2015-01-06 14:04:58 +00:00
Mark Haines
5e23a19204 Merge pull request #28 from matrix-org/erikj-perf
Database performance improvements.
2015-01-06 13:33:40 +00:00
Mark Haines
adb04b1e57 Update copyright notices 2015-01-06 13:21:39 +00:00
Erik Johnston
af1c7c7808 PEP8 2015-01-06 13:13:17 +00:00
Erik Johnston
12819d5082 Remove debug lines 2015-01-06 13:12:30 +00:00
Erik Johnston
52d8519008 Don't do batching when getting events. 2015-01-06 13:10:27 +00:00
Mark Haines
773de09774 Set a content-length for JSON responses 2015-01-06 13:05:19 +00:00
Erik Johnston
98933e3db6 Only fetch prev_content when a client is streaming/paginating. Use transactions for event streams. 2015-01-06 13:03:23 +00:00
Kegan Dougal
78edb47cc5 SYN-208/SYN-228: Add runtime checks on startup to enforce that JPEG/PNG support is included when installing pillow. 2015-01-06 11:43:04 +00:00
Mark Haines
3c8c3bf3b7 SYN-229: Include Content-Length when downloading files 2015-01-06 11:32:36 +00:00
Erik Johnston
3e26720e05 Temporarily turn off 'redacted_because' and 'prev_content' keys 2015-01-06 11:26:58 +00:00
Erik Johnston
f4ea78e9e2 More debug logging 2015-01-06 11:24:18 +00:00
Erik Johnston
753126b8cc Add some debug logging 2015-01-06 11:18:12 +00:00
Erik Johnston
d7e8ea67b3 Reformat 2015-01-06 11:18:02 +00:00
Erik Johnston
f0128f9600 Add RoomMemberStore.get_users_in_room, so that we can get the list of joined users without having to retrieve the full events 2015-01-06 10:55:43 +00:00
Erik Johnston
96a5ba41f5 Merge branch 'develop' of github.com:matrix-org/synapse into erikj-perf 2015-01-06 10:53:04 +00:00
Mark Haines
90d60e3fe4 Merge branch 'hotfixes-v0.6.0a' 2014-12-29 14:01:07 +00:00
Mark Haines
af61c29527 Return the argument passed to the callback in a deferred callback, otherwise twisted will replace the deferred result with 'None' 2014-12-29 13:54:05 +00:00
Matthew Hodgson
0e93e01fcb spell out that VoIP needs TURN 2014-12-24 19:45:28 +00:00
Matthew Hodgson
407c299828 improve error msg 2014-12-24 17:50:42 +00:00
Matthew Hodgson
1eb319806b clarify these instructions a media-repo specific 2014-12-24 16:56:32 +00:00
Matthew Hodgson
d90e586c85 spell out that upgrading is just installing over the top 2014-12-24 16:56:20 +00:00
Mark Haines
24b5d01853 Include version in User-Agent and Server headers 2014-12-22 10:16:02 +00:00
Erik Johnston
74ee4048c2 Merge branch 'master' of github.com:matrix-org/synapse into erikj-perf 2014-12-21 11:47:45 +00:00
Mark Haines
420ccfc925 Merge branch 'hotfixes-v0.6.0' 2014-12-19 17:52:58 +00:00
Kegan Dougal
4640239d34 Mock ratelimiter to make tests pass. 2014-12-19 17:49:47 +00:00
Matthew Hodgson
2a5b53bc4a more changelogs 2014-12-19 17:39:43 +00:00
Kegan Dougal
67a406a754 Rate limit display names and avatar urls per request rather than per event. 2014-12-19 17:36:33 +00:00
Erik Johnston
d61109f578 Merge branch 'hotfixes-v0.6.0' of github.com:matrix-org/synapse into erikj-perf 2014-12-19 16:37:08 +00:00
Mark Haines
efd27ff01b Set a state_key for the topic and room name, otherwise they won't be treated as room state 2014-12-19 15:31:27 +00:00
Mark Haines
9c71d945d6 Look for name, topic in the event content rather than the event itself when persisting room name and topic events 2014-12-19 15:16:48 +00:00
Mark Haines
f70e622d59 bump_presence_active_time when sending a message event 2014-12-19 14:30:57 +00:00
Mark Haines
a999f0dec3 Don't ratelimit room create events 2014-12-19 14:18:27 +00:00
Erik Johnston
41ce544abe Merge branch 'release-v0.6.0' of github.com:matrix-org/synapse into erikj-perf 2014-12-18 18:57:21 +00:00
Erik Johnston
f3788e3c78 Test some ideas that might help performance a bit 2014-12-17 23:37:08 +00:00
179 changed files with 1451 additions and 1966 deletions

View File

@@ -1,7 +1,21 @@
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)
=====================================
* Add new API for media upload and download that supports thumbnailing.
* Replicate media uploads over multiple homeservers so media is always served
to clients from their local homeserver. This obsoletes the
--content-addr parameter and confusion over accessing content directly
from remote homeservers.
* Implement exponential backoff when retrying federation requests when
sending to remote homeservers which are offline.
* Implement typing notifications.
* Fix bugs where we sent events with invalid signatures due to bugs where
we incorrectly persisted events.

View File

@@ -108,6 +108,18 @@ To install the synapse homeserver run::
This installs synapse, along with the libraries it uses, into
``$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
a TURN server. See docs/turn-howto.rst for details.
Troubleshooting Installation
----------------------------
@@ -239,6 +251,11 @@ Upgrading an existing homeserver
IMPORTANT: Before upgrading an existing homeserver to a new version, please
refer to UPGRADE.rst for any additional instructions.
Otherwise, simply re-install the new codebase over the current one - e.g.
by ``pip install --user --process-dependency-links
https://github.com/matrix-org/synapse/tarball/master``
if using pip, or by ``git pull`` if running off a git working copy.
Setting up Federation
=====================

View File

@@ -52,7 +52,7 @@ resulting conflicts during the upgrade process.
Before running the command the homeserver should be first completely
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
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
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
homeserver. You may notice that the homeserver takes a few seconds longer to

View File

@@ -1 +1 @@
0.6.0
0.6.1b

View File

@@ -23,14 +23,27 @@ import argparse
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)
c = conn.execute(
"SELECT json FROM event_json where room_id = ?",
(room_id,)
sql = (
"SELECT json FROM event_json as j "
"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.sort(key=lambda e: e.depth)
@@ -128,11 +141,16 @@ if __name__ == "__main__":
)
parser.add_argument(
"-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('room')
args = parser.parse_args()
make_graph(args.db, args.room, args.prefix)
make_graph(args.db, args.room, args.prefix, args.limit)

View File

@@ -1,6 +1,8 @@
Media Repository
Media Repository
================
*Synapse implementation-specific details for the media repository*
The media repository is where attachments and avatar photos are stored.
It stores attachment content and thumbnails for media uploaded by local users.
It caches attachment content and thumbnails for media uploaded by remote users.

View File

@@ -1,17 +0,0 @@
.loggedin {
visibility: hidden;
}
p {
font-family: monospace;
}
table
{
border-spacing:5px;
}
th,td
{
padding:5px;
}

View File

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

View File

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

View File

@@ -1,17 +0,0 @@
.loggedin {
visibility: hidden;
}
p {
font-family: monospace;
}
table
{
border-spacing:5px;
}
th,td
{
padding:5px;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +0,0 @@
.loggedin {
visibility: hidden;
}
p {
font-family: monospace;
}

View File

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

View File

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

View File

@@ -1,17 +0,0 @@
.loggedin {
visibility: hidden;
}
p {
font-family: monospace;
}
table
{
border-spacing:5px;
}
th,td
{
padding:5px;
}

View File

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

View File

@@ -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
View 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);

View File

@@ -14,7 +14,7 @@
# limitations under the License.
$copyright = <<EOT;
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# 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"

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,4 +1,4 @@
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -18,6 +18,8 @@ from synapse.storage import prepare_database, UpgradeDatabaseException
from synapse.server import HomeServer
from synapse.python_dependencies import check_requirements
from twisted.internet import reactor
from twisted.enterprise import adbapi
from twisted.web.resource import Resource
@@ -39,6 +41,8 @@ from synapse.util.logcontext import LoggingContext
from daemonize import Daemonize
import twisted.manhole.telnet
import synapse
import logging
import os
import re
@@ -198,7 +202,10 @@ def setup():
config.setup_logging()
check_requirements()
logger.info("Server hostname: %s", config.server_name)
logger.info("Server version: %s", synapse.__version__)
if re.search(":[0-9]+$", config.server_name):
domain_with_port = config.server_name
@@ -234,13 +241,20 @@ def setup():
except UpgradeDatabaseException:
sys.stderr.write(
"\nFailed to upgrade database.\n"
"Have you followed any instructions in UPGRADES.rst?\n"
"Have you checked for version specific instructions in"
" UPGRADES.rst?\n"
)
sys.exit(1)
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:
f = twisted.manhole.telnet.ShellFactory()
@@ -277,6 +291,7 @@ def run():
def main():
with LoggingContext("main"):
check_requirements()
setup()

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,4 +1,4 @@
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -20,7 +20,10 @@ import os
class DatabaseConfig(Config):
def __init__(self, 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
def add_arguments(cls, parser):

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -66,7 +66,10 @@ class LoggingConfig(Config):
formatter = logging.Formatter(log_format)
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:
handler = logging.StreamHandler()
handler.setFormatter(formatter)

View File

@@ -1,4 +1,4 @@
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014, 2015 matrix.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@ class ContentRepositoryConfig(Config):
super(ContentRepositoryConfig, cls).add_arguments(parser)
db_group = parser.add_argument_group("content_repository")
db_group.add_argument(
"--max-upload-size", default="1M"
"--max-upload-size", default="10M"
)
db_group.add_argument(
"--media-store-path", default=cls.default_path("media_store")

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -47,8 +47,12 @@ class ServerConfig(Config):
def add_arguments(cls, parser):
super(ServerConfig, cls).add_arguments(parser)
server_group = parser.add_argument_group("server")
server_group.add_argument("-H", "--server-name", default="localhost",
help="The name of the server")
server_group.add_argument(
"-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",
help="The signing key to sign messages with")
server_group.add_argument("-p", "--bind-port", metavar="PORT",

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,4 +1,4 @@
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,4 +1,4 @@
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -15,12 +15,10 @@
from synapse.util.frozenutils import freeze, unfreeze
import copy
class _EventInternalMetadata(object):
def __init__(self, internal_metadata_dict):
self.__dict__ = copy.deepcopy(internal_metadata_dict)
self.__dict__ = internal_metadata_dict
def get_dict(self):
return dict(self.__dict__)
@@ -49,10 +47,10 @@ def _event_dict_property(key):
class EventBase(object):
def __init__(self, event_dict, signatures={}, unsigned={},
internal_metadata_dict={}):
self.signatures = copy.deepcopy(signatures)
self.unsigned = copy.deepcopy(unsigned)
self.signatures = signatures
self.unsigned = unsigned
self._event_dict = copy.deepcopy(event_dict)
self._event_dict = event_dict
self.internal_metadata = _EventInternalMetadata(
internal_metadata_dict
@@ -112,10 +110,16 @@ class EventBase(object):
class FrozenEvent(EventBase):
def __init__(self, event_dict, internal_metadata_dict={}):
event_dict = copy.deepcopy(event_dict)
event_dict = dict(event_dict)
signatures = copy.deepcopy(event_dict.pop("signatures", {}))
unsigned = copy.deepcopy(event_dict.pop("unsigned", {}))
# Signatures is a dict of dicts, and this is faster than doing a
# copy.deepcopy
signatures = {
name: {sig_id: sig for sig_id, sig in sigs.items()}
for name, sigs in event_dict.pop("signatures", {}).items()
}
unsigned = dict(event_dict.pop("unsigned", {}))
frozen_dict = freeze(event_dict)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -33,12 +33,6 @@ class EventBuilder(EventBase):
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):
return FrozenEvent.from_event(self)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -89,16 +89,24 @@ def prune_event(event):
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
if not isinstance(e, EventBase):
return e
# Should this strip out None's?
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"]:
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"]
d["user_id"] = d.pop("sender", None)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -256,23 +256,21 @@ class ReplicationLayer(object):
@defer.inlineCallbacks
@log_function
def get_state_for_context(self, destination, context, event_id):
"""Requests all of the `current` state PDUs for a given context from
def get_state_for_room(self, destination, room_id, event_id):
"""Requests all of the `current` state PDUs for a given room from
a remote home server.
Args:
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.
Returns:
Deferred: Results in a list of PDUs.
"""
result = yield self.transport_layer.get_context_state(
destination,
context,
event_id=event_id,
result = yield self.transport_layer.get_room_state(
destination, room_id, event_id=event_id,
)
pdus = [
@@ -288,9 +286,9 @@ class ReplicationLayer(object):
@defer.inlineCallbacks
@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(
destination, context, event_id,
destination, room_id, event_id,
)
auth_chain = [
@@ -304,9 +302,9 @@ class ReplicationLayer(object):
@defer.inlineCallbacks
@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(
origin, context, versions, limit
origin, room_id, versions, limit
)
defer.returnValue((200, self._transaction_from_pdus(pdus).get_dict()))
@@ -380,12 +378,10 @@ class ReplicationLayer(object):
@defer.inlineCallbacks
@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:
pdus = yield self.handler.get_state_for_pdu(
origin,
context,
event_id,
origin, room_id, event_id,
)
auth_chain = yield self.store.get_auth_chain(
[pdu.event_id for pdu in pdus]
@@ -413,7 +409,7 @@ class ReplicationLayer(object):
@defer.inlineCallbacks
@log_function
def on_pull_request(self, origin, versions):
raise NotImplementedError("Pull transacions not implemented")
raise NotImplementedError("Pull transactions not implemented")
@defer.inlineCallbacks
def on_query_request(self, query_type, args):
@@ -422,30 +418,21 @@ class ReplicationLayer(object):
defer.returnValue((200, response))
else:
defer.returnValue(
(404, "No handler for Query type '%s'" % (query_type, ))
(404, "No handler for Query type '%s'" % (query_type,))
)
@defer.inlineCallbacks
def on_make_join_request(self, context, user_id):
pdu = yield self.handler.on_make_join_request(context, user_id)
def on_make_join_request(self, room_id, user_id):
pdu = yield self.handler.on_make_join_request(room_id, user_id)
time_now = self._clock.time_msec()
defer.returnValue({
"event": pdu.get_pdu_json(time_now),
})
defer.returnValue({"event": pdu.get_pdu_json(time_now)})
@defer.inlineCallbacks
def on_invite_request(self, origin, content):
pdu = self.event_from_pdu_json(content)
ret_pdu = yield self.handler.on_invite_request(origin, pdu)
time_now = self._clock.time_msec()
defer.returnValue(
(
200,
{
"event": ret_pdu.get_pdu_json(time_now),
}
)
)
defer.returnValue((200, {"event": ret_pdu.get_pdu_json(time_now)}))
@defer.inlineCallbacks
def on_send_join_request(self, origin, content):
@@ -462,26 +449,17 @@ class ReplicationLayer(object):
}))
@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()
auth_pdus = yield self.handler.on_event_auth(event_id)
defer.returnValue(
(
200,
{
"auth_chain": [
a.get_pdu_json(time_now) for a in auth_pdus
],
}
)
)
defer.returnValue((200, {
"auth_chain": [a.get_pdu_json(time_now) for a in auth_pdus],
}))
@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(
destination=destination,
context=context,
user_id=user_id,
destination, room_id, user_id
)
pdu_dict = ret["event"]
@@ -494,10 +472,10 @@ class ReplicationLayer(object):
def send_join(self, destination, pdu):
time_now = self._clock.time_msec()
_, content = yield self.transport_layer.send_join(
destination,
pdu.room_id,
pdu.event_id,
pdu.get_pdu_json(time_now),
destination=destination,
room_id=pdu.room_id,
event_id=pdu.event_id,
content=pdu.get_pdu_json(time_now),
)
logger.debug("Got content: %s", content)
@@ -507,9 +485,6 @@ class ReplicationLayer(object):
for p in content.get("state", [])
]
# FIXME: We probably want to do something with the auth_chain given
# to us
auth_chain = [
self.event_from_pdu_json(p, outlier=True)
for p in content.get("auth_chain", [])
@@ -523,11 +498,11 @@ class ReplicationLayer(object):
})
@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()
code, content = yield self.transport_layer.send_invite(
destination=destination,
context=context,
room_id=room_id,
event_id=event_id,
content=pdu.get_pdu_json(time_now),
)
@@ -657,7 +632,7 @@ class ReplicationLayer(object):
"_handle_new_pdu getting state for %s",
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,
)
@@ -730,6 +705,7 @@ class _TransactionQueue(object):
destinations = set(destinations)
destinations.discard(self.server_name)
destinations.discard("localhost")
logger.debug("Sending to: %s", str(destinations))
@@ -814,6 +790,8 @@ class _TransactionQueue(object):
else:
logger.info("TX [%s] is ready for retry", destination)
logger.info("TX [%s] _attempt_new_transaction", destination)
if destination in self.pending_transactions:
# XXX: pending_transactions can get stuck on by a never-ending
# request at which point pending_pdus_by_dest just keeps growing.
@@ -826,12 +804,16 @@ class _TransactionQueue(object):
pending_edus = self.pending_edus_by_dest.pop(destination, [])
pending_failures = self.pending_failures_by_dest.pop(destination, [])
if 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:
return
logger.debug(
"TX [%s] Attempting new transaction "
"(pdus: %d, edus: %d, failures: %d)",
"TX [%s] Attempting new transaction"
" (pdus: %d, edus: %d, failures: %d)",
destination,
len(pending_pdus),
len(pending_edus),

View 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

View 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)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -13,14 +13,6 @@
# 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.
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 synapse.api.urls import FEDERATION_PREFIX as PREFIX
@@ -35,241 +27,8 @@ import re
logger = logging.getLogger(__name__)
class TransportLayer(object):
"""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
@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)
class TransportLayerServer(object):
"""Handles incoming federation HTTP requests"""
@defer.inlineCallbacks
def _authenticate_request(self, request):
@@ -373,8 +132,6 @@ class TransportLayer(object):
"""
self.request_handler = handler
# TODO(markjh): Namespace the federation URI paths
# This is for when someone asks us for everything since version X
self.server.register_path(
"GET",
@@ -528,34 +285,6 @@ class TransportLayer(object):
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
def _on_backfill_request(self, origin, context, v_list, limits):
if not limits:

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -144,7 +144,5 @@ class BaseHandler(object):
yield self.notifier.on_new_room_event(event, extra_users=extra_users)
yield federation_handler.handle_new_event(
event,
None,
destinations=destinations,
event, destinations=destinations,
)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -155,4 +155,4 @@ class DirectoryHandler(BaseHandler):
"room_id": room_id,
"sender": user_id,
"content": {"aliases": aliases},
})
}, ratelimit=False)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -46,7 +46,8 @@ class EventStreamHandler(BaseHandler):
@defer.inlineCallbacks
@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)
try:
@@ -69,16 +70,16 @@ class EventStreamHandler(BaseHandler):
pagin_config.from_token = None
rm_handler = self.hs.get_handlers().room_member_handler
logger.debug("BETA")
room_ids = yield rm_handler.get_rooms_for_user(auth_user)
logger.debug("ALPHA")
with PreserveLoggingContext():
events, tokens = yield self.notifier.get_events_for(
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": chunks,

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -75,14 +75,14 @@ class FederationHandler(BaseHandler):
@log_function
@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
been authed and handled by the state module, and sends it to any
remote home servers that may be interested.
Args:
event
snapshot (.storage.Snapshot): THe snapshot the event happened after
event: The event to send
destinations: A list of destinations to send it to
Returns:
Deferred: Resolved when it has successfully been queued for
@@ -154,7 +154,7 @@ class FederationHandler(BaseHandler):
replication = self.replication_layer
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,
)
@@ -281,7 +281,7 @@ class FederationHandler(BaseHandler):
"""
pdu = yield self.replication_layer.send_invite(
destination=target_host,
context=event.room_id,
room_id=event.room_id,
event_id=event.event_id,
pdu=event
)
@@ -617,13 +617,13 @@ class FederationHandler(BaseHandler):
@defer.inlineCallbacks
@log_function
def on_backfill_request(self, origin, context, pdu_list, limit):
in_room = yield self.auth.check_host_in_room(context, origin)
def on_backfill_request(self, origin, room_id, pdu_list, limit):
in_room = yield self.auth.check_host_in_room(room_id, origin)
if not in_room:
raise AuthError(403, "Host not in room.")
events = yield self.store.get_backfill_events(
context,
room_id,
pdu_list,
limit
)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -19,6 +19,7 @@ from synapse.api.constants import EventTypes, Membership
from synapse.api.errors import RoomError
from synapse.streams.config import PaginationConfig
from synapse.events.validator import EventValidator
from synapse.util.logcontext import PreserveLoggingContext
from ._base import BaseHandler
@@ -66,7 +67,7 @@ class MessageHandler(BaseHandler):
@defer.inlineCallbacks
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.
Args:
@@ -75,6 +76,7 @@ class MessageHandler(BaseHandler):
pagin_config (synapse.api.streams.PaginationConfig): The pagination
config rules to apply, if any.
feedback (bool): True to get compressed feedback with the messages
as_client_event (bool): True to get events in client-server format.
Returns:
dict: Pagination API results
"""
@@ -98,7 +100,9 @@ class MessageHandler(BaseHandler):
)
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(),
"end": next_token.to_string(),
}
@@ -106,7 +110,7 @@ class MessageHandler(BaseHandler):
defer.returnValue(chunk)
@defer.inlineCallbacks
def create_and_send_event(self, event_dict):
def create_and_send_event(self, event_dict, ratelimit=True):
""" Given a dict from a client, create and handle a new event.
Creates an FrozenEvent object, filling out auth_events, prev_events,
@@ -123,7 +127,8 @@ class MessageHandler(BaseHandler):
self.validator.validate_new(builder)
self.ratelimit(builder.user_id)
if ratelimit:
self.ratelimit(builder.user_id)
# TODO(paul): Why does 'event' not have a 'user' object?
user = self.hs.parse_userid(builder.user_id)
assert self.hs.is_mine(user), "User must be our own: %s" % (user,)
@@ -152,6 +157,11 @@ class MessageHandler(BaseHandler):
context=context,
)
if event.type == EventTypes.Message:
presence = self.hs.get_handlers().presence_handler
with PreserveLoggingContext():
presence.bump_presence_active_time(user)
defer.returnValue(event)
@defer.inlineCallbacks
@@ -204,7 +214,7 @@ class MessageHandler(BaseHandler):
@defer.inlineCallbacks
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.
This snapshot may include messages for all rooms where the user is
@@ -215,6 +225,7 @@ class MessageHandler(BaseHandler):
pagin_config (synapse.api.streams.PaginationConfig): The pagination
config used to determine how many messages *PER ROOM* to return.
feedback (bool): True to get feedback along with these messages.
as_client_event (bool): True to get events in client-server format.
Returns:
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
@@ -256,7 +267,7 @@ class MessageHandler(BaseHandler):
}
if event.membership == Membership.INVITE:
d["inviter"] = event.user_id
d["inviter"] = event.sender
rooms_ret.append(d)
@@ -273,7 +284,10 @@ class MessageHandler(BaseHandler):
end_token = now_token.copy_and_replace("room_key", token[1])
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(),
"end": end_token.to_string(),
}

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -16,7 +16,7 @@
from twisted.internet import defer
from synapse.api.errors import SynapseError, AuthError, CodeMessageException
from synapse.api.constants import Membership
from synapse.api.constants import EventTypes, Membership
from synapse.util.logcontext import PreserveLoggingContext
from ._base import BaseHandler
@@ -194,6 +194,8 @@ class ProfileHandler(BaseHandler):
if not self.hs.is_mine(user):
return
self.ratelimit(user.to_string())
joins = yield self.store.get_rooms_for_user_where_membership_is(
user.to_string(),
[Membership.JOIN],
@@ -201,7 +203,7 @@ class ProfileHandler(BaseHandler):
for j in joins:
content = {
"membership": j.content["membership"],
"membership": Membership.JOIN,
}
yield self.distributor.fire(
@@ -210,9 +212,9 @@ class ProfileHandler(BaseHandler):
msg_handler = self.hs.get_handlers().message_handler
yield msg_handler.create_and_send_event({
"type": j.type,
"type": EventTypes.Member,
"room_id": j.room_id,
"state_key": j.state_key,
"state_key": user.to_string(),
"content": content,
"sender": j.state_key,
})
"sender": user.to_string()
}, ratelimit=False)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -130,6 +130,7 @@ class RoomCreationHandler(BaseHandler):
"type": EventTypes.Name,
"room_id": room_id,
"sender": user_id,
"state_key": "",
"content": {"name": name},
})
@@ -139,6 +140,7 @@ class RoomCreationHandler(BaseHandler):
"type": EventTypes.Topic,
"room_id": room_id,
"sender": user_id,
"state_key": "",
"content": {"topic": topic},
})
@@ -243,14 +245,12 @@ class RoomMemberHandler(BaseHandler):
self.distributor.declare("user_left_room")
@defer.inlineCallbacks
def get_room_members(self, room_id, membership=Membership.JOIN):
def get_room_members(self, room_id):
hs = self.hs
memberships = yield self.store.get_room_members(
room_id=room_id, membership=membership
)
users = yield self.store.get_users_in_room(room_id)
defer.returnValue([hs.parse_userid(m.user_id) for m in memberships])
defer.returnValue([hs.parse_userid(u) for u in users])
@defer.inlineCallbacks
def fetch_room_distributions_into(self, room_id, localusers=None,
@@ -425,10 +425,22 @@ class RoomMemberHandler(BaseHandler):
event.room_id,
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:
should_do_dance = False
elif room_host:
elif room_host: # TODO: Shouldn't this be remote_room_host?
should_do_dance = True
else:
# TODO(markjh): get prev_state from snapshot
@@ -442,7 +454,8 @@ class RoomMemberHandler(BaseHandler):
should_do_dance = not self.hs.is_mine(inviter)
room_host = inviter.domain
else:
should_do_dance = False
# return the same error as join_room_alias does
raise SynapseError(404, "No known servers")
if should_do_dance:
handler = self.hs.get_handlers().federation_handler
@@ -529,11 +542,10 @@ class RoomListHandler(BaseHandler):
def get_public_room_list(self):
chunk = yield self.store.get_rooms(is_public=True)
for room in chunk:
joined_members = yield self.store.get_room_members(
joined_users = yield self.store.get_users_in_room(
room_id=room["room_id"],
membership=Membership.JOIN
)
room["num_joined_members"] = len(joined_members)
room["num_joined_members"] = len(joined_users)
# FIXME (erikj): START is no longer a valid value
defer.returnValue({"start": "START", "end": "END", "chunk": chunk})

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -83,9 +83,15 @@ class TypingNotificationHandler(BaseHandler):
if member in self._member_typing_timer:
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_timer[member] = self.clock.call_later(
timeout / 1000, lambda: self._stopped_typing(member)
timeout / 1000.0, _cb
)
if was_present:
@@ -114,6 +120,10 @@ class TypingNotificationHandler(BaseHandler):
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)
@defer.inlineCallbacks
@@ -136,8 +146,10 @@ class TypingNotificationHandler(BaseHandler):
del self._member_typing_until[member]
self.clock.cancel_call_later(self._member_typing_timer[member])
del self._member_typing_timer[member]
if member in self._member_typing_timer:
# Don't cancel it - either it already expired, or the real
# stopped_typing() will cancel it
del self._member_typing_timer[member]
@defer.inlineCallbacks
def _push_update(self, room_id, user, typing):

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -0,0 +1,18 @@
# -*- 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 synapse import __version__
AGENT_NAME = ("Synapse/%s" % (__version__,)).encode("ascii")

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -14,6 +14,7 @@
# limitations under the License.
from synapse.http.agent_name import AGENT_NAME
from twisted.internet import defer, reactor
from twisted.web.client import (
Agent, readBody, FileBodyProducer, PartialDownloadError
@@ -51,7 +52,8 @@ class SimpleHttpClient(object):
"POST",
uri.encode("ascii"),
headers=Headers({
"Content-Type": ["application/x-www-form-urlencoded"]
b"Content-Type": [b"application/x-www-form-urlencoded"],
b"User-Agent": [AGENT_NAME],
}),
bodyProducer=FileBodyProducer(StringIO(query_bytes))
)
@@ -86,6 +88,9 @@ class SimpleHttpClient(object):
response = yield self.agent.request(
"GET",
uri.encode("ascii"),
headers=Headers({
b"User-Agent": [AGENT_NAME],
})
)
body = yield readBody(response)
@@ -108,7 +113,8 @@ class CaptchaServerHttpClient(SimpleHttpClient):
url.encode("ascii"),
bodyProducer=FileBodyProducer(StringIO(query_bytes)),
headers=Headers({
"Content-Type": ["application/x-www-form-urlencoded"]
b"Content-Type": [b"application/x-www-form-urlencoded"],
b"User-Agent": [AGENT_NAME],
})
)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -20,6 +20,7 @@ from twisted.web.client import readBody, _AgentBase, _URI
from twisted.web.http_headers import Headers
from twisted.web._newclient import ResponseDone
from synapse.http.agent_name import AGENT_NAME
from synapse.http.endpoint import matrix_federation_endpoint
from synapse.util.async import sleep
from synapse.util.logcontext import PreserveLoggingContext
@@ -83,7 +84,7 @@ class MatrixFederationHttpClient(object):
query_bytes=b"", retry_on_dns_fail=True):
""" Creates and sends a request to the given url
"""
headers_dict[b"User-Agent"] = [b"Synapse"]
headers_dict[b"User-Agent"] = [AGENT_NAME]
headers_dict[b"Host"] = [destination]
url_bytes = urlparse.urlunparse(

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -14,14 +14,16 @@
# limitations under the License.
from syutil.jsonutil import (
encode_canonical_json, encode_pretty_printed_json
)
from synapse.http.agent_name import AGENT_NAME
from synapse.api.errors import (
cs_exception, SynapseError, CodeMessageException
)
from synapse.util.logcontext import LoggingContext
from syutil.jsonutil import (
encode_canonical_json, encode_pretty_printed_json
)
from twisted.internet import defer, reactor
from twisted.web import server, resource
from twisted.web.server import NOT_DONE_YET
@@ -230,6 +232,8 @@ def respond_with_json_bytes(request, code, json_bytes, send_cors=False,
request.setResponseCode(code, message=response_code_message)
request.setHeader(b"Content-Type", b"application/json")
request.setHeader(b"Server", AGENT_NAME)
request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),))
if send_cors:
request.setHeader("Access-Control-Allow-Origin", "*")

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -0,0 +1,45 @@
# -*- 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.
import PIL.Image
# check for JPEG support.
try:
PIL.Image._getdecoder("rgb", "jpeg", None)
except IOError as e:
if str(e).startswith("decoder jpeg not available"):
raise Exception(
"FATAL: jpeg codec not supported. Install pillow correctly! "
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
" pip install pillow --user'"
)
except Exception:
# any other exception is fine
pass
# check for PNG support.
try:
PIL.Image._getdecoder("rgb", "zip", None)
except IOError as e:
if str(e).startswith("decoder zip not available"):
raise Exception(
"FATAL: zip codec not supported. Install pillow correctly! "
" 'sudo apt-get install libjpeg-dev' then 'pip uninstall pillow &&"
" pip install pillow --user'"
)
except Exception:
# any other exception is fine
pass

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -139,6 +139,7 @@ class BaseMediaResource(Resource):
@download.addBoth
def callback(media_info):
del self.downloads[key]
return media_info
return download
@defer.inlineCallbacks
@@ -201,7 +202,8 @@ class BaseMediaResource(Resource):
defer.returnValue(media_info)
@defer.inlineCallbacks
def _respond_with_file(self, request, media_type, file_path):
def _respond_with_file(self, request, media_type, file_path,
file_size=None):
logger.debug("Responding with %r", file_path)
if os.path.isfile(file_path):
@@ -215,13 +217,20 @@ class BaseMediaResource(Resource):
request.setHeader(
b"Cache-Control", b"public,max-age=86400,s-maxage=86400"
)
if file_size is None:
stat = os.stat(file_path)
file_size = stat.st_size
request.setHeader(
b"Content-Length", b"%d" % (file_size,)
)
with open(file_path, "rb") as f:
yield FileSender().beginFileTransfer(f, request)
request.finish()
else:
self._respond_404()
self._respond_404(request)
def _get_thumbnail_requirements(self, media_type):
if media_type == "image/jpeg":

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -46,23 +46,29 @@ class DownloadResource(BaseMediaResource):
def _respond_local_file(self, request, media_id):
media_info = yield self.store.get_local_media(media_id)
if not media_info:
self._respond_404()
self._respond_404(request)
return
media_type = media_info["media_type"]
media_length = media_info["media_length"]
file_path = self.filepaths.local_media_filepath(media_id)
yield self._respond_with_file(request, media_type, file_path)
yield self._respond_with_file(
request, media_type, file_path, media_length
)
@defer.inlineCallbacks
def _respond_remote_file(self, request, server_name, media_id):
media_info = yield self._get_remote_media(server_name, media_id)
media_type = media_info["media_type"]
media_length = media_info["media_length"]
filesystem_id = media_info["filesystem_id"]
file_path = self.filepaths.remote_media_filepath(
server_name, filesystem_id
)
yield self._respond_with_file(request, media_type, file_path)
yield self._respond_with_file(
request, media_type, file_path, media_length
)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
# 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.
@@ -100,11 +100,12 @@ class ThumbnailResource(BaseMediaResource):
t_type = thumbnail_info["thumbnail_type"]
t_method = thumbnail_info["thumbnail_method"]
file_id = thumbnail_info["filesystem_id"]
t_length = thumbnail_info["thumbnail_length"]
file_path = self.filepaths.remote_media_thumbnail(
server_name, file_id, t_width, t_height, t_type, t_method,
)
yield self._respond_with_file(request, t_type, file_path)
yield self._respond_with_file(request, t_type, file_path, t_length)
else:
yield self._respond_default_thumbnail(
request, media_info, width, height, method, m_type,
@@ -139,11 +140,12 @@ class ThumbnailResource(BaseMediaResource):
t_height = thumbnail_info["thumbnail_height"]
t_type = thumbnail_info["thumbnail_type"]
t_method = thumbnail_info["thumbnail_method"]
t_length = thumbnail_info["thumbnail_length"]
file_path = self.filepaths.default_thumbnail(
top_level_type, sub_type, t_width, t_height, t_type, t_method,
)
yield self.respond_with_file(request, t_type, file_path)
yield self.respond_with_file(request, t_type, file_path, t_length)
def _select_thumbnail(self, desired_width, desired_height, desired_method,
desired_type, thumbnail_infos):

Some files were not shown because too many files have changed in this diff Show More