comparison sat_frontends/jp/cmd_event.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 040ca99e25fe
comparison
equal deleted inserted replaced
3039:a1bc34f90fa5 3040:fee60f17ebac
21 from . import base 21 from . import base
22 from sat.core.i18n import _ 22 from sat.core.i18n import _
23 from sat.tools.common.ansi import ANSI as A 23 from sat.tools.common.ansi import ANSI as A
24 from sat_frontends.jp.constants import Const as C 24 from sat_frontends.jp.constants import Const as C
25 from sat_frontends.jp import common 25 from sat_frontends.jp import common
26 from functools import partial
27 from dateutil import parser as du_parser 26 from dateutil import parser as du_parser
28 import calendar 27 import calendar
29 import time 28 import time
30 29
31 __commands__ = ["Event"] 30 __commands__ = ["Event"]
45 use_pubsub=True, 44 use_pubsub=True,
46 pubsub_flags={C.SINGLE_ITEM}, 45 pubsub_flags={C.SINGLE_ITEM},
47 use_verbose=True, 46 use_verbose=True,
48 help=_("get event data"), 47 help=_("get event data"),
49 ) 48 )
50 self.need_loop = True
51 49
52 def add_parser_options(self): 50 def add_parser_options(self):
53 pass 51 pass
54 52
55 def eventInviteeGetCb(self, result): 53 async def start(self):
56 event_date, event_data = result 54 try:
57 event_data["date"] = event_date 55 event_tuple = await self.host.bridge.eventGet(
58 self.output(event_data) 56 self.args.service,
59 self.host.quit() 57 self.args.node,
60 58 self.args.item,
61 def start(self): 59 self.profile,
62 self.host.bridge.eventGet( 60 )
63 self.args.service, 61 except Exception as e:
64 self.args.node, 62 self.disp(f"can't get event data: {e}", error=True)
65 self.args.item, 63 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
66 self.profile, 64 else:
67 callback=self.eventInviteeGetCb, 65 event_date, event_data = event_tuple
68 errback=partial( 66 event_data["date"] = event_date
69 self.errback, 67 await self.output(event_data)
70 msg=_("can't get event data: {}"), 68 self.host.quit()
71 exit_code=C.EXIT_BRIDGE_ERRBACK,
72 ),
73 )
74 69
75 70
76 class EventBase(object): 71 class EventBase(object):
77 def add_parser_options(self): 72 def add_parser_options(self):
78 self.parser.add_argument( 73 self.parser.add_argument(
124 "create", 119 "create",
125 use_pubsub=True, 120 use_pubsub=True,
126 help=_("create or replace event"), 121 help=_("create or replace event"),
127 ) 122 )
128 EventBase.__init__(self) 123 EventBase.__init__(self)
129 self.need_loop = True 124
130 125 async def start(self):
131 def eventCreateCb(self, node):
132 self.disp(_("Event created successfuly on node {node}").format(node=node))
133 self.host.quit()
134
135 def start(self):
136 fields = self.parseFields() 126 fields = self.parseFields()
137 date = self.parseDate() 127 date = self.parseDate()
138 self.host.bridge.eventCreate( 128 try:
139 date, 129 node = await self.host.bridge.eventCreate(
140 fields, 130 date,
141 self.args.service, 131 fields,
142 self.args.node, 132 self.args.service,
143 self.args.id, 133 self.args.node,
144 self.profile, 134 self.args.id,
145 callback=self.eventCreateCb, 135 self.profile,
146 errback=partial( 136 )
147 self.errback, 137 except Exception as e:
148 msg=_("can't create event: {}"), 138 self.disp(f"can't create event: {e}", error=True)
149 exit_code=C.EXIT_BRIDGE_ERRBACK, 139 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
150 ), 140 else:
151 ) 141 self.disp(_(f"Event created successfuly on node {node}"))
142 self.host.quit()
152 143
153 144
154 class Modify(EventBase, base.CommandBase): 145 class Modify(EventBase, base.CommandBase):
155 def __init__(self, host): 146 def __init__(self, host):
156 super(Modify, self).__init__( 147 super(Modify, self).__init__(
159 use_pubsub=True, 150 use_pubsub=True,
160 pubsub_flags={C.NODE}, 151 pubsub_flags={C.NODE},
161 help=_("modify an existing event"), 152 help=_("modify an existing event"),
162 ) 153 )
163 EventBase.__init__(self) 154 EventBase.__init__(self)
164 self.need_loop = True 155
165 156 async def start(self):
166 def start(self):
167 fields = self.parseFields() 157 fields = self.parseFields()
168 date = 0 if not self.args.date else self.parseDate() 158 date = 0 if not self.args.date else self.parseDate()
169 self.host.bridge.eventModify( 159 try:
170 self.args.service, 160 self.host.bridge.eventModify(
171 self.args.node, 161 self.args.service,
172 self.args.id, 162 self.args.node,
173 date, 163 self.args.id,
174 fields, 164 date,
175 self.profile, 165 fields,
176 callback=self.host.quit, 166 self.profile,
177 errback=partial( 167 )
178 self.errback, 168 except Exception as e:
179 msg=_("can't update event data: {}"), 169 self.disp(f"can't update event data: {e}", error=True)
180 exit_code=C.EXIT_BRIDGE_ERRBACK, 170 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
181 ), 171 else:
182 ) 172 self.host.quit()
183 173
184 174
185 class InviteeGet(base.CommandBase): 175 class InviteeGet(base.CommandBase):
186 def __init__(self, host): 176 def __init__(self, host):
187 base.CommandBase.__init__( 177 base.CommandBase.__init__(
188 self, 178 self,
189 host, 179 host,
190 "get", 180 "get",
191 use_output=C.OUTPUT_DICT, 181 use_output=C.OUTPUT_DICT,
192 use_pubsub=True, 182 use_pubsub=True,
193 pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM}, 183 pubsub_flags={C.NODE},
194 use_verbose=True, 184 use_verbose=True,
195 help=_("get event attendance"), 185 help=_("get event attendance"),
196 ) 186 )
197 self.need_loop = True
198 187
199 def add_parser_options(self): 188 def add_parser_options(self):
200 pass 189 self.parser.add_argument(
201 190 "-j", "--jid", default="", help=_("bare jid of the invitee")
202 def eventInviteeGetCb(self, event_data): 191 )
203 self.output(event_data) 192
204 self.host.quit() 193 async def start(self):
205 194 try:
206 def start(self): 195 event_data = await self.host.bridge.eventInviteeGet(
207 self.host.bridge.eventInviteeGet( 196 self.args.service,
208 self.args.service, 197 self.args.node,
209 self.args.node, 198 self.args.jid,
210 self.profile, 199 self.profile,
211 callback=self.eventInviteeGetCb, 200 )
212 errback=partial( 201 except Exception as e:
213 self.errback, 202 self.disp(f"can't get event data: {e}", error=True)
214 msg=_("can't get event data: {}"), 203 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
215 exit_code=C.EXIT_BRIDGE_ERRBACK, 204 else:
216 ), 205 await self.output(event_data)
217 ) 206 self.host.quit()
218 207
219 208
220 class InviteeSet(base.CommandBase): 209 class InviteeSet(base.CommandBase):
221 def __init__(self, host): 210 def __init__(self, host):
222 super(InviteeSet, self).__init__( 211 super(InviteeSet, self).__init__(
225 use_output=C.OUTPUT_DICT, 214 use_output=C.OUTPUT_DICT,
226 use_pubsub=True, 215 use_pubsub=True,
227 pubsub_flags={C.NODE}, 216 pubsub_flags={C.NODE},
228 help=_("set event attendance"), 217 help=_("set event attendance"),
229 ) 218 )
230 self.need_loop = True
231 219
232 def add_parser_options(self): 220 def add_parser_options(self):
233 self.parser.add_argument( 221 self.parser.add_argument(
234 "-f", 222 "-f",
235 "--field", 223 "--field",
238 dest="fields", 226 dest="fields",
239 metavar=("KEY", "VALUE"), 227 metavar=("KEY", "VALUE"),
240 help=_("configuration field to set"), 228 help=_("configuration field to set"),
241 ) 229 )
242 230
243 def start(self): 231 async def start(self):
244 fields = dict(self.args.fields) if self.args.fields else {} 232 fields = dict(self.args.fields) if self.args.fields else {}
245 self.host.bridge.eventInviteeSet( 233 try:
246 self.args.service, 234 self.host.bridge.eventInviteeSet(
247 self.args.node, 235 self.args.service,
248 fields, 236 self.args.node,
249 self.profile, 237 fields,
250 callback=self.host.quit, 238 self.profile,
251 errback=partial( 239 )
252 self.errback, 240 except Exception as e:
253 msg=_("can't set event data: {}"), 241 self.disp(f"can't set event data: {e}", error=True)
254 exit_code=C.EXIT_BRIDGE_ERRBACK, 242 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
255 ), 243 else:
256 ) 244 self.host.quit()
257 245
258 246
259 class InviteesList(base.CommandBase): 247 class InviteesList(base.CommandBase):
260 def __init__(self, host): 248 def __init__(self, host):
261 extra_outputs = {"default": self.default_output} 249 extra_outputs = {"default": self.default_output}
268 use_pubsub=True, 256 use_pubsub=True,
269 pubsub_flags={C.NODE}, 257 pubsub_flags={C.NODE},
270 use_verbose=True, 258 use_verbose=True,
271 help=_("get event attendance"), 259 help=_("get event attendance"),
272 ) 260 )
273 self.need_loop = True
274 261
275 def add_parser_options(self): 262 def add_parser_options(self):
276 self.parser.add_argument( 263 self.parser.add_argument(
277 "-m", 264 "-m",
278 "--missing", 265 "--missing",
397 A.RESET, 384 A.RESET,
398 str(attendees_missing), 385 str(attendees_missing),
399 ) 386 )
400 ) 387 )
401 388
402 def eventInviteesListCb(self, event_data, prefilled_data): 389 async def start(self):
403 """fill nicknames and keep only requested people 390 if self.args.no_rsvp and not self.args.missing:
404 391 self.parser.error(_("you need to use --missing if you use --no-rsvp"))
405 @param event_data(dict): R.S.V.P. answers 392 if not self.args.missing:
406 @param prefilled_data(dict): prefilled data with all people 393 prefilled = {}
407 only filled if --missing is used 394 else:
408 """ 395 # we get prefilled data with all people
396 try:
397 affiliations = await self.host.bridge.psNodeAffiliationsGet(
398 self.args.service,
399 self.args.node,
400 self.profile,
401 )
402 except Exception as e:
403 self.disp(f"can't get node affiliations: {e}", error=True)
404 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
405 else:
406 # we fill all affiliations with empty data, answered one will be filled
407 # below. We only consider people with "publisher" affiliation as invited,
408 # creators are not, and members can just observe
409 prefilled = {
410 jid_: {}
411 for jid_, affiliation in affiliations.items()
412 if affiliation in ("publisher",)
413 }
414
415 try:
416 event_data = await self.host.bridge.eventInviteesList(
417 self.args.service,
418 self.args.node,
419 self.profile,
420 )
421 except Exception as e:
422 self.disp(f"can't get event data: {e}", error=True)
423 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
424
425 # we fill nicknames and keep only requested people
426
409 if self.args.no_rsvp: 427 if self.args.no_rsvp:
410 for jid_ in event_data: 428 for jid_ in event_data:
411 # if there is a jid in event_data 429 # if there is a jid in event_data it must be there in prefilled too
412 # it must be there in prefilled_data too 430 # otherwie somebody is not on the invitees list
413 # so no need to check for KeyError 431 try:
414 del prefilled_data[jid_] 432 del prefilled[jid_]
433 except KeyError:
434 self.disp(A.color(
435 C.A_WARNING,
436 f"We got a RSVP from somebody who was not in invitees "
437 f"list: {jid_}"
438 ),
439 error=True)
415 else: 440 else:
416 # we replace empty dicts for existing people with R.S.V.P. data 441 # we replace empty dicts for existing people with R.S.V.P. data
417 prefilled_data.update(event_data) 442 prefilled.update(event_data)
418 443
419 # we get nicknames for everybody, make it easier for organisers 444 # we get nicknames for everybody, make it easier for organisers
420 for jid_, data in prefilled_data.items(): 445 for jid_, data in prefilled.items():
421 id_data = self.host.bridge.identityGet(jid_, self.profile) 446 id_data = await self.host.bridge.identityGet(jid_, self.profile)
422 data["nick"] = id_data.get("nick", "") 447 data["nick"] = id_data.get("nick", "")
423 448
424 self.output(prefilled_data) 449 await self.output(prefilled)
425 self.host.quit() 450 self.host.quit()
426
427 def getList(self, prefilled_data={}):
428 self.host.bridge.eventInviteesList(
429 self.args.service,
430 self.args.node,
431 self.profile,
432 callback=partial(self.eventInviteesListCb, prefilled_data=prefilled_data),
433 errback=partial(
434 self.errback,
435 msg=_("can't get event data: {}"),
436 exit_code=C.EXIT_BRIDGE_ERRBACK,
437 ),
438 )
439
440 def psNodeAffiliationsGetCb(self, affiliations):
441 # we fill all affiliations with empty data
442 # answered one will be filled in eventInviteesListCb
443 # we only consider people with "publisher" affiliation as invited, creators are not, and members can just observe
444 prefilled = {
445 jid_: {}
446 for jid_, affiliation in affiliations.items()
447 if affiliation in ("publisher",)
448 }
449 self.getList(prefilled)
450
451 def start(self):
452 if self.args.no_rsvp and not self.args.missing:
453 self.parser.error(_("you need to use --missing if you use --no-rsvp"))
454 if self.args.missing:
455 self.host.bridge.psNodeAffiliationsGet(
456 self.args.service,
457 self.args.node,
458 self.profile,
459 callback=self.psNodeAffiliationsGetCb,
460 errback=partial(
461 self.errback,
462 msg=_("can't get event data: {}"),
463 exit_code=C.EXIT_BRIDGE_ERRBACK,
464 ),
465 )
466 else:
467 self.getList()
468 451
469 452
470 class InviteeInvite(base.CommandBase): 453 class InviteeInvite(base.CommandBase):
471 def __init__(self, host): 454 def __init__(self, host):
472 base.CommandBase.__init__( 455 base.CommandBase.__init__(
475 "invite", 458 "invite",
476 use_pubsub=True, 459 use_pubsub=True,
477 pubsub_flags={C.NODE, C.SINGLE_ITEM}, 460 pubsub_flags={C.NODE, C.SINGLE_ITEM},
478 help=_("invite someone to the event through email"), 461 help=_("invite someone to the event through email"),
479 ) 462 )
480 self.need_loop = True
481 463
482 def add_parser_options(self): 464 def add_parser_options(self):
483 self.parser.add_argument( 465 self.parser.add_argument(
484 "-e", 466 "-e",
485 "--email", 467 "--email",
522 "--body", 504 "--body",
523 default="", 505 default="",
524 help="body of the invitation email (default: generic body)", 506 help="body of the invitation email (default: generic body)",
525 ) 507 )
526 508
527 def start(self): 509 async def start(self):
528 email = self.args.email[0] if self.args.email else None 510 email = self.args.email[0] if self.args.email else None
529 emails_extra = self.args.email[1:] 511 emails_extra = self.args.email[1:]
530 512
531 self.host.bridge.eventInviteByEmail( 513 try:
532 self.args.service, 514 await self.host.bridge.eventInviteByEmail(
533 self.args.node, 515 self.args.service,
534 self.args.item, 516 self.args.node,
535 email, 517 self.args.item,
536 emails_extra, 518 email,
537 self.args.name, 519 emails_extra,
538 self.args.host_name, 520 self.args.name,
539 self.args.lang, 521 self.args.host_name,
540 self.args.url_template, 522 self.args.lang,
541 self.args.subject, 523 self.args.url_template,
542 self.args.body, 524 self.args.subject,
543 self.args.profile, 525 self.args.body,
544 callback=self.host.quit, 526 self.args.profile,
545 errback=partial( 527 )
546 self.errback, 528 except Exception as e:
547 msg=_("can't create invitation: {}"), 529 self.disp(f"can't create invitation: {e}", error=True)
548 exit_code=C.EXIT_BRIDGE_ERRBACK, 530 self.host.quit(C.EXIT_BRIDGE_ERRBACK)
549 ), 531 else:
550 ) 532 self.host.quit()
551 533
552 534
553 class Invitee(base.CommandBase): 535 class Invitee(base.CommandBase):
554 subcommands = (InviteeGet, InviteeSet, InviteesList, InviteeInvite) 536 subcommands = (InviteeGet, InviteeSet, InviteesList, InviteeInvite)
555 537