This repository has been archived by the owner on Apr 26, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Start adding store-and-forward direct-to-device messaging #1046
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e993925
Add store-and-forward direct-to-device messaging
641efb6
Fix the deduplication of incoming direct-to-device messages
b162cb2
Add some TODOs
ab34fde
Merge branch 'develop' into markjh/direct_to_device
3b8d0ce
More 0_0 in tests
4bbef62
Merge remote-tracking branch 'origin/develop' into markjh/direct_to_d…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright 2016 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 logging | ||
|
||
from twisted.internet import defer | ||
from synapse.http.servlet import parse_json_object_from_request | ||
|
||
from synapse.http import servlet | ||
from synapse.rest.client.v1.transactions import HttpTransactionStore | ||
from ._base import client_v2_patterns | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class SendToDeviceRestServlet(servlet.RestServlet): | ||
PATTERNS = client_v2_patterns( | ||
"/sendToDevice/(?P<message_type>[^/]*)/(?P<txn_id>[^/]*)$", | ||
releases=[], v2_alpha=False | ||
) | ||
|
||
def __init__(self, hs): | ||
""" | ||
Args: | ||
hs (synapse.server.HomeServer): server | ||
""" | ||
super(SendToDeviceRestServlet, self).__init__() | ||
self.hs = hs | ||
self.auth = hs.get_auth() | ||
self.store = hs.get_datastore() | ||
self.is_mine_id = hs.is_mine_id | ||
self.txns = HttpTransactionStore() | ||
|
||
@defer.inlineCallbacks | ||
def on_PUT(self, request, message_type, txn_id): | ||
try: | ||
defer.returnValue( | ||
self.txns.get_client_transaction(request, txn_id) | ||
) | ||
except KeyError: | ||
pass | ||
|
||
requester = yield self.auth.get_user_by_req(request) | ||
|
||
content = parse_json_object_from_request(request) | ||
|
||
# TODO: Prod the notifier to wake up sync streams. | ||
# TODO: Implement replication for the messages. | ||
# TODO: Send the messages to remote servers if needed. | ||
|
||
local_messages = {} | ||
for user_id, by_device in content["messages"].items(): | ||
if self.is_mine_id(user_id): | ||
messages_by_device = { | ||
device_id: { | ||
"content": message_content, | ||
"type": message_type, | ||
"sender": requester.user.to_string(), | ||
} | ||
for device_id, message_content in by_device.items() | ||
} | ||
local_messages[user_id] = messages_by_device | ||
|
||
yield self.store.add_messages_to_device_inbox(local_messages) | ||
|
||
response = (200, {}) | ||
self.txns.store_client_transaction(request, txn_id, response) | ||
defer.returnValue(response) | ||
|
||
|
||
def register_servlets(hs, http_server): | ||
SendToDeviceRestServlet(hs).register(http_server) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright 2016 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 logging | ||
import ujson | ||
|
||
from twisted.internet import defer | ||
|
||
from ._base import SQLBaseStore | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class DeviceInboxStore(SQLBaseStore): | ||
|
||
@defer.inlineCallbacks | ||
def add_messages_to_device_inbox(self, messages_by_user_then_device): | ||
""" | ||
Args: | ||
messages_by_user_and_device(dict): | ||
Dictionary of user_id to device_id to message. | ||
Returns: | ||
A deferred that resolves when the messages have been inserted. | ||
""" | ||
|
||
def select_devices_txn(txn, user_id, devices): | ||
if not devices: | ||
return [] | ||
sql = ( | ||
"SELECT user_id, device_id FROM devices" | ||
" WHERE user_id = ? AND device_id IN (" | ||
+ ",".join("?" * len(devices)) | ||
+ ")" | ||
) | ||
# TODO: Maybe this needs to be done in batches if there are | ||
# too many local devices for a given user. | ||
args = [user_id] + devices | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there is a |
||
txn.execute(sql, args) | ||
return [tuple(row) for row in txn.fetchall()] | ||
|
||
def add_messages_to_device_inbox_txn(txn, stream_id): | ||
local_users_and_devices = set() | ||
for user_id, messages_by_device in messages_by_user_then_device.items(): | ||
local_users_and_devices.update( | ||
select_devices_txn(txn, user_id, messages_by_device.keys()) | ||
) | ||
|
||
sql = ( | ||
"INSERT INTO device_inbox" | ||
" (user_id, device_id, stream_id, message_json)" | ||
" VALUES (?,?,?,?)" | ||
) | ||
rows = [] | ||
for user_id, messages_by_device in messages_by_user_then_device.items(): | ||
for device_id, message in messages_by_device.items(): | ||
message_json = ujson.dumps(message) | ||
# Only insert into the local inbox if the device exists on | ||
# this server | ||
if (user_id, device_id) in local_users_and_devices: | ||
rows.append((user_id, device_id, stream_id, message_json)) | ||
|
||
txn.executemany(sql, rows) | ||
|
||
with self._device_inbox_id_gen.get_next() as stream_id: | ||
yield self.runInteraction( | ||
"add_messages_to_device_inbox", | ||
add_messages_to_device_inbox_txn, | ||
stream_id | ||
) | ||
|
||
def get_new_messages_for_device( | ||
self, user_id, device_id, current_stream_id, limit=100 | ||
): | ||
""" | ||
Args: | ||
user_id(str): The recipient user_id. | ||
device_id(str): The recipient device_id. | ||
current_stream_id(int): The current position of the to device | ||
message stream. | ||
Returns: | ||
Deferred ([dict], int): List of messages for the device and where | ||
in the stream the messages got to. | ||
""" | ||
def get_new_messages_for_device_txn(txn): | ||
sql = ( | ||
"SELECT stream_id, message_json FROM device_inbox" | ||
" WHERE user_id = ? AND device_id = ?" | ||
" AND stream_id <= ?" | ||
" ORDER BY stream_id ASC" | ||
" LIMIT ?" | ||
) | ||
txn.execute(sql, (user_id, device_id, current_stream_id, limit)) | ||
messages = [] | ||
for row in txn.fetchall(): | ||
stream_pos = row[0] | ||
messages.append(ujson.loads(row[1])) | ||
if len(messages) < limit: | ||
stream_pos = current_stream_id | ||
return (messages, stream_pos) | ||
|
||
return self.runInteraction( | ||
"get_new_messages_for_device", get_new_messages_for_device_txn, | ||
) | ||
|
||
def delete_messages_for_device(self, user_id, device_id, up_to_stream_id): | ||
""" | ||
Args: | ||
user_id(str): The recipient user_id. | ||
device_id(str): The recipient device_id. | ||
up_to_stream_id(int): Where to delete messages up to. | ||
Returns: | ||
A deferred that resolves when the messages have been deleted. | ||
""" | ||
def delete_messages_for_device_txn(txn): | ||
sql = ( | ||
"DELETE FROM device_inbox" | ||
" WHERE user_id = ? AND device_id = ?" | ||
" AND stream_id <= ?" | ||
) | ||
txn.execute(sql, (user_id, device_id, up_to_stream_id)) | ||
|
||
return self.runInteraction( | ||
"delete_messages_for_device", delete_messages_for_device_txn | ||
) | ||
|
||
def get_to_device_stream_token(self): | ||
return self._device_inbox_id_gen.get_current_token() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use underscores fore APIs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
camelcased:
underscores:
lowercase:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I retract my previous statement, and replace it with: your style isn't inconsistent enough.