Mercurial > libervia-backend
comparison sat_frontends/jp/cmd_message.py @ 3040:fee60f17ebac
jp: jp asyncio port:
/!\ this commit is huge. Jp is temporarily not working with `dbus` bridge /!\
This patch implements the port of jp to asyncio, so it is now correctly using the bridge
asynchronously, and it can be used with bridges like `pb`. This also simplify the code,
notably for things which were previously implemented with many callbacks (like pagination
with RSM).
During the process, some behaviours have been modified/fixed, in jp and backends, check
diff for details.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 25 Sep 2019 08:56:41 +0200 |
parents | ab2696e34d29 |
children | 9d0df638c8b4 |
comparison
equal
deleted
inserted
replaced
3039:a1bc34f90fa5 | 3040:fee60f17ebac |
---|---|
23 from sat_frontends.tools import jid | 23 from sat_frontends.tools import jid |
24 from sat.core.i18n import _ | 24 from sat.core.i18n import _ |
25 from sat.tools.utils import clean_ustr | 25 from sat.tools.utils import clean_ustr |
26 from sat.tools.common import data_format | 26 from sat.tools.common import data_format |
27 from sat.tools.common.ansi import ANSI as A | 27 from sat.tools.common.ansi import ANSI as A |
28 from functools import partial | |
29 | 28 |
30 __commands__ = ["Message"] | 29 __commands__ = ["Message"] |
31 | 30 |
32 | 31 |
33 class Send(base.CommandBase): | 32 class Send(base.CommandBase): |
34 def __init__(self, host): | 33 def __init__(self, host): |
35 super(Send, self).__init__(host, "send", help=_("send a message to a contact")) | 34 super(Send, self).__init__(host, "send", help=_("send a message to a contact")) |
36 self.need_loop=True | |
37 | 35 |
38 def add_parser_options(self): | 36 def add_parser_options(self): |
39 self.parser.add_argument( | 37 self.parser.add_argument( |
40 "-l", "--lang", type=str, default="", help=_("language of the message") | 38 "-l", "--lang", type=str, default="", help=_("language of the message") |
41 ) | 39 ) |
82 syntax.add_argument("-r", "--rich", action="store_true", help=_("rich body")) | 80 syntax.add_argument("-r", "--rich", action="store_true", help=_("rich body")) |
83 self.parser.add_argument( | 81 self.parser.add_argument( |
84 "jid", help=_("the destination jid") | 82 "jid", help=_("the destination jid") |
85 ) | 83 ) |
86 | 84 |
87 def multi_send_cb(self): | 85 async def sendStdin(self, dest_jid): |
88 self.sent += 1 | |
89 if self.sent == self.to_send: | |
90 self.host.quit(self.errcode) | |
91 | |
92 def multi_send_eb(self, failure_, msg): | |
93 self.disp(_("Can't send message [{msg}]: {reason}").format( | |
94 msg=msg, reason=failure_)) | |
95 self.errcode = C.EXIT_BRIDGE_ERRBACK | |
96 self.multi_send_cb() | |
97 | |
98 def sendStdin(self, dest_jid): | |
99 """Send incomming data on stdin to jabber contact | 86 """Send incomming data on stdin to jabber contact |
100 | 87 |
101 @param dest_jid: destination jid | 88 @param dest_jid: destination jid |
102 """ | 89 """ |
103 header = "\n" if self.args.new_line else "" | 90 header = "\n" if self.args.new_line else "" |
91 # FIXME: stdin is not read asynchronously at the moment | |
104 stdin_lines = [ | 92 stdin_lines = [ |
105 stream.decode("utf-8", "ignore") for stream in sys.stdin.readlines() | 93 stream for stream in sys.stdin.readlines() |
106 ] | 94 ] |
107 extra = {} | 95 extra = {} |
108 if self.args.subject is None: | 96 if self.args.subject is None: |
109 subject = {} | 97 subject = {} |
110 else: | 98 else: |
111 subject = {self.args.subject_lang: self.args.subject} | 99 subject = {self.args.subject_lang: self.args.subject} |
112 | 100 |
113 if self.args.xhtml or self.args.rich: | 101 if self.args.xhtml or self.args.rich: |
114 key = "xhtml" if self.args.xhtml else "rich" | 102 key = "xhtml" if self.args.xhtml else "rich" |
115 if self.args.lang: | 103 if self.args.lang: |
116 key = "{}_{}".format(key, self.args.lang) | 104 key = f"{key}_{self.args.lang}" |
117 extra[key] = clean_ustr("".join(stdin_lines)) | 105 extra[key] = clean_ustr("".join(stdin_lines)) |
118 stdin_lines = [] | 106 stdin_lines = [] |
119 | 107 |
120 if self.args.separate: # we send stdin in several messages | 108 to_send = [] |
121 self.to_send = 0 | 109 |
122 self.sent = 0 | 110 error = False |
123 self.errcode = 0 | 111 |
124 | 112 if self.args.separate: |
113 # we send stdin in several messages | |
125 if header: | 114 if header: |
126 self.to_send += 1 | 115 # first we sent the header |
127 self.host.bridge.messageSend( | 116 try: |
117 await self.host.bridge.messageSend( | |
118 dest_jid, | |
119 {self.args.lang: header}, | |
120 subject, | |
121 self.args.type, | |
122 profile_key=self.profile, | |
123 ) | |
124 except Exception as e: | |
125 self.disp(f"can't send header: {e}", error=True) | |
126 error = True | |
127 | |
128 to_send.extend({self.args.lang: clean_ustr(l.replace("\n", ""))} | |
129 for l in stdin_lines) | |
130 else: | |
131 # we sent all in a single message | |
132 if not (self.args.xhtml or self.args.rich): | |
133 msg = {self.args.lang: header + clean_ustr("".join(stdin_lines))} | |
134 else: | |
135 msg = {} | |
136 to_send.append(msg) | |
137 | |
138 for msg in to_send: | |
139 try: | |
140 await self.host.bridge.messageSend( | |
128 dest_jid, | 141 dest_jid, |
129 {self.args.lang: header}, | 142 msg, |
130 subject, | |
131 self.args.type, | |
132 profile_key=self.profile, | |
133 callback=lambda: None, | |
134 errback=lambda ignore: ignore, | |
135 ) | |
136 | |
137 self.to_send += len(stdin_lines) | |
138 for line in stdin_lines: | |
139 self.host.bridge.messageSend( | |
140 dest_jid, | |
141 {self.args.lang: line.replace("\n", "")}, | |
142 subject, | 143 subject, |
143 self.args.type, | 144 self.args.type, |
144 extra, | 145 extra, |
145 profile_key=self.host.profile, | 146 profile_key=self.host.profile) |
146 callback=self.multi_send_cb, | 147 except Exception as e: |
147 errback=partial(self.multi_send_eb, msg=line), | 148 self.disp(f"can't send message {msg!r}: {e}", error=True) |
148 ) | 149 error = True |
149 | 150 |
150 else: | 151 if error: |
151 msg = ( | 152 # at least one message sending failed |
152 {self.args.lang: header + clean_ustr("".join(stdin_lines))} | 153 self.host.quit(C.EXIT_BRIDGE_ERRBACK) |
153 if not (self.args.xhtml or self.args.rich) | 154 |
154 else {} | 155 self.host.quit() |
155 ) | 156 |
156 self.host.bridge.messageSend( | 157 async def start(self): |
157 dest_jid, | |
158 msg, | |
159 subject, | |
160 self.args.type, | |
161 extra, | |
162 profile_key=self.host.profile, | |
163 callback=self.host.quit, | |
164 errback=partial(self.errback, | |
165 msg=_("Can't send message: {}"))) | |
166 | |
167 def encryptionNamespaceGetCb(self, namespace, jid_): | |
168 self.host.bridge.messageEncryptionStart( | |
169 jid_, namespace, not self.args.encrypt_noreplace, | |
170 self.profile, | |
171 callback=lambda: self.sendStdin(jid_), | |
172 errback=partial(self.errback, | |
173 msg=_("Can't start encryption session: {}"), | |
174 exit_code=C.EXIT_BRIDGE_ERRBACK, | |
175 )) | |
176 | |
177 | |
178 def start(self): | |
179 if self.args.xhtml and self.args.separate: | 158 if self.args.xhtml and self.args.separate: |
180 self.disp( | 159 self.disp( |
181 "argument -s/--separate is not compatible yet with argument -x/--xhtml", | 160 "argument -s/--separate is not compatible yet with argument -x/--xhtml", |
182 error=True, | 161 error=True, |
183 ) | 162 ) |
184 self.host.quit(2) | 163 self.host.quit(C.EXIT_BAD_ARG) |
185 | 164 |
186 jids = self.host.check_jids([self.args.jid]) | 165 jids = await self.host.check_jids([self.args.jid]) |
187 jid_ = jids[0] | 166 jid_ = jids[0] |
188 | 167 |
189 if self.args.encrypt_noreplace and self.args.encrypt is None: | 168 if self.args.encrypt_noreplace and self.args.encrypt is None: |
190 self.parser.error("You need to use --encrypt if you use --encrypt-noreplace") | 169 self.parser.error("You need to use --encrypt if you use --encrypt-noreplace") |
191 | 170 |
192 if self.args.encrypt is not None: | 171 if self.args.encrypt is not None: |
193 self.host.bridge.encryptionNamespaceGet(self.args.encrypt, | 172 try: |
194 callback=partial(self.encryptionNamespaceGetCb, jid_=jid_), | 173 namespace = await self.host.bridge.encryptionNamespaceGet( |
195 errback=partial(self.errback, | 174 self.args.encrypt) |
196 msg=_("Can't get encryption namespace: {}"), | 175 except Exception as e: |
197 exit_code=C.EXIT_BRIDGE_ERRBACK, | 176 self.disp(f"can't get encryption namespace: {e}", error=True) |
198 )) | 177 self.host.quit(C.EXIT_BRIDGE_ERRBACK) |
199 else: | 178 |
200 self.sendStdin(jid_) | 179 try: |
180 await self.host.bridge.messageEncryptionStart( | |
181 jid_, namespace, not self.args.encrypt_noreplace, self.profile | |
182 ) | |
183 except Exception as e: | |
184 self.disp(f"can't start encryption session: {e}", error=True) | |
185 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
186 | |
187 await self.sendStdin(jid_) | |
201 | 188 |
202 | 189 |
203 class MAM(base.CommandBase): | 190 class MAM(base.CommandBase): |
204 | 191 |
205 def __init__(self, host): | 192 def __init__(self, host): |
206 super(MAM, self).__init__( | 193 super(MAM, self).__init__( |
207 host, "mam", use_output=C.OUTPUT_MESS, use_verbose=True, help=_("query archives using MAM")) | 194 host, "mam", use_output=C.OUTPUT_MESS, use_verbose=True, |
208 self.need_loop=True | 195 help=_("query archives using MAM")) |
209 | 196 |
210 def add_parser_options(self): | 197 def add_parser_options(self): |
211 self.parser.add_argument( | 198 self.parser.add_argument( |
212 "-s", "--service", default="", | 199 "-s", "--service", default="", |
213 help=_("jid of the service (default: profile's server")) | 200 help=_("jid of the service (default: profile's server")) |
233 help=_("find page before this item"), metavar='ITEM_ID') | 220 help=_("find page before this item"), metavar='ITEM_ID') |
234 rsm_page_group.add_argument( | 221 rsm_page_group.add_argument( |
235 "--index", dest="rsm_index", type=int, | 222 "--index", dest="rsm_index", type=int, |
236 help=_("index of the page to retrieve")) | 223 help=_("index of the page to retrieve")) |
237 | 224 |
238 def _sessionInfosGetCb(self, session_info, data, metadata): | 225 async def start(self): |
239 self.host.own_jid = jid.JID(session_info["jid"]) | |
240 self.output(data) | |
241 # FIXME: metadata are not displayed correctly and don't play nice with output | |
242 # they should be added to output data somehow | |
243 if self.verbosity: | |
244 for value in ("rsm_first", "rsm_last", "rsm_index", "rsm_count", | |
245 "mam_complete", "mam_stable"): | |
246 if value in metadata: | |
247 label = value.split("_")[1] | |
248 self.disp(A.color( | |
249 C.A_HEADER, label, ': ' , A.RESET, metadata[value])) | |
250 | |
251 self.host.quit() | |
252 | |
253 def _MAMGetCb(self, result): | |
254 data, metadata, profile = result | |
255 self.host.bridge.sessionInfosGet(self.profile, | |
256 callback=partial(self._sessionInfosGetCb, data=data, metadata=metadata), | |
257 errback=self.errback) | |
258 | |
259 def start(self): | |
260 extra = {} | 226 extra = {} |
261 if self.args.mam_start is not None: | 227 if self.args.mam_start is not None: |
262 extra["mam_start"] = float(self.args.mam_start) | 228 extra["mam_start"] = float(self.args.mam_start) |
263 if self.args.mam_end is not None: | 229 if self.args.mam_end is not None: |
264 extra["mam_end"] = float(self.args.mam_end) | 230 extra["mam_end"] = float(self.args.mam_end) |
267 for suff in ('max', 'after', 'before', 'index'): | 233 for suff in ('max', 'after', 'before', 'index'): |
268 key = 'rsm_' + suff | 234 key = 'rsm_' + suff |
269 value = getattr(self.args,key) | 235 value = getattr(self.args,key) |
270 if value is not None: | 236 if value is not None: |
271 extra[key] = str(value) | 237 extra[key] = str(value) |
272 self.host.bridge.MAMGet( | 238 try: |
273 self.args.service, data_format.serialise(extra), self.profile, | 239 data, metadata, profile = await self.host.bridge.MAMGet( |
274 callback=self._MAMGetCb, errback=self.errback) | 240 self.args.service, data_format.serialise(extra), self.profile) |
241 except Exception as e: | |
242 self.disp(f"can't retrieve MAM archives: {e}", error=True) | |
243 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
244 | |
245 try: | |
246 session_info = await self.host.bridge.sessionInfosGet(self.profile) | |
247 except Exception as e: | |
248 self.disp(f"can't get session infos: {e}", error=True) | |
249 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
250 | |
251 # we need to fill own_jid for message output | |
252 self.host.own_jid = jid.JID(session_info["jid"]) | |
253 | |
254 await self.output(data) | |
255 | |
256 # FIXME: metadata are not displayed correctly and don't play nice with output | |
257 # they should be added to output data somehow | |
258 if self.verbosity: | |
259 for value in ("rsm_first", "rsm_last", "rsm_index", "rsm_count", | |
260 "mam_complete", "mam_stable"): | |
261 if value in metadata: | |
262 label = value.split("_")[1] | |
263 self.disp(A.color( | |
264 C.A_HEADER, label, ': ' , A.RESET, metadata[value])) | |
265 | |
266 self.host.quit() | |
275 | 267 |
276 | 268 |
277 class Message(base.CommandBase): | 269 class Message(base.CommandBase): |
278 subcommands = (Send, MAM) | 270 subcommands = (Send, MAM) |
279 | 271 |