@@ -14,8 +14,16 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
15(C) 2015 by Adam Tauber, <asciimoo@gmail.com>
15(C) 2015 by Adam Tauber, <asciimoo@gmail.com>
18+from hashlib import sha256
19+from importlib import import_module
20+from os import listdir, makedirs, remove, stat, utime
21+from os.path import abspath, basename, dirname, exists, join
22+from shutil import copyfile
17−from sys import exit, version_info
23+from sys import version_info
24+from traceback import print_exc
18−from searx import logger
26+from searx import logger, settings, static_path
20if version_info[0] == 3:
28if version_info[0] == 3:
21 unicode = str
29 unicode = str
@@ -54,7 +62,9 @@ class PluginStore():
54 for plugin in self.plugins:
62 for plugin in self.plugins:
55 yield plugin
63 yield plugin
57− def register(self, *plugins):
65+ def register(self, *plugins, external=False):
67+ plugins = load_external_plugins(plugins)
58 for plugin in plugins:
68 for plugin in plugins:
59 for plugin_attr, plugin_attr_type in required_attrs:
69 for plugin_attr, plugin_attr_type in required_attrs:
60 if not hasattr(plugin, plugin_attr) or not isinstance(getattr(plugin, plugin_attr), plugin_attr_type):
70 if not hasattr(plugin, plugin_attr) or not isinstance(getattr(plugin, plugin_attr), plugin_attr_type):
@@ -77,6 +87,84 @@ class PluginStore():
77 return ret
87 return ret
90+def load_external_plugins(plugin_names):
92+ for name in plugin_names:
93+ logger.debug('loading plugin: {0}'.format(name))
95+ pkg = import_module(name)
96+ except Exception as e:
97+ logger.critical('failed to load plugin module {0}: {1}'.format(name, e))
100+ pkg.__base_path = dirname(abspath(pkg.__file__))
102+ prepare_package_resources(pkg, name)
105+ logger.debug('plugin "{0}" loaded'.format(name))
109+def sync_resource(base_path, resource_path, name, target_dir, plugin_dir):
110+ dep_path = join(base_path, resource_path)
111+ file_name = basename(dep_path)
112+ resource_path = join(target_dir, file_name)
113+ if not exists(resource_path) or sha_sum(dep_path) != sha_sum(resource_path):
115+ copyfile(dep_path, resource_path)
116+ # copy atime_ns and mtime_ns, so the weak ETags (generated by
117+ # the HTTP server) do not change
118+ dep_stat = stat(dep_path)
119+ utime(resource_path, ns=(dep_stat.st_atime_ns, dep_stat.st_mtime_ns))
121+ logger.critical('failed to copy plugin resource {0} for plugin {1}'.format(file_name, name))
124+ # returning with the web path of the resource
125+ return join('plugins/external_plugins', plugin_dir, file_name)
128+def prepare_package_resources(pkg, name):
129+ plugin_dir = 'plugin_' + name
130+ target_dir = join(static_path, 'plugins/external_plugins', plugin_dir)
132+ makedirs(target_dir, exist_ok=True)
134+ logger.critical('failed to create resource directory {0} for plugin {1}'.format(target_dir, name))
139+ if hasattr(pkg, 'js_dependencies'):
140+ resources.extend(map(basename, pkg.js_dependencies))
141+ pkg.js_dependencies = tuple([
142+ sync_resource(pkg.__base_path, x, name, target_dir, plugin_dir)
143+ for x in pkg.js_dependencies
145+ if hasattr(pkg, 'css_dependencies'):
146+ resources.extend(map(basename, pkg.css_dependencies))
147+ pkg.css_dependencies = tuple([
148+ sync_resource(pkg.__base_path, x, name, target_dir, plugin_dir)
149+ for x in pkg.css_dependencies
152+ for f in listdir(target_dir):
153+ if basename(f) not in resources:
154+ resource_path = join(target_dir, basename(f))
156+ remove(resource_path)
158+ logger.critical('failed to remove unused resource file {0} for plugin {1}'.format(resource_path, name))
162+def sha_sum(filename):
163+ with open(filename, "rb") as f:
165+ return sha256(bytes).hexdigest()
80plugins = PluginStore()
168plugins = PluginStore()
81plugins.register(oa_doi_rewrite)
169plugins.register(oa_doi_rewrite)
82plugins.register(https_rewrite)
170plugins.register(https_rewrite)
@@ -86,3 +174,6 @@ plugins.register(self_info)
86plugins.register(search_on_category_select)
174plugins.register(search_on_category_select)
87plugins.register(tracker_url_remover)
175plugins.register(tracker_url_remover)
88plugins.register(vim_hotkeys)
176plugins.register(vim_hotkeys)
177+# load external plugins
178+if 'plugins' in settings:
179+ plugins.register(*settings['plugins'], external=True)