@@ -14,8 +14,16 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >.
1414
1515(C) 2015 by Adam Tauber, <asciimoo@gmail.com>
1616'''
17−from sys import exit, version_info
18−from searx import logger
17+
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
23+from sys import version_info
24+from traceback import print_exc
25+
26+from searx import logger, settings, static_path
1927
2028if version_info[0] == 3:
2129 unicode = str
@@ -54,7 +62,9 @@ class PluginStore():
5462 for plugin in self.plugins:
5563 yield plugin
5664
57− def register(self, *plugins):
65+ def register(self, *plugins, external=False):
66+ if external:
67+ plugins = load_external_plugins(plugins)
5868 for plugin in plugins:
5969 for plugin_attr, plugin_attr_type in required_attrs:
6070 if not hasattr(plugin, plugin_attr) or not isinstance(getattr(plugin, plugin_attr), plugin_attr_type):
@@ -77,6 +87,84 @@ class PluginStore():
7787 return ret
7888
7989
90+def load_external_plugins(plugin_names):
91+ plugins = []
92+ for name in plugin_names:
93+ logger.debug('loading plugin: {0}'.format(name))
94+ try:
95+ pkg = import_module(name)
96+ except Exception as e:
97+ logger.critical('failed to load plugin module {0}: {1}'.format(name, e))
98+ exit(3)
99+
100+ pkg.__base_path = dirname(abspath(pkg.__file__))
101+
102+ prepare_package_resources(pkg, name)
103+
104+ plugins.append(pkg)
105+ logger.debug('plugin "{0}" loaded'.format(name))
106+ return plugins
107+
108+
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):
114+ try:
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))
120+ except:
121+ logger.critical('failed to copy plugin resource {0} for plugin {1}'.format(file_name, name))
122+ exit(3)
123+
124+ # returning with the web path of the resource
125+ return join('plugins/external_plugins', plugin_dir, file_name)
126+
127+
128+def prepare_package_resources(pkg, name):
129+ plugin_dir = 'plugin_' + name
130+ target_dir = join(static_path, 'plugins/external_plugins', plugin_dir)
131+ try:
132+ makedirs(target_dir, exist_ok=True)
133+ except:
134+ logger.critical('failed to create resource directory {0} for plugin {1}'.format(target_dir, name))
135+ exit(3)
136+
137+ resources = []
138+
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
144+ ])
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
150+ ])
151+
152+ for f in listdir(target_dir):
153+ if basename(f) not in resources:
154+ resource_path = join(target_dir, basename(f))
155+ try:
156+ remove(resource_path)
157+ except:
158+ logger.critical('failed to remove unused resource file {0} for plugin {1}'.format(resource_path, name))
159+ exit(3)
160+
161+
162+def sha_sum(filename):
163+ with open(filename, "rb") as f:
164+ bytes = f.read()
165+ return sha256(bytes).hexdigest()
166+
167+
80168plugins = PluginStore()
81169plugins.register(oa_doi_rewrite)
82170plugins.register(https_rewrite)
@@ -86,3 +174,6 @@ plugins.register(self_info)
86174plugins.register(search_on_category_select)
87175plugins.register(tracker_url_remover)
88176plugins.register(vim_hotkeys)
177+# load external plugins
178+if 'plugins' in settings:
179+ plugins.register(*settings['plugins'], external=True)