diff 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
line wrap: on
line diff
--- a/src/cagou/core/cagou_main.py	Sat Dec 24 14:20:49 2016 +0100
+++ b/src/cagou/core/cagou_main.py	Sun Dec 25 16:41:21 2016 +0100
@@ -56,7 +56,7 @@
 from cagou_widget import CagouWidget
 from . import widgets_handler
 from .common import IconButton
-from .menu import MenusWidget
+from . import menu
 from importlib import import_module
 import os.path
 import glob
@@ -152,7 +152,7 @@
         self.manager.switch_to(screen)
 
 
-class RootMenus(MenusWidget):
+class RootMenus(menu.MenusWidget):
     pass
 
 
@@ -292,7 +292,8 @@
         self.app.host = self
         self.media_dir = self.app.media_dir = config.getConfig(main_config, '', 'media_dir')
         self.app.default_avatar = os.path.join(self.media_dir, "misc/default_avatar.png")
-        self._plg_wids = []  # widget plugins
+        self._plg_wids = []  # main widgets plugins
+        self._plg_wids_upload = []  # upload widgets plugins
         self._import_plugins()
         self._visible_widgets = {}  # visible widgets by classes
 
@@ -343,11 +344,30 @@
         del self.app._profile_manager
         super(Cagou, self).postInit(profile_manager)
 
-    def _defaultFactory(self, plugin_info, target, profiles):
-        """factory used to create widget instance when PLUGIN_INFO["factory"] is not set"""
+    def _defaultFactoryMain(self, plugin_info, target, profiles):
+        """default factory used to create main widgets instances
+
+        used when PLUGIN_INFO["factory"] is not set
+        @param plugin_info(dict): plugin datas
+        @param target: QuickWidget target
+        @param profiles(iterable): list of profiles
+        """
         main_cls = plugin_info['main']
         return self.widgets.getOrCreateWidget(main_cls, target, on_new_widget=None, profiles=iter(self.profiles))
 
+    def _defaultFactoryUpload(self, plugin_info, callback, cancel_cb, profiles):
+        """default factory used to create upload widgets instances
+
+        @param plugin_info(dict): plugin datas
+        @param callback(callable): method to call with path to file to upload
+        @param cancel_cb(callable): call when upload is cancelled
+            upload widget must be used as first argument
+        @param profiles(iterable): list of profiles
+            None if not specified
+        """
+        main_cls = plugin_info['main']
+        return main_cls(callback=callback, cancel_cb=cancel_cb)
+
     ## plugins & kv import ##
 
     def _import_kv(self):
@@ -369,29 +389,65 @@
             plugin_glob = "plugin*.py"
         plug_lst = [os.path.splitext(p)[0] for p in map(os.path.basename, glob.glob(os.path.join(plugins_path, plugin_glob)))]
 
-        imported_names = set()  # use to avoid loading 2 times plugin with same import name
+        imported_names_main = set()  # used to avoid loading 2 times plugin with same import name
+        imported_names_upload = set()
         for plug in plug_lst:
             plugin_path = 'cagou.plugins.' + plug
+
+            # we get type from plugin name
+            suff = plug[7:]
+            if u'_' not in suff:
+                log.error(u"invalid plugin name: {}, skipping".format(plug))
+                continue
+            plugin_type = suff[:suff.find(u'_')]
+
+            # and select the variable to use according to type
+            if plugin_type == C.PLUG_TYPE_WID:
+                imported_names = imported_names_main
+                default_factory = self._defaultFactoryMain
+            elif plugin_type == C.PLUG_TYPE_UPLOAD:
+                imported_names = imported_names_upload
+                default_factory = self._defaultFactoryUpload
+            else:
+                log.error(u"unknown plugin type {type_} for plugin {file_}, skipping".format(
+                    type_ = plugin_type,
+                    file_ = plug
+                    ))
+                continue
+            plugins_set = self._getPluginsSet(plugin_type)
+
             mod = import_module(plugin_path)
             try:
                 plugin_info = mod.PLUGIN_INFO
             except AttributeError:
                 plugin_info = {}
 
+            plugin_info['plugin_file'] = plug
+            plugin_info['plugin_type'] = plugin_type
+
+            if 'platforms' in plugin_info:
+                if sys.platform not in plugin_info['platforms']:
+                    log.info(u"{plugin_file} is not used on this platform, skipping".format(**plugin_info))
+                    continue
+
             # import name is used to differentiate plugins
             if 'import_name' not in plugin_info:
                 plugin_info['import_name'] = plug
-            if 'import_name' in imported_names:
+            if plugin_info['import_name'] in imported_names:
                 log.warning(_(u"there is already a plugin named {}, ignoring new one").format(plugin_info['import_name']))
                 continue
             if plugin_info['import_name'] == C.WID_SELECTOR:
+                if plugin_type != C.PLUG_TYPE_WID:
+                    log.error(u"{import_name} import name can only be used with {type_} type, skipping {name}".format(type_=C.PLUG_TYPE_WID, **plugin_info))
+                    continue
                 # if WidgetSelector exists, it will be our default widget
                 self.default_wid = plugin_info
 
             # we want everything optional, so we use plugin file name
             # if actual name is not found
             if 'name' not in plugin_info:
-                plugin_info['name'] = plug[plug.rfind('_')+1:]
+                name_start = 8 + len(plugin_type)
+                plugin_info['name'] = plug[name_start:]
 
             # we need to load the kv file
             if 'kv_file' not in plugin_info:
@@ -406,7 +462,7 @@
             # factory is used to create the instance
             # if not found, we use a defaut one with getOrCreateWidget
             if 'factory' not in plugin_info:
-                plugin_info['factory'] = self._defaultFactory
+                plugin_info['factory'] = default_factory
 
             # icons
             for size in ('small', 'medium'):
@@ -421,38 +477,53 @@
                         path = C.DEFAULT_WIDGET_ICON.format(media=self.media_dir)
                 plugin_info[key] = path
 
-            self._plg_wids.append(plugin_info)
+            plugins_set.append(plugin_info)
         if not self._plg_wids:
             log.error(_(u"no widget plugin found"))
             return
 
         # we want widgets sorted by names
         self._plg_wids.sort(key=lambda p: p['name'].lower())
+        self._plg_wids_upload.sort(key=lambda p: p['name'].lower())
 
         if self.default_wid is None:
             # we have no selector widget, we use the first widget as default
             self.default_wid = self._plg_wids[0]
 
-    def getPluggedWidgets(self, except_cls=None):
+    def _getPluginsSet(self, type_):
+        if type_ == C.PLUG_TYPE_WID:
+            return self._plg_wids
+        elif type_ == C.PLUG_TYPE_UPLOAD:
+            return self._plg_wids_upload
+        else:
+            raise KeyError(u"{} plugin type is unknown".format(type_))
+
+    def getPluggedWidgets(self, type_=C.PLUG_TYPE_WID, except_cls=None):
         """get available widgets plugin infos
 
+        @param type_(unicode): type of widgets to get
+            one of C.PLUG_TYPE_* constant
         @param except_cls(None, class): if not None,
             widgets from this class will be excluded
         @return (iter[dict]): available widgets plugin infos
         """
-        for plugin_data in self._plg_wids:
+        plugins_set = self._getPluginsSet(type_)
+        for plugin_data in plugins_set:
             if plugin_data['main'] == except_cls:
                 continue
             yield plugin_data
 
-    def getPluginInfo(self, **kwargs):
+    def getPluginInfo(self, type_=C.PLUG_TYPE_WID, **kwargs):
         """get first plugin info corresponding to filters
 
+        @param type_(unicode): type of widgets to get
+            one of C.PLUG_TYPE_* constant
         @param **kwargs: filter(s) to use, each key present here must also
             exist and be of the same value in requested plugin info
         @return (dict, None): found plugin info or None
         """
-        for plugin_info in self._plg_wids:
+        plugins_set = self._getPluginsSet(type_)
+        for plugin_info in plugins_set:
             for k, w in kwargs.iteritems():
                 try:
                     if plugin_info[k] != w: