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