class PluginObject(object): tabName = 'Dust-B-Gone' maxVersion = '0.93.99' ############################################################################# def __init__(self, main): def updateDustLimit(): try: self.dustTableModel.updateDustList( self.getSelectedWlt(), str2coin(self.dustLimitText.text())) self.beGoneDustButton.setEnabled( len(self.dustTableModel.dustTxOutlist) > 0) if self.dustTableModel.wlt: self.lblHeader.setText( tr("""<b>Dust Outputs for Wallet: %s</b>""" % self.dustTableModel.wlt.labelName)) except NegativeValueError: pass except TooMuchPrecisionError: pass except: LOGEXCEPT("Unexpected exception") pass def sendDust(): try: utxiList = [] for utxo in self.dustTableModel.dustTxOutlist: # The PyCreateAndSignTx method require PyTx and PyBtcAddress objects rawTx = TheBDM.getTxByHash(utxo.getTxHash()).serialize() a160 = CheckHash160(utxo.getRecipientScrAddr()) for pyAddr in self.dustTableModel.wlt.addrMap.values(): if a160 == pyAddr.getAddr160(): pubKey = pyAddr.binPublicKey65.toBinStr() txoIdx = utxo.getTxOutIndex() utxiList.append( UnsignedTxInput(rawTx, txoIdx, None, pubKey)) break # Make copies, destroy them in the finally clause privKeyMap = {} for addrObj in self.dustTableModel.wlt.addrMap.values(): scrAddr = SCRADDR_P2PKH_BYTE + addrObj.getAddr160() if self.dustTableModel.wlt.useEncryption and self.dustTableModel.wlt.isLocked: # Target wallet is encrypted... unlockdlg = DlgUnlockWallet(self.dustTableModel.wlt, self.main, self.main, 'Unlock Wallet to Import') if not unlockdlg.exec_(): QMessageBox.critical(self, 'Wallet is Locked', \ 'Cannot send dust without unlocking the wallet!', \ QMessageBox.Ok) return privKeyMap[scrAddr] = addrObj.binPrivKey32_Plain.copy() signedTx = PyCreateAndSignTx( utxiList, [], privKeyMap, SIGHASH_NONE | SIGHASH_ANYONECANPAY) print "-------------" print binary_to_hex(signedTx.serialize()) # sock = socket.create_connection(('dust-b-gone.bitcoin.petertodd.org',80)) # sock.send(signedTx.serialize()) # sock.send(b'\n') # sock.close() except socket.error as err: QMessageBox.critical( self.main, tr('Negative Value'), tr(""" Failed to connect to dust-b-gone server: %s""" % err.strerror), QMessageBox.Ok) except NegativeValueError: QMessageBox.critical( self.main, tr('Negative Value'), tr(""" You must enter a positive value of at least 0.0000 0001 and less than %s for the dust limit.""" % MAX_DUST_LIMIT_STR), QMessageBox.Ok) except TooMuchPrecisionError: QMessageBox.critical( self.main.main, tr('Too much precision'), tr(""" Bitcoins can only be specified down to 8 decimal places. The smallest unit of a Bitcoin is 0.0000 0001 BTC. Please enter a dust limit of at least 0.0000 0001 and less than %s.""" % MAX_DUST_LIMIT_STR), QMessageBox.Ok) finally: for scraddr in privKeyMap: privKeyMap[scraddr].destroy() self.main = main self.lblHeader = QRichLabel( tr("""<b>Dust Outputs for Wallet: None Selected</b>"""), doWrap=False) self.beGoneDustButton = QPushButton("Remove Dust") self.beGoneDustButton.setEnabled(False) self.main.connect(self.beGoneDustButton, SIGNAL('clicked()'), sendDust) topRow = makeHorizFrame([self.lblHeader, 'stretch']) secondRow = makeHorizFrame([self.beGoneDustButton, 'stretch']) self.dustLimitLabel = QLabel("Max Dust Value (BTC): ") self.dustLimitText = QLineEdit() self.dustLimitText.setFont(GETFONT('Fixed')) self.dustLimitText.setMinimumWidth( tightSizeNChar(self.dustLimitText, 6)[0]) self.dustLimitText.setMaximumWidth( tightSizeNChar(self.dustLimitText, 12)[0]) self.dustLimitText.setAlignment(Qt.AlignRight) self.dustLimitText.setText(coin2str(DEFAULT_DUST_LIMIT)) self.main.connect(self.dustLimitText, SIGNAL('textChanged(QString)'), updateDustLimit) limitPanel = makeHorizFrame( [self.dustLimitLabel, self.dustLimitText, 'stretch']) self.dustTableModel = DustDisplayModel() self.dustTableView = QTableView() self.dustTableView.setModel(self.dustTableModel) self.dustTableView.setSelectionMode(QTableView.NoSelection) self.dustTableView.verticalHeader().setDefaultSectionSize(20) self.dustTableView.verticalHeader().hide() h = tightSizeNChar(self.dustTableView, 1)[1] self.dustTableView.setMinimumHeight(2 * (1.3 * h)) self.dustTableView.setMaximumHeight(10 * (1.3 * h)) initialColResize(self.dustTableView, [100, .7, .3]) self.dustTableView.setContextMenuPolicy(Qt.CustomContextMenu) self.lblTxioInfo = QRichLabel('') self.lblTxioInfo.setMinimumWidth( tightSizeNChar(self.lblTxioInfo, 30)[0]) self.main.connect(self.main.walletsView, SIGNAL('clicked(QModelIndex)'), updateDustLimit) self.dustBGoneFrame = makeVertFrame( [topRow, secondRow, limitPanel, self.dustTableView, 'stretch']) # Now set the scrollarea widget to the layout self.tabToDisplay = QScrollArea() self.tabToDisplay.setWidgetResizable(True) self.tabToDisplay.setWidget(self.dustBGoneFrame) def getSelectedWlt(self): wlt = None selectedWltList = self.main.walletsView.selectedIndexes() if len(selectedWltList) > 0: row = selectedWltList[0].row() wltID = str(self.main.walletsView.model().index( row, WLTVIEWCOLS.ID).data().toString()) wlt = self.main.walletMap[wltID] return wlt ############################################################################# def getTabToDisplay(self): return self.tabToDisplay def injectShutdownFunc(self): try: self.main.writeSetting('DustLedgerCols', saveTableView(self.dustTableView)) except: LOGEXCEPT('Strange error during shutdown')
class PluginUpdaterDialog(SizePersistedDialog): initial_extra_size = QSize(350, 100) forum_label_text = _('Plugin homepage') def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE): SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog') self.gui = gui self.forum_link = None self.zip_url = None self.model = None self.do_restart = False self._initialize_controls() self._create_context_menu() display_plugins = read_available_plugins() if display_plugins: self.model = DisplayPluginModel(display_plugins) self.proxy_model = DisplayPluginSortFilterModel(self) self.proxy_model.setSourceModel(self.model) self.plugin_view.setModel(self.proxy_model) self.plugin_view.resizeColumnsToContents() self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed) self.plugin_view.doubleClicked.connect(self.install_button.click) self.filter_combo.setCurrentIndex(initial_filter) self._select_and_focus_view() else: error_dialog(self.gui, _('Update Check Failed'), _('Unable to reach the plugin index page.'), det_msg=INDEX_URL, show=True) self.filter_combo.setEnabled(False) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() def _initialize_controls(self): self.setWindowTitle(_('User plugins')) self.setWindowIcon(QIcon(I('plugins/plugin_updater.png'))) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png', _('User Plugins')) layout.addLayout(title_layout) header_layout = QHBoxLayout() layout.addLayout(header_layout) self.filter_combo = PluginFilterComboBox(self) self.filter_combo.setMinimumContentsLength(20) self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed) header_layout.addWidget(QLabel(_('Filter list of plugins')+':', self)) header_layout.addWidget(self.filter_combo) header_layout.addStretch(10) # filter plugins by name header_layout.addWidget(QLabel(_('Filter by name')+':', self)) self.filter_by_name_lineedit = QLineEdit(self) self.filter_by_name_lineedit.setText("") self.filter_by_name_lineedit.textChanged.connect(self._filter_name_lineedit_changed) header_layout.addWidget(self.filter_by_name_lineedit) self.plugin_view = QTableView(self) self.plugin_view.horizontalHeader().setStretchLastSection(True) self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection) self.plugin_view.setAlternatingRowColors(True) self.plugin_view.setSortingEnabled(True) self.plugin_view.setIconSize(QSize(28, 28)) layout.addWidget(self.plugin_view) details_layout = QHBoxLayout() layout.addLayout(details_layout) forum_label = self.forum_label = QLabel('') forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) forum_label.linkActivated.connect(self._forum_label_activated) details_layout.addWidget(QLabel(_('Description')+':', self), 0, Qt.AlignLeft) details_layout.addWidget(forum_label, 1, Qt.AlignRight) self.description = QLabel(self) self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.description.setMinimumHeight(40) self.description.setWordWrap(True) layout.addWidget(self.description) self.button_box = QDialogButtonBox(QDialogButtonBox.Close) self.button_box.rejected.connect(self.reject) self.finished.connect(self._finished) self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.AcceptRole) self.install_button.setToolTip(_('Install the selected plugin')) self.install_button.clicked.connect(self._install_clicked) self.install_button.setEnabled(False) self.configure_button = self.button_box.addButton(' '+_('&Customize plugin ')+' ', QDialogButtonBox.ResetRole) self.configure_button.setToolTip(_('Customize the options for this plugin')) self.configure_button.clicked.connect(self._configure_clicked) self.configure_button.setEnabled(False) layout.addWidget(self.button_box) def update_forum_label(self): txt = '' if self.forum_link: txt = '<a href="%s">%s</a>' % (self.forum_link, self.forum_label_text) self.forum_label.setText(txt) def _create_context_menu(self): self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu) self.install_action = QAction(QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self) self.install_action.setToolTip(_('Install the selected plugin')) self.install_action.triggered.connect(self._install_clicked) self.install_action.setEnabled(False) self.plugin_view.addAction(self.install_action) self.history_action = QAction(QIcon(I('chapters.png')), _('Version &History'), self) self.history_action.setToolTip(_('Show history of changes to this plugin')) self.history_action.triggered.connect(self._history_clicked) self.history_action.setEnabled(False) self.plugin_view.addAction(self.history_action) self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &Forum Thread'), self) self.forum_action.triggered.connect(self._forum_label_activated) self.forum_action.setEnabled(False) self.plugin_view.addAction(self.forum_action) sep1 = QAction(self) sep1.setSeparator(True) self.plugin_view.addAction(sep1) self.toggle_enabled_action = QAction(_('Enable/&Disable plugin'), self) self.toggle_enabled_action.setToolTip(_('Enable or disable this plugin')) self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked) self.toggle_enabled_action.setEnabled(False) self.plugin_view.addAction(self.toggle_enabled_action) self.uninstall_action = QAction(_('&Remove plugin'), self) self.uninstall_action.setToolTip(_('Uninstall the selected plugin')) self.uninstall_action.triggered.connect(self._uninstall_clicked) self.uninstall_action.setEnabled(False) self.plugin_view.addAction(self.uninstall_action) sep2 = QAction(self) sep2.setSeparator(True) self.plugin_view.addAction(sep2) self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self) self.donate_enabled_action.setToolTip(_('Donate to the developer of this plugin')) self.donate_enabled_action.triggered.connect(self._donate_clicked) self.donate_enabled_action.setEnabled(False) self.plugin_view.addAction(self.donate_enabled_action) sep3 = QAction(self) sep3.setSeparator(True) self.plugin_view.addAction(sep3) self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self) self.configure_action.setToolTip(_('Customize the options for this plugin')) self.configure_action.triggered.connect(self._configure_clicked) self.configure_action.setEnabled(False) self.plugin_view.addAction(self.configure_action) def _finished(self, *args): if self.model: update_plugins = filter(filter_upgradeable_plugins, self.model.display_plugins) self.gui.recalc_update_label(len(update_plugins)) def _plugin_current_changed(self, current, previous): if current.isValid(): actual_idx = self.proxy_model.mapToSource(current) display_plugin = self.model.display_plugins[actual_idx.row()] self.description.setText(display_plugin.description) self.forum_link = display_plugin.forum_link self.zip_url = display_plugin.zip_url self.forum_action.setEnabled(bool(self.forum_link)) self.install_button.setEnabled(display_plugin.is_valid_to_install()) self.install_action.setEnabled(self.install_button.isEnabled()) self.uninstall_action.setEnabled(display_plugin.is_installed()) self.history_action.setEnabled(display_plugin.has_changelog) self.configure_button.setEnabled(display_plugin.is_installed()) self.configure_action.setEnabled(self.configure_button.isEnabled()) self.toggle_enabled_action.setEnabled(display_plugin.is_installed()) self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link)) else: self.description.setText('') self.forum_link = None self.zip_url = None self.forum_action.setEnabled(False) self.install_button.setEnabled(False) self.install_action.setEnabled(False) self.uninstall_action.setEnabled(False) self.history_action.setEnabled(False) self.configure_button.setEnabled(False) self.configure_action.setEnabled(False) self.toggle_enabled_action.setEnabled(False) self.donate_enabled_action.setEnabled(False) self.update_forum_label() def _donate_clicked(self): plugin = self._selected_display_plugin() if plugin and plugin.donation_link: open_url(QUrl(plugin.donation_link)) def _select_and_focus_view(self, change_selection=True): if change_selection and self.plugin_view.model().rowCount() > 0: self.plugin_view.selectRow(0) else: idx = self.plugin_view.selectionModel().currentIndex() self._plugin_current_changed(idx, 0) self.plugin_view.setFocus() def _filter_combo_changed(self, idx): self.filter_by_name_lineedit.setText("") # clear the name filter text when a different group was selected self.proxy_model.set_filter_criteria(idx) if idx == FILTER_NOT_INSTALLED: self.plugin_view.sortByColumn(5, Qt.DescendingOrder) else: self.plugin_view.sortByColumn(0, Qt.AscendingOrder) self._select_and_focus_view() def _filter_name_lineedit_changed(self, text): self.proxy_model.set_filter_text(text) # set the filter text for filterAcceptsRow def _forum_label_activated(self): if self.forum_link: open_url(QUrl(self.forum_link)) def _selected_display_plugin(self): idx = self.plugin_view.selectionModel().currentIndex() actual_idx = self.proxy_model.mapToSource(idx) return self.model.display_plugins[actual_idx.row()] def _uninstall_plugin(self, name_to_remove): if DEBUG: prints('Removing plugin: ', name_to_remove) remove_plugin(name_to_remove) # Make sure that any other plugins that required this plugin # to be uninstalled first have the requirement removed for display_plugin in self.model.display_plugins: # Make sure we update the status and display of the # plugin we just uninstalled if name_to_remove in display_plugin.uninstall_plugins: if DEBUG: prints('Removing uninstall dependency for: ', display_plugin.name) display_plugin.uninstall_plugins.remove(name_to_remove) if display_plugin.name == name_to_remove: if DEBUG: prints('Resetting plugin to uninstalled status: ', display_plugin.name) display_plugin.installed_version = None display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.refresh_plugin(display_plugin) def _uninstall_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog(self, _('Are you sure?'), '<p>'+ _('Are you sure you want to uninstall the <b>%s</b> plugin?')%display_plugin.name, show_copy_button=False): return self._uninstall_plugin(display_plugin.name) if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.reset() self._select_and_focus_view() else: self._select_and_focus_view(change_selection=False) def _install_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog(self, _('Install %s')%display_plugin.name, '<p>' + _('Installing plugins is a <b>security risk</b>. ' 'Plugins can contain a virus/malware. ' 'Only install it if you got it from a trusted source.' ' Are you sure you want to proceed?'), show_copy_button=False): return if display_plugin.uninstall_plugins: uninstall_names = list(display_plugin.uninstall_plugins) if DEBUG: prints('Uninstalling plugin: ', ', '.join(uninstall_names)) for name_to_remove in uninstall_names: self._uninstall_plugin(name_to_remove) plugin_zip_url = display_plugin.zip_url if DEBUG: prints('Downloading plugin zip attachment: ', plugin_zip_url) self.gui.status_bar.showMessage(_('Downloading plugin zip attachment: %s') % plugin_zip_url) zip_path = self._download_zip(plugin_zip_url) if DEBUG: prints('Installing plugin: ', zip_path) self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path) do_restart = False try: try: plugin = add_plugin(zip_path) except NameConflict as e: return error_dialog(self.gui, _('Already exists'), unicode(e), show=True) # Check for any toolbars to add to. widget = ConfigWidget(self.gui) widget.gui = self.gui widget.check_for_add_to_toolbars(plugin) self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name) d = info_dialog(self.gui, _('Success'), _('Plugin <b>{0}</b> successfully installed under <b>' ' {1} plugins</b>. You may have to restart calibre ' 'for the plugin to take effect.').format(plugin.name, plugin.type), show_copy_button=False) b = d.bb.addButton(_('Restart calibre now'), d.bb.AcceptRole) b.setIcon(QIcon(I('lt.png'))) d.do_restart = False def rf(): d.do_restart = True b.clicked.connect(rf) d.set_details('') d.exec_() b.clicked.disconnect() do_restart = d.do_restart display_plugin.plugin = plugin # We cannot read the 'actual' version information as the plugin will not be loaded yet display_plugin.installed_version = display_plugin.available_version except: if DEBUG: prints('ERROR occurred while installing plugin: %s'%display_plugin.name) traceback.print_exc() error_dialog(self.gui, _('Install Plugin Failed'), _('A problem occurred while installing this plugin.' ' This plugin will now be uninstalled.' ' Please post the error message in details below into' ' the forum thread for this plugin and restart Calibre.'), det_msg=traceback.format_exc(), show=True) if DEBUG: prints('Due to error now uninstalling plugin: %s'%display_plugin.name) remove_plugin(display_plugin.name) display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.reset() self._select_and_focus_view() else: self.model.refresh_plugin(display_plugin) self._select_and_focus_view(change_selection=False) if do_restart: self.do_restart = True self.accept() def _history_clicked(self): display_plugin = self._selected_display_plugin() text = self._read_version_history_html(display_plugin.forum_link) if text: dlg = VersionHistoryDialog(self, display_plugin.name, text) dlg.exec_() else: return error_dialog(self, _('Version history missing'), _('Unable to find the version history for %s')%display_plugin.name, show=True) def _configure_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.is_customizable(): return info_dialog(self, _('Plugin not customizable'), _('Plugin: %s does not need customization')%plugin.name, show=True) from calibre.customize import InterfaceActionBase if isinstance(plugin, InterfaceActionBase) and not getattr(plugin, 'actual_iaction_plugin_loaded', False): return error_dialog(self, _('Must restart'), _('You must restart calibre before you can' ' configure the <b>%s</b> plugin')%plugin.name, show=True) plugin.do_user_config(self.parent()) def _toggle_enabled_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.can_be_disabled: return error_dialog(self,_('Plugin cannot be disabled'), _('The plugin: %s cannot be disabled')%plugin.name, show=True) if is_disabled(plugin): enable_plugin(plugin) else: disable_plugin(plugin) self.model.refresh_plugin(display_plugin) def _read_version_history_html(self, forum_link): br = browser() br.set_handle_gzip(True) try: raw = br.open_novisit(forum_link).read() if not raw: return None except: traceback.print_exc() return None raw = raw.decode('utf-8', errors='replace') root = html.fromstring(raw) spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]') for spoiler_node in spoiler_nodes: try: if spoiler_node.getprevious() is None: # This is a spoiler node that has been indented using [INDENT] # Need to go up to parent div, then previous node to get header heading_node = spoiler_node.getparent().getprevious() else: # This is a spoiler node after a BR tag from the heading heading_node = spoiler_node.getprevious().getprevious() if heading_node is None: continue if heading_node.text_content().lower().find('version history') != -1: div_node = spoiler_node.xpath('div')[0] text = html.tostring(div_node, method='html', encoding=unicode) return re.sub('<div\s.*?>', '<div>', text) except: if DEBUG: prints('======= MobileRead Parse Error =======') traceback.print_exc() prints(html.tostring(spoiler_node)) return None def _download_zip(self, plugin_zip_url): from calibre.ptempfile import PersistentTemporaryFile br = browser(user_agent='%s %s' % (__appname__, __version__)) raw = br.open_novisit(plugin_zip_url).read() with PersistentTemporaryFile('.zip') as pt: pt.write(raw) return pt.name
class PluginObject(object): tabName = 'Pass Phrase Finder' maxVersion = '0.93.99' ############################################################################# def __init__(self, main): self.searchThread = None self.passPhraseFinder = None self.isSearchOver = False def updateResults(resultStr): self.resultStr += resultStr def updateDisplay(): if len(self.resultStr) > 0: self.resultsDisplay.append(self.resultStr) self.resultStr = '' self.resultsDisplay.moveCursor(QtGui.QTextCursor.End) self.resultsDisplay.repaint() if self.isSearchOver: endSearch() # Call this from another thread to end the search def terminateSearch(): self.isSearchOver = True # Call this from the main thread to end the search def endSearch(): self.main.extraHeartbeatAlways.remove(updateDisplay) self.searchButton.setEnabled(True) self.stopButton.setEnabled(False) # If the thread is still searching tell the pass phrase finder to stop if self.passPhraseFinder and self.searchThread and not self.searchThread.isFinished(): self.passPhraseFinder.isStopped = True def searchForPassphrase(): # Get the selected wallet from the main screen wlt = self.getSelectedWlt() if wlt and not wlt.watchingOnly and wlt.isLocked: self.resultStr = '' self.passPhraseFinder = PassPhraseFinder(wlt) self.resultsDisplay.setText(QString('')) self.main.extraHeartbeatAlways.append(updateDisplay) if len(self.segOrdStrSet) > 0: # From self.segOrdStrList, create a list of lists of indexes that describe the segment orderings to search # In other words convert all of the strings in orderings list to lists of integers segOrdIntListList = [] for ordStr in self.segOrdStrSet: # The indexes provided by the users are 1 based, and the list indexes ought to be 0 based segOrdIntListList.append([int(indexStr)-1 for indexStr in ordStr.split(',')]) self.searchThread = PyBackgroundThread(self.passPhraseFinder.searchForPassPhrase, [segDef.getSegList() for segDef in self.segDefList], segOrdIntListList, updateResults, terminateSearch ) # Reset the isSearchOver flag self.isSearchOver = False self.searchThread.start() # Disable search button adn enabled stop button self.stopButton.setEnabled(True) self.searchButton.setEnabled(False) else: QMessageBox.warning(self.main, tr('Invalid'), tr(""" There are no valid segment combinations to search. Please add at least one segment and ordering to search."""), QMessageBox.Ok) else: QMessageBox.warning(self.main, tr('Invalid'), tr(""" No valid wallet is selected. Please select a locked non-watching-only from Available Wallets."""), QMessageBox.Ok) def addKnownSegment(): dlgEnterSegment = DlgEnterSegment(main, main) if dlgEnterSegment.exec_(): segmentText = str(dlgEnterSegment.editSegment.text()) if len(segmentText)>0: self.segDefList.append(KnownSeg(segmentText)) self.segDefTableModel.updateSegList(self.segDefList) def addUnknownCaseSegment(): dlgEnterSegment = DlgEnterSegment(main, main) if dlgEnterSegment.exec_(): segmentText = str(dlgEnterSegment.editSegment.text()) if len(segmentText)>0: self.segDefList.append(UnknownCaseSeg(segmentText)) self.segDefTableModel.updateSegList(self.segDefList) def addUnknownOrderSegment(): dlgEnterSegment = DlgEnterSegment(main, main, isUnknownOrder=True) if dlgEnterSegment.exec_(): segmentText = str(dlgEnterSegment.editSegment.text()) minLen = int(str(dlgEnterSegment.minSelector.currentText())) maxLen = int(str(dlgEnterSegment.maxSelector.currentText())) if len(segmentText)>0: self.segDefList.append(UnknownSeg(segmentText, minLen, maxLen)) self.segDefTableModel.updateSegList(self.segDefList) def addOrdering(): if len(self.segDefList) > 0: dlgSpecifyOrdering = DlgSpecifyOrdering(main, main, len(self.segDefList)) if dlgSpecifyOrdering.exec_(): self.segOrdStrSet.add(str(dlgSpecifyOrdering.parseOrderingList()).strip('[]')) self.updateOrderingListBox() else: QMessageBox.warning(self.main, tr('Not Ready'), tr(""" No segments have been entered. You must enter some segments before you can order them."""), QMessageBox.Ok) self.main = main self.segDefList = [] self.segOrdStrSet = set() segmentHeader = QRichLabel(tr("""<b>Build segments for pass phrase search: </b>"""), doWrap=False) self.knownButton = QPushButton("Add Known Segment") self.unknownCaseButton = QPushButton("Add Unknown Case Segment") self.unknownOrderButton = QPushButton("Add Unknown Order Segment") self.main.connect(self.knownButton, SIGNAL('clicked()'), addKnownSegment) self.main.connect(self.unknownCaseButton, SIGNAL('clicked()'), addUnknownCaseSegment) self.main.connect(self.unknownOrderButton, SIGNAL('clicked()'), addUnknownOrderSegment) topRow = makeHorizFrame([segmentHeader, self.knownButton, self.unknownCaseButton, self.unknownOrderButton, 'stretch']) self.segDefTableModel = SegDefDisplayModel() self.segDefTableView = QTableView() self.segDefTableView.setModel(self.segDefTableModel) self.segDefTableView.setSelectionBehavior(QTableView.SelectRows) self.segDefTableView.setSelectionMode(QTableView.SingleSelection) self.segDefTableView.verticalHeader().setDefaultSectionSize(20) self.segDefTableView.verticalHeader().hide() h = tightSizeNChar(self.segDefTableView, 1)[1] self.segDefTableView.setMinimumHeight(2 * (1.3 * h)) self.segDefTableView.setMaximumHeight(10 * (1.3 * h)) initialColResize(self.segDefTableView, [.1, .2, .4, .1, .1, .1]) self.segDefTableView.customContextMenuRequested.connect(self.showSegContextMenu) self.segDefTableView.setContextMenuPolicy(Qt.CustomContextMenu) segmentOrderingsHeader = QRichLabel(tr("""<b>Specify orderings for pass phrase search: </b>"""), doWrap=False) self.addOrderingButton = QPushButton("Add Ordering") self.main.connect(self.addOrderingButton, SIGNAL('clicked()'), addOrdering) orderingButtonPanel = makeHorizFrame([segmentOrderingsHeader, self.addOrderingButton, 'stretch']) self.segOrdListBox = QListWidget() self.segOrdListBox.customContextMenuRequested.connect(self.showOrdContextMenu) self.segOrdListBox.setContextMenuPolicy(Qt.CustomContextMenu) self.searchButton = QPushButton("Search") self.main.connect(self.searchButton, SIGNAL('clicked()'), searchForPassphrase) self.stopButton = QPushButton("Stop Searching") self.stopButton.setEnabled(False) self.main.connect(self.stopButton, SIGNAL('clicked()'), endSearch) totalSearchLabel = QRichLabel(tr("""<b>Total Passphrase Tries To Search: </b>"""), doWrap=False) self.totalSearchTriesDisplay = QLineEdit() self.totalSearchTriesDisplay.setReadOnly(True) self.totalSearchTriesDisplay.setText(QString('0')) self.totalSearchTriesDisplay.setFont(GETFONT('Fixed')) self.totalSearchTriesDisplay.setMinimumWidth(tightSizeNChar(self.totalSearchTriesDisplay, 6)[0]) self.totalSearchTriesDisplay.setMaximumWidth(tightSizeNChar(self.totalSearchTriesDisplay, 12)[0]) searchButtonPanel = makeHorizFrame([self.searchButton, self.stopButton, 'stretch', totalSearchLabel, self.totalSearchTriesDisplay]) self.resultsDisplay = QTextEdit() self.resultsDisplay.setReadOnly(True) self.resultsDisplay.setFont(GETFONT('Fixed')) self.resultsDisplay.setMinimumHeight(100) self.searchPanel = makeVertFrame([topRow, self.segDefTableView, orderingButtonPanel, self.segOrdListBox, searchButtonPanel, self.resultsDisplay, 'stretch']) # Now set the scrollarea widget to the layout self.tabToDisplay = QScrollArea() self.tabToDisplay.setWidgetResizable(True) self.tabToDisplay.setWidget(self.searchPanel) def getSelectedWlt(self): wlt = None selectedWltList = self.main.walletsView.selectedIndexes() if len(selectedWltList)>0: row = selectedWltList[0].row() wltID = str(self.main.walletsView.model().index(row, WLTVIEWCOLS.ID).data().toString()) wlt = self.main.walletMap[wltID] return wlt def showSegContextMenu(self): menu = QMenu(self.segDefTableView) if len(self.segDefTableView.selectedIndexes())==0: return row = self.segDefTableView.selectedIndexes()[0].row() deleteSegMenuItem = menu.addAction("Delete Segment") action = menu.exec_(QCursor.pos()) if action == deleteSegMenuItem: self.deleteSegRow(row) def showOrdContextMenu(self): menu = QMenu(self.segOrdListBox) if len(self.segOrdListBox.selectedItems())==0: return item = self.segOrdListBox.currentItem() deleteOrdMenuItem = menu.addAction("Delete Ordering") action = menu.exec_(QCursor.pos()) if action == deleteOrdMenuItem: self.deleteOrdItem(item) def deleteSegRow(self, row): self.segDefList.remove(self.segDefList[row]) self.segDefTableModel.updateSegList(self.segDefList) self.segOrdStrSet.clear() self.updateOrderingListBox() def deleteOrdItem(self, ordItem): ordText = str(ordItem.text()) self.segOrdStrSet.remove(ordText) self.updateOrderingListBox() def getTabToDisplay(self): return self.tabToDisplay def updateOrderingListBox(self): self.segOrdListBox.clear() segOrdList = list(self.segOrdStrSet) segOrdList.sort() totalTries = 0 for ordStr in segOrdList: self.segOrdListBox.addItem(QListWidgetItem(ordStr)) totalTries += self.calculateTries(ordStr) if totalTries > BILLION_INT: self.totalSearchTriesDisplay.setText(OVER_BILLION_STR) else: self.totalSearchTriesDisplay.setText(str(totalTries)) def calculateTries(self, ordStr): ordIntList = [int(indexStr) for indexStr in ordStr.split(',')] totalTries = 1 # Multiply each of the totals of segment instance together. for ordInt in ordIntList: totalTries *= self.segDefList[ordInt-1].getSegListLen() return totalTries
class PluginUpdaterDialog(SizePersistedDialog): initial_extra_size = QSize(350, 100) def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE): SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog') self.gui = gui self.forum_link = None self.model = None self._initialize_controls() self._create_context_menu() display_plugins = read_available_plugins() if display_plugins: self.model = DisplayPluginModel(display_plugins) self.proxy_model = DisplayPluginSortFilterModel(self) self.proxy_model.setSourceModel(self.model) self.plugin_view.setModel(self.proxy_model) self.plugin_view.resizeColumnsToContents() self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed) self.plugin_view.doubleClicked.connect(self.install_button.click) self.filter_combo.setCurrentIndex(initial_filter) self._select_and_focus_view() else: error_dialog(self.gui, _('Update Check Failed'), _('Unable to reach the MobileRead plugins forum index page.'), det_msg=MR_INDEX_URL, show=True) self.filter_combo.setEnabled(False) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() def _initialize_controls(self): self.setWindowTitle(_('User plugins')) self.setWindowIcon(QIcon(I('plugins/plugin_updater.png'))) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png', _('User Plugins')) layout.addLayout(title_layout) header_layout = QHBoxLayout() layout.addLayout(header_layout) self.filter_combo = PluginFilterComboBox(self) self.filter_combo.setMinimumContentsLength(20) self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed) header_layout.addWidget(QLabel(_('Filter list of plugins')+':', self)) header_layout.addWidget(self.filter_combo) header_layout.addStretch(10) self.plugin_view = QTableView(self) self.plugin_view.horizontalHeader().setStretchLastSection(True) self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection) self.plugin_view.setAlternatingRowColors(True) self.plugin_view.setSortingEnabled(True) self.plugin_view.setIconSize(QSize(28, 28)) layout.addWidget(self.plugin_view) details_layout = QHBoxLayout() layout.addLayout(details_layout) forum_label = QLabel('<a href="http://www.foo.com/">Plugin Forum Thread</a>', self) forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) forum_label.linkActivated.connect(self._forum_label_activated) details_layout.addWidget(QLabel(_('Description')+':', self), 0, Qt.AlignLeft) details_layout.addWidget(forum_label, 1, Qt.AlignRight) self.description = QLabel(self) self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.description.setMinimumHeight(40) layout.addWidget(self.description) self.button_box = QDialogButtonBox(QDialogButtonBox.Close) self.button_box.rejected.connect(self.reject) self.finished.connect(self._finished) self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.AcceptRole) self.install_button.setToolTip(_('Install the selected plugin')) self.install_button.clicked.connect(self._install_clicked) self.install_button.setEnabled(False) self.configure_button = self.button_box.addButton(' '+_('&Customize plugin ')+' ', QDialogButtonBox.ResetRole) self.configure_button.setToolTip(_('Customize the options for this plugin')) self.configure_button.clicked.connect(self._configure_clicked) self.configure_button.setEnabled(False) layout.addWidget(self.button_box) def _create_context_menu(self): self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu) self.install_action = QAction(QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self) self.install_action.setToolTip(_('Install the selected plugin')) self.install_action.triggered.connect(self._install_clicked) self.install_action.setEnabled(False) self.plugin_view.addAction(self.install_action) self.history_action = QAction(QIcon(I('chapters.png')), _('Version &History'), self) self.history_action.setToolTip(_('Show history of changes to this plugin')) self.history_action.triggered.connect(self._history_clicked) self.history_action.setEnabled(False) self.plugin_view.addAction(self.history_action) self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &Forum Thread'), self) self.forum_action.triggered.connect(self._forum_label_activated) self.forum_action.setEnabled(False) self.plugin_view.addAction(self.forum_action) sep1 = QAction(self) sep1.setSeparator(True) self.plugin_view.addAction(sep1) self.toggle_enabled_action = QAction(_('Enable/&Disable plugin'), self) self.toggle_enabled_action.setToolTip(_('Enable or disable this plugin')) self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked) self.toggle_enabled_action.setEnabled(False) self.plugin_view.addAction(self.toggle_enabled_action) self.uninstall_action = QAction(_('&Remove plugin'), self) self.uninstall_action.setToolTip(_('Uninstall the selected plugin')) self.uninstall_action.triggered.connect(self._uninstall_clicked) self.uninstall_action.setEnabled(False) self.plugin_view.addAction(self.uninstall_action) sep2 = QAction(self) sep2.setSeparator(True) self.plugin_view.addAction(sep2) self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self) self.donate_enabled_action.setToolTip(_('Donate to the developer of this plugin')) self.donate_enabled_action.triggered.connect(self._donate_clicked) self.donate_enabled_action.setEnabled(False) self.plugin_view.addAction(self.donate_enabled_action) sep3 = QAction(self) sep3.setSeparator(True) self.plugin_view.addAction(sep3) self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self) self.configure_action.setToolTip(_('Customize the options for this plugin')) self.configure_action.triggered.connect(self._configure_clicked) self.configure_action.setEnabled(False) self.plugin_view.addAction(self.configure_action) def _finished(self, *args): if self.model: update_plugins = filter(filter_upgradeable_plugins, self.model.display_plugins) self.gui.recalc_update_label(len(update_plugins)) def _plugin_current_changed(self, current, previous): if current.isValid(): actual_idx = self.proxy_model.mapToSource(current) display_plugin = self.model.display_plugins[actual_idx.row()] self.description.setText(display_plugin.description) self.forum_link = display_plugin.forum_link self.forum_action.setEnabled(bool(self.forum_link)) self.install_button.setEnabled(display_plugin.is_valid_to_install()) self.install_action.setEnabled(self.install_button.isEnabled()) self.uninstall_action.setEnabled(display_plugin.is_installed()) self.history_action.setEnabled(display_plugin.has_changelog) self.configure_button.setEnabled(display_plugin.is_installed()) self.configure_action.setEnabled(self.configure_button.isEnabled()) self.toggle_enabled_action.setEnabled(display_plugin.is_installed()) self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link)) else: self.description.setText('') self.forum_link = None self.forum_action.setEnabled(False) self.install_button.setEnabled(False) self.install_action.setEnabled(False) self.uninstall_action.setEnabled(False) self.history_action.setEnabled(False) self.configure_button.setEnabled(False) self.configure_action.setEnabled(False) self.toggle_enabled_action.setEnabled(False) self.donate_enabled_action.setEnabled(False) def _donate_clicked(self): plugin = self._selected_display_plugin() if plugin and plugin.donation_link: open_url(QUrl(plugin.donation_link)) def _select_and_focus_view(self, change_selection=True): if change_selection and self.plugin_view.model().rowCount() > 0: self.plugin_view.selectRow(0) else: idx = self.plugin_view.selectionModel().currentIndex() self._plugin_current_changed(idx, 0) self.plugin_view.setFocus() def _filter_combo_changed(self, idx): self.proxy_model.set_filter_criteria(idx) if idx == FILTER_NOT_INSTALLED: self.plugin_view.sortByColumn(5, Qt.DescendingOrder) else: self.plugin_view.sortByColumn(0, Qt.AscendingOrder) self._select_and_focus_view() def _forum_label_activated(self): if self.forum_link: open_url(QUrl(self.forum_link)) def _selected_display_plugin(self): idx = self.plugin_view.selectionModel().currentIndex() actual_idx = self.proxy_model.mapToSource(idx) return self.model.display_plugins[actual_idx.row()] def _uninstall_plugin(self, name_to_remove): if DEBUG: prints('Removing plugin: ', name_to_remove) remove_plugin(name_to_remove) # Make sure that any other plugins that required this plugin # to be uninstalled first have the requirement removed for display_plugin in self.model.display_plugins: # Make sure we update the status and display of the # plugin we just uninstalled if name_to_remove in display_plugin.uninstall_plugins: if DEBUG: prints('Removing uninstall dependency for: ', display_plugin.name) display_plugin.uninstall_plugins.remove(name_to_remove) if display_plugin.name == name_to_remove: if DEBUG: prints('Resetting plugin to uninstalled status: ', display_plugin.name) display_plugin.installed_version = None display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.refresh_plugin(display_plugin) def _uninstall_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog(self, _('Are you sure?'), '<p>'+ _('Are you sure you want to uninstall the <b>%s</b> plugin?')%display_plugin.name, show_copy_button=False): return self._uninstall_plugin(display_plugin.name) if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.reset() self._select_and_focus_view() else: self._select_and_focus_view(change_selection=False) def _install_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog(self, _('Install %s')%display_plugin.name, '<p>' + \ _('Installing plugins is a <b>security risk</b>. ' 'Plugins can contain a virus/malware. ' 'Only install it if you got it from a trusted source.' ' Are you sure you want to proceed?'), show_copy_button=False): return if display_plugin.uninstall_plugins: uninstall_names = list(display_plugin.uninstall_plugins) if DEBUG: prints('Uninstalling plugin: ', ', '.join(uninstall_names)) for name_to_remove in uninstall_names: self._uninstall_plugin(name_to_remove) if DEBUG: prints('Locating zip file for %s: %s'% (display_plugin.name, display_plugin.forum_link)) self.gui.status_bar.showMessage( _('Locating zip file for %(name)s: %(link)s') % dict( name=display_plugin.name, link=display_plugin.forum_link)) plugin_zip_url = self._read_zip_attachment_url(display_plugin.forum_link) if not plugin_zip_url: return error_dialog(self.gui, _('Install Plugin Failed'), _('Unable to locate a plugin zip file for <b>%s</b>') % display_plugin.name, det_msg=display_plugin.forum_link, show=True) if DEBUG: prints('Downloading plugin zip attachment: ', plugin_zip_url) self.gui.status_bar.showMessage(_('Downloading plugin zip attachment: %s') % plugin_zip_url) zip_path = self._download_zip(plugin_zip_url) if DEBUG: prints('Installing plugin: ', zip_path) self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path) try: try: plugin = add_plugin(zip_path) except NameConflict as e: return error_dialog(self.gui, _('Already exists'), unicode(e), show=True) # Check for any toolbars to add to. widget = ConfigWidget(self.gui) widget.gui = self.gui widget.check_for_add_to_toolbars(plugin) self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name) info_dialog(self.gui, _('Success'), _('Plugin <b>{0}</b> successfully installed under <b>' ' {1} plugins</b>. You may have to restart calibre ' 'for the plugin to take effect.').format(plugin.name, plugin.type), show=True, show_copy_button=False) display_plugin.plugin = plugin # We cannot read the 'actual' version information as the plugin will not be loaded yet display_plugin.installed_version = display_plugin.available_version except: if DEBUG: prints('ERROR occurred while installing plugin: %s'%display_plugin.name) traceback.print_exc() error_dialog(self.gui, _('Install Plugin Failed'), _('A problem occurred while installing this plugin.' ' This plugin will now be uninstalled.' ' Please post the error message in details below into' ' the forum thread for this plugin and restart Calibre.'), det_msg=traceback.format_exc(), show=True) if DEBUG: prints('Due to error now uninstalling plugin: %s'%display_plugin.name) remove_plugin(display_plugin.name) display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.reset() self._select_and_focus_view() else: self.model.refresh_plugin(display_plugin) self._select_and_focus_view(change_selection=False) def _history_clicked(self): display_plugin = self._selected_display_plugin() text = self._read_version_history_html(display_plugin.forum_link) if text: dlg = VersionHistoryDialog(self, display_plugin.name, text) dlg.exec_() else: return error_dialog(self, _('Version history missing'), _('Unable to find the version history for %s')%display_plugin.name, show=True) def _configure_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.is_customizable(): return info_dialog(self, _('Plugin not customizable'), _('Plugin: %s does not need customization')%plugin.name, show=True) from calibre.customize import InterfaceActionBase if isinstance(plugin, InterfaceActionBase) and not getattr(plugin, 'actual_iaction_plugin_loaded', False): return error_dialog(self, _('Must restart'), _('You must restart calibre before you can' ' configure the <b>%s</b> plugin')%plugin.name, show=True) plugin.do_user_config(self.parent()) def _toggle_enabled_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.can_be_disabled: return error_dialog(self,_('Plugin cannot be disabled'), _('The plugin: %s cannot be disabled')%plugin.name, show=True) if is_disabled(plugin): enable_plugin(plugin) else: disable_plugin(plugin) self.model.refresh_plugin(display_plugin) def _read_version_history_html(self, forum_link): br = browser() br.set_handle_gzip(True) try: raw = br.open_novisit(forum_link).read() if not raw: return None except: traceback.print_exc() return None raw = raw.decode('utf-8', errors='replace') root = html.fromstring(raw) spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]') for spoiler_node in spoiler_nodes: try: if spoiler_node.getprevious() is None: # This is a spoiler node that has been indented using [INDENT] # Need to go up to parent div, then previous node to get header heading_node = spoiler_node.getparent().getprevious() else: # This is a spoiler node after a BR tag from the heading heading_node = spoiler_node.getprevious().getprevious() if heading_node is None: continue if heading_node.text_content().lower().find('version history') != -1: div_node = spoiler_node.xpath('div')[0] text = html.tostring(div_node, method='html', encoding=unicode) return re.sub('<div\s.*?>', '<div>', text) except: if DEBUG: prints('======= MobileRead Parse Error =======') traceback.print_exc() prints(html.tostring(spoiler_node)) return None def _read_zip_attachment_url(self, forum_link): br = browser() br.set_handle_gzip(True) try: raw = br.open_novisit(forum_link).read() if not raw: return None except: traceback.print_exc() return None raw = raw.decode('utf-8', errors='replace') root = html.fromstring(raw) attachment_nodes = root.xpath('//fieldset/table/tr/td/a') for attachment_node in attachment_nodes: try: filename = attachment_node.text_content().lower() if filename.find('.zip') != -1: full_url = MR_URL + attachment_node.attrib['href'] return full_url except: if DEBUG: prints('======= MobileRead Parse Error =======') traceback.print_exc() prints(html.tostring(attachment_node)) return None def _download_zip(self, plugin_zip_url): from calibre.ptempfile import PersistentTemporaryFile br = browser() br.set_handle_gzip(True) raw = br.open_novisit(plugin_zip_url).read() pt = PersistentTemporaryFile('.zip') pt.write(raw) pt.close() return pt.name
class PluginObject(object): tabName = 'Dust-B-Gone' maxVersion = '0.93.99' ############################################################################# def __init__(self, main): def updateDustLimit(): try: self.dustTableModel.updateDustList(self.getSelectedWlt(), str2coin(self.dustLimitText.text())) self.beGoneDustButton.setEnabled(len(self.dustTableModel.dustTxOutlist)>0) if self.dustTableModel.wlt: self.lblHeader.setText(tr("""<b>Dust Outputs for Wallet: %s</b>""" % self.dustTableModel.wlt.labelName)) except NegativeValueError: pass except TooMuchPrecisionError: pass except: LOGEXCEPT("Unexpected exception") pass def sendDust(): try: utxiList = [] for utxo in self.dustTableModel.dustTxOutlist: # The PyCreateAndSignTx method require PyTx and PyBtcAddress objects rawTx = TheBDM.getTxByHash(utxo.getTxHash()).serialize() a160 = CheckHash160(utxo.getRecipientScrAddr()) for pyAddr in self.dustTableModel.wlt.addrMap.values(): if a160 == pyAddr.getAddr160(): pubKey = pyAddr.binPublicKey65.toBinStr() txoIdx = utxo.getTxOutIndex() utxiList.append(UnsignedTxInput(rawTx, txoIdx, None, pubKey)) break # Make copies, destroy them in the finally clause privKeyMap = {} for addrObj in self.dustTableModel.wlt.addrMap.values(): scrAddr = SCRADDR_P2PKH_BYTE + addrObj.getAddr160() if self.dustTableModel.wlt.useEncryption and self.dustTableModel.wlt.isLocked: # Target wallet is encrypted... unlockdlg = DlgUnlockWallet(self.dustTableModel.wlt, self.main, self.main, 'Unlock Wallet to Import') if not unlockdlg.exec_(): QMessageBox.critical(self, 'Wallet is Locked', \ 'Cannot send dust without unlocking the wallet!', \ QMessageBox.Ok) return privKeyMap[scrAddr] = addrObj.binPrivKey32_Plain.copy() signedTx = PyCreateAndSignTx(utxiList, [], privKeyMap, SIGHASH_NONE|SIGHASH_ANYONECANPAY ) print "-------------" print binary_to_hex(signedTx.serialize()) # sock = socket.create_connection(('dust-b-gone.bitcoin.petertodd.org',80)) # sock.send(signedTx.serialize()) # sock.send(b'\n') # sock.close() except socket.error as err: QMessageBox.critical(self.main, tr('Negative Value'), tr(""" Failed to connect to dust-b-gone server: %s""" % err.strerror), QMessageBox.Ok) except NegativeValueError: QMessageBox.critical(self.main, tr('Negative Value'), tr(""" You must enter a positive value of at least 0.0000 0001 and less than %s for the dust limit.""" % MAX_DUST_LIMIT_STR), QMessageBox.Ok) except TooMuchPrecisionError: QMessageBox.critical(self.main.main, tr('Too much precision'), tr(""" Bitcoins can only be specified down to 8 decimal places. The smallest unit of a Groestlcoin is 0.0000 0001 GRS. Please enter a dust limit of at least 0.0000 0001 and less than %s.""" % MAX_DUST_LIMIT_STR), QMessageBox.Ok) finally: for scraddr in privKeyMap: privKeyMap[scraddr].destroy() self.main = main self.lblHeader = QRichLabel(tr("""<b>Dust Outputs for Wallet: None Selected</b>"""), doWrap=False) self.beGoneDustButton = QPushButton("Remove Dust") self.beGoneDustButton.setEnabled(False) self.main.connect(self.beGoneDustButton, SIGNAL('clicked()'), sendDust) topRow = makeHorizFrame([self.lblHeader,'stretch']) secondRow = makeHorizFrame([self.beGoneDustButton, 'stretch']) self.dustLimitLabel = QLabel("Max Dust Value (GRS): ") self.dustLimitText = QLineEdit() self.dustLimitText.setFont(GETFONT('Fixed')) self.dustLimitText.setMinimumWidth(tightSizeNChar(self.dustLimitText, 6)[0]) self.dustLimitText.setMaximumWidth(tightSizeNChar(self.dustLimitText, 12)[0]) self.dustLimitText.setAlignment(Qt.AlignRight) self.dustLimitText.setText(coin2str(DEFAULT_DUST_LIMIT)) self.main.connect(self.dustLimitText, SIGNAL('textChanged(QString)'), updateDustLimit) limitPanel = makeHorizFrame([self.dustLimitLabel, self.dustLimitText, 'stretch']) self.dustTableModel = DustDisplayModel() self.dustTableView = QTableView() self.dustTableView.setModel(self.dustTableModel) self.dustTableView.setSelectionMode(QTableView.NoSelection) self.dustTableView.verticalHeader().setDefaultSectionSize(20) self.dustTableView.verticalHeader().hide() h = tightSizeNChar(self.dustTableView, 1)[1] self.dustTableView.setMinimumHeight(2 * (1.3 * h)) self.dustTableView.setMaximumHeight(10 * (1.3 * h)) initialColResize(self.dustTableView, [100, .7, .3]) self.dustTableView.setContextMenuPolicy(Qt.CustomContextMenu) self.lblTxioInfo = QRichLabel('') self.lblTxioInfo.setMinimumWidth(tightSizeNChar(self.lblTxioInfo, 30)[0]) self.main.connect(self.main.walletsView, SIGNAL('clicked(QModelIndex)'), updateDustLimit) self.dustBGoneFrame = makeVertFrame([topRow, secondRow, limitPanel, self.dustTableView, 'stretch']) # Now set the scrollarea widget to the layout self.tabToDisplay = QScrollArea() self.tabToDisplay.setWidgetResizable(True) self.tabToDisplay.setWidget(self.dustBGoneFrame) def getSelectedWlt(self): wlt = None selectedWltList = self.main.walletsView.selectedIndexes() if len(selectedWltList)>0: row = selectedWltList[0].row() wltID = str(self.main.walletsView.model().index(row, WLTVIEWCOLS.ID).data().toString()) wlt = self.main.walletMap[wltID] return wlt ############################################################################# def getTabToDisplay(self): return self.tabToDisplay def injectShutdownFunc(self): try: self.main.writeSetting('DustLedgerCols', saveTableView(self.dustTableView)) except: LOGEXCEPT('Strange error during shutdown')
class PluginUpdaterDialog(SizePersistedDialog): initial_extra_size = QSize(350, 100) def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE): SizePersistedDialog.__init__(self, gui, "Plugin Updater plugin:plugin updater dialog") self.gui = gui self.forum_link = None self.model = None self.do_restart = False self._initialize_controls() self._create_context_menu() display_plugins = read_available_plugins() if display_plugins: self.model = DisplayPluginModel(display_plugins) self.proxy_model = DisplayPluginSortFilterModel(self) self.proxy_model.setSourceModel(self.model) self.plugin_view.setModel(self.proxy_model) self.plugin_view.resizeColumnsToContents() self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed) self.plugin_view.doubleClicked.connect(self.install_button.click) self.filter_combo.setCurrentIndex(initial_filter) self._select_and_focus_view() else: error_dialog( self.gui, _("Update Check Failed"), _("Unable to reach the MobileRead plugins forum index page."), det_msg=MR_INDEX_URL, show=True, ) self.filter_combo.setEnabled(False) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() def _initialize_controls(self): self.setWindowTitle(_("User plugins")) self.setWindowIcon(QIcon(I("plugins/plugin_updater.png"))) layout = QVBoxLayout(self) self.setLayout(layout) title_layout = ImageTitleLayout(self, "plugins/plugin_updater.png", _("User Plugins")) layout.addLayout(title_layout) header_layout = QHBoxLayout() layout.addLayout(header_layout) self.filter_combo = PluginFilterComboBox(self) self.filter_combo.setMinimumContentsLength(20) self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed) header_layout.addWidget(QLabel(_("Filter list of plugins") + ":", self)) header_layout.addWidget(self.filter_combo) header_layout.addStretch(10) self.plugin_view = QTableView(self) self.plugin_view.horizontalHeader().setStretchLastSection(True) self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.plugin_view.setSelectionMode(QAbstractItemView.SingleSelection) self.plugin_view.setAlternatingRowColors(True) self.plugin_view.setSortingEnabled(True) self.plugin_view.setIconSize(QSize(28, 28)) layout.addWidget(self.plugin_view) details_layout = QHBoxLayout() layout.addLayout(details_layout) forum_label = QLabel('<a href="http://www.foo.com/">Plugin Forum Thread</a>', self) forum_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard) forum_label.linkActivated.connect(self._forum_label_activated) details_layout.addWidget(QLabel(_("Description") + ":", self), 0, Qt.AlignLeft) details_layout.addWidget(forum_label, 1, Qt.AlignRight) self.description = QLabel(self) self.description.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.description.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.description.setMinimumHeight(40) self.description.setWordWrap(True) layout.addWidget(self.description) self.button_box = QDialogButtonBox(QDialogButtonBox.Close) self.button_box.rejected.connect(self.reject) self.finished.connect(self._finished) self.install_button = self.button_box.addButton(_("&Install"), QDialogButtonBox.AcceptRole) self.install_button.setToolTip(_("Install the selected plugin")) self.install_button.clicked.connect(self._install_clicked) self.install_button.setEnabled(False) self.configure_button = self.button_box.addButton( " " + _("&Customize plugin ") + " ", QDialogButtonBox.ResetRole ) self.configure_button.setToolTip(_("Customize the options for this plugin")) self.configure_button.clicked.connect(self._configure_clicked) self.configure_button.setEnabled(False) layout.addWidget(self.button_box) def _create_context_menu(self): self.plugin_view.setContextMenuPolicy(Qt.ActionsContextMenu) self.install_action = QAction(QIcon(I("plugins/plugin_upgrade_ok.png")), _("&Install"), self) self.install_action.setToolTip(_("Install the selected plugin")) self.install_action.triggered.connect(self._install_clicked) self.install_action.setEnabled(False) self.plugin_view.addAction(self.install_action) self.history_action = QAction(QIcon(I("chapters.png")), _("Version &History"), self) self.history_action.setToolTip(_("Show history of changes to this plugin")) self.history_action.triggered.connect(self._history_clicked) self.history_action.setEnabled(False) self.plugin_view.addAction(self.history_action) self.forum_action = QAction(QIcon(I("plugins/mobileread.png")), _("Plugin &Forum Thread"), self) self.forum_action.triggered.connect(self._forum_label_activated) self.forum_action.setEnabled(False) self.plugin_view.addAction(self.forum_action) sep1 = QAction(self) sep1.setSeparator(True) self.plugin_view.addAction(sep1) self.toggle_enabled_action = QAction(_("Enable/&Disable plugin"), self) self.toggle_enabled_action.setToolTip(_("Enable or disable this plugin")) self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked) self.toggle_enabled_action.setEnabled(False) self.plugin_view.addAction(self.toggle_enabled_action) self.uninstall_action = QAction(_("&Remove plugin"), self) self.uninstall_action.setToolTip(_("Uninstall the selected plugin")) self.uninstall_action.triggered.connect(self._uninstall_clicked) self.uninstall_action.setEnabled(False) self.plugin_view.addAction(self.uninstall_action) sep2 = QAction(self) sep2.setSeparator(True) self.plugin_view.addAction(sep2) self.donate_enabled_action = QAction(QIcon(I("donate.png")), _("Donate to developer"), self) self.donate_enabled_action.setToolTip(_("Donate to the developer of this plugin")) self.donate_enabled_action.triggered.connect(self._donate_clicked) self.donate_enabled_action.setEnabled(False) self.plugin_view.addAction(self.donate_enabled_action) sep3 = QAction(self) sep3.setSeparator(True) self.plugin_view.addAction(sep3) self.configure_action = QAction(QIcon(I("config.png")), _("&Customize plugin"), self) self.configure_action.setToolTip(_("Customize the options for this plugin")) self.configure_action.triggered.connect(self._configure_clicked) self.configure_action.setEnabled(False) self.plugin_view.addAction(self.configure_action) def _finished(self, *args): if self.model: update_plugins = filter(filter_upgradeable_plugins, self.model.display_plugins) self.gui.recalc_update_label(len(update_plugins)) def _plugin_current_changed(self, current, previous): if current.isValid(): actual_idx = self.proxy_model.mapToSource(current) display_plugin = self.model.display_plugins[actual_idx.row()] self.description.setText(display_plugin.description) self.forum_link = display_plugin.forum_link self.forum_action.setEnabled(bool(self.forum_link)) self.install_button.setEnabled(display_plugin.is_valid_to_install()) self.install_action.setEnabled(self.install_button.isEnabled()) self.uninstall_action.setEnabled(display_plugin.is_installed()) self.history_action.setEnabled(display_plugin.has_changelog) self.configure_button.setEnabled(display_plugin.is_installed()) self.configure_action.setEnabled(self.configure_button.isEnabled()) self.toggle_enabled_action.setEnabled(display_plugin.is_installed()) self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link)) else: self.description.setText("") self.forum_link = None self.forum_action.setEnabled(False) self.install_button.setEnabled(False) self.install_action.setEnabled(False) self.uninstall_action.setEnabled(False) self.history_action.setEnabled(False) self.configure_button.setEnabled(False) self.configure_action.setEnabled(False) self.toggle_enabled_action.setEnabled(False) self.donate_enabled_action.setEnabled(False) def _donate_clicked(self): plugin = self._selected_display_plugin() if plugin and plugin.donation_link: open_url(QUrl(plugin.donation_link)) def _select_and_focus_view(self, change_selection=True): if change_selection and self.plugin_view.model().rowCount() > 0: self.plugin_view.selectRow(0) else: idx = self.plugin_view.selectionModel().currentIndex() self._plugin_current_changed(idx, 0) self.plugin_view.setFocus() def _filter_combo_changed(self, idx): self.proxy_model.set_filter_criteria(idx) if idx == FILTER_NOT_INSTALLED: self.plugin_view.sortByColumn(5, Qt.DescendingOrder) else: self.plugin_view.sortByColumn(0, Qt.AscendingOrder) self._select_and_focus_view() def _forum_label_activated(self): if self.forum_link: open_url(QUrl(self.forum_link)) def _selected_display_plugin(self): idx = self.plugin_view.selectionModel().currentIndex() actual_idx = self.proxy_model.mapToSource(idx) return self.model.display_plugins[actual_idx.row()] def _uninstall_plugin(self, name_to_remove): if DEBUG: prints("Removing plugin: ", name_to_remove) remove_plugin(name_to_remove) # Make sure that any other plugins that required this plugin # to be uninstalled first have the requirement removed for display_plugin in self.model.display_plugins: # Make sure we update the status and display of the # plugin we just uninstalled if name_to_remove in display_plugin.uninstall_plugins: if DEBUG: prints("Removing uninstall dependency for: ", display_plugin.name) display_plugin.uninstall_plugins.remove(name_to_remove) if display_plugin.name == name_to_remove: if DEBUG: prints("Resetting plugin to uninstalled status: ", display_plugin.name) display_plugin.installed_version = None display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.refresh_plugin(display_plugin) def _uninstall_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog( self, _("Are you sure?"), "<p>" + _("Are you sure you want to uninstall the <b>%s</b> plugin?") % display_plugin.name, show_copy_button=False, ): return self._uninstall_plugin(display_plugin.name) if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.reset() self._select_and_focus_view() else: self._select_and_focus_view(change_selection=False) def _install_clicked(self): display_plugin = self._selected_display_plugin() if not question_dialog( self, _("Install %s") % display_plugin.name, "<p>" + _( "Installing plugins is a <b>security risk</b>. " "Plugins can contain a virus/malware. " "Only install it if you got it from a trusted source." " Are you sure you want to proceed?" ), show_copy_button=False, ): return if display_plugin.uninstall_plugins: uninstall_names = list(display_plugin.uninstall_plugins) if DEBUG: prints("Uninstalling plugin: ", ", ".join(uninstall_names)) for name_to_remove in uninstall_names: self._uninstall_plugin(name_to_remove) if DEBUG: prints("Locating zip file for %s: %s" % (display_plugin.name, display_plugin.forum_link)) self.gui.status_bar.showMessage( _("Locating zip file for %(name)s: %(link)s") % dict(name=display_plugin.name, link=display_plugin.forum_link) ) plugin_zip_url = self._read_zip_attachment_url(display_plugin.forum_link) if not plugin_zip_url: return error_dialog( self.gui, _("Install Plugin Failed"), _("Unable to locate a plugin zip file for <b>%s</b>") % display_plugin.name, det_msg=display_plugin.forum_link, show=True, ) if DEBUG: prints("Downloading plugin zip attachment: ", plugin_zip_url) self.gui.status_bar.showMessage(_("Downloading plugin zip attachment: %s") % plugin_zip_url) zip_path = self._download_zip(plugin_zip_url) if DEBUG: prints("Installing plugin: ", zip_path) self.gui.status_bar.showMessage(_("Installing plugin: %s") % zip_path) do_restart = False try: try: plugin = add_plugin(zip_path) except NameConflict as e: return error_dialog(self.gui, _("Already exists"), unicode(e), show=True) # Check for any toolbars to add to. widget = ConfigWidget(self.gui) widget.gui = self.gui widget.check_for_add_to_toolbars(plugin) self.gui.status_bar.showMessage(_("Plugin installed: %s") % display_plugin.name) d = info_dialog( self.gui, _("Success"), _( "Plugin <b>{0}</b> successfully installed under <b>" " {1} plugins</b>. You may have to restart calibre " "for the plugin to take effect." ).format(plugin.name, plugin.type), show_copy_button=False, ) b = d.bb.addButton(_("Restart calibre now"), d.bb.AcceptRole) b.setIcon(QIcon(I("lt.png"))) d.do_restart = False def rf(): d.do_restart = True b.clicked.connect(rf) d.set_details("") d.exec_() b.clicked.disconnect() do_restart = d.do_restart display_plugin.plugin = plugin # We cannot read the 'actual' version information as the plugin will not be loaded yet display_plugin.installed_version = display_plugin.available_version except: if DEBUG: prints("ERROR occurred while installing plugin: %s" % display_plugin.name) traceback.print_exc() error_dialog( self.gui, _("Install Plugin Failed"), _( "A problem occurred while installing this plugin." " This plugin will now be uninstalled." " Please post the error message in details below into" " the forum thread for this plugin and restart Calibre." ), det_msg=traceback.format_exc(), show=True, ) if DEBUG: prints("Due to error now uninstalling plugin: %s" % display_plugin.name) remove_plugin(display_plugin.name) display_plugin.plugin = None display_plugin.uninstall_plugins = [] if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]: self.model.reset() self._select_and_focus_view() else: self.model.refresh_plugin(display_plugin) self._select_and_focus_view(change_selection=False) if do_restart: self.do_restart = True self.accept() def _history_clicked(self): display_plugin = self._selected_display_plugin() text = self._read_version_history_html(display_plugin.forum_link) if text: dlg = VersionHistoryDialog(self, display_plugin.name, text) dlg.exec_() else: return error_dialog( self, _("Version history missing"), _("Unable to find the version history for %s") % display_plugin.name, show=True, ) def _configure_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.is_customizable(): return info_dialog( self, _("Plugin not customizable"), _("Plugin: %s does not need customization") % plugin.name, show=True ) from calibre.customize import InterfaceActionBase if isinstance(plugin, InterfaceActionBase) and not getattr(plugin, "actual_iaction_plugin_loaded", False): return error_dialog( self, _("Must restart"), _("You must restart calibre before you can" " configure the <b>%s</b> plugin") % plugin.name, show=True, ) plugin.do_user_config(self.parent()) def _toggle_enabled_clicked(self): display_plugin = self._selected_display_plugin() plugin = display_plugin.plugin if not plugin.can_be_disabled: return error_dialog( self, _("Plugin cannot be disabled"), _("The plugin: %s cannot be disabled") % plugin.name, show=True ) if is_disabled(plugin): enable_plugin(plugin) else: disable_plugin(plugin) self.model.refresh_plugin(display_plugin) def _read_version_history_html(self, forum_link): br = browser() br.set_handle_gzip(True) try: raw = br.open_novisit(forum_link).read() if not raw: return None except: traceback.print_exc() return None raw = raw.decode("utf-8", errors="replace") root = html.fromstring(raw) spoiler_nodes = root.xpath('//div[@class="smallfont" and strong="Spoiler"]') for spoiler_node in spoiler_nodes: try: if spoiler_node.getprevious() is None: # This is a spoiler node that has been indented using [INDENT] # Need to go up to parent div, then previous node to get header heading_node = spoiler_node.getparent().getprevious() else: # This is a spoiler node after a BR tag from the heading heading_node = spoiler_node.getprevious().getprevious() if heading_node is None: continue if heading_node.text_content().lower().find("version history") != -1: div_node = spoiler_node.xpath("div")[0] text = html.tostring(div_node, method="html", encoding=unicode) return re.sub("<div\s.*?>", "<div>", text) except: if DEBUG: prints("======= MobileRead Parse Error =======") traceback.print_exc() prints(html.tostring(spoiler_node)) return None def _read_zip_attachment_url(self, forum_link): br = browser() br.set_handle_gzip(True) try: raw = br.open_novisit(forum_link).read() if not raw: return None except: traceback.print_exc() return None raw = raw.decode("utf-8", errors="replace") root = html.fromstring(raw) attachment_nodes = root.xpath("//fieldset/table/tr/td/a") for attachment_node in attachment_nodes: try: filename = attachment_node.text_content().lower() if filename.find(".zip") != -1: full_url = MR_URL + attachment_node.attrib["href"] return full_url except: if DEBUG: prints("======= MobileRead Parse Error =======") traceback.print_exc() prints(html.tostring(attachment_node)) return None def _download_zip(self, plugin_zip_url): from calibre.ptempfile import PersistentTemporaryFile br = browser() br.set_handle_gzip(True) raw = br.open_novisit(plugin_zip_url).read() pt = PersistentTemporaryFile(".zip") pt.write(raw) pt.close() return pt.name