Mercurial > libervia-desktop-kivy
comparison src/cagou/core/cagou_main.py @ 86:c711be670ecd
core, chat: upload plugin system:
- extented plugin system so it's not only used with main widget. It is also used for upload widgets and can be extended more
- plugin file name is used to detect the type: plugin_wid_* for main widgets, plugin_upload_* for upload widget plugins
- a new UploadMenu class allows to easily add an upload button which will use loaded plugins
- plugin_info can now specify a list of allowed platforms in "platforms" key
- file upload in chat has been moved to a plugin
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 25 Dec 2016 16:41:21 +0100 |
parents | c2a7234d13d2 |
children | 17094a075fd2 |
comparison
equal
deleted
inserted
replaced
85:c2a7234d13d2 | 86:c711be670ecd |
---|---|
54 from kivy.uix.screenmanager import ScreenManager, Screen, FallOutTransition, RiseInTransition | 54 from kivy.uix.screenmanager import ScreenManager, Screen, FallOutTransition, RiseInTransition |
55 from kivy.uix.dropdown import DropDown | 55 from kivy.uix.dropdown import DropDown |
56 from cagou_widget import CagouWidget | 56 from cagou_widget import CagouWidget |
57 from . import widgets_handler | 57 from . import widgets_handler |
58 from .common import IconButton | 58 from .common import IconButton |
59 from .menu import MenusWidget | 59 from . import menu |
60 from importlib import import_module | 60 from importlib import import_module |
61 import os.path | 61 import os.path |
62 import glob | 62 import glob |
63 import cagou.plugins | 63 import cagou.plugins |
64 import cagou.kv | 64 import cagou.kv |
150 else: | 150 else: |
151 screen.add_widget(note) | 151 screen.add_widget(note) |
152 self.manager.switch_to(screen) | 152 self.manager.switch_to(screen) |
153 | 153 |
154 | 154 |
155 class RootMenus(MenusWidget): | 155 class RootMenus(menu.MenusWidget): |
156 pass | 156 pass |
157 | 157 |
158 | 158 |
159 class RootBody(BoxLayout): | 159 class RootBody(BoxLayout): |
160 pass | 160 pass |
290 self._import_kv() | 290 self._import_kv() |
291 self.app = CagouApp() | 291 self.app = CagouApp() |
292 self.app.host = self | 292 self.app.host = self |
293 self.media_dir = self.app.media_dir = config.getConfig(main_config, '', 'media_dir') | 293 self.media_dir = self.app.media_dir = config.getConfig(main_config, '', 'media_dir') |
294 self.app.default_avatar = os.path.join(self.media_dir, "misc/default_avatar.png") | 294 self.app.default_avatar = os.path.join(self.media_dir, "misc/default_avatar.png") |
295 self._plg_wids = [] # widget plugins | 295 self._plg_wids = [] # main widgets plugins |
296 self._plg_wids_upload = [] # upload widgets plugins | |
296 self._import_plugins() | 297 self._import_plugins() |
297 self._visible_widgets = {} # visible widgets by classes | 298 self._visible_widgets = {} # visible widgets by classes |
298 | 299 |
299 @property | 300 @property |
300 def visible_widgets(self): | 301 def visible_widgets(self): |
341 self.app.root_window.softinput_mode = "below_target" | 342 self.app.root_window.softinput_mode = "below_target" |
342 profile_manager = self.app._profile_manager | 343 profile_manager = self.app._profile_manager |
343 del self.app._profile_manager | 344 del self.app._profile_manager |
344 super(Cagou, self).postInit(profile_manager) | 345 super(Cagou, self).postInit(profile_manager) |
345 | 346 |
346 def _defaultFactory(self, plugin_info, target, profiles): | 347 def _defaultFactoryMain(self, plugin_info, target, profiles): |
347 """factory used to create widget instance when PLUGIN_INFO["factory"] is not set""" | 348 """default factory used to create main widgets instances |
349 | |
350 used when PLUGIN_INFO["factory"] is not set | |
351 @param plugin_info(dict): plugin datas | |
352 @param target: QuickWidget target | |
353 @param profiles(iterable): list of profiles | |
354 """ | |
348 main_cls = plugin_info['main'] | 355 main_cls = plugin_info['main'] |
349 return self.widgets.getOrCreateWidget(main_cls, target, on_new_widget=None, profiles=iter(self.profiles)) | 356 return self.widgets.getOrCreateWidget(main_cls, target, on_new_widget=None, profiles=iter(self.profiles)) |
357 | |
358 def _defaultFactoryUpload(self, plugin_info, callback, cancel_cb, profiles): | |
359 """default factory used to create upload widgets instances | |
360 | |
361 @param plugin_info(dict): plugin datas | |
362 @param callback(callable): method to call with path to file to upload | |
363 @param cancel_cb(callable): call when upload is cancelled | |
364 upload widget must be used as first argument | |
365 @param profiles(iterable): list of profiles | |
366 None if not specified | |
367 """ | |
368 main_cls = plugin_info['main'] | |
369 return main_cls(callback=callback, cancel_cb=cancel_cb) | |
350 | 370 |
351 ## plugins & kv import ## | 371 ## plugins & kv import ## |
352 | 372 |
353 def _import_kv(self): | 373 def _import_kv(self): |
354 """import all kv files in cagou.kv""" | 374 """import all kv files in cagou.kv""" |
367 else: | 387 else: |
368 plugins_path = os.path.dirname(cagou.plugins.__file__) | 388 plugins_path = os.path.dirname(cagou.plugins.__file__) |
369 plugin_glob = "plugin*.py" | 389 plugin_glob = "plugin*.py" |
370 plug_lst = [os.path.splitext(p)[0] for p in map(os.path.basename, glob.glob(os.path.join(plugins_path, plugin_glob)))] | 390 plug_lst = [os.path.splitext(p)[0] for p in map(os.path.basename, glob.glob(os.path.join(plugins_path, plugin_glob)))] |
371 | 391 |
372 imported_names = set() # use to avoid loading 2 times plugin with same import name | 392 imported_names_main = set() # used to avoid loading 2 times plugin with same import name |
393 imported_names_upload = set() | |
373 for plug in plug_lst: | 394 for plug in plug_lst: |
374 plugin_path = 'cagou.plugins.' + plug | 395 plugin_path = 'cagou.plugins.' + plug |
396 | |
397 # we get type from plugin name | |
398 suff = plug[7:] | |
399 if u'_' not in suff: | |
400 log.error(u"invalid plugin name: {}, skipping".format(plug)) | |
401 continue | |
402 plugin_type = suff[:suff.find(u'_')] | |
403 | |
404 # and select the variable to use according to type | |
405 if plugin_type == C.PLUG_TYPE_WID: | |
406 imported_names = imported_names_main | |
407 default_factory = self._defaultFactoryMain | |
408 elif plugin_type == C.PLUG_TYPE_UPLOAD: | |
409 imported_names = imported_names_upload | |
410 default_factory = self._defaultFactoryUpload | |
411 else: | |
412 log.error(u"unknown plugin type {type_} for plugin {file_}, skipping".format( | |
413 type_ = plugin_type, | |
414 file_ = plug | |
415 )) | |
416 continue | |
417 plugins_set = self._getPluginsSet(plugin_type) | |
418 | |
375 mod = import_module(plugin_path) | 419 mod = import_module(plugin_path) |
376 try: | 420 try: |
377 plugin_info = mod.PLUGIN_INFO | 421 plugin_info = mod.PLUGIN_INFO |
378 except AttributeError: | 422 except AttributeError: |
379 plugin_info = {} | 423 plugin_info = {} |
380 | 424 |
425 plugin_info['plugin_file'] = plug | |
426 plugin_info['plugin_type'] = plugin_type | |
427 | |
428 if 'platforms' in plugin_info: | |
429 if sys.platform not in plugin_info['platforms']: | |
430 log.info(u"{plugin_file} is not used on this platform, skipping".format(**plugin_info)) | |
431 continue | |
432 | |
381 # import name is used to differentiate plugins | 433 # import name is used to differentiate plugins |
382 if 'import_name' not in plugin_info: | 434 if 'import_name' not in plugin_info: |
383 plugin_info['import_name'] = plug | 435 plugin_info['import_name'] = plug |
384 if 'import_name' in imported_names: | 436 if plugin_info['import_name'] in imported_names: |
385 log.warning(_(u"there is already a plugin named {}, ignoring new one").format(plugin_info['import_name'])) | 437 log.warning(_(u"there is already a plugin named {}, ignoring new one").format(plugin_info['import_name'])) |
386 continue | 438 continue |
387 if plugin_info['import_name'] == C.WID_SELECTOR: | 439 if plugin_info['import_name'] == C.WID_SELECTOR: |
440 if plugin_type != C.PLUG_TYPE_WID: | |
441 log.error(u"{import_name} import name can only be used with {type_} type, skipping {name}".format(type_=C.PLUG_TYPE_WID, **plugin_info)) | |
442 continue | |
388 # if WidgetSelector exists, it will be our default widget | 443 # if WidgetSelector exists, it will be our default widget |
389 self.default_wid = plugin_info | 444 self.default_wid = plugin_info |
390 | 445 |
391 # we want everything optional, so we use plugin file name | 446 # we want everything optional, so we use plugin file name |
392 # if actual name is not found | 447 # if actual name is not found |
393 if 'name' not in plugin_info: | 448 if 'name' not in plugin_info: |
394 plugin_info['name'] = plug[plug.rfind('_')+1:] | 449 name_start = 8 + len(plugin_type) |
450 plugin_info['name'] = plug[name_start:] | |
395 | 451 |
396 # we need to load the kv file | 452 # we need to load the kv file |
397 if 'kv_file' not in plugin_info: | 453 if 'kv_file' not in plugin_info: |
398 plugin_info['kv_file'] = u'{}.kv'.format(plug) | 454 plugin_info['kv_file'] = u'{}.kv'.format(plug) |
399 kv_path = os.path.join(plugins_path, plugin_info['kv_file']) | 455 kv_path = os.path.join(plugins_path, plugin_info['kv_file']) |
404 plugin_info['main'] = main_cls | 460 plugin_info['main'] = main_cls |
405 | 461 |
406 # factory is used to create the instance | 462 # factory is used to create the instance |
407 # if not found, we use a defaut one with getOrCreateWidget | 463 # if not found, we use a defaut one with getOrCreateWidget |
408 if 'factory' not in plugin_info: | 464 if 'factory' not in plugin_info: |
409 plugin_info['factory'] = self._defaultFactory | 465 plugin_info['factory'] = default_factory |
410 | 466 |
411 # icons | 467 # icons |
412 for size in ('small', 'medium'): | 468 for size in ('small', 'medium'): |
413 key = u'icon_{}'.format(size) | 469 key = u'icon_{}'.format(size) |
414 try: | 470 try: |
419 path = path.format(media=self.media_dir) | 475 path = path.format(media=self.media_dir) |
420 if not os.path.isfile(path): | 476 if not os.path.isfile(path): |
421 path = C.DEFAULT_WIDGET_ICON.format(media=self.media_dir) | 477 path = C.DEFAULT_WIDGET_ICON.format(media=self.media_dir) |
422 plugin_info[key] = path | 478 plugin_info[key] = path |
423 | 479 |
424 self._plg_wids.append(plugin_info) | 480 plugins_set.append(plugin_info) |
425 if not self._plg_wids: | 481 if not self._plg_wids: |
426 log.error(_(u"no widget plugin found")) | 482 log.error(_(u"no widget plugin found")) |
427 return | 483 return |
428 | 484 |
429 # we want widgets sorted by names | 485 # we want widgets sorted by names |
430 self._plg_wids.sort(key=lambda p: p['name'].lower()) | 486 self._plg_wids.sort(key=lambda p: p['name'].lower()) |
487 self._plg_wids_upload.sort(key=lambda p: p['name'].lower()) | |
431 | 488 |
432 if self.default_wid is None: | 489 if self.default_wid is None: |
433 # we have no selector widget, we use the first widget as default | 490 # we have no selector widget, we use the first widget as default |
434 self.default_wid = self._plg_wids[0] | 491 self.default_wid = self._plg_wids[0] |
435 | 492 |
436 def getPluggedWidgets(self, except_cls=None): | 493 def _getPluginsSet(self, type_): |
494 if type_ == C.PLUG_TYPE_WID: | |
495 return self._plg_wids | |
496 elif type_ == C.PLUG_TYPE_UPLOAD: | |
497 return self._plg_wids_upload | |
498 else: | |
499 raise KeyError(u"{} plugin type is unknown".format(type_)) | |
500 | |
501 def getPluggedWidgets(self, type_=C.PLUG_TYPE_WID, except_cls=None): | |
437 """get available widgets plugin infos | 502 """get available widgets plugin infos |
438 | 503 |
504 @param type_(unicode): type of widgets to get | |
505 one of C.PLUG_TYPE_* constant | |
439 @param except_cls(None, class): if not None, | 506 @param except_cls(None, class): if not None, |
440 widgets from this class will be excluded | 507 widgets from this class will be excluded |
441 @return (iter[dict]): available widgets plugin infos | 508 @return (iter[dict]): available widgets plugin infos |
442 """ | 509 """ |
443 for plugin_data in self._plg_wids: | 510 plugins_set = self._getPluginsSet(type_) |
511 for plugin_data in plugins_set: | |
444 if plugin_data['main'] == except_cls: | 512 if plugin_data['main'] == except_cls: |
445 continue | 513 continue |
446 yield plugin_data | 514 yield plugin_data |
447 | 515 |
448 def getPluginInfo(self, **kwargs): | 516 def getPluginInfo(self, type_=C.PLUG_TYPE_WID, **kwargs): |
449 """get first plugin info corresponding to filters | 517 """get first plugin info corresponding to filters |
450 | 518 |
519 @param type_(unicode): type of widgets to get | |
520 one of C.PLUG_TYPE_* constant | |
451 @param **kwargs: filter(s) to use, each key present here must also | 521 @param **kwargs: filter(s) to use, each key present here must also |
452 exist and be of the same value in requested plugin info | 522 exist and be of the same value in requested plugin info |
453 @return (dict, None): found plugin info or None | 523 @return (dict, None): found plugin info or None |
454 """ | 524 """ |
455 for plugin_info in self._plg_wids: | 525 plugins_set = self._getPluginsSet(type_) |
526 for plugin_info in plugins_set: | |
456 for k, w in kwargs.iteritems(): | 527 for k, w in kwargs.iteritems(): |
457 try: | 528 try: |
458 if plugin_info[k] != w: | 529 if plugin_info[k] != w: |
459 continue | 530 continue |
460 except KeyError: | 531 except KeyError: |