Browse Source

Merge remote-tracking branch 'origin/master'

Quentin Bonaventure quentin.bonaventure@openedition.org 8 years ago
parent
commit
5055b2a6ef
2 changed files with 329 additions and 129 deletions
  1. 257
    24
      lodel/plugin/core_scripts.py
  2. 72
    105
      lodel/plugin/plugins.py

+ 257
- 24
lodel/plugin/core_scripts.py View File

@@ -1,6 +1,11 @@
1
+import operator
2
+import shutil
3
+import tempfile
4
+import os, os.path
1 5
 from lodel.context import LodelContext
2 6
 LodelContext.expose_modules(globals(), {
3
-    'lodel.plugin.scripts': 'lodel_script'})
7
+    'lodel.plugin.scripts': 'lodel_script',
8
+    'lodel.logger': 'logger'})
4 9
 
5 10
 ##@package lodel.plugin.core_scripts
6 11
 #@brief Lodel2 internal scripts declaration
@@ -8,38 +13,266 @@ LodelContext.expose_modules(globals(), {
8 13
 #@ingroup lodel2_script
9 14
 
10 15
 
11
-##@brief Implements lodel_admin.py **discover-plugin** action
16
+##@brief Implements lodel_admin.py list-plugins action
12 17
 #@ingroup lodel2_plugins
13 18
 #@ingroup lodel2_script
14 19
 #
15
-#In depth directory scan to find plugins in order to build a plugin list.
16
-class DiscoverPlugin(lodel_script.LodelScript):
17
-    _action = 'discover-plugin'
18
-    _description = 'Walk through given folders looking for plugins'
19
-    
20
+class ListPlugins(lodel_script.LodelScript):
21
+    _action = 'plugins-list'
22
+    _description = "List all installed plugins"
23
+
24
+    @classmethod
25
+    def argparser_config(cls, parser):
26
+        parser.add_argument('-v', '--verbose',
27
+            help="Display more informations on installed plugins",
28
+            action='store_true')
29
+        parser.add_argument('-c', '--csv',
30
+            help="Format output in CSV format",
31
+            action='store_true')
32
+
33
+    @classmethod
34
+    def run(cls, args):
35
+        import lodel.plugin.plugins
36
+        from lodel.plugin.plugins import Plugin
37
+        if args.verbose:
38
+            #_discover do not returns duplicated names
39
+            tmp_plist = Plugin._discover(lodel.plugin.plugins.PLUGINS_PATH)
40
+            plist = []
41
+            #ordering the list by plugin's name
42
+            for pname in sorted(set([d['name'] for d in tmp_plist])):
43
+                for pinfos in tmp_plist:
44
+                    if pinfos['name'] == pname:
45
+                        plist.append(pinfos)
46
+        else:
47
+            pdict = Plugin.discover()
48
+            #casting to a list ordered by names
49
+            plist = []
50
+            for pname in sorted(pdict.keys()):
51
+                plist.append(pdict[pname])
52
+        if args.csv:
53
+            if args.verbose:
54
+                res = "name,version,path\n"
55
+                fmt = "%s,%s,%s\n"
56
+            else:
57
+                res = "name,version\n"
58
+                fmt = "%s,%s\n"
59
+        else:
60
+            res = "Installed plugins list :\n"
61
+            if args.verbose:
62
+                fmt = "\t- %s(%s) in %s\n"
63
+            else:
64
+                fmt = "\t- %s(%s)\n"
65
+        for pinfos in plist:
66
+            if args.verbose:
67
+                res += fmt % (
68
+                    pinfos['name'], pinfos['version'], pinfos['path'])
69
+            else:
70
+                res += fmt % (pinfos['name'], pinfos['version'])
71
+        print(res)
72
+
73
+
74
+##@brief Handle install & uninstall of lodel plugins
75
+class PluginManager(lodel_script.LodelScript):
76
+    _action = 'plugins'
77
+    _description = "Install/Uninstall plugins"
78
+
20 79
     @classmethod
21 80
     def argparser_config(cls, parser):
22
-        #parser.add_argument('-d', '--directory',
23
-        parser.add_argument('PLUGIN_PATH',
24
-            help="Directory to walk through looking for lodel2 plugins",
25
-            nargs='+')
26
-        parser.add_argument('-l', '--list-only', default=False,
27
-            action = 'store_true',
28
-            help="Use this option to print a list of discovered plugins \
29
-without modifying existing cache")
81
+        parser.add_argument('-u', '--uninstall',
82
+            help="Uninstall specified plugin",
83
+            action='store_true')
84
+        parser.add_argument('-c', '--clean',
85
+            help="Uninstall duplicated plugins with smallest version number",
86
+            action="store_true")
87
+        parser.add_argument('-n', '--plugin-name', nargs='*',
88
+            default = list(),
89
+            help="Indicate a plugin name to uninstall",
90
+            type=str)
91
+        parser.add_argument('-a', '--archive', nargs='*',
92
+            default = list(),
93
+            help="(NOT IMPLEMENTED) Specify a tarball containing a plugin \
94
+to install",
95
+            type=str)
96
+        parser.add_argument('-d', '--directory', nargs='*',
97
+            default = list(),
98
+            help="Specify a plugin by its directory",
99
+            type=str)
100
+        parser.add_argument('-f', '--force', action="store_true",
101
+            help="Force plugin directory deletion in case of check errors")
30 102
 
31 103
     @classmethod
32 104
     def run(cls, args):
105
+        if args.clean:
106
+            if args.uninstall or len(args.directory) > 0 \
107
+                    or len(args.archive) > 0:
108
+                raise RuntimeError("When using -c --clean option you can \
109
+only use option -n --name to clean plugins with specified names")
110
+            return cls.clean(args)
111
+        if args.uninstall:
112
+            return cls.uninstall(args)
113
+        return cls.install(args)
114
+    
115
+    ##@brief Handles plugins install
116
+    @classmethod
117
+    def install(cls, args):
118
+        import lodel.plugin.plugins
119
+        from lodel.plugin.plugins import Plugin
120
+        from lodel.plugin.exceptions import PluginError
121
+        if len(args.plugin_name) > 0:
122
+            raise RuntimeError("Unable to install a plugin from its name !\
123
+We do not know where to find it...")
124
+        plist = Plugin.discover()
125
+        errors = dict()
126
+        if len(args.archive) > 0:
127
+            raise NotImplementedError("Not supported yet")
128
+        
129
+        plugins_infos = {}
130
+        for cur_dir in args.directory:
131
+            try:
132
+                res = Plugin.dir_is_plugin(cur_dir, assert_in_package = False)
133
+                if res is False:
134
+                    errors[cur_dir] = PluginError("Not a plugin")
135
+                else:
136
+                    plugins_infos[res['name']] = res
137
+            except Exception as e:
138
+                errors[cur_dir] = e
139
+        #Abording because of previous errors
140
+        if len(errors) > 0:
141
+            msg = "Abording installation because of following errors :\n"
142
+            for path, expt in errors.items():
143
+                msg += ("\t- For path '%s' : %s\n" % (path, expt))
144
+            raise RuntimeError(msg)
145
+        #No errors continuing to install
146
+        for pname, pinfos in plugins_infos.items():
147
+            if pname in plist:
148
+                #Found an installed plugin with the same name
149
+                #Cehcking both versions
150
+                if plist[pname]['version'] == pinfos['version']:
151
+                    errors[pinfos['path']] = 'Abording installation of %s \
152
+found in %s because it seems to be allready installed in %s' % (
153
+    pname, pinfos['path'], plist[pname]['path'])
154
+                    continue
155
+                if plist[pname]['version'] > pinfos['version']:
156
+                    errors[pinfos['path']] = 'Abording installation of %s \
157
+found in %s because the same plugins with a greater version seems to be \
158
+installed in %s' % (pname, pinfos['path'], plist[pname]['path'])
159
+                    continue
160
+                logger.info("Found a plugin with the same name but with an \
161
+inferior version. Continuing to install")
162
+            #Checking that we can safely copy our plugin
163
+            dst_path = os.path.join(lodel.plugin.plugins.PLUGINS_PATH,
164
+                os.path.basename(os.path.dirname(pinfos['path'])))
165
+            orig_path = dst_path
166
+            if os.path.isdir(dst_path):
167
+                dst_path = tempfile.mkdtemp(
168
+                    prefix = os.path.basename(dst_path)+'_',
169
+                    dir = lodel.plugin.plugins.PLUGINS_PATH)
170
+                logger.warning("A plugin allready exists in %s. Installing \
171
+in %s" % (orig_path, dst_path))
172
+                shutil.rmtree(dst_path)
173
+                
174
+            #Installing the plugin
175
+            shutil.copytree(pinfos['path'], dst_path, symlinks = False)
176
+            print("%s(%s) installed in %s" % (
177
+                pname, pinfos['version'], dst_path))
178
+        if len(errors) > 0:
179
+            msg = "Following errors occurs during instalation process :\n"
180
+            for path, error_msg in errors.items():
181
+                msg += "\t- For '%s' : %s" % (path, error_msg)
182
+            print(msg)
183
+    
184
+    ##@brief Handles plugins uninstall
185
+    #@todo uninstall by path is broken
186
+    @classmethod
187
+    def uninstall(cls, args):
188
+        import lodel.plugin.plugins
33 189
         from lodel.plugin.plugins import Plugin
34
-        if args.PLUGIN_PATH is None or len(args.PLUGIN_PATH) == 0:
35
-            cls.help_exit("Specify a least one directory")
36
-        no_cache = args.list_only
37
-        res = Plugin.discover(args.PLUGIN_PATH, no_cache)
38
-        print("Found plugins in : %s" % ', '.join(args.PLUGIN_PATH))
39
-        for pname, pinfos in res['plugins'].items():
40
-            print("\t- %s(%s) in %s" % (
41
-                pname, pinfos['version'], pinfos['path']))
42
-            
190
+        if len(args.archive) > 0:
191
+            raise RuntimeError("Cannot uninstall plugin using -f --file \
192
+oprtions. Use -d --directory instead")
193
+        to_delete = dict() #Path to delete accumulator
194
+        errors = dict()
195
+        if len(args.directory) > 0:
196
+            #processing & checking -d --directory arguments
197
+            for path in args.directory:
198
+                apath = os.path.abspath(path)
199
+                if not apath.startswith(lodel.plugin.plugins.PLUGINS_PATH):
200
+                    errors[path] = "Not a subdir of %s"
201
+                    errors[path] %= lodel.plugin.plugins.PLUGINS_PATH
202
+                    continue
203
+                try:
204
+                    pinfos = Plugin.dir_is_plugin(apath)
205
+                except Exception as e:
206
+                    if not args.force:
207
+                        errors[path] = e
208
+                        continue
209
+                to_delete[path] = pinfos
210
+
211
+        if len(args.plugin_name) > 0:
212
+            #Processing -n --plugin-name arguments
213
+            plist = Plugin._discover(lodel.plugin.plugins.PLUGINS_PATH)
214
+            for pinfos in plist:
215
+                if pinfos['name'] in args.plugin_name:
216
+                    to_delete[pinfos['path']] = pinfos
217
+        
218
+        if len(errors) > 0:
219
+            msg = "Following errors detected before begining deletions :\n"
220
+            for path, errmsg in errors.items():
221
+                msg += "\t- For %s : %s" % (path, errmsg)
222
+            print(msg)
223
+            if not args.force:
224
+                exit(1)
225
+        
226
+        print("Begining deletion :")
227
+        for path, pinfos in to_delete.items():
228
+            #shutil.rmtree(path)
229
+            print("rm -R %s" % path)
230
+            print("\t%s(%s) in %s deleted" % (
231
+                pinfos['name'], pinfos['version'], pinfos['path']))
232
+
233
+    ##@brief Handles plugins clean
234
+    @classmethod
235
+    def clean(cls, args):
236
+        import lodel.plugin.plugins
237
+        from lodel.plugin.plugins import Plugin
238
+        if len(args.archive) > 0:
239
+            raise RuntimeError("Cannot specify plugins to uninstall using \
240
+-f --file option. You have to use -d --directory or -n --name")
241
+        if len(args.plugin_name) > 0:
242
+            names = args.plugin_name
243
+        else:
244
+            names = list(Plugin.discover().keys())
245
+        #_dicover do not remove duplicated names
246
+        full_list = Plugin._discover(lodel.plugin.plugins.PLUGINS_PATH)
247
+        #Casting into a dict with list of plugins infos
248
+        pdict = dict()
249
+        for pinfos in full_list:
250
+            if pinfos['name'] in names:
251
+                if pinfos['name'] in pdict:
252
+                    pdict[pinfos['name']].append(pinfos)
253
+                else:
254
+                    pdict[pinfos['name']] = [pinfos]
255
+        to_clean = list()
256
+        clean_count = 0
257
+        for pname, pinfos_l in pdict.items():
258
+            if len(pinfos_l) > 1:
259
+                #There are some plugins to clean
260
+                tmp_l = sorted(pinfos_l, key=lambda item: item['version'])
261
+                to_clean += tmp_l[:-1]
262
+                msg = "Found %s(%s). Cleaning " % (
263
+                    pname, tmp_l[-1]['version'])
264
+                for pinfos in to_clean:
265
+                    clean_count += 1
266
+                    str_info = '%s(%s)' % (pname, pinfos['version'])
267
+                    msg += "%s, " % (str_info)
268
+                    shutil.rmtree(pinfos['path'])
269
+                print(msg)
270
+        if clean_count > 0:
271
+            print("%d plugins were uninstalled" % clean_count)
272
+        else:
273
+            print("Allready clean")
274
+
275
+
43 276
 ##@brief Implements lodel_admin.py **hooks-list** action
44 277
 #@ingroup lodel2_script
45 278
 #@ingroup lodel2_hooks

+ 72
- 105
lodel/plugin/plugins.py View File

@@ -25,6 +25,16 @@ LodelContext.expose_modules(globals(), {
25 25
 # - {{__init__.py}}} containing informations like full_name, authors, licence etc.
26 26
 # - main.py containing hooks registration etc
27 27
 # - confspec.py containing a configuration specification dictionary named CONFSPEC
28
+#
29
+# All plugins are expected to be found in multiple locations :
30
+# - in the lodel package (lodel.plugins)
31
+# - in the context directorie in a plugins/ dir (symlink to lodel.plugins) <-
32
+#this is obsolete now, since we enforce ALL plugins to be in the lodel package
33
+#
34
+#@todo Check if the symlink in the lodelcontext dir is obsolete !!!
35
+#@warning The plugins dir is at two locations : in lodel package and in
36
+#instance directory. Some stuff seems to still needs plugins to be in 
37
+#the instance directory but it seems to be a really bad idea...
28 38
 
29 39
 ##@defgroup plugin_init_specs Plugins __init__.py specifications
30 40
 #@ingroup lodel2_plugins
@@ -52,10 +62,8 @@ LOADER_FILENAME_VARNAME = '__loader__'
52 62
 PLUGIN_DEPS_VARNAME = '__plugin_deps__'
53 63
 ##@brief Name of the optionnal activate method
54 64
 ACTIVATE_METHOD_NAME = '_activate'
55
-##@brief Discover stage cache filename
56
-DISCOVER_CACHE_FILENAME = '.plugin_discover_cache.json'
57 65
 ##@brief Default & failover value for plugins path list
58
-DEFAULT_PLUGINS_PATH_LIST = [os.path.join(LodelContext.context_dir(),'plugins')]
66
+PLUGINS_PATH = os.path.join(LodelContext.context_dir(),'plugins')
59 67
 
60 68
 ##@brief List storing the mandatory variables expected in a plugin __init__.py
61 69
 #file
@@ -87,11 +95,19 @@ class PluginVersion(object):
87 95
         if len(args) == 1:
88 96
             arg = args[0]
89 97
             if isinstance(arg, str):
98
+                #Casting from string to version numbers
90 99
                 spl = arg.split('.')
91 100
                 invalid = False
92 101
                 if len(spl) > 3:
93 102
                     raise PluginError("The string '%s' is not a valid plugin \
94 103
 version number" % arg)
104
+                if len(spl) < 3:
105
+                    spl += [ 0 for _ in range(3-len(spl))]
106
+                try:
107
+                    self.__version = [int(s) for s in spl]
108
+                except (ValueError, TypeError):
109
+                    raise PluginError("The string '%s' is not a valid lodel2 \
110
+plugin version number" % arg)
95 111
             else:
96 112
                 try:
97 113
                     if len(arg) >= 1:
@@ -99,8 +115,8 @@ version number" % arg)
99 115
                             raise PluginError("Expected maximum 3 value to \
100 116
 create a plugin version number but found '%s' as argument" % arg)
101 117
                         for i, v in enumerate(arg):
102
-                            self.__version[i] = arg[i]
103
-                except TypeError:
118
+                            self.__version[i] = int(arg[i])
119
+                except (TypeError, ValueError):
104 120
                     raise PluginError("Unable to convert argument into plugin \
105 121
 version number" % arg)
106 122
         elif len(args) > 3:
@@ -108,8 +124,8 @@ version number" % arg)
108 124
 but %d arguments found" % len(args))
109 125
         else: 
110 126
             for i,v in enumerate(args):
111
-                self.__version[i] = v
112
-    
127
+                self.__version[i] = int(v)
128
+
113 129
     ##@brief Property to access major version number
114 130
     @property
115 131
     def major(self):
@@ -149,8 +165,11 @@ a PluginVerison instance" % other)
149 165
             raise LodelFatalError("Invalid comparison callback given \
150 166
 to generic PluginVersion comparison function : '%s'" % cmp_fun_name)
151 167
         for property_name in self.PROPERTY_LIST:
152
-            if not cmpfun(getattr(self, pname), getattr(other, pname)):
153
-                return False
168
+            if not cmpfun(
169
+                    getattr(self, property_name),
170
+                    getattr(other, property_name)):
171
+                if property_name == self.PROPERTY_LIST[-1]:
172
+                    return False
154 173
         return True
155 174
 
156 175
     def __lt__(self, other):
@@ -175,7 +194,7 @@ to generic PluginVersion comparison function : '%s'" % cmp_fun_name)
175 194
         return '%d.%d.%d' % tuple(self.__version)
176 195
 
177 196
     def __repr__(self):
178
-        return {'major': self.major, 'minor': self.minor,
197
+        return "%s" % {'major': self.major, 'minor': self.minor,
179 198
             'revision': self.revision}
180 199
 
181 200
 ##@brief Plugin metaclass that allows to "catch" child class declaration
@@ -240,9 +259,6 @@ class MetaPlugType(type):
240 259
 # 3. the loader call load_all to register hooks etc
241 260
 class Plugin(object, metaclass=MetaPlugType):
242 261
     
243
-    ##@brief Stores plugin directories paths
244
-    _plugin_directories = None
245
-    
246 262
     ##@brief Stores Plugin instances indexed by name
247 263
     _plugin_instances = dict()
248 264
     
@@ -253,9 +269,6 @@ class Plugin(object, metaclass=MetaPlugType):
253 269
     ##@brief Attribute that stores plugins list from discover cache file
254 270
     _plugin_list = None
255 271
     
256
-    ##@brief Store dict representation of discover cache content
257
-    _discover_cache = None
258
-    
259 272
     #@brief Designed to store, in child classes, the confspec indicating \
260 273
     #where plugin list is stored
261 274
     _plist_confspecs = None
@@ -295,8 +308,7 @@ class Plugin(object, metaclass=MetaPlugType):
295 308
         self.loaded = False
296 309
         
297 310
         # Importing __init__.py infos in it
298
-        plugin_module = '%s.%s' % (VIRTUAL_PACKAGE_NAME,
299
-                                    plugin_name)
311
+        plugin_module = self.module_name()
300 312
         self.module = LodelContext.module(plugin_module)
301 313
 
302 314
         # loading confspecs
@@ -406,6 +418,19 @@ name differ from the one found in plugin's init file"
406 418
         module_name = self_modname+"."+base_mod
407 419
         return importlib.import_module(module_name)
408 420
    
421
+    ##@brief Return associated module name
422
+    def module_name(self):
423
+        if not self.path.startswith('./plugins'):
424
+            raise PluginError("Bad path for plugin %s : %s" % (
425
+                self.name, self.path))
426
+        mod_name = ''
427
+        pathbuff = self.path
428
+        while pathbuff != '.':
429
+            mod_name = os.path.basename(pathbuff) + '.' + mod_name
430
+            pathbuff = os.path.dirname(pathbuff)
431
+        #removing trailing '.' and add leading lodel.
432
+        return 'lodel.'+mod_name[:-1]
433
+
409 434
     ##@brief Check dependencies of plugin
410 435
     #@return A list of plugin name to be loaded before
411 436
     def check_deps(self):
@@ -561,35 +586,6 @@ name differ from the one found in plugin's init file"
561 586
             confspec_append(res, plcs)
562 587
         return res
563 588
 
564
-    ##@brief Attempt to read plugin discover cache
565
-    #@note If no cache yet make a discover with default plugin directory
566
-    #@return a dict (see @ref _discover() )
567
-    @classmethod
568
-    def plugin_cache(cls):
569
-        if cls._discover_cache is None:
570
-            if not os.path.isfile(DISCOVER_CACHE_FILENAME):
571
-                cls.discover()
572
-            with open(DISCOVER_CACHE_FILENAME) as pdcache_fd:
573
-                res = json.load(pdcache_fd)
574
-            #Check consistency of loaded cache
575
-            if 'path_list' not in res:
576
-                raise LodelFatalError("Malformed plugin's discover cache file \
577
-: '%s'. Unable to find plugin's paths list." % DISCOVER_CACHE_FILENAME)
578
-            expected_keys = ['type', 'path', 'version']
579
-            for pname in res['plugins']:
580
-                for ekey in expected_keys:
581
-                    if ekey not in res['plugins'][pname]:
582
-                        #Bad cache !
583
-                        logger.warning("Malformed plugin's discover cache \
584
-file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
585
-                        cls._discover_cache = cls.discover(res['path_list'])
586
-                        break
587
-            else:
588
-                #The cache we just read was OK
589
-                cls._discover_cache = res
590
-                
591
-        return cls._discover_cache
592
-
593 589
     ##@brief Register a new plugin
594 590
     # 
595 591
     #@param plugin_name str : The plugin name
@@ -602,11 +598,10 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
602 598
             msg %= plugin_name
603 599
             raise PluginError(msg)
604 600
         #Here we check that previous discover found a plugin with that name
605
-        pdcache = cls.plugin_cache()
606
-        if plugin_name not in pdcache['plugins']:
601
+        pdcache = cls.discover()
602
+        if plugin_name not in pdcache:
607 603
             raise PluginError("No plugin named %s found" % plugin_name)
608
-        pinfos = pdcache['plugins'][plugin_name]
609
-        ptype = pinfos['type']
604
+        ptype = pdcache[plugin_name]['type']
610 605
         pcls = MetaPlugType.type_from_name(ptype)
611 606
         plugin = pcls(plugin_name)
612 607
         cls._plugin_instances[plugin_name] = plugin
@@ -632,7 +627,6 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
632 627
     # @return the plugin directory path
633 628
     @classmethod
634 629
     def plugin_path(cls, plugin_name):
635
-        
636 630
         plist = cls.plugin_list()
637 631
         if plugin_name not in plist:
638 632
             raise PluginError("No plugin named '%s' found" % plugin_name)
@@ -649,8 +643,11 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
649 643
     #This module name is the "virtual" module where we imported the plugin.
650 644
     #
651 645
     #Typically composed like VIRTUAL_PACKAGE_NAME.PLUGIN_NAME
646
+    #@warning Brokes subdire feature
652 647
     #@param plugin_name str : a plugin name
653 648
     #@return a string representing a module name
649
+    #@todo fix broken subdir capabilitie ( @see module_name() )
650
+    #@todo check if used, else delete it
654 651
     @classmethod
655 652
     def plugin_module_name(cls, plugin_name):
656 653
         return "%s.%s" % (VIRTUAL_PACKAGE_NAME, plugin_name)
@@ -668,8 +665,6 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
668 665
     ##@brief Attempt to "restart" the Plugin class
669 666
     @classmethod
670 667
     def clear(cls):
671
-        if cls._plugin_directories is not None:
672
-            cls._plugin_directories = None
673 668
         if cls._plugin_instances != dict():
674 669
             cls._plugin_instances = dict()
675 670
         if cls._load_called != []:
@@ -682,20 +677,18 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
682 677
         pass
683 678
     
684 679
     ##@brief Reccursively walk throught paths to find plugin, then stores
685
-    #found plugin in a file...
686
-    #@param paths list : list of directory paths
687
-    #@param no_cache bool : if true only return a list of found plugins 
688
-    #without modifying the cache file
689
-    #@return a dict {'path_list': [...], 'plugins': { see @ref _discover }}
690
-    #@todo add max_depth and symlink following options
680
+    #found plugin in a static var
681
+    #
682
+    #Found plugins are stored in cls._plugin_list
683
+    #@note The discover is run only if no cached datas are found
684
+    #@return a list of dict with plugin infos { see @ref _discover }
685
+    #@todo add max_depth and no symlink following feature
691 686
     @classmethod
692
-    def discover(cls, paths = None, no_cache = False):
687
+    def discover(cls):
688
+        if cls._plugin_list is not None:
689
+            return cls._plugin_list
693 690
         logger.info("Running plugin discover")
694
-        if paths is None:
695
-            paths = DEFAULT_PLUGINS_PATH_LIST
696
-        tmp_res = []
697
-        for path in paths:
698
-            tmp_res += cls._discover(path)
691
+        tmp_res = cls._discover(PLUGINS_PATH)
699 692
         #Formating and dedoubloning result
700 693
         result = dict()
701 694
         for pinfos in tmp_res:
@@ -707,11 +700,7 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
707 700
             else:
708 701
                 #dropped
709 702
                 pass
710
-        result = {'path_list': paths, 'plugins': result}
711
-        #Writing to cache
712
-        if not no_cache:
713
-            with open(DISCOVER_CACHE_FILENAME, 'w+') as pdcache:
714
-                pdcache.write(json.dumps(result))
703
+        cls._plugin_list = result
715 704
         return result
716 705
     
717 706
     ##@brief Return discover result
@@ -722,17 +711,6 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
722 711
     #from the plugin's discover cache
723 712
     @classmethod
724 713
     def plugin_list(cls, refresh = False):
725
-        try:
726
-            infos = cls._load_discover_cache()
727
-            path_list = infos['path_list']
728
-        except PluginError:
729
-            refresh = True
730
-            path_list = DEFAULT_PLUGINS_PATH_LIST
731
-
732
-        if cls._plugin_list is None or refresh:
733
-            if not os.path.isfile(DISCOVER_CACHE_FILENAME) or refresh:
734
-                infos = cls.discover(path_list)
735
-        cls._plugin_list = infos['plugins']
736 714
         return cls._plugin_list
737 715
 
738 716
     ##@brief Return a list of child Class Plugin
@@ -740,32 +718,20 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
740 718
     def plugin_types(cls):
741 719
         return MetaPlugType.all_types()
742 720
 
743
-    ##@brief Attempt to open and load plugin discover cache
744
-    #@return discover cache
745
-    #@throw PluginError when open or load fails
746
-    @classmethod
747
-    def _load_discover_cache(cls):
748
-        try:
749
-            pdcache = open(DISCOVER_CACHE_FILENAME, 'r')
750
-        except Exception as e:
751
-            msg = "Unable to open discover cache : %s"
752
-            msg %= e
753
-            raise PluginError(msg)
754
-        try:
755
-            res = json.load(pdcache)
756
-        except Exception as e:
757
-            msg = "Unable to load discover cache : %s"
758
-            msg %= e
759
-            raise PluginError(msg)
760
-        pdcache.close()
761
-        return res
762
-
763 721
     ##@brief Check if a directory is a plugin module
764 722
     #@param path str : path to check
723
+    #@param assert_in_package bool : if False didn't check that path is
724
+    #a subdir of PLUGINS_PATH
765 725
     #@return a dict with name, version and path if path is a plugin module, else False
766 726
     @classmethod
767
-    def dir_is_plugin(cls, path):
727
+    def dir_is_plugin(cls, path, assert_in_package = True):
768 728
         log_msg = "%s is not a plugin directory because : " % path
729
+        if assert_in_package:
730
+            #Check that path is a subdir of PLUGINS_PATH
731
+            abspath = os.path.abspath(path)
732
+            if not abspath.startswith(os.path.abspath(PLUGINS_PATH)):
733
+                raise PluginError(
734
+                    "%s is not a subdir of %s" % log_msg, PLUGINS_PATH)
769 735
         #Checks that path exists
770 736
         if not os.path.isdir(path):
771 737
             raise ValueError(
@@ -805,7 +771,7 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
805 771
             ptype = DEFAULT_PLUGIN_TYPE
806 772
         pname = getattr(initmod, PLUGIN_NAME_VARNAME)
807 773
         return {'name': pname,
808
-            'version': pversion,
774
+            'version': PluginVersion(pversion),
809 775
             'path': path,
810 776
             'type': ptype}
811 777
     
@@ -857,7 +823,8 @@ file : '%s'. Running discover again..." % DISCOVER_CACHE_FILENAME)
857 823
                     #Check if it is a plugin directory
858 824
                     test_result = cls.dir_is_plugin(f_path)
859 825
                     if not (test_result is False):
860
-                        logger.info("Plugin found in %s" % f_path)
826
+                        logger.info("Plugin '%s' found in %s" % (
827
+                            test_result['name'],f_path))
861 828
                         res.append(test_result)
862 829
                     else:
863 830
                         to_explore.append(f_path)

Loading…
Cancel
Save