コード例 #1
0
    def __init__(self):
        super().__init__()
        self.resize(gc.FRAME_WIDTH, gc.FRAME_HEIGHT)
        self.move(gc.FHD_W / 2 - gc.FRAME_WIDTH / 2,
                  gc.FHD_H / 2 - gc.FRAME_HEIGHT / 2)
        self.setWindowTitle(gc.WINDOW_TITLE)

        self._constructUI()

        self.client = None
        self.article_version_history = None

        os.chdir(utils.get_prefix_path())

        self.tx_monitor = BackgroundWorker(job=self._get_actions,
                                           job_done=self._process_actions,
                                           error=self._actions_fetch_failed,
                                           run_in_loop=True)

        self.warmup_worker = None
        self.worker_update = None
        self.worker_add = None
        self.worker_search = None
        self.worker_get = None

        self.loading_box = None
コード例 #2
0
    def _add_article_action(self):
        LOG.debug('_add_article_action called')
        self._set_app_status('Adding an article...')
        title = self.title_edit.text()

        if title == "":
            self._client_action_failed("Article name must not be empty")
            return

        # Because of windows/linux filename restrictions
        forbidden_chars = ['<', '>', ':', '"', '/', '\\', '|', '?', '*']
        for char in forbidden_chars:
            if char in title:
                self._client_action_failed(
                    "Article cannot consist of following chars: {}".format(
                        "".join(forbidden_chars)))
                return

        self.worker_add = BackgroundWorker(
            job=lambda: self.client.article_exists(title),
            job_done=lambda result: self._add_article_action_select_authorized(
                title) if result == 0 else self._client_action_failed(
                    "Article already exists"),
            error=self._client_action_failed)
        self.worker_add.start()
コード例 #3
0
    def init_client(self, eth_private_key, eth_provider, top_level_address):
        self._set_app_status('Initializing client...')
        self.client = DWClient(eth_private_key, eth_provider,
                               top_level_address)
        self._set_app_status('Client initialized')
        self.tx_monitor.start()

        self.warmup_worker = BackgroundWorker(
            job=self.client.get_transaction_price,
            job_done=lambda result: self._set_estimated_price(result),
            error=lambda error: None)
        self.warmup_worker.start()
コード例 #4
0
    def _search_article_action(self):
        LOG.debug('_search_article_action called')
        self._set_app_status('Searching for an article...')
        self.articles_list.clear()
        self.article_content_editor.clear()

        title = self.title_edit.text()

        self.worker_search = BackgroundWorker(
            job=lambda: self.client.get_titles(),
            job_done=lambda result: self._search_article_action_success(
                result, title),
            error=self._client_action_failed)
        self.worker_search.start()
コード例 #5
0
    def _get_article_action(self, title):
        title = title.text()
        LOG.debug('_get_article_action called with title: {}'.format(title))
        self._set_app_status('Loading data for an article: {}'.format(title))
        self.title_edit.setText(title)

        self.article_content_editor.clear()
        self.version_history_list.clear()

        self.worker_get = BackgroundWorker(
            job=lambda: self.client.get_article(title),
            job_done=lambda result: self._get_article_action_success(title),
            error=self._client_action_failed)
        self.worker_get.start()
コード例 #6
0
    def _add_article_action_open_and_push(self, title, authorized,
                                          allow_author):
        LOG.debug("_add_article_action_open_and_push: " + title + " " +
                  str(authorized) + " " + str(allow_author))

        path = self._open_file(title)

        self.worker_add = BackgroundWorker(
            job=lambda: self.client.add_article(title, path, authorized,
                                                allow_author),
            job_done=lambda result: self._close_loading(),
            error=self._client_action_failed)
        self.worker_add.start()

        self._set_app_status(
            'Synchronizing article with distributed systems...')

        self._show_loading("Adding article to IPFS")
コード例 #7
0
    def _update_article_action(self):
        LOG.info('_update_article_action called')
        self._set_app_status('Updating article...')

        title = self.title_edit.text()

        # update button should only be active when title is specified
        assert (title is not None)
        assert (title != "")

        path = self._open_file(title)

        self._set_app_status(
            'Synchronizing article with distributed systems...')

        self.worker_update = BackgroundWorker(
            job=lambda: self.client.update_article(title, path),
            job_done=lambda result: self._close_loading(),
            error=self._client_action_failed)
        self.worker_update.start()

        self._show_loading("Updating article on IPFS")
コード例 #8
0
class GUI(QMainWindow):
    """
    GUI for Distributed Wikipedia Client
    """
    def __init__(self):
        super().__init__()
        self.resize(gc.FRAME_WIDTH, gc.FRAME_HEIGHT)
        self.move(gc.FHD_W / 2 - gc.FRAME_WIDTH / 2,
                  gc.FHD_H / 2 - gc.FRAME_HEIGHT / 2)
        self.setWindowTitle(gc.WINDOW_TITLE)

        self._constructUI()

        self.client = None
        self.article_version_history = None

        os.chdir(utils.get_prefix_path())

        self.tx_monitor = BackgroundWorker(job=self._get_actions,
                                           job_done=self._process_actions,
                                           error=self._actions_fetch_failed,
                                           run_in_loop=True)

        self.warmup_worker = None
        self.worker_update = None
        self.worker_add = None
        self.worker_search = None
        self.worker_get = None

        self.loading_box = None

    def _constructUI(self):
        self.top_level = QLabel(
            "Current top level address: " + DEV_TOP_LEVEL_ADDRESS, self)

        self.top_level.resize(QSize(400, 25))
        self.top_level.move(50, 20)

        self.estimated_tx_price = QLabel("estimated tx price: loading...",
                                         self)
        self.estimated_tx_price.adjustSize()
        self.estimated_tx_price.move(550, 505)

        self.title_edit = QLineEdit(
            self, placeholderText='Put unique article title here')

        self.title_edit.resize(QSize(250, 25))
        self.title_edit.move(50, 50)

        self.btn_update_article = QPushButton('Update article', self)
        self.btn_update_article.resize(QSize(90, 25))
        self.btn_update_article.move(510, 50)
        self.btn_update_article.clicked.connect(self._update_article_action)

        # This button is active only when article is selected.
        self.btn_update_article.setDisabled(True)

        btn_search_article = QPushButton('Search article', self)
        btn_search_article.resize(QSize(90, 25))
        btn_search_article.move(310, 50)
        btn_search_article.clicked.connect(self._search_article_action)

        self.btn_add_article = QPushButton('Add article', self)
        self.btn_add_article.resize(QSize(90, 25))
        self.btn_add_article.move(410, 50)
        self.btn_add_article.clicked.connect(self._add_article_action)

        version_history_label = QLabel("Version history", self)
        version_history_label.move(410, 95)

        self.version_history_list = QListWidget(self)
        self.version_history_list.resize(QSize(350, 100))
        self.version_history_list.move(410, 120)
        self.version_history_list.itemClicked.connect(
            self._show_clicked_article_version)

        content_editor_label = QLabel("Content viewer", self)
        content_editor_label.move(410, 225)

        self.article_content_editor = QPlainTextEdit(self, readOnly=True)
        self.article_content_editor.resize(QSize(350, 250))
        self.article_content_editor.move(410, 250)

        articles_list_label = QLabel("Articles", self)
        articles_list_label.move(50, 95)

        self.articles_list = QListWidget(self)
        self.articles_list.resize(QSize(350, 380))
        self.articles_list.move(50, 120)

        self.articles_list.show()
        self.articles_list.itemClicked.connect(self._get_article_action)

        action_status_label = QLabel(
            "Last " + str(gc.NUMBER_OF_DISPLAYED_TRANSACTIONS) +
            " blockchain actions", self)

        action_status_label.adjustSize()
        action_status_label.move(50, 505)

        self.action_status = QTableWidget(self)
        self.action_status.resize(QSize(710, 175))
        self.action_status.move(50, 530)
        self.action_status.setColumnCount(2)
        self.action_status.horizontalHeader().\
            setSectionResizeMode(QHeaderView.Fixed)
        self.action_status.setHorizontalHeaderLabels(['Action', 'Status'])
        self.action_status.setColumnWidth(0, 473)
        self.action_status.setColumnWidth(1, 220)
        self.action_status.setRowCount(gc.NUMBER_OF_DISPLAYED_TRANSACTIONS)

        self.statusBar().showMessage('Connected')

    def _show_articles_list(self, articles, prefix):
        count = 0
        self.articles_list.setDisabled(False)

        for article in articles:
            if prefix in article:
                self.articles_list.addItem(article)
                count += 1

        if count == 0:
            self.articles_list.addItem("No articles found")
            self.articles_list.setDisabled(True)

        self.articles_list.show()

    def _set_estimated_price(self, price):
        text = "estimated tx price: {0:.3f} gwei".format(price / 10**9)
        self.estimated_tx_price.setText(text)
        self.estimated_tx_price.adjustSize()

    def _add_action(self, action, status):
        row = self.action_status.rowCount()
        self.action_status.insertRow(row)

        self.action_status.setItem(row, 0, QTableWidgetItem(action))
        self.action_status.setItem(row, 1, QTableWidgetItem(status))

        if status == 'failed':
            self.action_status.item(row, 1).\
                setBackground(QtGui.QColor(200, 0, 0))
        elif status == 'success':
            self.action_status.item(row, 1).\
                setBackground(QtGui.QColor(0, 200, 0))

    def _get_actions(self):
        return self.client.get_unprocessed_tx(
            gc.NUMBER_OF_DISPLAYED_TRANSACTIONS)

    def _actions_fetch_failed(self, cause):
        self.action_status.clear()
        self.action_status.setHorizontalHeaderLabels(['Action', 'Status'])
        self._add_action("Failed to fetch transactions, check your connection",
                         cause)
        self.action_status.setRowCount(gc.NUMBER_OF_DISPLAYED_TRANSACTIONS)

    def _process_actions(self, action_list):
        self.action_status.clear()
        self.action_status.setHorizontalHeaderLabels(['Action', 'Status'])
        for action in action_list:
            self._add_action(action['description'], action['status'])

        self.action_status.sortItems(0)
        self.action_status.sortItems(1)

        self.action_status.setRowCount(gc.NUMBER_OF_DISPLAYED_TRANSACTIONS)

    def _set_app_status(self, text):
        self.statusBar().showMessage('Status: {}'.format(text))

    def _show_clicked_article_version(self, item):
        LOG.info('_show_clicked_article_version')
        LOG.info('clicked article version: %s', item.text())
        self._set_app_status('Loading preview of article {}'.format(
            item.text()))
        article_version_id = int(item.text().split('::')[0])
        article_content = self.client.get_version_by_index(article_version_id)
        self.article_content_editor.clear()
        self.article_content_editor.insertPlainText(article_content)
        self._set_app_status('Loaded preview of article {}'.format(
            item.text()))

    def _client_action_failed(self, cause):
        self._close_loading()
        self._set_app_status('Error occurred: {}'.format(cause))

    def _update_article_action(self):
        LOG.info('_update_article_action called')
        self._set_app_status('Updating article...')

        title = self.title_edit.text()

        # update button should only be active when title is specified
        assert (title is not None)
        assert (title != "")

        path = self._open_file(title)

        self._set_app_status(
            'Synchronizing article with distributed systems...')

        self.worker_update = BackgroundWorker(
            job=lambda: self.client.update_article(title, path),
            job_done=lambda result: self._close_loading(),
            error=self._client_action_failed)
        self.worker_update.start()

        self._show_loading("Updating article on IPFS")

    def _open_file(self, filename):
        """
        Opens file with provided filename in default system editor.
        Creates file if it doesn't exists.
        """
        self._set_app_status('Opening file...')
        path = os.path.join(utils.get_prefix_path(), filename)
        utils.create_file_if_not_exists(path)

        print(path)

        if os.name == 'nt':
            subprocess.call(('notepad.exe', path))  # XXX: select editor...
        elif os.name == 'posix':
            subprocess.call(('xdg-open', path))
        self._set_app_status('File closed')
        return path

    def _show_loading(self, message):
        self.loading_box = QMessageBox()
        self.loading_box.setIcon(QMessageBox.Information)
        self.loading_box.setStandardButtons(QMessageBox.Close)
        self.loading_box.button(QMessageBox.Close).setDisabled(True)
        self.loading_box.setText(message)
        self.loading_box.setWindowTitle("Warning")
        self.loading_box.exec()

    def _close_loading(self):
        if self.loading_box is not None:
            self.loading_box.close()
        self.loading_box = None

    def _add_article_action_select_authorized(self, title):
        self.auth_window = AddAuthorized(
            lambda auth, allow_author: self._add_article_action_open_and_push(
                title, auth, allow_author))
        self.auth_window.show()

    def _add_article_action_open_and_push(self, title, authorized,
                                          allow_author):
        LOG.debug("_add_article_action_open_and_push: " + title + " " +
                  str(authorized) + " " + str(allow_author))

        path = self._open_file(title)

        self.worker_add = BackgroundWorker(
            job=lambda: self.client.add_article(title, path, authorized,
                                                allow_author),
            job_done=lambda result: self._close_loading(),
            error=self._client_action_failed)
        self.worker_add.start()

        self._set_app_status(
            'Synchronizing article with distributed systems...')

        self._show_loading("Adding article to IPFS")

    def _add_article_action(self):
        LOG.debug('_add_article_action called')
        self._set_app_status('Adding an article...')
        title = self.title_edit.text()

        if title == "":
            self._client_action_failed("Article name must not be empty")
            return

        # Because of windows/linux filename restrictions
        forbidden_chars = ['<', '>', ':', '"', '/', '\\', '|', '?', '*']
        for char in forbidden_chars:
            if char in title:
                self._client_action_failed(
                    "Article cannot consist of following chars: {}".format(
                        "".join(forbidden_chars)))
                return

        self.worker_add = BackgroundWorker(
            job=lambda: self.client.article_exists(title),
            job_done=lambda result: self._add_article_action_select_authorized(
                title) if result == 0 else self._client_action_failed(
                    "Article already exists"),
            error=self._client_action_failed)
        self.worker_add.start()

    def _get_article_action_success(self, title):
        self.client.initialize_article_data(title)
        self.btn_update_article.setDisabled(False)

        self.version_history_list.addItems(self.client.get_versions_list())
        self._set_app_status(
            'Data for an article: \"{}\" has finished loading'.format(title))

    def _get_article_action(self, title):
        title = title.text()
        LOG.debug('_get_article_action called with title: {}'.format(title))
        self._set_app_status('Loading data for an article: {}'.format(title))
        self.title_edit.setText(title)

        self.article_content_editor.clear()
        self.version_history_list.clear()

        self.worker_get = BackgroundWorker(
            job=lambda: self.client.get_article(title),
            job_done=lambda result: self._get_article_action_success(title),
            error=self._client_action_failed)
        self.worker_get.start()

    def _search_article_action_success(self, articles, title):
        self._show_articles_list(articles, title)
        self._set_app_status('Listed found articles')

    def _search_article_action(self):
        LOG.debug('_search_article_action called')
        self._set_app_status('Searching for an article...')
        self.articles_list.clear()
        self.article_content_editor.clear()

        title = self.title_edit.text()

        self.worker_search = BackgroundWorker(
            job=lambda: self.client.get_titles(),
            job_done=lambda result: self._search_article_action_success(
                result, title),
            error=self._client_action_failed)
        self.worker_search.start()

    def init_client(self, eth_private_key, eth_provider, top_level_address):
        self._set_app_status('Initializing client...')
        self.client = DWClient(eth_private_key, eth_provider,
                               top_level_address)
        self._set_app_status('Client initialized')
        self.tx_monitor.start()

        self.warmup_worker = BackgroundWorker(
            job=self.client.get_transaction_price,
            job_done=lambda result: self._set_estimated_price(result),
            error=lambda error: None)
        self.warmup_worker.start()

    def start(self, app):
        login_window = Login(self.init_client)
        result = login_window.exec()

        if result == 0:
            self.close()
            sys.exit(0)

        self.show()
        sys.exit(app.exec_())

    def closeEvent(self, QCloseEvent):
        self._set_app_status('Closing application...')
        utils.kill_ipfsd_processes()
        # Delete GUI session
        self.deleteLater()