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: