mirror of https://github.com/watcha-fr/synapse
Merge pull request #456 from matrix-org/store_event_actions
Send unread notification countspull/4/merge
commit
c232780081
@ -0,0 +1,55 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# 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. |
||||
|
||||
from twisted.internet import defer |
||||
|
||||
import bulk_push_rule_evaluator |
||||
|
||||
import logging |
||||
|
||||
from synapse.api.constants import EventTypes |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
class ActionGenerator: |
||||
def __init__(self, store): |
||||
self.store = store |
||||
# really we want to get all user ids and all profile tags too, |
||||
# since we want the actions for each profile tag for every user and |
||||
# also actions for a client with no profile tag for each user. |
||||
# Currently the event stream doesn't support profile tags on an |
||||
# event stream, so we just run the rules for a client with no profile |
||||
# tag (ie. we just need all the users). |
||||
|
||||
@defer.inlineCallbacks |
||||
def handle_push_actions_for_event(self, event, handler): |
||||
if event.type == EventTypes.Redaction and event.redacts is not None: |
||||
yield self.store.remove_push_actions_for_event_id( |
||||
event.room_id, event.redacts |
||||
) |
||||
|
||||
bulk_evaluator = yield bulk_push_rule_evaluator.evaluator_for_room_id( |
||||
event.room_id, self.store |
||||
) |
||||
|
||||
actions_by_user = yield bulk_evaluator.action_for_event_by_user(event, handler) |
||||
|
||||
yield self.store.set_push_actions_for_event_and_users( |
||||
event, |
||||
[ |
||||
(uid, None, actions) for uid, actions in actions_by_user.items() |
||||
] |
||||
) |
@ -0,0 +1,124 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# 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. |
||||
|
||||
import logging |
||||
import simplejson as json |
||||
|
||||
from twisted.internet import defer |
||||
|
||||
from synapse.types import UserID |
||||
|
||||
import baserules |
||||
from push_rule_evaluator import PushRuleEvaluator |
||||
|
||||
from synapse.events.utils import serialize_event |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
def decode_rule_json(rule): |
||||
rule['conditions'] = json.loads(rule['conditions']) |
||||
rule['actions'] = json.loads(rule['actions']) |
||||
return rule |
||||
|
||||
|
||||
@defer.inlineCallbacks |
||||
def evaluator_for_room_id(room_id, store): |
||||
users = yield store.get_users_in_room(room_id) |
||||
rules_by_user = yield store.bulk_get_push_rules(users) |
||||
rules_by_user = { |
||||
uid: baserules.list_with_base_rules( |
||||
[decode_rule_json(rule_list) for rule_list in rules_by_user[uid]] |
||||
if uid in rules_by_user else [], |
||||
UserID.from_string(uid), |
||||
) |
||||
for uid in users |
||||
} |
||||
member_events = yield store.get_current_state( |
||||
room_id=room_id, |
||||
event_type='m.room.member', |
||||
) |
||||
display_names = {} |
||||
for ev in member_events: |
||||
if ev.content.get("displayname"): |
||||
display_names[ev.state_key] = ev.content.get("displayname") |
||||
|
||||
defer.returnValue(BulkPushRuleEvaluator( |
||||
room_id, rules_by_user, display_names, users, store |
||||
)) |
||||
|
||||
|
||||
class BulkPushRuleEvaluator: |
||||
""" |
||||
Runs push rules for all users in a room. |
||||
This is faster than running PushRuleEvaluator for each user because it |
||||
fetches all the rules for all the users in one (batched) db query |
||||
rather than doing multiple queries per-user. It currently uses |
||||
the same logic to run the actual rules, but could be optimised further |
||||
(see https://matrix.org/jira/browse/SYN-562) |
||||
""" |
||||
def __init__(self, room_id, rules_by_user, display_names, users_in_room, store): |
||||
self.room_id = room_id |
||||
self.rules_by_user = rules_by_user |
||||
self.display_names = display_names |
||||
self.users_in_room = users_in_room |
||||
self.store = store |
||||
|
||||
@defer.inlineCallbacks |
||||
def action_for_event_by_user(self, event, handler): |
||||
actions_by_user = {} |
||||
|
||||
for uid, rules in self.rules_by_user.items(): |
||||
display_name = None |
||||
if uid in self.display_names: |
||||
display_name = self.display_names[uid] |
||||
|
||||
is_guest = yield self.store.is_guest(UserID.from_string(uid)) |
||||
filtered = yield handler._filter_events_for_client( |
||||
uid, [event], is_guest=is_guest |
||||
) |
||||
if len(filtered) == 0: |
||||
continue |
||||
|
||||
for rule in rules: |
||||
if 'enabled' in rule and not rule['enabled']: |
||||
continue |
||||
|
||||
# XXX: profile tags |
||||
if BulkPushRuleEvaluator.event_matches_rule( |
||||
event, rule, |
||||
display_name, len(self.users_in_room), None |
||||
): |
||||
actions = [x for x in rule['actions'] if x != 'dont_notify'] |
||||
if len(actions) > 0: |
||||
actions_by_user[uid] = actions |
||||
break |
||||
defer.returnValue(actions_by_user) |
||||
|
||||
@staticmethod |
||||
def event_matches_rule(event, rule, |
||||
display_name, room_member_count, profile_tag): |
||||
matches = True |
||||
|
||||
# passing the clock all the way into here is extremely awkward and push |
||||
# rules do not care about any of the relative timestamps, so we just |
||||
# pass 0 for the current time. |
||||
client_event = serialize_event(event, 0) |
||||
|
||||
for cond in rule['conditions']: |
||||
matches &= PushRuleEvaluator._event_fulfills_condition( |
||||
client_event, cond, display_name, room_member_count, profile_tag |
||||
) |
||||
return matches |
@ -0,0 +1,110 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# 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. |
||||
|
||||
from ._base import SQLBaseStore |
||||
from twisted.internet import defer |
||||
|
||||
import logging |
||||
import simplejson as json |
||||
|
||||
logger = logging.getLogger(__name__) |
||||
|
||||
|
||||
class EventPushActionsStore(SQLBaseStore): |
||||
@defer.inlineCallbacks |
||||
def set_push_actions_for_event_and_users(self, event, tuples): |
||||
""" |
||||
:param event: the event set actions for |
||||
:param tuples: list of tuples of (user_id, profile_tag, actions) |
||||
""" |
||||
values = [] |
||||
for uid, profile_tag, actions in tuples: |
||||
values.append({ |
||||
'room_id': event.room_id, |
||||
'event_id': event.event_id, |
||||
'user_id': uid, |
||||
'profile_tag': profile_tag, |
||||
'actions': json.dumps(actions) |
||||
}) |
||||
|
||||
yield self.runInteraction( |
||||
"set_actions_for_event_and_users", |
||||
self._simple_insert_many_txn, |
||||
EventPushActionsTable.table_name, |
||||
values |
||||
) |
||||
|
||||
@defer.inlineCallbacks |
||||
def get_unread_event_push_actions_by_room_for_user( |
||||
self, room_id, user_id, last_read_event_id |
||||
): |
||||
def _get_unread_event_push_actions_by_room(txn): |
||||
sql = ( |
||||
"SELECT stream_ordering, topological_ordering" |
||||
" FROM events" |
||||
" WHERE room_id = ? AND event_id = ?" |
||||
) |
||||
txn.execute( |
||||
sql, (room_id, last_read_event_id) |
||||
) |
||||
results = txn.fetchall() |
||||
if len(results) == 0: |
||||
return [] |
||||
|
||||
stream_ordering = results[0][0] |
||||
topological_ordering = results[0][1] |
||||
|
||||
sql = ( |
||||
"SELECT ea.event_id, ea.actions" |
||||
" FROM event_push_actions ea, events e" |
||||
" WHERE ea.room_id = e.room_id" |
||||
" AND ea.event_id = e.event_id" |
||||
" AND ea.user_id = ?" |
||||
" AND ea.room_id = ?" |
||||
" AND (" |
||||
" e.topological_ordering > ?" |
||||
" OR (e.topological_ordering = ? AND e.stream_ordering > ?)" |
||||
")" |
||||
) |
||||
txn.execute(sql, ( |
||||
user_id, room_id, |
||||
topological_ordering, topological_ordering, stream_ordering |
||||
) |
||||
) |
||||
return [ |
||||
{"event_id": row[0], "actions": row[1]} for row in txn.fetchall() |
||||
] |
||||
|
||||
ret = yield self.runInteraction( |
||||
"get_unread_event_push_actions_by_room", |
||||
_get_unread_event_push_actions_by_room |
||||
) |
||||
defer.returnValue(ret) |
||||
|
||||
@defer.inlineCallbacks |
||||
def remove_push_actions_for_event_id(self, room_id, event_id): |
||||
def f(txn): |
||||
txn.execute( |
||||
"DELETE FROM event_push_actions WHERE room_id = ? AND event_id = ?", |
||||
(room_id, event_id) |
||||
) |
||||
yield self.runInteraction( |
||||
"remove_push_actions_for_event_id", |
||||
f |
||||
) |
||||
|
||||
|
||||
class EventPushActionsTable(object): |
||||
table_name = "event_push_actions" |
@ -0,0 +1,26 @@ |
||||
/* 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. |
||||
*/ |
||||
|
||||
CREATE TABLE IF NOT EXISTS event_push_actions( |
||||
room_id TEXT NOT NULL, |
||||
event_id TEXT NOT NULL, |
||||
user_id TEXT NOT NULL, |
||||
profile_tag VARCHAR(32), |
||||
actions TEXT NOT NULL, |
||||
CONSTRAINT event_id_user_id_profile_tag_uniqueness UNIQUE (room_id, event_id, user_id, profile_tag) |
||||
); |
||||
|
||||
|
||||
CREATE INDEX event_push_actions_room_id_event_id_user_id_profile_tag on event_push_actions(room_id, event_id, user_id, profile_tag); |
@ -0,0 +1,22 @@ |
||||
/* 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. |
||||
*/ |
||||
|
||||
ALTER TABLE users ADD is_guest admin SMALLINT DEFAULT 0 NOT NULL; |
||||
/* |
||||
* NB: any guest users created between 27 and 28 will be incorrectly |
||||
* marked as not guests: we don't bother to fill these in correctly |
||||
* because guest access is not really complete in 27 anyway so it's |
||||
* very unlikley there will be any guest users created. |
||||
*/ |
Loading…
Reference in new issue