Qt: add download_plugin_dialog
This commit is contained in:
@@ -56,12 +56,25 @@ class PluginsDialog(WindowModalDialog):
|
|||||||
self.grid.addWidget(widget, i, 1)
|
self.grid.addWidget(widget, i, 1)
|
||||||
|
|
||||||
def do_toggle(self, cb, name, i):
|
def do_toggle(self, cb, name, i):
|
||||||
|
if self.plugins.requires_download(name):
|
||||||
|
cb.setChecked(False)
|
||||||
|
self.download_plugin_dialog(cb, name, i)
|
||||||
|
return
|
||||||
p = self.plugins.toggle(name)
|
p = self.plugins.toggle(name)
|
||||||
cb.setChecked(bool(p))
|
cb.setChecked(bool(p))
|
||||||
self.enable_settings_widget(p, name, i)
|
self.enable_settings_widget(p, name, i)
|
||||||
# note: all enabled plugins will receive this hook:
|
# note: all enabled plugins will receive this hook:
|
||||||
run_hook('init_qt', self.window.gui_object)
|
run_hook('init_qt', self.window.gui_object)
|
||||||
|
|
||||||
|
def download_plugin_dialog(self, cb, name, i):
|
||||||
|
import asyncio
|
||||||
|
if not self.window.question("Download plugin '%s'?"%name):
|
||||||
|
return
|
||||||
|
coro = self.plugins.download_external_plugin(name)
|
||||||
|
def on_success(x):
|
||||||
|
self.do_toggle(cb, name, i)
|
||||||
|
self.window.run_coroutine_dialog(coro, "Downloading '%s' "%name, on_result=on_success, on_cancelled=None)
|
||||||
|
|
||||||
def show_list(self):
|
def show_list(self):
|
||||||
descriptions = sorted(self.plugins.descriptions.items())
|
descriptions = sorted(self.plugins.descriptions.items())
|
||||||
i = 0
|
i = 0
|
||||||
|
|||||||
@@ -144,36 +144,68 @@ class Plugins(DaemonThread):
|
|||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
self.logger.exception(f"cannot initialize plugin {name}: {e}")
|
self.logger.exception(f"cannot initialize plugin {name}: {e}")
|
||||||
|
|
||||||
|
def requires_download(self, name):
|
||||||
|
metadata = self.external_plugin_metadata.get(name)
|
||||||
|
if not metadata:
|
||||||
|
return False
|
||||||
|
if os.path.exists(self.external_plugin_path(name)):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_plugin_hash(self, name: str)-> bool:
|
||||||
|
from .crypto import sha256
|
||||||
|
metadata = self.external_plugin_metadata.get(name)
|
||||||
|
filename = self.external_plugin_path(name)
|
||||||
|
if not os.path.exists(filename):
|
||||||
|
return False
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
s = f.read()
|
||||||
|
if sha256(s).hex() != metadata['hash']:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def download_external_plugin(self, name):
|
||||||
|
import aiohttp
|
||||||
|
metadata = self.external_plugin_metadata.get(name)
|
||||||
|
if metadata is None:
|
||||||
|
raise Exception("unknown external plugin %s" % name)
|
||||||
|
url = metadata['download_url']
|
||||||
|
filename = self.external_plugin_path(name)
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(url) as resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
with open(filename, 'wb') as fd:
|
||||||
|
async for chunk in resp.content.iter_chunked(10):
|
||||||
|
fd.write(chunk)
|
||||||
|
if not self.check_plugin_hash(name):
|
||||||
|
os.unlink(filename)
|
||||||
|
raise Exception("wrong plugin hash %s" % name)
|
||||||
|
|
||||||
def load_external_plugin(self, name):
|
def load_external_plugin(self, name):
|
||||||
if name in self.plugins:
|
if name in self.plugins:
|
||||||
return self.plugins[name]
|
return self.plugins[name]
|
||||||
# If we do not have the metadata, it was not detected by `load_external_plugins`
|
# If we do not have the metadata, it was not detected by `load_external_plugins`
|
||||||
# on startup, or added by manual user installation after that point.
|
# on startup, or added by manual user installation after that point.
|
||||||
metadata = self.external_plugin_metadata.get(name, None)
|
metadata = self.external_plugin_metadata.get(name)
|
||||||
if metadata is None:
|
if metadata is None:
|
||||||
self.logger.exception("attempted to load unknown external plugin %s" % name)
|
self.logger.exception("attempted to load unknown external plugin %s" % name)
|
||||||
return
|
return
|
||||||
|
filename = self.external_plugin_path(name)
|
||||||
from .crypto import sha256
|
if not os.path.exists(filename):
|
||||||
external_plugin_dir = self.get_external_plugin_dir()
|
|
||||||
plugin_file_path = os.path.join(external_plugin_dir, name + '.zip')
|
|
||||||
if not os.path.exists(plugin_file_path):
|
|
||||||
return
|
return
|
||||||
with open(plugin_file_path, 'rb') as f:
|
if not self.check_plugin_hash(name):
|
||||||
s = f.read()
|
self.logger.exception("wrong hash for plugin '%s'" % name)
|
||||||
if sha256(s).hex() != metadata['hash']:
|
os.unlink(filename)
|
||||||
self.logger.exception("wrong hash for plugin '%s'" % plugin_file_path)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
zipfile = zipimport.zipimporter(plugin_file_path)
|
zipfile = zipimport.zipimporter(filename)
|
||||||
except zipimport.ZipImportError:
|
except zipimport.ZipImportError:
|
||||||
self.logger.exception("unable to load zip plugin '%s'" % plugin_file_path)
|
self.logger.exception("unable to load zip plugin '%s'" % filename)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
module = zipfile.load_module(name)
|
module = zipfile.load_module(name)
|
||||||
except zipimport.ZipImportError as e:
|
except zipimport.ZipImportError as e:
|
||||||
self.logger.exception(f"unable to load zip plugin '{plugin_file_path}' package '{name}'")
|
self.logger.exception(f"unable to load zip plugin '{filename}' package '{name}'")
|
||||||
return
|
return
|
||||||
sys.modules['electrum_external_plugins.'+ name] = module
|
sys.modules['electrum_external_plugins.'+ name] = module
|
||||||
full_name = f'electrum_external_plugins.{name}.{self.gui_name}'
|
full_name = f'electrum_external_plugins.{name}.{self.gui_name}'
|
||||||
@@ -202,6 +234,9 @@ class Plugins(DaemonThread):
|
|||||||
def get_external_plugin_dir(self):
|
def get_external_plugin_dir(self):
|
||||||
return self.user_pkgpath
|
return self.user_pkgpath
|
||||||
|
|
||||||
|
def external_plugin_path(self, name):
|
||||||
|
return os.path.join(self.get_external_plugin_dir(), name + '.zip')
|
||||||
|
|
||||||
def find_external_plugins(self):
|
def find_external_plugins(self):
|
||||||
""" read json file """
|
""" read json file """
|
||||||
from .constants import read_json
|
from .constants import read_json
|
||||||
@@ -425,8 +460,8 @@ class BasePlugin(Logger):
|
|||||||
def read_file(self, filename: str) -> bytes:
|
def read_file(self, filename: str) -> bytes:
|
||||||
""" note: only for external plugins """
|
""" note: only for external plugins """
|
||||||
import zipfile
|
import zipfile
|
||||||
plugin_file_path = os.path.join(self.parent.get_external_plugin_dir(), self.name + '.zip')
|
plugin_filename = self.parent.external_plugin_path(self.name)
|
||||||
with zipfile.ZipFile(plugin_file_path) as myzip:
|
with zipfile.ZipFile(plugin_filename) as myzip:
|
||||||
with myzip.open(os.path.join(self.name, filename)) as myfile:
|
with myzip.open(os.path.join(self.name, filename)) as myfile:
|
||||||
s = myfile.read()
|
s = myfile.read()
|
||||||
return s
|
return s
|
||||||
|
|||||||
Reference in New Issue
Block a user