Skip to content
Snippets Groups Projects
Commit adf25906 authored by inso's avatar inso
Browse files

Introducing plugins manager

parent 2d39a1d5
No related branches found
No related tags found
No related merge requests found
Showing
with 459 additions and 5 deletions
from PyQt5.QtWidgets import QMessageBox
def display_messagebox():
QMessageBox.about(None, "About", "Sakia")
\ No newline at end of file
......@@ -14,7 +14,7 @@ from sakia.data.repositories import SakiaDatabase
from sakia.data.entities import Transaction, Connection, Identity, Dividend
from sakia.data.processors import BlockchainProcessor, NodesProcessor, IdentitiesProcessor, \
CertificationsProcessor, SourcesProcessor, TransactionsProcessor, ConnectionsProcessor, DividendsProcessor
from sakia.data.files import AppDataFile, UserParametersFile
from sakia.data.files import AppDataFile, UserParametersFile, PluginsDirectory
from sakia.decorators import asyncify
from sakia.money import *
import asyncio
......@@ -60,6 +60,7 @@ class Application(QObject):
parameters = attr.ib()
db = attr.ib()
currency = attr.ib()
plugins_dir = attr.ib()
network_service = attr.ib(default=None)
blockchain_service = attr.ib(default=None)
identities_service = attr.ib(default=None)
......@@ -81,7 +82,7 @@ class Application(QObject):
qapp.setAttribute(Qt.AA_EnableHighDpiScaling, True)
options = SakiaOptions.from_arguments(argv)
app_data = AppDataFile.in_config_path(options.config_path).load_or_init()
app = cls(qapp, loop, options, app_data, None, None, options.currency)
app = cls(qapp, loop, options, app_data, None, None, options.currency, None)
#app.set_proxy()
app.get_last_version()
app.load_profile(app_data.default)
......@@ -96,6 +97,7 @@ class Application(QObject):
:param profile_name:
:return:
"""
self.plugins_dir = PluginsDirectory.in_config_path(self.options.config_path, profile_name).load_or_init()
self.parameters = UserParametersFile.in_config_path(self.options.config_path, profile_name).load_or_init()
self.db = SakiaDatabase.load_or_init(self.options, profile_name)
......
......@@ -9,3 +9,4 @@ from .app_data import AppData
from .source import Source
from .dividend import Dividend
from .contact import Contact
from .plugin import Plugin
import attr
@attr.s(frozen=True)
class Plugin:
name = attr.ib()
description = attr.ib()
version = attr.ib()
imported = attr.ib()
module = attr.ib()
from .user_parameters import UserParametersFile
from .app_data import AppDataFile
from .plugins import PluginsDirectory, Plugin
\ No newline at end of file
import attr
import os
import sys
import logging
import importlib
from ..entities import Plugin
@attr.s(frozen=True)
class PluginsDirectory:
"""
The repository for UserParameters
"""
_path = attr.ib()
plugins = attr.ib(default=[])
_logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
@classmethod
def in_config_path(cls, config_path, profile_name):
plugins_path = os.path.join(config_path, profile_name, "plugins")
if not os.path.exists(plugins_path):
os.makedirs(plugins_path)
return cls(plugins_path)
def load_or_init(self):
"""
Init plugins
"""
try:
for file in os.listdir(self._path):
if file.endswith(".zip"):
sys.path.append(os.path.join(self._path, file))
module_name = os.path.splitext(os.path.basename(file))[0]
try:
plugin_module = importlib.import_module(module_name)
self.plugins.append(Plugin(plugin_module.PLUGIN_NAME,
plugin_module.PLUGIN_DESCRIPTION,
plugin_module.PLUGIN_VERSION,
True,
plugin_module))
except ImportError as e:
self.plugins.append(Plugin(module_name, "", "",
False, None))
self._logger.debug(str(e) + " with sys.path " + str(sys.path))
except OSError as e:
self._logger.debug(str(e))
return self
import asyncio
from PyQt5.QtCore import QObject
from .model import PluginsManagerModel
from .view import PluginsManagerView
import attr
@attr.s()
class PluginsManagerController(QObject):
"""
The PluginManager view
"""
view = attr.ib()
model = attr.ib()
def __attrs_post_init__(self):
super().__init__()
self.view.button_box.rejected.connect(self.view.close)
@classmethod
def create(cls, parent, app):
"""
Instanciate a PluginManager component
:param sakia.gui.component.controller.ComponentController parent:
:param sakia.app.Application app: sakia application
:return: a new PluginManager controller
:rtype: PluginManagerController
"""
view = PluginsManagerView(parent.view if parent else None)
model = PluginsManagerModel(app)
plugin = cls(view, model)
view.set_table_plugins_model(model.init_plugins_table())
view.button_delete.clicked.connect(plugin.delete_plugin)
return plugin
@classmethod
def open_dialog(cls, parent, app):
"""
Certify and identity
:param sakia.gui.component.controller.ComponentController parent: the parent
:param sakia.core.Application app: the application
:param sakia.core.Account account: the account certifying the identity
:param sakia.core.Community community: the community
:return:
"""
dialog = cls.create(parent, app)
return dialog.exec()
def delete_plugin(self):
plugin_index = self.view.selected_plugin_index()
plugin = self.model.plugin(plugin_index)
self.model.delete_plugin(plugin)
def async_exec(self):
future = asyncio.Future()
self.view.finished.connect(lambda r: future.set_result(r))
self.view.open()
return future
def exec(self):
self.view.exec()
from PyQt5.QtCore import QObject, Qt
from .table_model import PluginsTableModel, PluginsFilterProxyModel
import attr
@attr.s()
class PluginsManagerModel(QObject):
"""
The model of Plugin component
"""
app = attr.ib()
def __attrs_post_init__(self):
super().__init__()
def init_plugins_table(self):
"""
Generates a plugins table model
:return:
"""
self._model = PluginsTableModel(self, self.app)
self._proxy = PluginsFilterProxyModel(self)
self._proxy.setSourceModel(self._model)
self._proxy.setDynamicSortFilter(True)
self._proxy.setSortRole(Qt.DisplayRole)
self._model.init_plugins()
return self._proxy
def delete_plugin(self, plugin):
self.app.plugins_dir.uninstall_plugin(plugin)
self._model.remove_plugin(plugin)
def plugin(self, index):
plugin_name = self._proxy.plugin_name(index)
if plugin_name:
try:
return next(p for p in self.app.plugins_dir.plugins if p.name == plugin_name)
except StopIteration:
pass
return None
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PluginDialog</class>
<widget class="QDialog" name="PluginDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>629</width>
<height>316</height>
</rect>
</property>
<property name="windowTitle">
<string>Plugins manager</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Installed plugins list</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTableView" name="table_plugins">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="topMargin">
<number>6</number>
</property>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_save">
<property name="text">
<string>Install a new plugin</string>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_delete">
<property name="text">
<string>Uninstall selected plugin</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<property name="enabled">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../../../res/icons/icons.qrc"/>
</resources>
<connections/>
<slots>
<slot>open_manage_wallet_coins()</slot>
<slot>change_displayed_wallet(int)</slot>
<slot>transfer_mode_changed(bool)</slot>
<slot>recipient_mode_changed(bool)</slot>
<slot>change_current_community(int)</slot>
<slot>amount_changed()</slot>
<slot>relative_amount_changed()</slot>
</slots>
</ui>
from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel,\
QModelIndex, QT_TRANSLATE_NOOP
from sakia.data.entities import Plugin
class PluginsFilterProxyModel(QSortFilterProxyModel):
def __init__(self, parent):
"""
History of all transactions
:param PyQt5.QtWidgets.QWidget parent: parent widget
"""
super().__init__(parent)
self.app = None
def columnCount(self, parent):
return self.sourceModel().columnCount(None) - 1
def setSourceModel(self, source_model):
self.app = source_model.app
super().setSourceModel(source_model)
def lessThan(self, left, right):
"""
Sort table by given column number.
"""
source_model = self.sourceModel()
left_data = source_model.data(left, Qt.DisplayRole)
right_data = source_model.data(right, Qt.DisplayRole)
return left_data < right_data
def plugin_name(self, index):
"""
Gets available table data at given index
:param index:
:return: tuple containing (Identity, Transfer)
"""
if index.isValid() and index.row() < self.rowCount(QModelIndex()):
source_index = self.mapToSource(index)
contact_name_col = PluginsTableModel.columns_types.index('name')
contact_name = self.sourceModel().contacts_data[source_index.row()][contact_name_col]
return contact_name
return None
def data(self, index, role):
source_index = self.mapToSource(index)
model = self.sourceModel()
source_data = model.data(source_index, role)
return source_data
class PluginsTableModel(QAbstractTableModel):
"""
A Qt abstract item model to display plugins in a table view
"""
columns_types = (
'name',
'description',
'version',
'imported'
)
columns_headers = (
QT_TRANSLATE_NOOP("PluginsTableModel", 'Name'),
QT_TRANSLATE_NOOP("PluginsTableModel", 'Description'),
QT_TRANSLATE_NOOP("PluginsTableModel", 'Version'),
QT_TRANSLATE_NOOP("PluginsTableModel", 'Imported'),
)
def __init__(self, parent, app):
"""
History of all transactions
:param PyQt5.QtWidgets.QWidget parent: parent widget
:param sakia.app.Application app: the main application
"""
super().__init__(parent)
self.app = app
self.plugins_data = []
def add_plugin(self, plugin):
self.beginInsertRows(QModelIndex(), len(self.plugins_data), len(self.plugins_data))
self.plugins_data.append(self.data_plugin(plugin))
self.endInsertRows()
def remove_plugin(self, plugin):
for i, data in enumerate(self.plugins_data):
if data[PluginsTableModel.columns_types.index('name')] == plugin.name:
self.beginRemoveRows(QModelIndex(), i, i)
self.plugins_data.pop(i)
self.endRemoveRows()
return
def data_plugin(self, plugin):
"""
Converts a plugin to table data
:param sakia.data.entities.Plugin plugin: the plugin
:return: data as tuple
"""
return plugin.name, plugin.description, plugin.version, plugin.imported
def init_plugins(self):
self.beginResetModel()
self.plugins_data = []
for plugin in self.app.plugins_dir.plugins:
self.plugins_data.append(self.data_plugin(plugin))
self.endResetModel()
def rowCount(self, parent):
return len(self.plugins_data)
def columnCount(self, parent):
return len(PluginsTableModel.columns_types)
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return PluginsTableModel.columns_headers[section]
def data(self, index, role):
row = index.row()
col = index.column()
if not index.isValid():
return QVariant()
if role in (Qt.DisplayRole, Qt.ForegroundRole, Qt.ToolTipRole):
return self.plugins_data[row][col]
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
from PyQt5.QtWidgets import QDialog, QAbstractItemView, QHeaderView
from PyQt5.QtCore import QModelIndex
from .plugins_manager_uic import Ui_PluginDialog
class PluginsManagerView(QDialog, Ui_PluginDialog):
"""
The view of the plugins manager component
"""
def __init__(self, parent):
"""
:param parent:
"""
super().__init__(parent)
self.setupUi(self)
def set_table_plugins_model(self, model):
"""
Define the table history model
:param QAbstractItemModel model:
:return:
"""
self.table_plugins.setModel(model)
self.table_plugins.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_plugins.setSortingEnabled(True)
self.table_plugins.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
self.table_plugins.resizeRowsToContents()
self.table_plugins.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
def selected_plugin_index(self):
indexes = self.table_plugins.selectedIndexes()
if indexes:
return indexes[0]
return QModelIndex()
......@@ -24,9 +24,9 @@ class MainWindowController(QObject):
Init
:param MainWindowView view: the ui of the mainwindow component
:param sakia.gui.main_window.model.MainWindowModel: the model of the mainwindow component
:param sakia.gui.status_bar.controller.StatusBarController status_bar: the controller of the status bar component
:param sakia.gui.toolbar.controller.ToolbarController toolbar: the controller of the toolbar component
:param sakia.gui.navigation.contoller.NavigationController navigation: the controller of the navigation
:param sakia.gui.main_window.status_bar.controller.StatusBarController status_bar: the controller of the status bar component
:param sakia.gui.main_window.toolbar.controller.ToolbarController toolbar: the controller of the toolbar component
:param sakia.gui.navigation.controller.NavigationController navigation: the controller of the navigation
:param PasswordAsker password_asker: the password asker of the application
:type: sakia.core.app.Application
......@@ -83,6 +83,7 @@ class MainWindowController(QObject):
main_window.view.showMaximized()
else:
main_window.view.show()
main_window.model.load_plugins(main_window)
main_window.refresh(app.currency)
return main_window
......
......@@ -10,3 +10,7 @@ class MainWindowModel(QObject):
super().__init__(parent)
self.app = app
def load_plugins(self, main_window):
for plugin in self.app.plugins_dir.plugins:
if plugin.imported:
plugin.module.plugin_exec(self.app, main_window)
......@@ -5,9 +5,11 @@ from sakia.gui.dialogs.connection_cfg.controller import ConnectionConfigControll
from sakia.gui.dialogs.revocation.controller import RevocationController
from sakia.gui.dialogs.transfer.controller import TransferController
from sakia.gui.dialogs.contact.controller import ContactController
from sakia.gui.dialogs.plugins_manager.controller import PluginsManagerController
from sakia.gui.preferences import PreferencesDialog
from .model import ToolbarModel
from .view import ToolbarView
import sys
class ToolbarController(QObject):
......@@ -28,6 +30,7 @@ class ToolbarController(QObject):
self.view.button_send_money.clicked.connect(self.open_transfer_money_dialog)
self.view.action_add_connection.triggered.connect(self.open_add_connection_dialog)
self.view.action_parameters.triggered.connect(self.open_settings_dialog)
self.view.action_plugins.triggered.connect(self.open_plugins_manager_dialog)
self.view.action_about.triggered.connect(self.open_about_dialog)
self.view.action_revoke_uid.triggered.connect(self.open_revocation_dialog)
self.view.button_contacts.clicked.connect(self.open_contacts_dialog)
......@@ -66,6 +69,9 @@ class ToolbarController(QObject):
def open_settings_dialog(self):
PreferencesDialog(self.model.app).exec()
def open_plugins_manager_dialog(self):
PluginsManagerController.open_dialog(self, self.model.app)
def open_add_connection_dialog(self):
connection_config = ConnectionConfigController.create_connection(self, self.model.app)
connection_config.exec()
......
......@@ -27,6 +27,9 @@ class ToolbarView(QFrame, Ui_SakiaToolbar):
self.action_parameters = QAction(self.tr("Settings"), tool_menu)
tool_menu.addAction(self.action_parameters)
self.action_plugins = QAction(self.tr("Plugins manager"), tool_menu)
tool_menu.addAction(self.action_plugins)
self.action_about = QAction(self.tr("About"), tool_menu)
tool_menu.addAction(self.action_about)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment