comparison libervia/backend/plugins/plugin_xep_0059.py @ 4071:4b842c1fb686

refactoring: renamed `sat` package to `libervia.backend`
author Goffi <goffi@goffi.org>
date Fri, 02 Jun 2023 11:49:51 +0200
parents sat/plugins/plugin_xep_0059.py@524856bd7b19
children 0d7bb4df2343
comparison
equal deleted inserted replaced
4070:d10748475025 4071:4b842c1fb686
1 #!/usr/bin/env python3
2
3 # Result Set Management (XEP-0059)
4 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
5 # Copyright (C) 2013-2016 Adrien Cossa (souliane@mailoo.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from typing import Optional
21 from zope.interface import implementer
22 from twisted.words.protocols.jabber import xmlstream
23 from wokkel import disco
24 from wokkel import iwokkel
25 from wokkel import rsm
26 from libervia.backend.core.i18n import _
27 from libervia.backend.core.constants import Const as C
28 from libervia.backend.core.log import getLogger
29
30
31 log = getLogger(__name__)
32
33
34 PLUGIN_INFO = {
35 C.PI_NAME: "Result Set Management",
36 C.PI_IMPORT_NAME: "XEP-0059",
37 C.PI_TYPE: "XEP",
38 C.PI_MODES: C.PLUG_MODE_BOTH,
39 C.PI_PROTOCOLS: ["XEP-0059"],
40 C.PI_MAIN: "XEP_0059",
41 C.PI_HANDLER: "yes",
42 C.PI_DESCRIPTION: _("""Implementation of Result Set Management"""),
43 }
44
45 RSM_PREFIX = "rsm_"
46
47
48 class XEP_0059(object):
49 # XXX: RSM management is done directly in Wokkel.
50
51 def __init__(self, host):
52 log.info(_("Result Set Management plugin initialization"))
53
54 def get_handler(self, client):
55 return XEP_0059_handler()
56
57 def parse_extra(self, extra):
58 """Parse extra dictionnary to retrieve RSM arguments
59
60 @param extra(dict): data for parse
61 @return (rsm.RSMRequest, None): request with parsed arguments
62 or None if no RSM arguments have been found
63 """
64 if int(extra.get(RSM_PREFIX + 'max', 0)) < 0:
65 raise ValueError(_("rsm_max can't be negative"))
66
67 rsm_args = {}
68 for arg in ("max", "after", "before", "index"):
69 try:
70 argname = "max_" if arg == "max" else arg
71 rsm_args[argname] = extra.pop(RSM_PREFIX + arg)
72 except KeyError:
73 continue
74
75 if rsm_args:
76 return rsm.RSMRequest(**rsm_args)
77 else:
78 return None
79
80 def response2dict(self, rsm_response, data=None):
81 """Return a dict with RSM response
82
83 Key set in data can be:
84 - rsm_first: first item id in the page
85 - rsm_last: last item id in the page
86 - rsm_index: position of the first item in the full set (may be approximate)
87 - rsm_count: total number of items in the full set (may be approximage)
88 If a value doesn't exists, it's not set.
89 All values are set as strings.
90 @param rsm_response(rsm.RSMResponse): response to parse
91 @param data(dict, None): dict to update with rsm_* data.
92 If None, a new dict is created
93 @return (dict): data dict
94 """
95 if data is None:
96 data = {}
97 if rsm_response.first is not None:
98 data["first"] = rsm_response.first
99 if rsm_response.last is not None:
100 data["last"] = rsm_response.last
101 if rsm_response.index is not None:
102 data["index"] = rsm_response.index
103 return data
104
105 def get_next_request(
106 self,
107 rsm_request: rsm.RSMRequest,
108 rsm_response: rsm.RSMResponse,
109 log_progress: bool = True,
110 ) -> Optional[rsm.RSMRequest]:
111 """Generate next request to paginate through all items
112
113 Page will be retrieved forward
114 @param rsm_request: last request used
115 @param rsm_response: response from the last request
116 @return: request to retrive next page, or None if we are at the end
117 or if pagination is not possible
118 """
119 if rsm_request.max == 0:
120 log.warning("Can't do pagination if max is 0")
121 return None
122 if rsm_response is None:
123 # may happen if result set it empty, or we are at the end
124 return None
125 if (
126 rsm_response.count is not None
127 and rsm_response.index is not None
128 ):
129 next_index = rsm_response.index + rsm_request.max
130 if next_index >= rsm_response.count:
131 # we have reached the last page
132 return None
133
134 if log_progress:
135 log.debug(
136 f"retrieving items {next_index} to "
137 f"{min(next_index+rsm_request.max, rsm_response.count)} on "
138 f"{rsm_response.count} ({next_index/rsm_response.count*100:.2f}%)"
139 )
140
141 if rsm_response.last is None:
142 if rsm_response.count:
143 log.warning("Can't do pagination, no \"last\" received")
144 return None
145
146 return rsm.RSMRequest(
147 max_=rsm_request.max,
148 after=rsm_response.last
149 )
150
151
152 @implementer(iwokkel.IDisco)
153 class XEP_0059_handler(xmlstream.XMPPHandler):
154
155 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
156 return [disco.DiscoFeature(rsm.NS_RSM)]
157
158 def getDiscoItems(self, requestor, target, nodeIdentifier=""):
159 return []