Mercurial > libervia-backend
comparison libervia/backend/memory/memory.py @ 4130:02f0adc745c6
core: notifications implementation, first draft:
add a new table for notifications, and methods/bridge methods to manipulate them.
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 16 Oct 2023 17:29:31 +0200 |
parents | 4b842c1fb686 |
children | 54b8cf8c8daf |
comparison
equal
deleted
inserted
replaced
4129:51744ad00a42 | 4130:02f0adc745c6 |
---|---|
14 # GNU Affero General Public License for more details. | 14 # GNU Affero General Public License for more details. |
15 | 15 |
16 # You should have received a copy of the GNU Affero General Public License | 16 # You should have received a copy of the GNU Affero General Public License |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 17 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 |
19 from collections import namedtuple | |
20 import copy | |
21 from dataclasses import dataclass | |
22 from functools import partial | |
23 import mimetypes | |
19 import os.path | 24 import os.path |
20 import copy | 25 from pathlib import Path |
26 import time | |
27 from typing import Dict, Optional, Tuple | |
28 from uuid import uuid4 | |
29 | |
21 import shortuuid | 30 import shortuuid |
22 import mimetypes | 31 from twisted.internet import defer, error, reactor |
23 import time | |
24 from functools import partial | |
25 from typing import Optional, Tuple, Dict | |
26 from pathlib import Path | |
27 from uuid import uuid4 | |
28 from collections import namedtuple | |
29 from twisted.python import failure | 32 from twisted.python import failure |
30 from twisted.internet import defer, reactor, error | |
31 from twisted.words.protocols.jabber import jid | 33 from twisted.words.protocols.jabber import jid |
34 | |
35 from libervia.backend.core import exceptions | |
36 from libervia.backend.core.constants import Const as C | |
37 from libervia.backend.core.core_types import SatXMPPEntity | |
32 from libervia.backend.core.i18n import _ | 38 from libervia.backend.core.i18n import _ |
33 from libervia.backend.core.log import getLogger | 39 from libervia.backend.core.log import getLogger |
34 from libervia.backend.core import exceptions | |
35 from libervia.backend.core.constants import Const as C | |
36 from libervia.backend.memory.sqla import Storage | |
37 from libervia.backend.memory.persistent import PersistentDict | |
38 from libervia.backend.memory.params import Params | |
39 from libervia.backend.memory.disco import Discovery | |
40 from libervia.backend.memory.crypto import BlockCipher | 40 from libervia.backend.memory.crypto import BlockCipher |
41 from libervia.backend.memory.crypto import PasswordHasher | 41 from libervia.backend.memory.crypto import PasswordHasher |
42 from libervia.backend.memory.disco import Discovery | |
43 from libervia.backend.memory.params import Params | |
44 from libervia.backend.memory.persistent import PersistentDict | |
45 from libervia.backend.memory.sqla import ( | |
46 Notification, | |
47 NotificationPriority, | |
48 NotificationStatus, | |
49 NotificationType, | |
50 Storage, | |
51 ) | |
42 from libervia.backend.tools import config as tools_config | 52 from libervia.backend.tools import config as tools_config |
43 from libervia.backend.tools.common import data_format | 53 from libervia.backend.tools.common import data_format |
44 from libervia.backend.tools.common import regex | 54 from libervia.backend.tools.common import regex |
45 | 55 |
46 | 56 |
1846 self._cache_path, | 1856 self._cache_path, |
1847 regex.path_escape(namespace), | 1857 regex.path_escape(namespace), |
1848 *(regex.path_escape(a) for a in args) | 1858 *(regex.path_escape(a) for a in args) |
1849 ) | 1859 ) |
1850 | 1860 |
1861 ## Notifications ## | |
1862 | |
1863 | |
1864 def _add_notification( | |
1865 self, | |
1866 type_: str, | |
1867 body_plain: str, | |
1868 body_rich: str, | |
1869 title: str, | |
1870 is_global: bool, | |
1871 requires_action: bool, | |
1872 priority: str, | |
1873 expire_at: float, | |
1874 extra_s: str, | |
1875 profile_key: str | |
1876 ) -> defer.Deferred: | |
1877 client = self.host.get_client(profile_key) | |
1878 | |
1879 if not client.is_admin: | |
1880 raise exceptions.PermissionError("Only admins can add a notification") | |
1881 | |
1882 try: | |
1883 notification_type = NotificationType[type_] | |
1884 notification_priority = NotificationPriority[priority] | |
1885 except KeyError as e: | |
1886 raise exceptions.DataError( | |
1887 f"invalid notification type or priority data: {e}" | |
1888 ) | |
1889 | |
1890 return defer.ensureDeferred( | |
1891 self.add_notification( | |
1892 client, | |
1893 notification_type, | |
1894 body_plain, | |
1895 body_rich or None, | |
1896 title or None, | |
1897 is_global, | |
1898 requires_action, | |
1899 notification_priority, | |
1900 expire_at or None, | |
1901 data_format.deserialise(extra_s) | |
1902 ) | |
1903 ) | |
1904 | |
1905 async def add_notification( | |
1906 self, | |
1907 client: SatXMPPEntity, | |
1908 type_: NotificationType, | |
1909 body_plain: str, | |
1910 body_rich: Optional[str] = None, | |
1911 title: Optional[str] = None, | |
1912 is_global: bool = False, | |
1913 requires_action: bool = False, | |
1914 priority: NotificationPriority = NotificationPriority.MEDIUM, | |
1915 expire_at: Optional[float] = None, | |
1916 extra: Optional[dict] = None, | |
1917 ) -> None: | |
1918 """Create and broadcast a new notification. | |
1919 | |
1920 @param client: client associated with the notification. If None, the notification | |
1921 will be global (i.e. for all profiles). | |
1922 @param type_: type of the notification. | |
1923 @param body_plain: plain text body. | |
1924 @param body_rich: rich text (XHTML) body. | |
1925 @param title: optional title. | |
1926 @param is_global: True if the notification is for all profiles. | |
1927 @param requires_action: True if the notification requires user action (e.g. a | |
1928 dialog need to be answered). | |
1929 @priority: how urgent the notification is | |
1930 @param expire_at: expiration timestamp for the notification. | |
1931 @param extra: additional data. | |
1932 """ | |
1933 notification = await self.storage.add_notification( | |
1934 None if is_global else client, type_, body_plain, body_rich, title, | |
1935 requires_action, priority, expire_at, extra | |
1936 ) | |
1937 self.host.bridge.notification_new( | |
1938 str(notification.id), | |
1939 notification.timestamp, | |
1940 type_.value, | |
1941 body_plain, | |
1942 body_rich or '', | |
1943 title or '', | |
1944 requires_action, | |
1945 priority.value, | |
1946 expire_at or 0, | |
1947 data_format.serialise(extra) if extra else '', | |
1948 C.PROF_KEY_ALL if is_global else client.profile | |
1949 ) | |
1950 | |
1951 def _get_notifications(self, filters_s: str, profile_key: str) -> defer.Deferred: | |
1952 """Fetch notifications for bridge with given filters and profile key. | |
1953 | |
1954 @param filters_s: serialized filter conditions. Keys can be: | |
1955 :type_ (str): | |
1956 Filter by type of the notification. | |
1957 :status (str): | |
1958 Filter by status of the notification. | |
1959 :requires_action (bool): | |
1960 Filter by notifications that require user action. | |
1961 :min_priority (str): | |
1962 Filter by minimum priority value. | |
1963 @param profile_key: key of the profile to fetch notifications for. | |
1964 @return: Deferred which fires with a list of serialised notifications. | |
1965 """ | |
1966 client = self.host.get_client(profile_key) | |
1967 | |
1968 filters = data_format.deserialise(filters_s) | |
1969 | |
1970 try: | |
1971 if 'type' in filters: | |
1972 filters['type_'] = NotificationType[filters.pop('type')] | |
1973 if 'status' in filters: | |
1974 filters['status'] = NotificationStatus[filters['status']] | |
1975 if 'min_priority' in filters: | |
1976 filters['min_priority'] = NotificationPriority[filters['min_priority']].value | |
1977 except KeyError as e: | |
1978 raise exceptions.DataError(f"invalid filter data: {e}") | |
1979 | |
1980 d = defer.ensureDeferred(self.storage.get_notifications(client, **filters)) | |
1981 d.addCallback( | |
1982 lambda notifications: data_format.serialise( | |
1983 [notification.serialise() for notification in notifications] | |
1984 ) | |
1985 ) | |
1986 return d | |
1987 | |
1988 def _delete_notification( | |
1989 self, | |
1990 id_: str, | |
1991 is_global: bool, | |
1992 profile_key: str | |
1993 ) -> defer.Deferred: | |
1994 client = self.host.get_client(profile_key) | |
1995 if is_global and not client.is_admin: | |
1996 raise exceptions.PermissionError( | |
1997 "Only admins can delete global notifications" | |
1998 ) | |
1999 return defer.ensureDeferred(self.delete_notification(client, id_, is_global)) | |
2000 | |
2001 async def delete_notification( | |
2002 self, | |
2003 client: SatXMPPEntity, | |
2004 id_: str, | |
2005 is_global: bool=False | |
2006 ) -> None: | |
2007 """Delete a notification | |
2008 | |
2009 the notification must be from the requesting profile. | |
2010 @param id_: ID of the notification | |
2011 is_global: if True, a global notification will be removed. | |
2012 """ | |
2013 await self.storage.delete_notification(None if is_global else client, id_) | |
2014 self.host.bridge.notification_deleted( | |
2015 id_, | |
2016 C.PROF_KEY_ALL if is_global else client.profile | |
2017 ) | |
2018 | |
2019 def _notifications_expired_clean( | |
2020 self, limit_timestamp: float, profile_key: str | |
2021 ) -> defer.Deferred: | |
2022 if profile_key == C.PROF_KEY_NONE: | |
2023 client = None | |
2024 else: | |
2025 client = self.host.get_client(profile_key) | |
2026 | |
2027 return defer.ensureDeferred( | |
2028 self.storage.clean_expired_notifications( | |
2029 client, | |
2030 None if limit_timestamp == -1.0 else limit_timestamp | |
2031 ) | |
2032 ) | |
2033 | |
2034 | |
1851 ## Misc ## | 2035 ## Misc ## |
1852 | 2036 |
1853 def is_entity_available(self, client, entity_jid): | 2037 def is_entity_available(self, client, entity_jid): |
1854 """Tell from the presence information if the given entity is available. | 2038 """Tell from the presence information if the given entity is available. |
1855 | 2039 |